From cdb8783cabe110ca2b09a7ac40040d064d8a3b0b Mon Sep 17 00:00:00 2001 From: Saint <50557035+wolfyres@users.noreply.github.com> Date: Sat, 25 Apr 2026 12:15:14 +0100 Subject: [PATCH] Add files via upload --- contracts/treasury/src/lib.rs | 310 ++++++++++++++++++++++++++++++++++ 1 file changed, 310 insertions(+) create mode 100644 contracts/treasury/src/lib.rs diff --git a/contracts/treasury/src/lib.rs b/contracts/treasury/src/lib.rs new file mode 100644 index 0000000..c2855b7 --- /dev/null +++ b/contracts/treasury/src/lib.rs @@ -0,0 +1,310 @@ +//! NovaFund Treasury Management Contract +//! +//! Manages XLM/USDC platform reserves. All withdrawals require an approved +//! on-chain governance proposal (DAO-gated). Deposits are open to any caller. +//! Every withdrawal is recorded for audit purposes. + +#![no_std] + +use shared::errors::Error; +use soroban_sdk::{ + contract, contractimpl, contracttype, symbol_short, token, Address, Bytes, Env, Vec, +}; + +#[cfg(test)] +mod tests; + +// --------------------------------------------------------------------------- +// Storage keys +// --------------------------------------------------------------------------- + +#[contracttype] +#[derive(Clone)] +pub enum DataKey { + /// Admin / governance contract address + Admin, + /// Supported reserve token addresses (XLM native wrapper, USDC, …) + SupportedTokens, + /// Withdrawal record by sequential id + Withdrawal(u64), + /// Next withdrawal id counter + NextWithdrawalId, + /// Executed governance proposal ids (prevents replay) + ProposalUsed(u64), +} + +// --------------------------------------------------------------------------- +// Types +// --------------------------------------------------------------------------- + +/// Immutable record written for every withdrawal – the audit trail. +#[contracttype] +#[derive(Clone, Debug)] +pub struct WithdrawalRecord { + /// Sequential id + pub id: u64, + /// Governance proposal that authorised this withdrawal + pub proposal_id: u64, + /// Token transferred + pub token: Address, + /// Destination address + pub recipient: Address, + /// Amount transferred (7-decimal Stellar units) + pub amount: i128, + /// Human-readable purpose / memo (IPFS hash or short label) + pub memo: Bytes, + /// Ledger timestamp at execution + pub executed_at: u64, +} + +// --------------------------------------------------------------------------- +// Contract +// --------------------------------------------------------------------------- + +#[contract] +pub struct TreasuryContract; + +#[contractimpl] +impl TreasuryContract { + // ----------------------------------------------------------------------- + // Initialisation + // ----------------------------------------------------------------------- + + /// Initialise the treasury. + /// + /// * `admin` – governance contract (or multisig) that controls withdrawals + /// * `supported_tokens` – initial list of accepted reserve tokens + pub fn initialize( + env: Env, + admin: Address, + supported_tokens: Vec
, + ) -> Result<(), Error> { + admin.require_auth(); + + let storage = env.storage().instance(); + if storage.has(&DataKey::Admin) { + return Err(Error::AlreadyInit); + } + if supported_tokens.is_empty() { + return Err(Error::InvInput); + } + + storage.set(&DataKey::Admin, &admin); + storage.set(&DataKey::SupportedTokens, &supported_tokens); + storage.set(&DataKey::NextWithdrawalId, &0_u64); + + Ok(()) + } + + // ----------------------------------------------------------------------- + // Governance administration + // ----------------------------------------------------------------------- + + /// Replace the admin / governance address. Only the current admin may call this. + pub fn set_admin(env: Env, new_admin: Address) -> Result<(), Error> { + let storage = env.storage().instance(); + let admin: Address = storage.get(&DataKey::Admin).ok_or(Error::NotInit)?; + admin.require_auth(); + + storage.set(&DataKey::Admin, &new_admin); + env.events() + .publish((symbol_short!("tr_admin"),), (admin, new_admin)); + Ok(()) + } + + /// Add a token to the supported-reserves list. Only admin. + pub fn add_token(env: Env, token: Address) -> Result<(), Error> { + let storage = env.storage().instance(); + let admin: Address = storage.get(&DataKey::Admin).ok_or(Error::NotInit)?; + admin.require_auth(); + + let mut tokens: Vec = storage + .get(&DataKey::SupportedTokens) + .ok_or(Error::NotInit)?; + + // Idempotent – skip if already present + for i in 0..tokens.len() { + if tokens.get(i).unwrap() == token { + return Ok(()); + } + } + tokens.push_back(token.clone()); + storage.set(&DataKey::SupportedTokens, &tokens); + + // Line 135 in contracts/treasury/src/lib.rs + env.events() + .publish((symbol_short!("tok_add"),), (token,)); + Ok(()) + // This will resolve the CI failure and allow you to proceed with implementing the full audit log feature for contract upgrades. + } + + // ----------------------------------------------------------------------- + // Deposits (permissionless) + // ----------------------------------------------------------------------- + + /// Deposit `amount` of `token` into the treasury. + /// + /// Any address may fund the treasury; the caller must have approved the + /// token transfer beforehand (standard SEP-41 allowance flow). + pub fn deposit(env: Env, depositor: Address, token: Address, amount: i128) -> Result<(), Error> { + depositor.require_auth(); + + if amount <= 0 { + return Err(Error::InvInput); + } + + Self::assert_supported_token(&env, &token)?; + + let client = token::Client::new(&env, &token); + client.transfer(&depositor, &env.current_contract_address(), &amount); + + env.events() + .publish((symbol_short!("tr_dep"),), (depositor, token, amount)); + Ok(()) + } + + // ----------------------------------------------------------------------- + // Withdrawals (DAO-gated) + // ----------------------------------------------------------------------- + + /// Execute a governance-approved withdrawal. + /// + /// * `caller` – must be the admin (governance contract) + /// * `proposal_id` – the on-chain proposal that approved this action + /// * `token` – reserve token to transfer + /// * `recipient` – destination address + /// * `amount` – amount to transfer + /// * `memo` – purpose / IPFS reference (audit trail) + /// + /// Each `proposal_id` can only be used once to prevent replay. + pub fn withdraw( + env: Env, + caller: Address, + proposal_id: u64, + token: Address, + recipient: Address, + amount: i128, + memo: Bytes, + ) -> Result