diff --git a/src/components/member_manager.cairo b/src/components/member_manager.cairo index 721a80b..6c48d6e 100644 --- a/src/components/member_manager.cairo +++ b/src/components/member_manager.cairo @@ -29,28 +29,20 @@ pub mod MemberManagerComponent { pub struct Storage { /// The total number of administrators in the organization. pub admin_count: u64, - /// Maps a contract address to a boolean indicating admin status. pub admin_ca: Map, - /// Maps a unique member ID (`u256`) to a `MemberNode`. pub members: Map, - /// Counter for the total number of members. pub member_count: u256, - /// Role value weights for governance or weighted calculations. pub role_value: Vec, - /// Member configuration node. pub config: MemberConfigNode, - /// Maps member addresses to their invite details. pub member_invites: Map, - /// Address of the associated factory contract. pub factory: ContractAddress, - /// Address of the core organization contract. pub core_org: ContractAddress, } @@ -475,7 +467,7 @@ pub mod MemberManagerComponent { } } - /// Asserts the caller is an admin. + /// Asserts the caller is an admin. /// /// Reverts with `"UNAUTHORIZED"` if not. fn assert_admin(self: @ComponentState) { diff --git a/src/contracts/vault.cairo b/src/contracts/vault.cairo index 88da5d8..3eb03f4 100644 --- a/src/contracts/vault.cairo +++ b/src/contracts/vault.cairo @@ -1,3 +1,15 @@ +/// ## A Starknet contract for managing an organization's financial vault. +/// +/// This contract is responsible for: +/// - Securely holding a single type of ERC20 token. +/// - Processing deposits and withdrawals from authorized addresses. +/// - Executing payments to organization members. +/// - Allocating funds for bonuses. +/// - Recording all transactions for auditing purposes. +/// - Providing security features like an emergency freeze. +/// +/// It leverages OpenZeppelin's `OwnableComponent` for access control and `UpgradeableComponent` +/// for future contract upgrades. #[starknet::contract] pub mod Vault { use core::num::traits::Zero; @@ -17,34 +29,55 @@ pub mod Vault { component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); + /// Defines the storage layout for the `Vault` contract. #[storage] struct Storage { + /// Maps a contract address to a boolean indicating if it's permitted to interact with the + /// vault. permitted_addresses: Map, + /// The total balance of the managed token held by the vault. available_funds: u256, + /// The portion of the total balance allocated for bonus payments. total_bonus: u256, + /// Maps a transaction ID (`u64`) to a `Transaction` struct, storing a history of all vault + /// operations. transaction_history: Map< u64, Transaction, >, // No 1. Transaction x, no 2, transaction y etc for history, and it begins with 1 + /// A counter for the total number of transactions processed. transactions_count: u64, + /// The current operational status of the vault (e.g., VAULTACTIVE, VAULTFROZEN). vault_status: VaultStatus, + /// The contract address of the single ERC20 token this vault manages. token: ContractAddress, + /// Substorage for the Ownable component. #[substorage(v0)] ownable: OwnableComponent::Storage, + /// Substorage for the Upgradeable component. #[substorage(v0)] upgradeable: UpgradeableComponent::Storage, } + /// Events emitted by the `Vault` contract. #[event] #[derive(Drop, starknet::Event)] pub enum Event { + /// Emitted when a deposit is successfully made. DepositSuccessful: DepositSuccessful, + /// Emitted when a withdrawal is successfully processed. WithdrawalSuccessful: WithdrawalSuccessful, + /// Emitted when the vault's operations are frozen. VaultFrozen: VaultFrozen, + /// Emitted when the vault's operations are resumed from a frozen state. VaultResumed: VaultResumed, + /// Emitted each time a new transaction is recorded. TransactionRecorded: TransactionRecorded, + /// Emitted when funds are allocated to the bonus pool. BonusAllocation: BonusAllocation, + /// Flat event for Ownable component events. #[flat] OwnableEvent: OwnableComponent::Event, + /// Flat event for Upgradeable component events. #[flat] UpgradeableEvent: UpgradeableComponent::Event, // TODO: @@ -52,46 +85,52 @@ pub mod Vault { // Threshold Will be decided. } + /// Event data for a successful deposit. #[derive(Copy, Drop, starknet::Event)] pub struct DepositSuccessful { - caller: ContractAddress, - token: ContractAddress, - amount: u256, - timestamp: u64, + pub caller: ContractAddress, + pub token: ContractAddress, + pub amount: u256, + pub timestamp: u64, } + /// Event data for a successful withdrawal. #[derive(Copy, Drop, starknet::Event)] pub struct WithdrawalSuccessful { - caller: ContractAddress, - token: ContractAddress, - amount: u256, - timestamp: u64, + pub caller: ContractAddress, + pub token: ContractAddress, + pub amount: u256, + pub timestamp: u64, } + /// Event data for a vault freeze. #[derive(Copy, Drop, starknet::Event)] pub struct VaultFrozen { - caller: ContractAddress, - timestamp: u64, + pub caller: ContractAddress, + pub timestamp: u64, } + /// Event data for unfreezing the vault. #[derive(Copy, Drop, starknet::Event)] pub struct VaultResumed { - caller: ContractAddress, - timestamp: u64, + pub caller: ContractAddress, + pub timestamp: u64, } + /// Event data for a recorded transaction. #[derive(Drop, starknet::Event)] pub struct TransactionRecorded { - transaction_type: TransactionType, - caller: ContractAddress, - transaction_details: Transaction, - token: ContractAddress, + pub transaction_type: TransactionType, + pub caller: ContractAddress, + pub transaction_details: Transaction, + pub token: ContractAddress, } + /// Event data for a bonus allocation. #[derive(Drop, starknet::Event)] pub struct BonusAllocation { - amount: u256, - timestamp: u64, + pub amount: u256, + pub timestamp: u64, } #[abi(embed_v0)] @@ -102,6 +141,11 @@ pub mod Vault { // TODO: // Add to this constructor, a way to add addresses and store them as permitted addresses here + /// Initializes the Vault contract. + /// + /// ### Parameters + /// - `token`: The contract address of the ERC20 token to be managed. + /// - `owner`: The address that will have initial ownership and permissions. #[constructor] fn constructor( ref self: ContractState, @@ -124,8 +168,21 @@ pub mod Vault { // Implement a storage variable, that will be in the constructor, for the token address to be // supplied at deployment For now, we want a single-token implementation + /// # VaultImpl + /// + /// Public-facing implementation of the `IVault` interface. #[abi(embed_v0)] pub impl VaultImpl of IVault { + /// Accepts a deposit of the managed token. + /// + /// ### Parameters + /// - `amount`: The amount to deposit. + /// - `address`: The address from which the funds are being sent. + /// + /// ### Panics + /// - If `amount` or `address` is zero. + /// - If the direct caller or the source `address` is not permitted. + /// - If the vault is frozen. fn deposit_funds(ref self: ContractState, amount: u256, address: ContractAddress) { assert(amount.is_non_zero(), 'Invalid Amount'); assert(address.is_non_zero(), 'Invalid Address'); @@ -154,6 +211,17 @@ pub mod Vault { self.emit(DepositSuccessful { caller: address, token, timestamp, amount }) } + /// Withdraws the managed token to a specified address. + /// + /// ### Parameters + /// - `amount`: The amount to withdraw. + /// - `address`: The address to receive the funds. + /// + /// ### Panics + /// - If `amount` or `address` is zero. + /// - If the direct caller or the destination `address` is not permitted. + /// - If the vault is frozen. + /// - If the requested amount exceeds the vault's balance. fn withdraw_funds(ref self: ContractState, amount: u256, address: ContractAddress) { assert(amount.is_non_zero(), 'Invalid Amount'); assert(address.is_non_zero(), 'Invalid Address'); @@ -184,6 +252,15 @@ pub mod Vault { self.emit(WithdrawalSuccessful { caller: address, token, amount, timestamp }) } + /// Allocates a portion of the vault's funds to the bonus pool. + /// + /// ### Parameters + /// - `amount`: The amount to allocate for bonuses. + /// - `address`: The address initiating the allocation. + /// + /// ### Panics + /// - If `amount` or `address` is zero. + /// - If the direct caller or the source `address` is not permitted. fn add_to_bonus_allocation( ref self: ContractState, amount: u256, address: ContractAddress, ) { @@ -203,6 +280,11 @@ pub mod Vault { ); } + /// Freezes all vault operations as a security measure. + /// + /// ### Panics + /// - If the caller is not a permitted address. + /// - If the vault is already frozen. fn emergency_freeze(ref self: ContractState) { let caller = get_caller_address(); let permitted = self.permitted_addresses.entry(caller).read(); @@ -212,6 +294,11 @@ pub mod Vault { self.vault_status.write(VaultStatus::VAULTFROZEN); } + /// Resumes vault operations after a freeze. + /// + /// ### Panics + /// - If the caller is not a permitted address. + /// - If the vault is not currently frozen. fn unfreeze_vault(ref self: ContractState) { let caller = get_caller_address(); let permitted = self.permitted_addresses.entry(caller).read(); @@ -220,7 +307,13 @@ pub mod Vault { self.vault_status.write(VaultStatus::VAULTRESUMED); } + // fn bulk_transfer(ref self: ContractState, recipients: Span) {} + + /// Returns the vault's total balance of the managed token. + /// + /// ### Returns + /// - `u256`: The total balance. fn get_balance(self: @ContractState) -> u256 { // let caller = get_caller_address(); // assert(self.permitted_addresses.entry(caller).read(), 'Caller Not Permitted'); @@ -231,16 +324,35 @@ pub mod Vault { balance } + /// Returns the funds currently available for use. + /// + /// ### Returns + /// - `u256`: The available fund balance. fn get_available_funds(self: @ContractState) -> u256 { self.available_funds.read() } + /// Returns the total amount allocated for bonuses. + /// + /// ### Returns + /// - `u256`: The bonus allocation amount. fn get_bonus_allocation(self: @ContractState) -> u256 { // let caller = get_caller_address(); // assert(self.permitted_addresses.entry(caller).read(), 'Caller Not Permitted'); self.total_bonus.read() } + /// Pays a member from the vault's funds. + /// + /// ### Parameters + /// - `recipient`: The address of the member to pay. + /// - `amount`: The amount of the payment. + /// + /// ### Panics + /// - If `recipient` or `amount` is zero. + /// - If the caller is not a permitted address. + /// - If the payment amount exceeds the vault's balance. + /// - If the token transfer fails. fn pay_member(ref self: ContractState, recipient: ContractAddress, amount: u256) { assert(recipient.is_non_zero(), 'Invalid Address'); assert(amount.is_non_zero(), 'Invalid Amount'); @@ -260,10 +372,18 @@ pub mod Vault { self._sync_available_funds(); } + /// Returns the current status of the vault. + /// + /// ### Returns + /// - `VaultStatus`: The vault's current status enum. fn get_vault_status(self: @ContractState) -> VaultStatus { self.vault_status.read() } + /// Returns the entire transaction history of the vault. + /// + /// ### Returns + /// - `Array`: A list of all recorded transactions. fn get_transaction_history(self: @ContractState) -> Array { let mut transaction_history = array![]; @@ -275,14 +395,31 @@ pub mod Vault { transaction_history } + /// Grants another contract permission to call functions on this vault. + /// + /// ### Parameters + /// - `org_address`: The contract address to be granted permission. + /// + /// ### Panics + /// - If `org_address` is zero. fn allow_org_core_address(ref self: ContractState, org_address: ContractAddress) { assert(org_address.is_non_zero(), 'Invalid Address'); self.permitted_addresses.entry(org_address).write(true); } } + /// # InternalFunctions + /// + /// Internal helper functions for privileged operations within the vault. #[generate_trait] impl InternalFunctions of InternalTrait { + /// Adds a new transaction to the history. + /// + /// ### Parameters + /// - `transaction`: The `Transaction` struct to record. + /// + /// ### Panics + /// - If the caller is not a permitted address. fn _add_transaction(ref self: ContractState, transaction: Transaction) { let caller = get_caller_address(); assert(self.permitted_addresses.entry(caller).read(), 'Caller not permitted'); @@ -291,6 +428,16 @@ pub mod Vault { self.transactions_count.write(current_transaction_count + 1); } + /// Creates and stores a transaction record, and emits an event. + /// + /// ### Parameters + /// - `token_address`: The address of the token involved. + /// - `amount`: The transaction amount. + /// - `transaction_type`: The type of transaction (e.g., DEPOSIT, WITHDRAWAL). + /// - `caller`: The original initiator of the transaction. + /// + /// ### Panics + /// - If the function's direct caller is not a permitted address. fn _record_transaction( ref self: ContractState, token_address: ContractAddress, @@ -322,6 +469,8 @@ pub mod Vault { ); } + /// Updates the `available_funds` storage variable to match the contract's actual token + /// balance. fn _sync_available_funds(ref self: ContractState) { let token_address = self.token.read(); let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; diff --git a/src/interfaces/ivault.cairo b/src/interfaces/ivault.cairo index e6a0d97..1300d4c 100644 --- a/src/interfaces/ivault.cairo +++ b/src/interfaces/ivault.cairo @@ -1,19 +1,155 @@ use littlefinger::structs::vault_structs::{Transaction, VaultStatus}; use starknet::ContractAddress; +/// # IVault +/// +/// This trait defines the public interface for a vault component. It outlines the core +/// functionalities for managing an organization's funds, including deposits, withdrawals, +/// member payments, and security measures like emergency freezes. The interface is designed +/// to be implemented by a Starknet component responsible for the secure handling and +/// tracking of financial assets. #[starknet::interface] pub trait IVault { + /// # deposit_funds + /// + /// Deposits a specified amount of a given token into the vault. + /// + /// ## Parameters + /// + /// - `ref self: TContractState`: The current state of the contract. + /// - `amount`: The amount of funds to deposit as a `u256`. + /// - `address`: The `ContractAddress` of the token being deposited. fn deposit_funds(ref self: TContractState, amount: u256, address: ContractAddress); + + /// # withdraw_funds + /// + /// Withdraws a specified amount of a given token from the vault. This is a privileged action. + /// + /// ## Parameters + /// + /// - `ref self: TContractState`: The current state of the contract. + /// - `amount`: The amount of funds to withdraw as a `u256`. + /// - `address`: The `ContractAddress` of the token being withdrawn. fn withdraw_funds(ref self: TContractState, amount: u256, address: ContractAddress); + + /// # emergency_freeze + /// + /// Halts all outbound transactions from the vault. This function serves as a security + /// measure to prevent unauthorized fund movements in case of a compromise. + /// + /// ## Parameters + /// + /// - `ref self: TContractState`: The current state of the contract. fn emergency_freeze(ref self: TContractState); + + /// # unfreeze_vault + /// + /// Lifts the emergency freeze, restoring normal vault operations. This is a privileged action. + /// + /// ## Parameters + /// + /// - `ref self: TContractState`: The current state of the contract. fn unfreeze_vault(ref self: TContractState); + // fn bulk_transfer(ref self: TContractState, recipients: Span); + + /// # pay_member + /// + /// Executes a payment from the vault to a specific member's address. + /// + /// ## Parameters + /// + /// - `ref self: TContractState`: The current state of the contract. + /// - `recipient`: The `ContractAddress` of the member to receive the payment. + /// - `amount`: The payment amount as a `u256`. fn pay_member(ref self: TContractState, recipient: ContractAddress, amount: u256); + + /// # add_to_bonus_allocation + /// + /// Allocates a certain amount of funds for bonus payments. These funds are tracked + /// separately from the main available balance. + /// + /// ## Parameters + /// + /// - `ref self: TContractState`: The current state of the contract. + /// - `amount`: The amount to allocate for bonuses. + /// - `address`: The `ContractAddress` of the token for the bonus allocation. fn add_to_bonus_allocation(ref self: TContractState, amount: u256, address: ContractAddress); + + /// # get_balance + /// + /// Retrieves the total balance of the vault for all managed assets. + /// + /// ## Parameters + /// + /// - `self: @TContractState`: A snapshot of the contract's state. + /// + /// ## Returns + /// + /// The total balance as a `u256`. fn get_balance(self: @TContractState) -> u256; + + /// # get_available_funds + /// + /// Retrieves the amount of funds available for general use, excluding any earmarked + /// allocations like bonuses. + /// + /// ## Parameters + /// + /// - `self: @TContractState`: A snapshot of the contract's state. + /// + /// ## Returns + /// + /// The available funds as a `u256`. fn get_available_funds(self: @TContractState) -> u256; + + /// # get_vault_status + /// + /// Returns the current operational status of the vault (e.g., Active, Frozen). + /// + /// ## Parameters + /// + /// - `self: @TContractState`: A snapshot of the contract's state. + /// + /// ## Returns + /// + /// The current `VaultStatus` enum. fn get_vault_status(self: @TContractState) -> VaultStatus; + + /// # get_bonus_allocation + /// + /// Retrieves the current total amount allocated for bonuses. + /// + /// ## Parameters + /// + /// - `self: @TContractState`: A snapshot of the contract's state. + /// + /// ## Returns + /// + /// The total bonus allocation as a `u256`. fn get_bonus_allocation(self: @TContractState) -> u256; + + /// # get_transaction_history + /// + /// Retrieves a log of all transactions processed by the vault. + /// + /// ## Parameters + /// + /// - `self: @TContractState`: A snapshot of the contract's state. + /// + /// ## Returns + /// + /// An `Array` containing the vault's transaction history. fn get_transaction_history(self: @TContractState) -> Array; + + /// # allow_org_core_address + /// + /// Grants permission to a core organization contract to interact with the vault. + /// This is necessary for enabling automated payments and other coordinated actions. + /// + /// ## Parameters + /// + /// - `ref self: TContractState`: The current state of the contract. + /// - `org_address`: The `ContractAddress` of the core organization contract to authorize. fn allow_org_core_address(ref self: TContractState, org_address: ContractAddress); }