diff --git a/Cargo.lock b/Cargo.lock index 13bbfee5..fbf60796 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -259,11 +259,9 @@ dependencies = [ ] [[package]] -name = "callora-batch-distribute-fuzz" +name = "callora-admin" version = "0.0.0" dependencies = [ - "callora-distribute", - "libfuzzer-sys", "soroban-sdk", ] @@ -276,6 +274,22 @@ dependencies = [ "soroban-sdk", ] +[[package]] +name = "callora-batch-claim" +version = "0.0.0" +dependencies = [ + "soroban-sdk", +] + +[[package]] +name = "callora-batch-distribute-fuzz" +version = "0.0.0" +dependencies = [ + "callora-distribute", + "libfuzzer-sys", + "soroban-sdk", +] + [[package]] name = "callora-checkpoint" version = "0.1.0" @@ -284,6 +298,15 @@ dependencies = [ "soroban-sdk", ] +[[package]] +name = "callora-checkpoint-fuzz" +version = "0.0.0" +dependencies = [ + "callora-checkpoint", + "libfuzzer-sys", + "soroban-sdk", +] + [[package]] name = "callora-cold" version = "0.0.1" @@ -318,6 +341,15 @@ dependencies = [ "soroban-sdk", ] +[[package]] +name = "callora-distribute-fuzz" +version = "0.0.0" +dependencies = [ + "callora-distribute", + "libfuzzer-sys", + "soroban-sdk", +] + [[package]] name = "callora-escrow" version = "0.1.0" @@ -370,15 +402,6 @@ dependencies = [ "soroban-sdk", ] -[[package]] -name = "callora-limits" -version = "0.0.1" -dependencies = [ - "callora-revenue-pool", - "callora-settlement", - "soroban-sdk", -] - [[package]] name = "callora-migrate" version = "0.1.0" @@ -458,8 +481,6 @@ name = "callora-stake" version = "0.1.0" dependencies = [ "callora-stake", -<<<<<<< HEAD -======= "soroban-sdk", ] @@ -467,7 +488,6 @@ dependencies = [ name = "callora-topics" version = "0.0.0" dependencies = [ ->>>>>>> 0e8429e4d47246754ef8cb1c58da2f364676e1b4 "soroban-sdk", ] @@ -505,6 +525,14 @@ dependencies = [ "soroban-sdk", ] +[[package]] +name = "callora-yield" +version = "0.0.1" +dependencies = [ + "callora-revenue-pool", + "soroban-sdk", +] + [[package]] name = "cast" version = "0.3.0" diff --git a/contracts/settlement/src/lib.rs b/contracts/settlement/src/lib.rs index cda866a6..434c9f95 100644 --- a/contracts/settlement/src/lib.rs +++ b/contracts/settlement/src/lib.rs @@ -809,7 +809,7 @@ impl CalloraSettlement { .persistent() .extend_ttl(&balance_key, 50000, 50000); - daily.amount = daily.amount.saturating_add(amount); + daily.amount = daily.amount.checked_add(amount).ok_or(SettlementError::DailyWithdrawCapExceeded)?; env.storage().persistent().set(&today_key, &daily); env.storage() .persistent() diff --git a/contracts/settlement/src/test_overflow_safe_math.rs b/contracts/settlement/src/test_overflow_safe_math.rs index bb07e83d..b1d06128 100644 --- a/contracts/settlement/src/test_overflow_safe_math.rs +++ b/contracts/settlement/src/test_overflow_safe_math.rs @@ -20,6 +20,9 @@ //! `InsufficientDeveloperBalance` rather than wrapping. //! //! 6. **Normal (non-overflow) paths** still produce correct arithmetic results. +//! +//! 7. **`withdraw_developer_balance`** (daily accumulator) — `checked_add` raises +//! `DailyWithdrawCapExceeded` rather than silently saturating on overflow. extern crate std; @@ -82,6 +85,23 @@ fn poke_dev_balance( }); } +/// Directly write a developer's `WithdrawalToday` state in persistent storage. +fn poke_withdrawal_today( + env: &Env, + contract_id: &Address, + developer: &Address, + amount: i128, + day: u64, +) { + use crate::types::DailyWithdrawState; + env.as_contract(contract_id, || { + env.storage().persistent().set( + &StorageKey::WithdrawalToday(developer.clone()), + &DailyWithdrawState { day, amount }, + ); + }); +} + /// Directly write the global pool's `total_balance` in instance storage. fn poke_global_pool_balance(env: &Env, contract_id: &Address, total_balance: i128) { use crate::types::GlobalPool; @@ -304,6 +324,45 @@ fn withdraw_more_than_balance_returns_error() { ); } +// --------------------------------------------------------------------------- +// 6. withdraw_developer_balance — daily accumulator overflow -> DailyWithdrawCapExceeded +// --------------------------------------------------------------------------- + +/// When the developer has no cap set (0 = unlimited), the daily withdrawal +/// accumulator still uses `checked_add` so that an i128 overflow fails +/// loudly instead of silently saturating. +#[test] +fn withdraw_daily_amount_overflow_raises_error() { + let (env, contract_id, _admin, vault, stored_usdc) = setup_with_usdc(); + let client = CalloraSettlementClient::new(&env, &contract_id); + let developer = Address::generate(&env); + + // Seed developer balance with a large amount so the withdrawal itself + // does not fail with InsufficientDeveloperBalance. + poke_dev_balance(&env, &contract_id, &developer, &stored_usdc, 1_000_000i128); + + // Seed the same-day withdrawal accumulator to i128::MAX - 1 so the + // next withdrawal's checked_add will overflow. + let today = env.ledger().timestamp() / 86400; + poke_withdrawal_today(&env, &contract_id, &developer, i128::MAX - 1, today); + + // Mint enough USDC to the contract so the transfer can proceed + // past the liquidity check. + let token_admin_client = soroban_sdk::token::StellarAssetClient::new(&env, &stored_usdc); + token_admin_client.mint(&contract_id, &1_000i128); + + // Withdraw 1 micro-unit — must fail because daily.amount would overflow. + let result = client.try_withdraw_developer_balance(&developer, &1i128, &None); + assert!( + result.is_err(), + "daily accumulator overflow should fail, got {result:?}" + ); +} + +// --------------------------------------------------------------------------- +// 7. Normal withdrawals still work correctly +// --------------------------------------------------------------------------- + /// Exact-balance withdrawal succeeds and leaves the developer at zero. #[test] fn withdraw_exact_balance_succeeds() {