From e4a86073d0fd4f7024b6d7e516884f85076210f2 Mon Sep 17 00:00:00 2001 From: nnennaokoye Date: Mon, 29 Jun 2026 04:46:23 +0100 Subject: [PATCH 1/5] Fix #336: Add paginated get_user_lots to prevent return value size limit overflow --- acbu_savings_vault/src/lib.rs | 67 ++++++++++++++++++++++++++++++++ acbu_savings_vault/tests/test.rs | 51 ++++++++++++++++++++++++ 2 files changed, 118 insertions(+) diff --git a/acbu_savings_vault/src/lib.rs b/acbu_savings_vault/src/lib.rs index 6d610f4..f9de45a 100644 --- a/acbu_savings_vault/src/lib.rs +++ b/acbu_savings_vault/src/lib.rs @@ -492,6 +492,65 @@ impl SavingsVault { ends } + /// Returns deposit lots for a specific term with pagination support. + /// + /// This function prevents exceeding Soroban's return value size limit by allowing + /// callers to request lots in chunks. Use `offset` to skip lots and `limit` to + /// specify the maximum number of lots to return. + /// + /// # Arguments + /// * `user` - The address whose deposit lots to retrieve + /// * `term_seconds` - The term bucket to query + /// * `offset` - Number of lots to skip from the beginning (0-indexed) + /// * `limit` - Maximum number of lots to return (use 0 for no limit, but be cautious) + /// + /// # Returns + /// A paginated Vec of DepositLot structures + pub fn get_user_lots( + env: Env, + user: Address, + term_seconds: u64, + offset: u32, + limit: u32, + ) -> Vec { + let key = (DEPOSIT_KEY, user, term_seconds); + let lots: Vec = env + .storage() + .temporary() + .get(&key) + .unwrap_or(Vec::new(&env)); + + let total_lots = lots.len(); + let offset_u32 = u32::try_from(offset).unwrap_or(0); + + if offset_u32 >= total_lots { + return Vec::new(&env); + } + + let limit_u32 = if limit == 0 || limit > 100 { + // Default to max 100 lots per call to prevent oversized returns + 100 + } else { + limit + }; + + let end_idx = if offset_u32 + limit_u32 < total_lots { + offset_u32 + limit_u32 + } else { + total_lots + }; + let mut result = Vec::new(&env); + + for i in offset_u32..end_idx { + let idx = u32::try_from(i).unwrap_or(0); + if let Some(lot) = lots.get(idx) { + result.push_back(lot); + } + } + + result + } + /// Pause the vault, disabling deposits and withdrawals (admin only). pub fn pause(env: Env) { let admin = Self::load_admin(&env).unwrap_or_else(|e| env.panic_with_error(e)); @@ -554,6 +613,14 @@ impl SavingsVault { .unwrap_or(0) } + /// Check if the contract has been initialized. + /// + /// Backend services can call this before invoking other functions to avoid + /// cryptic storage-not-found errors from uninitialized contracts. + pub fn is_initialized(env: Env) -> bool { + env.storage().instance().has(&SharedDataKey::Version) + } + /// Stage a WASM upgrade to `new_wasm_hash`/`new_version` and start the upgrade /// timelock (admin only). `new_version` must exceed the current version. Apply /// later with [`Self::execute_upgrade`] or abort with [`Self::cancel_upgrade`]. diff --git a/acbu_savings_vault/tests/test.rs b/acbu_savings_vault/tests/test.rs index 03ef952..19fce67 100644 --- a/acbu_savings_vault/tests/test.rs +++ b/acbu_savings_vault/tests/test.rs @@ -486,3 +486,54 @@ fn test_deposit_fails_and_does_not_write_to_storage_when_balance_insufficient() // Verify that NO deposit lot was recorded in storage for the user. assert_eq!(client.get_balance(&user, &term_seconds), 0); } + +/// Issue #336 regression test: get_user_lots with pagination prevents +/// exceeding Soroban's return value size limit when users have many deposit lots. +#[test] +fn test_get_user_lots_pagination() { + let env = Env::default(); + env.mock_all_auths(); + env.ledger().with_mut(|l| l.timestamp = 1_000_000); + + let admin = Address::generate(&env); + let user = Address::generate(&env); + let acbu_token = env + .register_stellar_asset_contract_v2(admin.clone()) + .address(); + + let contract_id = env.register_contract(None, SavingsVault); + let client = SavingsVaultClient::new(&env, &contract_id); + + client.initialize(&admin, &acbu_token, &0i128, &0i128); + + let term_seconds = 3600u64; + let num_lots = 150; // More than default limit of 100 + + let token_admin = soroban_sdk::token::StellarAssetClient::new(&env, &acbu_token); + token_admin.mint(&user, &(num_lots as i128 * 1_000_000)); + + // Create many deposit lots + for _ in 0..num_lots { + client.deposit(&user, &1_000_000i128, &term_seconds); + } + + // Test pagination: first page (offset 0, limit 100) + let page1 = client.get_user_lots(&user, &term_seconds, &0u32, &100u32); + assert_eq!(page1.len(), 100, "First page should return 100 lots"); + + // Test pagination: second page (offset 100, limit 100) + let page2 = client.get_user_lots(&user, &term_seconds, &100u32, &100u32); + assert_eq!(page2.len(), 50, "Second page should return remaining 50 lots"); + + // Test pagination: offset beyond available lots + let page3 = client.get_user_lots(&user, &term_seconds, &200u32, &100u32); + assert_eq!(page3.len(), 0, "Offset beyond available lots should return empty"); + + // Test pagination: limit of 0 should use default max of 100 + let page4 = client.get_user_lots(&user, &term_seconds, &0u32, &0u32); + assert_eq!(page4.len(), 100, "Limit 0 should default to max 100"); + + // Test pagination: custom limit + let page5 = client.get_user_lots(&user, &term_seconds, &0u32, &25u32); + assert_eq!(page5.len(), 25, "Custom limit of 25 should return 25 lots"); +} From ef0fbdcae2a37402f8cd45a0ef711e81e2858e85 Mon Sep 17 00:00:00 2001 From: nnennaokoye Date: Mon, 29 Jun 2026 04:46:55 +0100 Subject: [PATCH 2/5] Fix #337: Add rate limiting to verify_reserves to prevent spam and ledger load --- acbu_reserve_tracker/src/lib.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/acbu_reserve_tracker/src/lib.rs b/acbu_reserve_tracker/src/lib.rs index 15b2fed..c095880 100644 --- a/acbu_reserve_tracker/src/lib.rs +++ b/acbu_reserve_tracker/src/lib.rs @@ -51,6 +51,7 @@ pub struct DataKey { pub version: Symbol, pub pending_admin: Symbol, pub pending_admin_eligible_at: Symbol, + pub last_verify_call: Symbol, } const DATA_KEY: DataKey = DataKey { @@ -62,6 +63,7 @@ const DATA_KEY: DataKey = DataKey { version: symbol_short!("VERSION"), pending_admin: symbol_short!("PEND_ADM"), pending_admin_eligible_at: symbol_short!("PEND_ETA"), + last_verify_call: symbol_short!("LAST_VFY"), }; /// Admin rotation timelock: the pending admin must wait this long before @@ -69,6 +71,10 @@ const DATA_KEY: DataKey = DataKey { /// or malicious transfer. const ADMIN_TIMELOCK_SECONDS: u64 = 86_400; +/// Rate limit for verify_reserves calls to prevent spam and ledger load. +/// Only one verify_reserves call is allowed per this cooldown period. +const VERIFY_RESERVES_COOLDOWN_SECONDS: u64 = 60; // 1 minute cooldown + contractmeta!(key = "version", val = "1"); #[contract] @@ -123,7 +129,24 @@ impl ReserveTrackerContract { /// `acbu_client.balance(&env.current_contract_address())`, which always returned 0 /// because the reserve tracker holds no ACBU — causing reserve checks to be skipped /// entirely (fix for issue #193). + /// + /// Rate limited to prevent spam: only one call per VERIFY_RESERVES_COOLDOWN_SECONDS. pub fn verify_reserves(env: Env) -> bool { + let now = env.ledger().timestamp(); + + // Check rate limit + let last_call: Option = env.storage().instance().get(&DATA_KEY.last_verify_call); + if let Some(last) = last_call { + if now.saturating_sub(last) < VERIFY_RESERVES_COOLDOWN_SECONDS { + // Rate limit exceeded - return false instead of panicking to avoid + // revealing timing information to potential attackers + return false; + } + } + + // Update last call timestamp + env.storage().instance().set(&DATA_KEY.last_verify_call, &now); + let total_acbu_supply = Self::get_total_supply_from_token(&env); if total_acbu_supply == 0 { env.panic_with_error(ReserveTrackerError::ZeroSupply); @@ -385,6 +408,14 @@ impl ReserveTrackerContract { env.storage().instance().get(&DATA_KEY.admin).unwrap() } + /// Check if the contract has been initialized. + /// + /// Backend services can call this before invoking other functions to avoid + /// cryptic storage-not-found errors from uninitialized contracts. + pub fn is_initialized(env: Env) -> bool { + env.storage().instance().has(&SharedDataKey::Version) + } + /// Pending successor, if a transfer is in progress. pub fn get_pending_admin(env: Env) -> Option
{ env.storage().instance().get(&DATA_KEY.pending_admin) From 91a9f27958f4cd8c604dd814758227b9404ac561 Mon Sep 17 00:00:00 2001 From: nnennaokoye Date: Mon, 29 Jun 2026 04:47:30 +0100 Subject: [PATCH 3/5] Fix #354: Add unit tests for pure functions in shared crate --- shared/src/lib.rs | 16 ++++ shared/tests/test_utilities.rs | 168 +++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 shared/tests/test_utilities.rs diff --git a/shared/src/lib.rs b/shared/src/lib.rs index 81c2659..9387cc3 100644 --- a/shared/src/lib.rs +++ b/shared/src/lib.rs @@ -398,3 +398,19 @@ pub fn calculate_deviation(value1: i128, value2: i128) -> i128 { }; (diff * BASIS_POINTS) / value2 } + +/// Check if a contract is initialized by verifying the version key exists. +/// +/// This helper function allows backend services and other contracts to check +/// if a contract has been initialized before calling its functions. This prevents +/// cryptic storage-not-found errors from uninitialized contracts. +/// +/// # Arguments +/// * `env` - The Soroban environment +/// +/// # Returns +/// * `true` if the contract is initialized (version key exists) +/// * `false` if the contract is not initialized +pub fn is_initialized(env: &soroban_sdk::Env) -> bool { + env.storage().instance().has(&DataKey::Version) +} diff --git a/shared/tests/test_utilities.rs b/shared/tests/test_utilities.rs new file mode 100644 index 0000000..804a6e3 --- /dev/null +++ b/shared/tests/test_utilities.rs @@ -0,0 +1,168 @@ +#![cfg(test)] + +use shared::{calculate_amount_after_fee, calculate_fee, calculate_deviation, BASIS_POINTS}; + +#[test] +fn test_calculate_fee_zero_fee_rate() { + let amount = 10_000_000i128; + let fee_rate = 0i128; + assert_eq!(calculate_fee(amount, fee_rate), 0); +} + +#[test] +fn test_calculate_fee_zero_amount() { + let amount = 0i128; + let fee_rate = 300i128; // 3% + assert_eq!(calculate_fee(amount, fee_rate), 0); +} + +#[test] +fn test_calculate_fee_1_percent() { + let amount = 10_000_000i128; + let fee_rate = 100i128; // 1% + let expected = 100_000i128; // 1% of 10M + assert_eq!(calculate_fee(amount, fee_rate), expected); +} + +#[test] +fn test_calculate_fee_3_percent() { + let amount = 10_000_000i128; + let fee_rate = 300i128; // 3% + let expected = 300_000i128; // 3% of 10M + assert_eq!(calculate_fee(amount, fee_rate), expected); +} + +#[test] +fn test_calculate_fee_10_percent() { + let amount = 10_000_000i128; + let fee_rate = 1_000i128; // 10% + let expected = 1_000_000i128; // 10% of 10M + assert_eq!(calculate_fee(amount, fee_rate), expected); +} + +#[test] +fn test_calculate_fee_100_percent() { + let amount = 10_000_000i128; + let fee_rate = BASIS_POINTS; // 100% + let expected = 10_000_000i128; // 100% of 10M + assert_eq!(calculate_fee(amount, fee_rate), expected); +} + +#[test] +fn test_calculate_fee_large_amount() { + let amount = 1_000_000_000_000i128; // 1M USDC (7 decimals) + let fee_rate = 300i128; // 3% + let expected = 30_000_000_000i128; // 3% of 1M + assert_eq!(calculate_fee(amount, fee_rate), expected); +} + +#[test] +fn test_calculate_fee_small_amount() { + let amount = 1i128; + let fee_rate = 300i128; // 3% + // 1 * 300 / 10000 = 0 (integer division) + assert_eq!(calculate_fee(amount, fee_rate), 0); +} + +#[test] +fn test_calculate_amount_after_fee_basic() { + let amount = 10_000_000i128; + let fee_rate = 300i128; // 3% + let fee = 300_000i128; + let expected = 9_700_000i128; + assert_eq!(calculate_amount_after_fee(amount, fee_rate), expected); +} + +#[test] +fn test_calculate_amount_after_fee_zero_fee() { + let amount = 10_000_000i128; + let fee_rate = 0i128; + assert_eq!(calculate_amount_after_fee(amount, fee_rate), amount); +} + +#[test] +fn test_calculate_amount_after_fee_zero_amount() { + let amount = 0i128; + let fee_rate = 300i128; + assert_eq!(calculate_amount_after_fee(amount, fee_rate), 0); +} + +#[test] +fn test_calculate_amount_after_fee_high_fee() { + let amount = 10_000_000i128; + let fee_rate = 5_000i128; // 50% + let expected = 5_000_000i128; + assert_eq!(calculate_amount_after_fee(amount, fee_rate), expected); +} + +#[test] +fn test_calculate_deviation_equal_values() { + let value1 = 100i128; + let value2 = 100i128; + assert_eq!(calculate_deviation(value1, value2), 0); +} + +#[test] +fn test_calculate_deviation_value1_greater() { + let value1 = 150i128; + let value2 = 100i128; + // (150 - 100) * 10000 / 100 = 5000 bps = 50% + assert_eq!(calculate_deviation(value1, value2), 5_000); +} + +#[test] +fn test_calculate_deviation_value2_greater() { + let value1 = 100i128; + let value2 = 150i128; + // (150 - 100) * 10000 / 150 = 3333 bps ≈ 33.33% + assert_eq!(calculate_deviation(value1, value2), 3_333); +} + +#[test] +fn test_calculate_deviation_small_deviation() { + let value1 = 101i128; + let value2 = 100i128; + // (101 - 100) * 10000 / 100 = 100 bps = 1% + assert_eq!(calculate_deviation(value1, value2), 100); +} + +#[test] +fn test_calculate_deviation_large_deviation() { + let value1 = 200i128; + let value2 = 100i128; + // (200 - 100) * 10000 / 100 = 10000 bps = 100% + assert_eq!(calculate_deviation(value1, value2), 10_000); +} + +#[test] +fn test_calculate_deviation_zero_divisor() { + let value1 = 100i128; + let value2 = 0i128; + // Division by zero should return i128::MAX + assert_eq!(calculate_deviation(value1, value2), i128::MAX); +} + +#[test] +fn test_calculate_deviation_both_zero() { + let value1 = 0i128; + let value2 = 0i128; + // Both zero should return i128::MAX (division by zero) + assert_eq!(calculate_deviation(value1, value2), i128::MAX); +} + +#[test] +fn test_calculate_deviation_negative_value1() { + let value1 = -100i128; + let value2 = 100i128; + // (100 - (-100)) * 10000 / 100 = 20000 bps = 200% + assert_eq!(calculate_deviation(value1, value2), 20_000); +} + +#[test] +fn test_calculate_deviation_7decimal_rates() { + // Test with realistic 7-decimal rate values + let value1 = 1_050_000i128; // 1.05 USD + let value2 = 1_000_000i128; // 1.00 USD + // (1_050_000 - 1_000_000) * 10000 / 1_000_000 = 500 bps = 5% + assert_eq!(calculate_deviation(value1, value2), 500); +} From 17c33f756af3bd699e3fa20aea51aa28022309b9 Mon Sep 17 00:00:00 2001 From: nnennaokoye Date: Mon, 29 Jun 2026 04:48:08 +0100 Subject: [PATCH 4/5] Fix #358: Add initialization check helper for multiple contracts --- acbu_minting/src/lib.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/acbu_minting/src/lib.rs b/acbu_minting/src/lib.rs index 1c37ba4..c091b6e 100644 --- a/acbu_minting/src/lib.rs +++ b/acbu_minting/src/lib.rs @@ -1298,6 +1298,14 @@ impl MintingContract { env.storage().instance().get(&DATA_KEY.admin).unwrap() } + /// Check if the contract has been initialized. + /// + /// Backend services can call this before invoking other functions to avoid + /// cryptic storage-not-found errors from uninitialized contracts. + pub fn is_initialized(env: Env) -> bool { + env.storage().instance().has(&SharedDataKey::Version) + } + /// Pending successor, if a transfer is in progress. pub fn get_pending_admin(env: Env) -> Option
{ env.storage().instance().get(&DATA_KEY.pending_admin) From 740540a8163ac722764a8c078f224179cc846b83 Mon Sep 17 00:00:00 2001 From: nnennaokoye Date: Mon, 29 Jun 2026 05:14:12 +0100 Subject: [PATCH 5/5] pr fix --- acbu_reserve_tracker/src/lib.rs | 12 +++------ acbu_savings_vault/src/lib.rs | 5 ++-- acbu_savings_vault/tests/test.rs | 8 +----- shared/tests/test_utilities.rs | 44 ++++++++++++-------------------- 4 files changed, 24 insertions(+), 45 deletions(-) diff --git a/acbu_reserve_tracker/src/lib.rs b/acbu_reserve_tracker/src/lib.rs index c095880..309066e 100644 --- a/acbu_reserve_tracker/src/lib.rs +++ b/acbu_reserve_tracker/src/lib.rs @@ -73,7 +73,7 @@ const ADMIN_TIMELOCK_SECONDS: u64 = 86_400; /// Rate limit for verify_reserves calls to prevent spam and ledger load. /// Only one verify_reserves call is allowed per this cooldown period. -const VERIFY_RESERVES_COOLDOWN_SECONDS: u64 = 60; // 1 minute cooldown +const VERIFY_RESERVES_COOLDOWN_SECONDS: u64 = 60; contractmeta!(key = "version", val = "1"); @@ -133,20 +133,16 @@ impl ReserveTrackerContract { /// Rate limited to prevent spam: only one call per VERIFY_RESERVES_COOLDOWN_SECONDS. pub fn verify_reserves(env: Env) -> bool { let now = env.ledger().timestamp(); - - // Check rate limit + let last_call: Option = env.storage().instance().get(&DATA_KEY.last_verify_call); if let Some(last) = last_call { if now.saturating_sub(last) < VERIFY_RESERVES_COOLDOWN_SECONDS { - // Rate limit exceeded - return false instead of panicking to avoid - // revealing timing information to potential attackers return false; } } - - // Update last call timestamp + env.storage().instance().set(&DATA_KEY.last_verify_call, &now); - + let total_acbu_supply = Self::get_total_supply_from_token(&env); if total_acbu_supply == 0 { env.panic_with_error(ReserveTrackerError::ZeroSupply); diff --git a/acbu_savings_vault/src/lib.rs b/acbu_savings_vault/src/lib.rs index f9de45a..b9a4d82 100644 --- a/acbu_savings_vault/src/lib.rs +++ b/acbu_savings_vault/src/lib.rs @@ -502,7 +502,7 @@ impl SavingsVault { /// * `user` - The address whose deposit lots to retrieve /// * `term_seconds` - The term bucket to query /// * `offset` - Number of lots to skip from the beginning (0-indexed) - /// * `limit` - Maximum number of lots to return (use 0 for no limit, but be cautious) + /// * `limit` - Maximum number of lots to return (0 defaults to max 100) /// /// # Returns /// A paginated Vec of DepositLot structures @@ -522,13 +522,12 @@ impl SavingsVault { let total_lots = lots.len(); let offset_u32 = u32::try_from(offset).unwrap_or(0); - + if offset_u32 >= total_lots { return Vec::new(&env); } let limit_u32 = if limit == 0 || limit > 100 { - // Default to max 100 lots per call to prevent oversized returns 100 } else { limit diff --git a/acbu_savings_vault/tests/test.rs b/acbu_savings_vault/tests/test.rs index 19fce67..a0a24f4 100644 --- a/acbu_savings_vault/tests/test.rs +++ b/acbu_savings_vault/tests/test.rs @@ -507,33 +507,27 @@ fn test_get_user_lots_pagination() { client.initialize(&admin, &acbu_token, &0i128, &0i128); let term_seconds = 3600u64; - let num_lots = 150; // More than default limit of 100 + let num_lots = 150; let token_admin = soroban_sdk::token::StellarAssetClient::new(&env, &acbu_token); token_admin.mint(&user, &(num_lots as i128 * 1_000_000)); - // Create many deposit lots for _ in 0..num_lots { client.deposit(&user, &1_000_000i128, &term_seconds); } - // Test pagination: first page (offset 0, limit 100) let page1 = client.get_user_lots(&user, &term_seconds, &0u32, &100u32); assert_eq!(page1.len(), 100, "First page should return 100 lots"); - // Test pagination: second page (offset 100, limit 100) let page2 = client.get_user_lots(&user, &term_seconds, &100u32, &100u32); assert_eq!(page2.len(), 50, "Second page should return remaining 50 lots"); - // Test pagination: offset beyond available lots let page3 = client.get_user_lots(&user, &term_seconds, &200u32, &100u32); assert_eq!(page3.len(), 0, "Offset beyond available lots should return empty"); - // Test pagination: limit of 0 should use default max of 100 let page4 = client.get_user_lots(&user, &term_seconds, &0u32, &0u32); assert_eq!(page4.len(), 100, "Limit 0 should default to max 100"); - // Test pagination: custom limit let page5 = client.get_user_lots(&user, &term_seconds, &0u32, &25u32); assert_eq!(page5.len(), 25, "Custom limit of 25 should return 25 lots"); } diff --git a/shared/tests/test_utilities.rs b/shared/tests/test_utilities.rs index 804a6e3..7ad87d3 100644 --- a/shared/tests/test_utilities.rs +++ b/shared/tests/test_utilities.rs @@ -12,62 +12,61 @@ fn test_calculate_fee_zero_fee_rate() { #[test] fn test_calculate_fee_zero_amount() { let amount = 0i128; - let fee_rate = 300i128; // 3% + let fee_rate = 300i128; assert_eq!(calculate_fee(amount, fee_rate), 0); } #[test] fn test_calculate_fee_1_percent() { let amount = 10_000_000i128; - let fee_rate = 100i128; // 1% - let expected = 100_000i128; // 1% of 10M + let fee_rate = 100i128; + let expected = 100_000i128; assert_eq!(calculate_fee(amount, fee_rate), expected); } #[test] fn test_calculate_fee_3_percent() { let amount = 10_000_000i128; - let fee_rate = 300i128; // 3% - let expected = 300_000i128; // 3% of 10M + let fee_rate = 300i128; + let expected = 300_000i128; assert_eq!(calculate_fee(amount, fee_rate), expected); } #[test] fn test_calculate_fee_10_percent() { let amount = 10_000_000i128; - let fee_rate = 1_000i128; // 10% - let expected = 1_000_000i128; // 10% of 10M + let fee_rate = 1_000i128; + let expected = 1_000_000i128; assert_eq!(calculate_fee(amount, fee_rate), expected); } #[test] fn test_calculate_fee_100_percent() { let amount = 10_000_000i128; - let fee_rate = BASIS_POINTS; // 100% - let expected = 10_000_000i128; // 100% of 10M + let fee_rate = BASIS_POINTS; + let expected = 10_000_000i128; assert_eq!(calculate_fee(amount, fee_rate), expected); } #[test] fn test_calculate_fee_large_amount() { - let amount = 1_000_000_000_000i128; // 1M USDC (7 decimals) - let fee_rate = 300i128; // 3% - let expected = 30_000_000_000i128; // 3% of 1M + let amount = 1_000_000_000_000i128; + let fee_rate = 300i128; + let expected = 30_000_000_000i128; assert_eq!(calculate_fee(amount, fee_rate), expected); } #[test] fn test_calculate_fee_small_amount() { let amount = 1i128; - let fee_rate = 300i128; // 3% - // 1 * 300 / 10000 = 0 (integer division) + let fee_rate = 300i128; assert_eq!(calculate_fee(amount, fee_rate), 0); } #[test] fn test_calculate_amount_after_fee_basic() { let amount = 10_000_000i128; - let fee_rate = 300i128; // 3% + let fee_rate = 300i128; let fee = 300_000i128; let expected = 9_700_000i128; assert_eq!(calculate_amount_after_fee(amount, fee_rate), expected); @@ -90,7 +89,7 @@ fn test_calculate_amount_after_fee_zero_amount() { #[test] fn test_calculate_amount_after_fee_high_fee() { let amount = 10_000_000i128; - let fee_rate = 5_000i128; // 50% + let fee_rate = 5_000i128; let expected = 5_000_000i128; assert_eq!(calculate_amount_after_fee(amount, fee_rate), expected); } @@ -106,7 +105,6 @@ fn test_calculate_deviation_equal_values() { fn test_calculate_deviation_value1_greater() { let value1 = 150i128; let value2 = 100i128; - // (150 - 100) * 10000 / 100 = 5000 bps = 50% assert_eq!(calculate_deviation(value1, value2), 5_000); } @@ -114,7 +112,6 @@ fn test_calculate_deviation_value1_greater() { fn test_calculate_deviation_value2_greater() { let value1 = 100i128; let value2 = 150i128; - // (150 - 100) * 10000 / 150 = 3333 bps ≈ 33.33% assert_eq!(calculate_deviation(value1, value2), 3_333); } @@ -122,7 +119,6 @@ fn test_calculate_deviation_value2_greater() { fn test_calculate_deviation_small_deviation() { let value1 = 101i128; let value2 = 100i128; - // (101 - 100) * 10000 / 100 = 100 bps = 1% assert_eq!(calculate_deviation(value1, value2), 100); } @@ -130,7 +126,6 @@ fn test_calculate_deviation_small_deviation() { fn test_calculate_deviation_large_deviation() { let value1 = 200i128; let value2 = 100i128; - // (200 - 100) * 10000 / 100 = 10000 bps = 100% assert_eq!(calculate_deviation(value1, value2), 10_000); } @@ -138,7 +133,6 @@ fn test_calculate_deviation_large_deviation() { fn test_calculate_deviation_zero_divisor() { let value1 = 100i128; let value2 = 0i128; - // Division by zero should return i128::MAX assert_eq!(calculate_deviation(value1, value2), i128::MAX); } @@ -146,7 +140,6 @@ fn test_calculate_deviation_zero_divisor() { fn test_calculate_deviation_both_zero() { let value1 = 0i128; let value2 = 0i128; - // Both zero should return i128::MAX (division by zero) assert_eq!(calculate_deviation(value1, value2), i128::MAX); } @@ -154,15 +147,12 @@ fn test_calculate_deviation_both_zero() { fn test_calculate_deviation_negative_value1() { let value1 = -100i128; let value2 = 100i128; - // (100 - (-100)) * 10000 / 100 = 20000 bps = 200% assert_eq!(calculate_deviation(value1, value2), 20_000); } #[test] fn test_calculate_deviation_7decimal_rates() { - // Test with realistic 7-decimal rate values - let value1 = 1_050_000i128; // 1.05 USD - let value2 = 1_000_000i128; // 1.00 USD - // (1_050_000 - 1_000_000) * 10000 / 1_000_000 = 500 bps = 5% + let value1 = 1_050_000i128; + let value2 = 1_000_000i128; assert_eq!(calculate_deviation(value1, value2), 500); }