diff --git a/packages/contracts/defindex-adapter/src/lib.rs b/packages/contracts/defindex-adapter/src/lib.rs index 101cbc7c..449255d4 100644 --- a/packages/contracts/defindex-adapter/src/lib.rs +++ b/packages/contracts/defindex-adapter/src/lib.rs @@ -4,8 +4,8 @@ use adapter_common::{ get_usdc, require_not_initialized, require_vault_auth, store_vault_and_usdc, AdapterError, }; use soroban_sdk::{ - contract, contractclient, contracterror, contractimpl, symbol_short, token::TokenClient, vec, - Address, Env, Symbol, Val, Vec, + contract, contractclient, contracterror, contractimpl, panic_with_error, symbol_short, + token::TokenClient, vec, Address, Env, Symbol, Val, Vec, }; // --------------------------------------------------------------------------- @@ -52,8 +52,10 @@ pub trait DefindexVaultInterface { pub enum ContractError { /// `initialize` was called on an adapter that already has a vault set. AlreadyInitialized = 1, + /// An intermediate arithmetic operation would overflow `i128`. + Overflow = 2, /// A state-mutating call was made before `initialize`. - NotInitialized = 2, + NotInitialized = 3, } impl From for ContractError { @@ -143,7 +145,10 @@ impl MeridianDefindexAdapter { let _ = client.deposit(&vec![&env, amount], &vec![&env, 0_i128], &adapter, &true); let shares_after = client.balance(&adapter); - shares_after - shares_before + match shares_after.checked_sub(shares_before) { + Some(delta) => delta, + None => panic_with_error!(&env, ContractError::Overflow), + } } /// Called by the vault to redeem `shares` dfTokens from the DeFindex vault. @@ -236,6 +241,7 @@ mod tests { const MDV_USDC: Symbol = symbol_short!("MDV_USDC"); const MDV_SH: Symbol = symbol_short!("MDV_SH"); const MDV_WAMT: Symbol = symbol_short!("MDV_WAMT"); + const MDV_FAULTY: Symbol = symbol_short!("MDV_FT"); #[contract] pub struct MockDefindexVault; @@ -269,8 +275,14 @@ mod tests { let amount = amounts_desired.get(0).unwrap_or(0); TokenClient::new(&env, &usdc).transfer(&from, &env.current_contract_address(), &amount); + let faulty: bool = env.storage().instance().get(&MDV_FAULTY).unwrap_or(false); let prev: i128 = env.storage().instance().get(&MDV_SH).unwrap_or(0); - env.storage().instance().set(&MDV_SH, &(prev + amount)); + if faulty { + // Simulate a buggy vault whose balance decreases after a deposit. + env.storage().instance().set(&MDV_SH, &0); + } else { + env.storage().instance().set(&MDV_SH, &(prev + amount)); + } Val::VOID.into() } @@ -312,6 +324,17 @@ mod tests { env.storage().instance().get(&MDV_SH).unwrap_or(0) } + // Allows tests to directly set the reported balance, to simulate + // an external vault returning an unexpected balance (e.g. balance + // decreasing after a deposit, which would trigger the Overflow path). + pub fn set_balance(env: Env, balance: i128) { + env.storage().instance().set(&MDV_SH, &balance); + } + + pub fn set_faulty(env: Env, faulty: bool) { + env.storage().instance().set(&MDV_FAULTY, &faulty); + } + pub fn get_asset_amounts_per_shares(env: Env, desired_shares: i128) -> Vec { // 1:1 valuation, matching the deposit/withdraw rate used above. vec![&env, desired_shares] @@ -538,4 +561,36 @@ mod tests { let recipient = Address::generate(&env); adapter.withdraw(&100_0000000_i128, &recipient); } + + #[test] + #[should_panic(expected = "Error(Contract, #2)")] + fn deposit_panics_with_overflow_when_balance_decreases() { + // Simulates a buggy or malicious external vault whose balance() + // returns a lower value after deposit than before. The unchecked + // subtraction would have panicked with an opaque trap; now it + // panics with the typed Overflow error (discriminant #2). + let (env, vault, usdc_id, adapter, dfx) = setup(); + let amount = 100_0000000_i128; + + TokenClient::new(&env, &usdc_id).transfer(&vault, &adapter.address, &amount); + + // First deposit succeeds normally. + adapter.deposit(&amount); + + // Fund the adapter for the second deposit, then flip the mock into + // faulty mode: deposit will reset the share balance to zero, + // so shares_after (0) < shares_before (amount) → Overflow. + TokenClient::new(&env, &usdc_id).transfer(&vault, &adapter.address, &amount); + dfx.set_faulty(&true); + + adapter.deposit(&amount); + } + + #[test] + fn contract_error_has_expected_variants() { + // Compile-time check that the variants exist and are distinct. + let _ = ContractError::AlreadyInitialized; + let _ = ContractError::Overflow; + let _ = ContractError::NotInitialized; + } }