Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 78 additions & 18 deletions contract/contracts/hello-world/src/autoshare_logic.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
use crate::base::errors::Error;
use crate::base::events::{
AdminTransferred, AuthorizationFailure, AutoshareCreated, AutoshareUpdated, ContractPaused,
ContractUnpaused, GroupActivated, GroupDeactivated, NotificationCategory, NotificationExpired,
NotificationExtended, NotificationLimitsConfigured, NotificationPriority, NotificationRevoked,
NotificationScheduled, ScheduledNotificationCancelled, Withdrawal,
AdminTransferred, AuthorizationFailure, AutoshareCreated, AutoshareUpdated, CategoryRegistered,
ContractPaused, ContractUnpaused, GroupActivated, GroupDeactivated, NotificationCategory,
NotificationExpired, NotificationExtended, NotificationLimitsConfigured, NotificationPriority,
NotificationRevoked, NotificationScheduled, ScheduledNotificationCancelled, Withdrawal,
};
use crate::base::types::{
AutoShareDetails, GroupMember, NotificationLimits, PaymentHistory, ScheduledNotification,
};
use crate::base::types::{AutoShareDetails, GroupMember, NotificationLimits, PaymentHistory, ScheduledNotification};
use soroban_sdk::{contracttype, token, Address, BytesN, Env, String, Vec};

/// Storage key layout (optimized):
Expand Down Expand Up @@ -42,6 +44,7 @@ pub enum DataKey {
ScheduledNotification(BytesN<32>),
NotificationRevokers(BytesN<32>),
NotificationLimits,
RegisteredCategories,
}

// ============================================================================
Expand Down Expand Up @@ -266,8 +269,71 @@ pub fn initialize_admin(env: Env, admin: Address) {

// Initialize empty supported tokens list in instance storage
let empty_tokens: Vec<Address> = Vec::new(&env);
env.storage().instance().set(&INSTANCE_TOKENS, &empty_tokens);
env.storage()
.instance()
.set(&INSTANCE_TOKENS, &empty_tokens);

seed_default_categories(&env);
}
}

fn seed_default_categories(env: &Env) {
let key = DataKey::RegisteredCategories;
if env.storage().persistent().has(&key) {
return;
}

let categories: Vec<NotificationCategory> = Vec::new(env);
env.storage().persistent().set(&key, &categories);
}

pub fn get_registered_categories(env: Env) -> Vec<NotificationCategory> {
let key = DataKey::RegisteredCategories;
env.storage()
.persistent()
.get(&key)
.unwrap_or(Vec::new(&env))
}

pub fn is_category_registered(env: Env, category: NotificationCategory) -> bool {
let categories = get_registered_categories(env.clone());
for i in 0..categories.len() {
if categories.get(i).unwrap() == category {
return true;
}
}
false
}

pub fn register_category(
env: Env,
admin: Address,
category: NotificationCategory,
) -> Result<(), Error> {
admin.require_auth();
require_admin(&env, &admin)?;

if is_category_registered(env.clone(), category) {
return Err(Error::AlreadyExists);
}

let key = DataKey::RegisteredCategories;
let mut categories: Vec<NotificationCategory> = env
.storage()
.persistent()
.get(&key)
.unwrap_or(Vec::new(&env));
categories.push_back(category);
env.storage().persistent().set(&key, &categories);

CategoryRegistered {
admin: admin.clone(),
category,
priority: NotificationPriority::Medium,
}
.publish(&env);

Ok(())
}

fn publish_authorization_failure(env: &Env, caller: &Address, action: &str) {
Expand Down Expand Up @@ -337,9 +403,8 @@ pub fn pause(env: Env, admin: Address) -> Result<(), Error> {
}

env.storage().instance().set(&INSTANCE_PAUSED, &true);
ContractPaused {}.publish(&env);
env.storage().persistent().set(&pause_key, &true);
ContractPaused {
admin: admin.clone(),
category: NotificationCategory::Admin,
priority: NotificationPriority::High,
}
Expand All @@ -362,9 +427,8 @@ pub fn unpause(env: Env, admin: Address) -> Result<(), Error> {
}

env.storage().instance().set(&INSTANCE_PAUSED, &false);
ContractUnpaused {}.publish(&env);
env.storage().persistent().set(&pause_key, &false);
ContractUnpaused {
admin: admin.clone(),
category: NotificationCategory::Admin,
priority: NotificationPriority::High,
}
Expand Down Expand Up @@ -469,10 +533,7 @@ pub fn set_usage_fee(env: Env, fee: u32, admin: Address) -> Result<(), Error> {
}

pub fn get_usage_fee(env: Env) -> u32 {
env.storage()
.instance()
.get(&INSTANCE_FEE)
.unwrap_or(10u32)
env.storage().instance().get(&INSTANCE_FEE).unwrap_or(10u32)
}

// ============================================================================
Expand Down Expand Up @@ -1182,7 +1243,6 @@ pub fn extend_notification_expiry(
Ok(())
}


// ============================================================================
// Notification Limits Configuration
// ============================================================================
Expand All @@ -1202,7 +1262,7 @@ pub fn configure_notification_limits(
admin.require_auth();

// Verify caller is admin
let current_admin = get_admin(env.clone()).ok_or(Error::Unauthorized)?;
let current_admin = get_admin(env.clone())?;
if admin != current_admin {
AuthorizationFailure {
caller: admin,
Expand Down Expand Up @@ -1259,14 +1319,14 @@ pub fn configure_notification_limits(
/// Returns default limits if none have been configured.
pub fn get_notification_limits(env: Env) -> NotificationLimits {
let key = DataKey::NotificationLimits;

env.storage()
.persistent()
.get::<DataKey, NotificationLimits>(&key)
.unwrap_or(NotificationLimits {
max_payload_size: 10_000,
max_expiration_seconds: 365 * 24 * 60 * 60, // 1 year
min_expiration_seconds: 60, // 1 minute
min_expiration_seconds: 60, // 1 minute
max_batch_size: 1000,
})
}
19 changes: 17 additions & 2 deletions contract/contracts/hello-world/src/base/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,24 @@ pub struct AutoshareCreated {
pub id: BytesN<32>,
}

/// Emitted when a notification category is registered on-chain.
#[contractevent]
#[derive(Clone)]
pub struct CategoryRegistered {
#[topic]
pub admin: Address,
#[topic]
pub category: NotificationCategory,
#[topic]
pub priority: NotificationPriority,
}

/// Emitted when the contract is paused by the admin.
#[contractevent]
#[derive(Clone)]
pub struct ContractPaused {
#[topic]
pub admin: Address,
#[topic]
pub category: NotificationCategory,
#[topic]
Expand All @@ -82,6 +96,8 @@ pub struct ContractPaused {
#[contractevent]
#[derive(Clone)]
pub struct ContractUnpaused {
#[topic]
pub admin: Address,
#[topic]
pub category: NotificationCategory,
#[topic]
Expand Down Expand Up @@ -254,7 +270,7 @@ pub struct NotificationExtended {
}

/// Emitted when protocol-level notification limits are configured or updated.
#[contractevent(data_format = "single-value")]
#[contractevent]
#[derive(Clone)]
pub struct NotificationLimitsConfigured {
#[topic]
Expand All @@ -268,4 +284,3 @@ pub struct NotificationLimitsConfigured {
pub min_expiration_seconds: u64,
pub max_batch_size: u32,
}

12 changes: 6 additions & 6 deletions contract/contracts/hello-world/src/base/metadata_validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@
///
/// Provides validation for notification metadata to ensure consistency
/// and prevent malformed data from being stored on-chain.

use crate::base::errors::Error;
use soroban_sdk::{String, Map};
use soroban_sdk::{Map, String};

/// Maximum length for metadata string fields (bytes)
const MAX_METADATA_STRING_LENGTH: u32 = 256;
Expand Down Expand Up @@ -141,7 +140,7 @@ mod tests {
#[test]
fn test_valid_metadata() {
let metadata = NotificationMetadata {
title: String::from_slice(&soroban_sdk::Env::new(), "Test"),
title: String::from_slice(&soroban_sdk::Env::default(), "Test"),
description: None,
data_uri: None,
custom_fields: None,
Expand All @@ -152,7 +151,7 @@ mod tests {
#[test]
fn test_empty_title_invalid() {
let metadata = NotificationMetadata {
title: String::from_slice(&soroban_sdk::Env::new(), ""),
title: String::from_slice(&soroban_sdk::Env::default(), ""),
description: None,
data_uri: None,
custom_fields: None,
Expand All @@ -162,8 +161,9 @@ mod tests {

#[test]
fn test_long_title_invalid() {
let env = soroban_sdk::Env::new();
let long_string = String::from_slice(&env, &"a".repeat(MAX_METADATA_STRING_LENGTH as usize + 1));
let env = soroban_sdk::Env::default();
let long_string =
String::from_slice(&env, &"a".repeat(MAX_METADATA_STRING_LENGTH as usize + 1));
let metadata = NotificationMetadata {
title: long_string,
description: None,
Expand Down
16 changes: 16 additions & 0 deletions contract/contracts/hello-world/src/interfaces/autoshare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,22 @@ pub trait AutoShareTrait {
/// Returns the current pause status.
fn get_paused_status(env: Env) -> bool;

/// Registers a notification category in the on-chain registry.
fn register_category(
env: Env,
admin: Address,
category: crate::base::events::NotificationCategory,
);

/// Returns all registered notification categories.
fn get_registered_categories(env: Env) -> Vec<crate::base::events::NotificationCategory>;

/// Returns whether a notification category is registered.
fn is_category_registered(
env: Env,
category: crate::base::events::NotificationCategory,
) -> bool;

/// Returns the current admin address.
fn get_admin(env: Env) -> Address;

Expand Down
36 changes: 33 additions & 3 deletions contract/contracts/hello-world/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,27 @@ impl AutoShareContract {
autoshare_logic::get_paused_status(&env)
}

/// Registers a notification category in the on-chain registry.
pub fn register_category(
env: Env,
admin: Address,
category: base::events::NotificationCategory,
) {
autoshare_logic::register_category(env, admin, category).unwrap();
}

/// Returns all registered notification categories.
pub fn get_registered_categories(
env: Env,
) -> soroban_sdk::Vec<base::events::NotificationCategory> {
autoshare_logic::get_registered_categories(env)
}

/// Returns whether a notification category is registered.
pub fn is_category_registered(env: Env, category: base::events::NotificationCategory) -> bool {
autoshare_logic::is_category_registered(env, category)
}

// ============================================================================
// AutoShare Group Management
// ============================================================================
Expand Down Expand Up @@ -343,7 +364,8 @@ impl AutoShareContract {
ttl_seconds: u64,
title: String,
) {
autoshare_logic::schedule_notification(env, notification_id, creator, ttl_seconds, title).unwrap();
autoshare_logic::schedule_notification(env, notification_id, creator, ttl_seconds, title)
.unwrap();
}

/// Returns the stored details for a scheduled notification.
Expand Down Expand Up @@ -389,8 +411,13 @@ impl AutoShareContract {
caller: Address,
extension_seconds: u64,
) {
autoshare_logic::extend_notification_expiry(env, notification_id, caller, extension_seconds)
.unwrap();
autoshare_logic::extend_notification_expiry(
env,
notification_id,
caller,
extension_seconds,
)
.unwrap();
}

// ============================================================================
Expand Down Expand Up @@ -459,6 +486,9 @@ mod tests {
#[path = "../tests/notification_test.rs"]
mod notification_test;

#[path = "../tests/category_registry_test.rs"]
mod category_registry_test;

#[path = "../tests/expiration_test.rs"]
mod expiration_test;

Expand Down
8 changes: 5 additions & 3 deletions contract/contracts/hello-world/src/preferences_logic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
/// Preferences are stored in persistent storage keyed by recipient address.
use crate::base::errors::Error;
use crate::base::preferences::{
CategoryPreference, ChannelPreference, DeliveryChannel, NotificationCategory,
RecipientPreferences, default_categories, default_channels, load_preferences, save_preferences,
default_categories, default_channels, load_preferences, save_preferences, CategoryPreference,
ChannelPreference, DeliveryChannel, NotificationCategory, RecipientPreferences,
};
use soroban_sdk::{Address, Env, Vec};

Expand Down Expand Up @@ -76,7 +76,9 @@ pub fn set_channel_preference(
}

if !found {
prefs.channels.push_back(ChannelPreference { channel, enabled });
prefs
.channels
.push_back(ChannelPreference { channel, enabled });
}

prefs.updated_at = env.ledger().timestamp();
Expand Down
Loading
Loading