diff --git a/contracts/invoice-token/src/storage.rs b/contracts/invoice-token/src/storage.rs index 4239af6..be7180f 100644 --- a/contracts/invoice-token/src/storage.rs +++ b/contracts/invoice-token/src/storage.rs @@ -1,20 +1,52 @@ -//! Storage helpers for balances, metadata, allowances, fees, roles, nonces, and history. +//! Storage module for the invoice-token contract. +//! +//! This module manages the lifecycle and access patterns for token state, including balances, +//! allowances, metadata, fees, nonces, and access control roles. +//! +//! # Storage Architecture +//! The storage architecture leverages two types of Soroban storage: +//! - **Instance Storage:** Used for global contract state (e.g., `Metadata`, `TotalSupply`, `FeeBps`, `RoleAdmin`, `RoleGrant`). +//! Instance storage shares the TTL of the contract instance. Whenever the contract is invoked, +//! instance storage is implicitly bumped based on the instance's bump policy. +//! - **Persistent Storage:** Used for user-specific state that outlives the current instance TTL and must be explicitly +//! bumped (e.g., `Balance`, `Allowance`, `Nonce`, `History`, `Frozen`). This ensures that user funds and approvals +//! are not lost even if the contract instance itself is archived. +//! +//! # TTL Bump Policies +//! When modifying persistent storage, developers should ensure TTLs are sufficiently bumped +//! (typically done in the top-level token interface functions invoking this module, or automatically by the host). use soroban_sdk::{Address, Symbol, Vec}; use crate::types::{AllowanceData, OwnershipHistoryRecord, StorageKey, TokenMetadata}; /// Load token metadata from instance storage. +/// +/// # Parameters +/// - `env`: The environment context. +/// +/// # Returns +/// Returns `Some(TokenMetadata)` if initialized, otherwise `None`. pub fn get_metadata(env: &soroban_sdk::Env) -> Option { env.storage().instance().get(&StorageKey::Metadata) } /// Save token metadata to instance storage. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `meta`: The metadata structure to store. pub fn set_metadata(env: &soroban_sdk::Env, meta: &TokenMetadata) { env.storage().instance().set(&StorageKey::Metadata, meta); } /// Load total supply from instance storage. +/// +/// # Parameters +/// - `env`: The environment context. +/// +/// # Returns +/// Returns the total token supply as `i128`. Defaults to `0` if not set. pub fn get_total_supply(env: &soroban_sdk::Env) -> i128 { env.storage() .instance() @@ -23,6 +55,10 @@ pub fn get_total_supply(env: &soroban_sdk::Env) -> i128 { } /// Save total supply to instance storage. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `amount`: The new total supply amount. pub fn set_total_supply(env: &soroban_sdk::Env, amount: i128) { env.storage() .instance() @@ -30,6 +66,13 @@ pub fn set_total_supply(env: &soroban_sdk::Env, amount: i128) { } /// Get balance for an address (persistent storage). +/// +/// # Parameters +/// - `env`: The environment context. +/// - `addr`: The address to query the balance for. +/// +/// # Returns +/// Returns the balance as `i128`. Defaults to `0` if the account does not exist or has no balance. pub fn get_balance(env: &soroban_sdk::Env, addr: &Address) -> i128 { env.storage() .persistent() @@ -38,6 +81,12 @@ pub fn get_balance(env: &soroban_sdk::Env, addr: &Address) -> i128 { } /// Set balance for an address (persistent storage). +/// If the amount is 0, the entry is removed from storage to save space. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `addr`: The address whose balance will be set. +/// - `amount`: The new balance. pub fn set_balance(env: &soroban_sdk::Env, addr: &Address, amount: i128) { if amount == 0 { env.storage() @@ -51,6 +100,13 @@ pub fn set_balance(env: &soroban_sdk::Env, addr: &Address, amount: i128) { } /// Check whether an account is frozen. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `account`: The address to check. +/// +/// # Returns +/// Returns `true` if the account is frozen, otherwise `false`. pub fn is_account_frozen(env: &soroban_sdk::Env, account: &Address) -> bool { env.storage() .persistent() @@ -59,6 +115,11 @@ pub fn is_account_frozen(env: &soroban_sdk::Env, account: &Address) -> bool { } /// Update an account's frozen state, removing unrestricted entries from storage. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `account`: The address to update. +/// - `frozen`: The new frozen state. pub fn set_account_frozen(env: &soroban_sdk::Env, account: &Address, frozen: bool) { let key = StorageKey::Frozen(account.clone()); if frozen { @@ -69,6 +130,15 @@ pub fn set_account_frozen(env: &soroban_sdk::Env, account: &Address, frozen: boo } /// Get allowance (from, spender). Returns 0 if expired or not set. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `from`: The owner of the funds. +/// - `spender`: The authorized spender. +/// - `current_ledger`: The current ledger sequence number. +/// +/// # Returns +/// Returns the available allowance as `i128`. Returns `0` if expired or not found. pub fn get_allowance( env: &soroban_sdk::Env, from: &Address, @@ -85,6 +155,13 @@ pub fn get_allowance( /// Set allowance (from, spender) -> (amount, expiration_ledger). /// Removes the key when amount is 0 to save persistent storage. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `from`: The owner of the funds. +/// - `spender`: The authorized spender. +/// - `amount`: The amount to authorize. +/// - `expiration_ledger`: The ledger sequence when this allowance expires. pub fn set_allowance( env: &soroban_sdk::Env, from: &Address, @@ -107,6 +184,14 @@ pub fn set_allowance( } /// Get raw allowance data (for decreasing allowance on transfer_from/burn_from). +/// +/// # Parameters +/// - `env`: The environment context. +/// - `from`: The owner of the funds. +/// - `spender`: The authorized spender. +/// +/// # Returns +/// Returns `Some(AllowanceData)` if found, otherwise `None`. pub fn get_allowance_data( env: &soroban_sdk::Env, from: &Address, @@ -120,6 +205,12 @@ pub fn get_allowance_data( // ==================== Fee (Issue #113) ==================== /// Get fee basis points from instance storage. Returns 0 if not set. +/// +/// # Parameters +/// - `env`: The environment context. +/// +/// # Returns +/// Returns the fee basis points as `i128`. pub fn get_fee_bps(env: &soroban_sdk::Env) -> i128 { env.storage() .instance() @@ -128,6 +219,10 @@ pub fn get_fee_bps(env: &soroban_sdk::Env) -> i128 { } /// Save fee basis points to instance storage. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `bps`: The fee basis points to set. pub fn set_fee_bps(env: &soroban_sdk::Env, bps: i128) { env.storage().instance().set(&StorageKey::FeeBps, &bps); } @@ -135,6 +230,13 @@ pub fn set_fee_bps(env: &soroban_sdk::Env, bps: i128) { // ==================== Role Admin (Issue #108) ==================== /// Get the admin address for a specific role. Returns None if unset. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `role`: The role symbol. +/// +/// # Returns +/// Returns the admin address `Some(Address)` if set, otherwise `None`. pub fn get_role_admin(env: &soroban_sdk::Env, role: &Symbol) -> Option
{ env.storage() .instance() @@ -142,6 +244,11 @@ pub fn get_role_admin(env: &soroban_sdk::Env, role: &Symbol) -> Option
} /// Set the admin address for a specific role. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `role`: The role symbol. +/// - `admin`: The admin address. pub fn set_role_admin(env: &soroban_sdk::Env, role: &Symbol, admin: &Address) { env.storage() .instance() @@ -149,6 +256,14 @@ pub fn set_role_admin(env: &soroban_sdk::Env, role: &Symbol, admin: &Address) { } /// Check whether `account` has been granted `role`. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `role`: The role symbol to check. +/// - `account`: The account address to verify. +/// +/// # Returns +/// Returns `true` if the account has the role, otherwise `false`. pub fn has_role(env: &soroban_sdk::Env, role: &Symbol, account: &Address) -> bool { env.storage() .instance() @@ -157,6 +272,12 @@ pub fn has_role(env: &soroban_sdk::Env, role: &Symbol, account: &Address) -> boo } /// Grant or revoke `role` for `account`. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `role`: The role symbol. +/// - `account`: The account address. +/// - `granted`: Boolean indicating whether to grant (`true`) or revoke (`false`). pub fn set_role_grant(env: &soroban_sdk::Env, role: &Symbol, account: &Address, granted: bool) { let key = StorageKey::RoleGrant(role.clone(), account.clone()); if granted { @@ -169,6 +290,13 @@ pub fn set_role_grant(env: &soroban_sdk::Env, role: &Symbol, account: &Address, // ==================== Nonce (Issue #106) ==================== /// Get the current nonce for an address. Starts at 0. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `addr`: The account address. +/// +/// # Returns +/// Returns the current nonce as `u64`. pub fn get_nonce(env: &soroban_sdk::Env, addr: &Address) -> u64 { env.storage() .persistent() @@ -177,6 +305,13 @@ pub fn get_nonce(env: &soroban_sdk::Env, addr: &Address) -> u64 { } /// Increment and return the new nonce for an address. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `addr`: The account address. +/// +/// # Returns +/// Returns the newly incremented nonce as `u64`. #[allow(dead_code)] pub fn increment_nonce(env: &soroban_sdk::Env, addr: &Address) -> u64 { let current = get_nonce(env, addr); @@ -190,6 +325,13 @@ pub fn increment_nonce(env: &soroban_sdk::Env, addr: &Address) -> u64 { // ==================== Ownership History (Issue #111) ==================== /// Get the full ownership history for an address. Returns empty Vec if none. +/// +/// # Parameters +/// - `env`: The environment context. +/// - `addr`: The account address. +/// +/// # Returns +/// Returns a `Vec`. pub fn get_token_history(env: &soroban_sdk::Env, addr: &Address) -> Vec { env.storage() .persistent() @@ -198,6 +340,11 @@ pub fn get_token_history(env: &soroban_sdk::Env, addr: &Address) -> Vec bool { #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] pub enum StorageKey { - /// Instance: token metadata and config. + /// Purpose: Stores global token configuration and metadata (admin, symbol, decimals, etc.). + /// Storage Type: Instance storage. + /// Access Pattern: Read frequently on metadata queries; written during initialization or by admin. + /// TTL Policy: Bumped automatically with the contract instance. Metadata, - /// Instance: total supply. + + /// Purpose: Tracks the total circulating supply of the token. + /// Storage Type: Instance storage. + /// Access Pattern: Read on `total_supply` queries; written during minting or burning operations. + /// TTL Policy: Bumped automatically with the contract instance. TotalSupply, - /// Persistent: balance by holder address. + + /// Purpose: Tracks the token balance for a specific holder address. + /// Storage Type: Persistent storage. + /// Access Pattern: Read on balance queries and transfers; written during transfers, minting, or burning. + /// TTL Policy: Must be explicitly bumped to prevent loss of user funds. Balance(soroban_sdk::Address), - /// Persistent: allowance (from, spender) -> AllowanceData. + + /// Purpose: Tracks the delegated spending allowance (`AllowanceData`) from an owner to a spender. + /// Storage Type: Persistent storage. + /// Access Pattern: Read on `allowance` queries and `transfer_from`; written during `approve`. + /// TTL Policy: Explicitly bumped; expires based on the `expiration_ledger` in `AllowanceData`. Allowance(soroban_sdk::Address, soroban_sdk::Address), - /// Instance: fee basis points. + + /// Purpose: Stores the fee basis points applied to transfers (if applicable). + /// Storage Type: Instance storage. + /// Access Pattern: Read during fee-enabled transfers; written by admin. + /// TTL Policy: Bumped automatically with the contract instance. FeeBps, - /// Instance: role admin mapping (role -> admin address). + + /// Purpose: Maps a specific role (e.g., minter) to its administrator address. + /// Storage Type: Instance storage. + /// Access Pattern: Read when checking administrative rights; written by super admin. + /// TTL Policy: Bumped automatically with the contract instance. RoleAdmin(soroban_sdk::Symbol), - /// Instance: role grant mapping (role, account) -> bool. + + /// Purpose: Tracks whether a specific account has been granted a particular role. + /// Storage Type: Instance storage. + /// Access Pattern: Read during role-restricted operations; written when granting/revoking roles. + /// TTL Policy: Bumped automatically with the contract instance. RoleGrant(soroban_sdk::Symbol, soroban_sdk::Address), - /// Persistent: nonce per address for permit-style transfers. + + /// Purpose: Tracks the current nonce for permit-style operations or replay protection. + /// Storage Type: Persistent storage. + /// Access Pattern: Read during signature verification; incremented on successful operation. + /// TTL Policy: Explicitly bumped to maintain sequence history. Nonce(soroban_sdk::Address), - /// Persistent: ownership history records for a token holder. + + /// Purpose: Stores a list of ownership history records (`OwnershipHistoryRecord`) for an address. + /// Storage Type: Persistent storage. + /// Access Pattern: Read on history queries; appended to during transfers. + /// TTL Policy: Explicitly bumped to preserve historical data. History(soroban_sdk::Address), - /// Persistent: whether an account is restricted from token operations. + + /// Purpose: Tracks whether an account is restricted/frozen from token operations. + /// Storage Type: Persistent storage. + /// Access Pattern: Read before transfers; written by admin to freeze/unfreeze accounts. + /// TTL Policy: Explicitly bumped to enforce compliance rules. Frozen(soroban_sdk::Address), }