From 8af8cc6222d7ed82b9a9372252e19fe19501bd8c Mon Sep 17 00:00:00 2001 From: Yasir Abdulsalam Date: Sun, 23 Aug 2026 17:01:15 +0100 Subject: [PATCH] feat(aid): implement initialization and configuration - Add Config type with admin, treasury, token, default_expiry_secs - Implement initialize with re-initialization guard - Add read-only getters for all config values - Add update_config (admin-only) - Add AlreadyInitialized error variant Closes #7 --- contracts/aid-contract/src/lib.rs | 105 ++++++++++++++++++++++++-- contracts/aid-contract/src/storage.rs | 48 ++++++++++++ contracts/aid-contract/src/types.rs | 11 +++ 3 files changed, 159 insertions(+), 5 deletions(-) diff --git a/contracts/aid-contract/src/lib.rs b/contracts/aid-contract/src/lib.rs index 38bf2cb..2035d61 100644 --- a/contracts/aid-contract/src/lib.rs +++ b/contracts/aid-contract/src/lib.rs @@ -1,3 +1,4 @@ +#![no_std] use soroban_sdk::{ contract, contractimpl, contracterror, panic_with_error, token, symbol_short, Address, Env, Symbol, Map, }; @@ -6,7 +7,6 @@ use shared::{emit, AID_CLAIMED, AID_CREATED, AID_REFUNDED, AID_SETTLED, Error}; use shared::storage::is_paused; const KEY_AIDS: Symbol = symbol_short!("aids"); -#![no_std] use soroban_sdk::{contract, contractimpl, contracterror, token, Address, Env}; @@ -51,13 +51,108 @@ impl AidContract { // Lifecycle // ----------------------------------------------------------------------- - /// Initialise the contract with an admin and the escrow token. + /// Initialize the contract with configuration. /// /// Must be called exactly once immediately after deployment. - pub fn initialize(env: Env, admin: Address, token: Address) { + /// + /// # Arguments + /// * `admin` - The admin address with governance privileges. + /// * `treasury` - The treasury address for fees or emergency withdrawals. + /// * `token` - The accepted escrow token address. + /// * `default_expiry_secs` - Default expiration time in seconds for new aids. + /// + /// # Errors + /// * [`shared::Error::AlreadyInitialized`] - If called more than once. + pub fn initialize( + env: Env, + admin: Address, + treasury: Address, + token: Address, + default_expiry_secs: u64, + ) -> Result<(), shared::Error> { + // Guard against re-initialization + if storage::is_initialized(&env) { + return Err(shared::Error::AlreadyInitialized); + } + + admin.require_auth(); + + // Store configuration shared::auth::set_admin(&env, &admin); - emit_module_initialized(&env, symbol_short!("aid"), 1, &admin, env.ledger().timestamp()); - env.storage().instance().set(&storage::DataKey::Token, &token); + storage::set_treasury(&env, &treasury); + storage::set_token(&env, &token); + storage::set_default_expiry(&env, default_expiry_secs); + storage::set_initialized(&env); + + emit_module_initialized( + &env, + symbol_short!("aid"), + 1, + &admin, + env.ledger().timestamp(), + ); + + Ok(()) + } + + /// Get the admin address. + pub fn get_admin(env: Env) -> Address { + shared::auth::get_admin(&env) + } + + /// Get the treasury address. + pub fn get_treasury(env: Env) -> Option
{ + storage::get_treasury(&env) + } + + /// Get the escrow token address. + pub fn get_token(env: Env) -> Option
{ + storage::get_token(&env) + } + + /// Get the default expiry in seconds. + pub fn get_default_expiry(env: Env) -> Option { + storage::get_default_expiry(&env) + } + + /// Check if the contract is initialized. + pub fn is_initialized(env: Env) -> bool { + storage::is_initialized(&env) + } + + /// Update configuration (admin only). + /// + /// # Arguments + /// * `admin` - Must be the current admin. + /// * `treasury` - New treasury address (None = keep existing). + /// * `default_expiry_secs` - New default expiry (None = keep existing). + /// + /// # Errors + /// * [`shared::Error::Unauthorized`] - If caller is not admin. + pub fn update_config( + env: Env, + admin: Address, + treasury: Option
, + default_expiry_secs: Option, + ) -> Result<(), shared::Error> { + // Verify admin authorization + let current_admin = shared::auth::get_admin(&env); + if admin != current_admin { + return Err(shared::Error::Unauthorized); + } + admin.require_auth(); + + // Update treasury if provided + if let Some(t) = treasury { + storage::set_treasury(&env, &t); + } + + // Update default expiry if provided + if let Some(e) = default_expiry_secs { + storage::set_default_expiry(&env, e); + } + + Ok(()) } // ----------------------------------------------------------------------- diff --git a/contracts/aid-contract/src/storage.rs b/contracts/aid-contract/src/storage.rs index d26103a..d90ffb9 100644 --- a/contracts/aid-contract/src/storage.rs +++ b/contracts/aid-contract/src/storage.rs @@ -37,6 +37,12 @@ pub enum DataKey { AidCounter, /// Token address escrowed by this contract (instance). Token, + /// Treasury address (instance). + Treasury, + /// Default expiry in seconds (instance). + DefaultExpiry, + /// Configuration marker (instance) - set on initialize. + Initialized, /// Append-only list of aid IDs created by a donor (persistent). DonorIndex(Address), /// Append-only list of aid IDs assigned to a recipient (persistent). @@ -138,4 +144,46 @@ pub fn append_recipient_index(env: &Env, recipient: &Address, aid_id: u64) { let mut ids = get_recipient_index(env, recipient); ids.push_back(aid_id); persistent_set(env, &DataKey::RecipientIndex(recipient.clone()), &ids); + + +// --------------------------------------------------------------------------- +// Treasury address — instance storage +// --------------------------------------------------------------------------- + +/// Read the configured treasury address, if initialised. +pub fn get_treasury(env: &Env) -> Option
{ + instance_get(env, &DataKey::Treasury) +} + +/// Store the treasury address (initialisation only). +pub fn set_treasury(env: &Env, treasury: &Address) { + instance_set(env, &DataKey::Treasury, treasury); +} + +// --------------------------------------------------------------------------- +// Default expiry — instance storage +// --------------------------------------------------------------------------- + +/// Read the default expiry in seconds, if set. +pub fn get_default_expiry(env: &Env) -> Option { + instance_get(env, &DataKey::DefaultExpiry) +} + +/// Store the default expiry in seconds. +pub fn set_default_expiry(env: &Env, expiry_secs: u64) { + instance_set(env, &DataKey::DefaultExpiry, &expiry_secs); +} + +// --------------------------------------------------------------------------- +// Initialization flag — instance storage +// --------------------------------------------------------------------------- + +/// Check if the contract has been initialized. +pub fn is_initialized(env: &Env) -> bool { + instance_get::(env, &DataKey::Initialized).unwrap_or(false) } + +/// Mark the contract as initialized. +pub fn set_initialized(env: &Env) { + instance_set(env, &DataKey::Initialized, &true); +}} diff --git a/contracts/aid-contract/src/types.rs b/contracts/aid-contract/src/types.rs index 4ef3bfe..30e9090 100644 --- a/contracts/aid-contract/src/types.rs +++ b/contracts/aid-contract/src/types.rs @@ -1,5 +1,16 @@ use soroban_sdk::{contracttype, Address, Vec}; + +/// Contract configuration stored in instance storage. +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Config { + pub admin: Address, + pub treasury: Address, + pub token: Address, + pub default_expiry_secs: u64, +} + /// Lifecycle state of an aid record. #[contracttype] #[derive(Clone, Copy, Debug, Eq, PartialEq)]