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
10 changes: 1 addition & 9 deletions src/components/member_manager.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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<ContractAddress, bool>,

/// Maps a unique member ID (`u256`) to a `MemberNode`.
pub members: Map<u256, MemberNode>,

/// Counter for the total number of members.
pub member_count: u256,

/// Role value weights for governance or weighted calculations.
pub role_value: Vec<u16>,

/// Member configuration node.
pub config: MemberConfigNode,

/// Maps member addresses to their invite details.
pub member_invites: Map<ContractAddress, MemberInvite>,

/// Address of the associated factory contract.
pub factory: ContractAddress,

/// Address of the core organization contract.
pub core_org: ContractAddress,
}
Expand Down Expand Up @@ -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<TContractState>) {
Expand Down
185 changes: 167 additions & 18 deletions src/contracts/vault.cairo
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,81 +29,108 @@ 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<ContractAddress, bool>,
/// 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:
// Add an event here that gets emitted if the money goes below a certain threshold
// 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)]
Expand All @@ -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,
Expand All @@ -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<ContractState> {
/// 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');
Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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,
) {
Expand All @@ -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();
Expand All @@ -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();
Expand All @@ -220,7 +307,13 @@ pub mod Vault {

self.vault_status.write(VaultStatus::VAULTRESUMED);
}

// fn bulk_transfer(ref self: ContractState, recipients: Span<ContractAddress>) {}

/// 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');
Expand All @@ -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');
Expand All @@ -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<Transaction>`: A list of all recorded transactions.
fn get_transaction_history(self: @ContractState) -> Array<Transaction> {
let mut transaction_history = array![];

Expand All @@ -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');
Expand All @@ -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,
Expand Down Expand Up @@ -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 };
Expand Down
Loading
Loading