From 35c9365588b93c22bd63a73a88d6a469c95beda9 Mon Sep 17 00:00:00 2001 From: esteblock Date: Wed, 4 Dec 2024 14:12:43 -0300 Subject: [PATCH 01/10] improve rounding errors --- apps/contracts/vault/src/lib.rs | 52 ++++++++++++++------------ apps/contracts/vault/src/strategies.rs | 2 +- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index 987ed0b..1060005 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -42,7 +42,7 @@ use storage::{ use strategies::{ get_strategy_asset, get_strategy_client, get_strategy_struct, invest_in_strategy, pause_strategy, unpause_strategy, - withdraw_from_strategy, + unwind_from_strategy, }; use token::{internal_burn, write_metadata, VaultToken}; use utils::{ @@ -285,43 +285,47 @@ impl VaultTrait for DeFindexVault { withdrawn_amounts.push_back(requested_withdrawal_amount); continue; } else { - let mut total_withdrawn_for_asset = 0; - // Partial withdrawal from idle funds - total_withdrawn_for_asset += idle_funds; - let remaining_withdrawal_amount = requested_withdrawal_amount - idle_funds; + let mut cumulative_amount_for_asset = idle_funds; + let remaining_amount_to_unwind = requested_withdrawal_amount + .checked_sub(idle_funds) + .unwrap(); - // Withdraw the remaining amount from strategies + + // Withdraw the remaining amount from invested funds in Strategies let total_invested_amount = asset_allocation.invested_amount; - - for strategy_allocation in asset_allocation.strategy_allocations.iter() { + + for (i, strategy_allocation) in asset_allocation.strategy_allocations.iter().enumerate() { // TODO: If strategy is paused, should we skip it? Otherwise, the calculation will go wrong. // if strategy.paused { // continue; // } - // Amount to unwind from strategy - let strategy_withdrawal_share = - (remaining_withdrawal_amount * strategy_allocation.amount) / total_invested_amount; - - if strategy_withdrawal_share > 0 { - withdraw_from_strategy(&e, &strategy_allocation.strategy_address, &strategy_withdrawal_share)?; - TokenClient::new(&e, &asset_address).transfer( - &e.current_contract_address(), - &from, - &strategy_withdrawal_share, - ); - total_withdrawn_for_asset += strategy_withdrawal_share; + // Amount to unwind from strategy. If this is the last strategy, we will take the remaining amount + // due to possible rounding errors. + let strategy_amount_to_withdraw:i128 = if i == (asset_allocation.strategy_allocations.len() as usize) - 1{ + remaining_amount_to_unwind.checked_sub(cumulative_amount_for_asset).unwrap() + } else { + remaining_amount_to_unwind + .checked_mul(strategy_allocation.amount) + .and_then(|result| result.checked_div(total_invested_amount)) + .ok_or_else(|| ContractError::ArithmeticError)? + }; + + if strategy_amount_to_withdraw > 0 { + unwind_from_strategy(&e, &strategy_allocation.strategy_address, &strategy_amount_to_withdraw)?; + cumulative_amount_for_asset += strategy_amount_to_withdraw; } } TokenClient::new(&e, &asset_address).transfer( &e.current_contract_address(), &from, - &total_withdrawn_for_asset, + &cumulative_amount_for_asset, ); - withdrawn_amounts.push_back(total_withdrawn_for_asset); + withdrawn_amounts.push_back(cumulative_amount_for_asset); } } - + + // TODO: Add minimuim amounts for withdrawn_amounts events::emit_withdraw_event(&e, from, withdraw_shares, withdrawn_amounts.clone()); Ok(withdrawn_amounts) @@ -700,7 +704,7 @@ impl VaultManagementTrait for DeFindexVault { match instruction.action { ActionType::Withdraw => match (&instruction.strategy, &instruction.amount) { (Some(strategy_address), Some(amount)) => { - withdraw_from_strategy(&e, strategy_address, amount)?; + unwind_from_strategy(&e, strategy_address, amount)?; } _ => return Err(ContractError::MissingInstructionData), }, diff --git a/apps/contracts/vault/src/strategies.rs b/apps/contracts/vault/src/strategies.rs index 340896b..885c9fb 100644 --- a/apps/contracts/vault/src/strategies.rs +++ b/apps/contracts/vault/src/strategies.rs @@ -124,7 +124,7 @@ pub fn unpause_strategy(e: &Env, strategy_address: Address) -> Result<(), Contra Err(ContractError::StrategyNotFound) } -pub fn withdraw_from_strategy( +pub fn unwind_from_strategy( e: &Env, strategy_address: &Address, amount: &i128, From cbdbe7a0f1b159e061de97ac3ee5b30efff82066 Mon Sep 17 00:00:00 2001 From: esteblock Date: Wed, 4 Dec 2024 16:18:58 -0300 Subject: [PATCH 02/10] docs --- .../defindex_factory/vault/fn.create_contract.html | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 apps/rust_docs/defindex_factory/vault/fn.create_contract.html diff --git a/apps/rust_docs/defindex_factory/vault/fn.create_contract.html b/apps/rust_docs/defindex_factory/vault/fn.create_contract.html new file mode 100644 index 0000000..3071fcc --- /dev/null +++ b/apps/rust_docs/defindex_factory/vault/fn.create_contract.html @@ -0,0 +1,11 @@ + + + + + Redirection + + +

Redirecting to ../../defindex_factory/fn.create_contract.html...

+ + + \ No newline at end of file From c638f1b71b26210cba90ed8ed2432d465b959bc9 Mon Sep 17 00:00:00 2001 From: esteblock Date: Wed, 4 Dec 2024 16:54:16 -0300 Subject: [PATCH 03/10] withdraw function comment --- apps/contracts/vault/src/lib.rs | 60 ++++++++++++++++++++++----------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index 1060005..8972010 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -224,19 +224,41 @@ impl VaultTrait for DeFindexVault { Ok((amounts, shares_to_mint)) } - /// Withdraws assets from the DeFindex Vault by burning Vault Share tokens. - /// - /// This function calculates the amount of assets to withdraw based on the number of Vault Share tokens being burned, - /// then transfers the appropriate assets back to the user, pulling from both idle funds and strategies - /// as needed. - /// - /// # Arguments: - /// * `e` - The environment. - /// * `shares_amount` - The amount of Vault Share tokens to burn for the withdrawal. - /// * `from` - The address of the user requesting the withdrawal. - /// - /// # Returns: - /// * `Result<(), ContractError>` - Ok if successful, otherwise returns a ContractError. + /// Handles the withdrawal process for a specified number of vault shares. + /// + /// This function performs the following steps: + /// 1. Validates the environment and the inputs: + /// - Ensures the contract is initialized. + /// - Checks that the withdrawal amount (`withdraw_shares`) is non-negative. + /// - Verifies the authorization of the `from` address. + /// 2. Collects applicable fees. + /// 3. Calculates the proportionate withdrawal amounts for each asset based on the number of shares. + /// 4. Burns the specified shares from the user's account. + /// 5. Processes the withdrawal for each asset: + /// - First attempts to cover the withdrawal amount using idle funds. + /// - If idle funds are insufficient, unwinds investments from the associated strategies + /// to cover the remaining amount, accounting for rounding errors in the last strategy. + /// 6. Transfers the withdrawn funds to the user's address (`from`). + /// 7. Emits an event to record the withdrawal details. + /// + /// ## Parameters: + /// - `e`: The contract environment (`Env`). + /// - `withdraw_shares`: The number of vault shares to withdraw. + /// - `from`: The address initiating the withdrawal. + /// + /// ## Returns: + /// - A `Result` containing a vector of withdrawn amounts for each asset (`Vec`), + /// or a `ContractError` if the withdrawal fails. + /// + /// ## Errors: + /// - `ContractError::AmountOverTotalSupply`: If the specified shares exceed the total supply. + /// - `ContractError::ArithmeticError`: If any arithmetic operation fails during calculations. + /// - `ContractError::WrongAmountsLength`: If there is a mismatch in asset allocation data. + /// + /// ## TODOs: + /// - Implement minimum amounts for withdrawals to ensure compliance with potential restrictions. + /// - Replace the returned vector with the original `asset_withdrawal_amounts` map for better structure. + fn withdraw( e: Env, withdraw_shares: i128, @@ -260,7 +282,7 @@ impl VaultTrait for DeFindexVault { )?; // Burn the shares after calculating the withdrawal amounts - // this will panic with error if the user does not have enough balance + // This will panic with error if the user does not have enough balance internal_burn(e.clone(), from.clone(), withdraw_shares); // Loop through each asset to handle the withdrawal @@ -283,7 +305,6 @@ impl VaultTrait for DeFindexVault { &requested_withdrawal_amount, ); withdrawn_amounts.push_back(requested_withdrawal_amount); - continue; } else { let mut cumulative_amount_for_asset = idle_funds; let remaining_amount_to_unwind = requested_withdrawal_amount @@ -302,7 +323,7 @@ impl VaultTrait for DeFindexVault { // Amount to unwind from strategy. If this is the last strategy, we will take the remaining amount // due to possible rounding errors. - let strategy_amount_to_withdraw:i128 = if i == (asset_allocation.strategy_allocations.len() as usize) - 1{ + let strategy_amount_to_unwind:i128 = if i == (asset_allocation.strategy_allocations.len() as usize) - 1{ remaining_amount_to_unwind.checked_sub(cumulative_amount_for_asset).unwrap() } else { remaining_amount_to_unwind @@ -311,9 +332,9 @@ impl VaultTrait for DeFindexVault { .ok_or_else(|| ContractError::ArithmeticError)? }; - if strategy_amount_to_withdraw > 0 { - unwind_from_strategy(&e, &strategy_allocation.strategy_address, &strategy_amount_to_withdraw)?; - cumulative_amount_for_asset += strategy_amount_to_withdraw; + if strategy_amount_to_unwind > 0 { + unwind_from_strategy(&e, &strategy_allocation.strategy_address, &strategy_amount_to_unwind)?; + cumulative_amount_for_asset += strategy_amount_to_unwind; } } TokenClient::new(&e, &asset_address).transfer( @@ -326,6 +347,7 @@ impl VaultTrait for DeFindexVault { } // TODO: Add minimuim amounts for withdrawn_amounts + // TODO: Return the asset_withdrawal_amounts Map instead of a vec events::emit_withdraw_event(&e, from, withdraw_shares, withdrawn_amounts.clone()); Ok(withdrawn_amounts) From c986c027174c3c00cc081fc2e15884a319a1fc83 Mon Sep 17 00:00:00 2001 From: esteblock Date: Wed, 4 Dec 2024 17:03:52 -0300 Subject: [PATCH 04/10] test withdraw:not_enough_balance --- .../vault/src/test/vault/withdraw.rs | 73 +++++++++++++++++-- 1 file changed, 68 insertions(+), 5 deletions(-) diff --git a/apps/contracts/vault/src/test/vault/withdraw.rs b/apps/contracts/vault/src/test/vault/withdraw.rs index 5d7f36a..c061792 100644 --- a/apps/contracts/vault/src/test/vault/withdraw.rs +++ b/apps/contracts/vault/src/test/vault/withdraw.rs @@ -14,7 +14,7 @@ use crate::test::{ #[test] -fn test_withdraw_not_yet_initialized() { +fn not_yet_initialized() { let test = DeFindexVaultTest::setup(); let users = DeFindexVaultTest::generate_random_users(&test.env, 1); @@ -24,7 +24,7 @@ fn test_withdraw_not_yet_initialized() { // check that withdraw with negative amount after initialized returns error #[test] -fn test_withdraw_negative_amount() { +fn negative_amount() { let test = DeFindexVaultTest::setup(); test.env.mock_all_auths(); let strategy_params_token0 = create_strategy_params_token0(&test); @@ -55,9 +55,9 @@ fn test_withdraw_negative_amount() { } -// check that withdraw without balance after initialized returns error InsufficientBalance +// check that withdraw without balance after initialized returns error AmountOverTotalSupply #[test] -fn test_withdraw_0_total_supply() { +fn zero_total_supply() { let test = DeFindexVaultTest::setup(); test.env.mock_all_auths(); let strategy_params_token0 = create_strategy_params_token0(&test); @@ -87,6 +87,69 @@ fn test_withdraw_0_total_supply() { assert_eq!(result, Err(Ok(ContractError::AmountOverTotalSupply))); } +// check that withdraw with not enough balance returns error InsufficientBalance +#[test] +fn not_enough_balance() { + let test = DeFindexVaultTest::setup(); + test.env.mock_all_auths(); + let strategy_params_token0 = create_strategy_params_token0(&test); + let assets: Vec = sorobanvec![ + &test.env, + AssetStrategySet { + address: test.token0.address.clone(), + strategies: strategy_params_token0.clone() + } + ]; + + test.defindex_contract.initialize( + &assets, + &test.manager, + &test.emergency_manager, + &test.vault_fee_receiver, + &2000u32, + &test.defindex_protocol_receiver, + &test.defindex_factory, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + ); + + // We need to generate 2 users, to have more total supply than the amount to withdraw + let users = DeFindexVaultTest::generate_random_users(&test.env, 2); + + let amount_to_deposit = 567890i128; + test.token0_admin_client.mint(&users[0], &amount_to_deposit); + test.token0_admin_client.mint(&users[1], &amount_to_deposit); + + assert_eq!(test.token0.balance(&users[0]), amount_to_deposit); + assert_eq!(test.token0.balance(&users[1]), amount_to_deposit); + + // first the user deposits + test.defindex_contract.deposit( + &sorobanvec![&test.env, amount_to_deposit], + &sorobanvec![&test.env, amount_to_deposit], + &users[0], + &false + ); + + test.defindex_contract.deposit( + &sorobanvec![&test.env, amount_to_deposit], + &sorobanvec![&test.env, amount_to_deposit], + &users[1], + &false + ); + + // check that the every user has vault shares + assert_eq!(test.defindex_contract.balance(&users[0]), amount_to_deposit - 1000); + assert_eq!(test.defindex_contract.balance(&users[1]), amount_to_deposit); + // check that total supply of vault shares is indeed amount_to_deposit*2 + assert_eq!(test.defindex_contract.total_supply(), amount_to_deposit*2); + + // now user 0 tries to withdraw amount_to_deposit - 1000 +1 (more that it has) + + let result = test.defindex_contract.try_withdraw(&(amount_to_deposit - 1000 +1), &users[0]); + assert_eq!(result, Err(Ok(ContractError::InsufficientBalance))); +} + #[test] fn withdraw_from_idle_success() { let test = DeFindexVaultTest::setup(); @@ -279,6 +342,6 @@ fn withdraw_from_strategy_success() { // test withdraw without mock all auths #[test] -fn test_withdraw_from_strategy_success_no_mock_all_auths() { +fn from_strategy_success_no_mock_all_auths() { todo!(); } \ No newline at end of file From 9f33f283335b13611157e2f26ebf078aa0361e81 Mon Sep 17 00:00:00 2001 From: esteblock Date: Thu, 5 Dec 2024 09:20:45 -0300 Subject: [PATCH 05/10] test withdraw with idle assets and 2 assets! --- .../vault/src/test/vault/withdraw.rs | 286 +++++++++++++++++- 1 file changed, 282 insertions(+), 4 deletions(-) diff --git a/apps/contracts/vault/src/test/vault/withdraw.rs b/apps/contracts/vault/src/test/vault/withdraw.rs index c061792..c7e08ec 100644 --- a/apps/contracts/vault/src/test/vault/withdraw.rs +++ b/apps/contracts/vault/src/test/vault/withdraw.rs @@ -1,13 +1,14 @@ -use soroban_sdk::{vec as sorobanvec, String, Vec}; +use soroban_sdk::{vec as sorobanvec, String, Vec, Map}; // use super::hodl_strategy::StrategyError; use crate::test::{ create_strategy_params_token0, + create_strategy_params_token1, defindex_vault::{ AssetStrategySet, AssetInvestmentAllocation, StrategyAllocation, - ContractError + ContractError, CurrentAssetInvestmentAllocation }, DeFindexVaultTest, }; @@ -151,7 +152,7 @@ fn not_enough_balance() { } #[test] -fn withdraw_from_idle_success() { +fn from_idle_one_strategy_success() { let test = DeFindexVaultTest::setup(); test.env.mock_all_auths(); let strategy_params_token0 = create_strategy_params_token0(&test); @@ -211,10 +212,30 @@ fn withdraw_from_idle_success() { let strategy_balance = test.token0.balance(&test.strategy_client_token0.address); assert_eq!(strategy_balance, 0); - // Df balance of user should be equal to deposited amount + // Df balance of user should be equal to deposited amount - 1000 let df_balance = test.defindex_contract.balance(&users[0]); assert_eq!(df_balance, amount_to_deposit - 1000 ); // 1000 gets locked in the vault forever + // check total manage funds + let mut total_managed_funds_expected = Map::new(&test.env); + let strategy_investments_expected = sorobanvec![&test.env, StrategyAllocation { + strategy_address: test.strategy_client_token0.address.clone(), + amount: 0, //funds has not been invested yet! + }]; + total_managed_funds_expected.set(test.token0.address.clone(), + CurrentAssetInvestmentAllocation { + asset: test.token0.address.clone(), + total_amount: amount_to_deposit, + idle_amount: amount_to_deposit, + invested_amount: 0i128, + strategy_allocations: strategy_investments_expected, + } + ); + + let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); + assert_eq!(total_managed_funds, total_managed_funds_expected); + + // user decides to withdraw a portion of deposited amount let amount_to_withdraw = 123456i128; test.defindex_contract @@ -241,6 +262,26 @@ fn withdraw_from_idle_success() { let df_balance = test.defindex_contract.balance(&users[0]); assert_eq!(df_balance, amount_to_deposit - amount_to_withdraw - 1000); + // check total manage funds + let mut total_managed_funds_expected = Map::new(&test.env); + let strategy_investments_expected = sorobanvec![&test.env, StrategyAllocation { + strategy_address: test.strategy_client_token0.address.clone(), + amount: 0, //funds has not been invested yet! + }]; + total_managed_funds_expected.set(test.token0.address.clone(), + CurrentAssetInvestmentAllocation { + asset: test.token0.address.clone(), + total_amount: amount_to_deposit - amount_to_withdraw, + idle_amount: amount_to_deposit - amount_to_withdraw, + invested_amount: 0i128, + strategy_allocations: strategy_investments_expected, + } + ); + + let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); + assert_eq!(total_managed_funds, total_managed_funds_expected); + + // user tries to withdraw more than deposited amount let amount_to_withdraw_more = amount_to_deposit + 1; let result = test @@ -262,6 +303,243 @@ fn withdraw_from_idle_success() { let user_balance = test.token0.balance(&users[0]); assert_eq!(user_balance, amount - 1000); + + // check total manage funds + let mut total_managed_funds_expected = Map::new(&test.env); + let strategy_investments_expected = sorobanvec![&test.env, StrategyAllocation { + strategy_address: test.strategy_client_token0.address.clone(), + amount: 0, //funds has not been invested yet! + }]; + total_managed_funds_expected.set(test.token0.address.clone(), + CurrentAssetInvestmentAllocation { + asset: test.token0.address.clone(), + total_amount: 1000, + idle_amount: 1000, + invested_amount: 0i128, + strategy_allocations: strategy_investments_expected, + } + ); + + let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); + assert_eq!(total_managed_funds, total_managed_funds_expected); + +} + +#[test] +fn from_idle_two_assets_success() { + let test = DeFindexVaultTest::setup(); + test.env.mock_all_auths(); + let strategy_params_token0 = create_strategy_params_token0(&test); + let strategy_params_token1 = create_strategy_params_token1(&test); + let assets: Vec = sorobanvec![ + &test.env, + AssetStrategySet { + address: test.token0.address.clone(), + strategies: strategy_params_token0.clone() + }, + AssetStrategySet { + address: test.token1.address.clone(), + strategies: strategy_params_token1.clone() + } + ]; + + test.defindex_contract.initialize( + &assets, + &test.manager, + &test.emergency_manager, + &test.vault_fee_receiver, + &2000u32, + &test.defindex_protocol_receiver, + &test.defindex_factory, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + ); + let amount = 1234567890i128; + + let users = DeFindexVaultTest::generate_random_users(&test.env, 1); + + test.token0_admin_client.mint(&users[0], &amount); + test.token1_admin_client.mint(&users[0], &amount); + assert_eq!(test.token0.balance(&users[0]), amount); + assert_eq!(test.token0.balance(&users[0]), amount); + + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, 0i128); + + // Deposit + let amount_to_deposit_0 = 567890i128; + let amount_to_deposit_1 = 987654i128; + test.defindex_contract.deposit( + &sorobanvec![&test.env, amount_to_deposit_0, amount_to_deposit_1], + &sorobanvec![&test.env, amount_to_deposit_0, amount_to_deposit_1], + &users[0], + &false + ); + + // Check Balances after deposit + + // Token balance of user + assert_eq!(test.token0.balance(&users[0]), amount - amount_to_deposit_0); + assert_eq!(test.token1.balance(&users[0]), amount - amount_to_deposit_1); + + // Token balance of vault should be amount_to_deposit + // Because balances are still in indle, balances are not in strategy, but in idle + + assert_eq!(test.token0.balance(&test.defindex_contract.address), amount_to_deposit_0); + assert_eq!(test.token1.balance(&test.defindex_contract.address), amount_to_deposit_1); + + // Token balance of hodl strategy should be 0 (all in idle) + assert_eq!(test.token0.balance(&test.strategy_client_token0.address), 0); + assert_eq!(test.token1.balance(&test.strategy_client_token1.address), 0); + + // Df balance of user should be equal to amount_to_deposit_0+amount_to_deposit_1 - 1000 + // 567890+987654-1000 = 1554544 + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, 1554544 ); + + // check total manage funds + let mut total_managed_funds_expected = Map::new(&test.env); + total_managed_funds_expected.set(test.token0.address.clone(), + CurrentAssetInvestmentAllocation { + asset: test.token0.address.clone(), + total_amount: amount_to_deposit_0, + idle_amount: amount_to_deposit_0, + invested_amount: 0i128, + strategy_allocations: sorobanvec![&test.env, + StrategyAllocation { + strategy_address: test.strategy_client_token0.address.clone(), + amount: 0, //funds has not been invested yet! + }], + } + ); + + total_managed_funds_expected.set(test.token1.address.clone(), + CurrentAssetInvestmentAllocation { + asset: test.token1.address.clone(), + total_amount: amount_to_deposit_1, + idle_amount: amount_to_deposit_1, + invested_amount: 0i128, + strategy_allocations: sorobanvec![&test.env, + StrategyAllocation { + strategy_address: test.strategy_client_token1.address.clone(), + amount: 0, //funds has not been invested yet! + }], + } + ); + let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); + assert_eq!(total_managed_funds, total_managed_funds_expected); + + // user decides to withdraw a portion of their vault shares + // from 1554544 it will withdraw 123456. + // total shares = 567890+987654 = 1555544 + // asset 0 = withdaw_shares*total_asset_0/total_shares = 123456*567890/1555544 = 45070.681279347 = 45070 + // asset 1 = withdaw_shares*total_asset_1/total_shares = 123456*987654/1555544 = 78385.318720653 = 78385 + + let amount_to_withdraw = 123456i128; + let result = test.defindex_contract + .withdraw(&amount_to_withdraw, &users[0]); + + let expected_result = sorobanvec![&test.env, 45070, 78385]; + assert_eq!(result, expected_result); + + // Token balance of user + assert_eq!(test.token0.balance(&users[0]), amount - amount_to_deposit_0 + 45070); + assert_eq!(test.token1.balance(&users[0]), amount - amount_to_deposit_1 + 78385); + + // Token balance of vault (still idle) + + assert_eq!(test.token0.balance(&test.defindex_contract.address), amount_to_deposit_0 - 45070); + assert_eq!(test.token1.balance(&test.defindex_contract.address), amount_to_deposit_1 - 78385); + + // Token balance of hodl strategy should be 0 (all in idle) + assert_eq!(test.token0.balance(&test.strategy_client_token0.address), 0); + assert_eq!(test.token1.balance(&test.strategy_client_token1.address), 0); + + // Df balance of user should be equal to amount_to_deposit_0+amount_to_deposit_1 - 1000 - 123456 + // 567890+987654-1000 -123456 = 1434088 + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, 1431088 ); + +// // Token balance of user should be amount - amount_to_deposit + amount_to_withdraw +// let user_balance = test.token0.balance(&users[0]); +// assert_eq!( +// user_balance, +// amount - amount_to_deposit + amount_to_withdraw +// ); + +// // Token balance of vault should be amount_to_deposit - amount_to_withdraw +// let vault_balance = test.token0.balance(&test.defindex_contract.address); +// assert_eq!(vault_balance, amount_to_deposit - amount_to_withdraw); + +// // Token balance of hodl strategy should be 0 (all in idle) +// let strategy_balance = test.token0.balance(&test.strategy_client_token0.address); +// assert_eq!(strategy_balance, 0); + +// // Df balance of user should be equal to deposited amount - amount_to_withdraw - 1000 +// let df_balance = test.defindex_contract.balance(&users[0]); +// assert_eq!(df_balance, amount_to_deposit - amount_to_withdraw - 1000); + +// // check total manage funds +// let mut total_managed_funds_expected = Map::new(&test.env); +// let strategy_investments_expected = sorobanvec![&test.env, StrategyAllocation { +// strategy_address: test.strategy_client_token0.address.clone(), +// amount: 0, //funds has not been invested yet! +// }]; +// total_managed_funds_expected.set(test.token0.address.clone(), +// CurrentAssetInvestmentAllocation { +// asset: test.token0.address.clone(), +// total_amount: amount_to_deposit - amount_to_withdraw, +// idle_amount: amount_to_deposit - amount_to_withdraw, +// invested_amount: 0i128, +// strategy_allocations: strategy_investments_expected, +// } +// ); + +// let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); +// assert_eq!(total_managed_funds, total_managed_funds_expected); + + +// // user tries to withdraw more than deposited amount +// let amount_to_withdraw_more = amount_to_deposit + 1; +// let result = test +// .defindex_contract +// .try_withdraw(&amount_to_withdraw_more, &users[0]); + +// assert_eq!(result, +// Err(Ok(ContractError::AmountOverTotalSupply))); + + +// // // withdraw remaining balance +// let result= test.defindex_contract +// .withdraw(&(amount_to_deposit - amount_to_withdraw - 1000), &users[0]); + +// assert_eq!(result, sorobanvec![&test.env, amount_to_deposit - amount_to_withdraw - 1000]); + +// let df_balance = test.defindex_contract.balance(&users[0]); +// assert_eq!(df_balance, 0i128); + +// let user_balance = test.token0.balance(&users[0]); +// assert_eq!(user_balance, amount - 1000); + +// // check total manage funds +// let mut total_managed_funds_expected = Map::new(&test.env); +// let strategy_investments_expected = sorobanvec![&test.env, StrategyAllocation { +// strategy_address: test.strategy_client_token0.address.clone(), +// amount: 0, //funds has not been invested yet! +// }]; +// total_managed_funds_expected.set(test.token0.address.clone(), +// CurrentAssetInvestmentAllocation { +// asset: test.token0.address.clone(), +// total_amount: 1000, +// idle_amount: 1000, +// invested_amount: 0i128, +// strategy_allocations: strategy_investments_expected, +// } +// ); + +// let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); +// assert_eq!(total_managed_funds, total_managed_funds_expected); + } #[test] From 3dc1533a998d357b70e085caa073d5989af3a969 Mon Sep 17 00:00:00 2001 From: esteblock Date: Thu, 5 Dec 2024 09:54:39 -0300 Subject: [PATCH 06/10] solve withdraw return vector order --- apps/contracts/vault/src/lib.rs | 108 +++++++++--------- .../vault/src/test/vault/withdraw.rs | 65 ++++++++--- 2 files changed, 100 insertions(+), 73 deletions(-) diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index 8972010..0ba6e5f 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -258,7 +258,7 @@ impl VaultTrait for DeFindexVault { /// ## TODOs: /// - Implement minimum amounts for withdrawals to ensure compliance with potential restrictions. /// - Replace the returned vector with the original `asset_withdrawal_amounts` map for better structure. - + /// - avoid the usage of a Map, choose between using map or vector fn withdraw( e: Env, withdraw_shares: i128, @@ -273,6 +273,7 @@ impl VaultTrait for DeFindexVault { collect_fees(&e)?; // Calculate the withdrawal amounts for each asset based on the share amounts + let assets = get_assets(&e); // TODO: let total_managed_funds = fetch_total_managed_funds(&e); let asset_withdrawal_amounts = calculate_asset_amounts_per_vault_shares( @@ -285,66 +286,65 @@ impl VaultTrait for DeFindexVault { // This will panic with error if the user does not have enough balance internal_burn(e.clone(), from.clone(), withdraw_shares); + let assets = get_assets(&e); // Use assets for iteration order // Loop through each asset to handle the withdrawal let mut withdrawn_amounts: Vec = Vec::new(&e); - for (asset_address, requested_withdrawal_amount) in asset_withdrawal_amounts.iter() { - - let asset_allocation = total_managed_funds - .get(asset_address.clone()) - .unwrap_or_else(|| panic_with_error!(&e, ContractError::WrongAmountsLength)); - // Check idle funds for this asset - let idle_funds = asset_allocation.idle_amount; - - // Withdraw from idle funds first - if idle_funds >= requested_withdrawal_amount { - // Idle funds cover the full amount - TokenClient::new(&e, &asset_address).transfer( - &e.current_contract_address(), - &from, - &requested_withdrawal_amount, - ); - withdrawn_amounts.push_back(requested_withdrawal_amount); - } else { - let mut cumulative_amount_for_asset = idle_funds; - let remaining_amount_to_unwind = requested_withdrawal_amount - .checked_sub(idle_funds) - .unwrap(); - - - // Withdraw the remaining amount from invested funds in Strategies - let total_invested_amount = asset_allocation.invested_amount; - - for (i, strategy_allocation) in asset_allocation.strategy_allocations.iter().enumerate() { - // TODO: If strategy is paused, should we skip it? Otherwise, the calculation will go wrong. - // if strategy.paused { - // continue; - // } - - // Amount to unwind from strategy. If this is the last strategy, we will take the remaining amount - // due to possible rounding errors. - let strategy_amount_to_unwind:i128 = if i == (asset_allocation.strategy_allocations.len() as usize) - 1{ - remaining_amount_to_unwind.checked_sub(cumulative_amount_for_asset).unwrap() - } else { - remaining_amount_to_unwind - .checked_mul(strategy_allocation.amount) - .and_then(|result| result.checked_div(total_invested_amount)) - .ok_or_else(|| ContractError::ArithmeticError)? - }; - - if strategy_amount_to_unwind > 0 { - unwind_from_strategy(&e, &strategy_allocation.strategy_address, &strategy_amount_to_unwind)?; - cumulative_amount_for_asset += strategy_amount_to_unwind; + for asset in assets.iter() { // Use assets instead of asset_withdrawal_amounts + let asset_address = &asset.address; + + if let Some(requested_withdrawal_amount) = asset_withdrawal_amounts.get(asset_address.clone()) { + let asset_allocation = total_managed_funds + .get(asset_address.clone()) + .unwrap_or_else(|| panic_with_error!(&e, ContractError::WrongAmountsLength)); + + let idle_funds = asset_allocation.idle_amount; + + if idle_funds >= requested_withdrawal_amount { + TokenClient::new(&e, asset_address).transfer( + &e.current_contract_address(), + &from, + &requested_withdrawal_amount, + ); + withdrawn_amounts.push_back(requested_withdrawal_amount); + } else { + let mut cumulative_amount_for_asset = idle_funds; + let remaining_amount_to_unwind = requested_withdrawal_amount + .checked_sub(idle_funds) + .unwrap(); + + let total_invested_amount = asset_allocation.invested_amount; + + for (i, strategy_allocation) in asset_allocation.strategy_allocations.iter().enumerate() { + let strategy_amount_to_unwind: i128 = if i == (asset_allocation.strategy_allocations.len() as usize) - 1 { + remaining_amount_to_unwind + .checked_sub(cumulative_amount_for_asset) + .unwrap() + } else { + remaining_amount_to_unwind + .checked_mul(strategy_allocation.amount) + .and_then(|result| result.checked_div(total_invested_amount)) + .unwrap_or(0) + }; + + if strategy_amount_to_unwind > 0 { + unwind_from_strategy(&e, &strategy_allocation.strategy_address, &strategy_amount_to_unwind)?; + cumulative_amount_for_asset += strategy_amount_to_unwind; + } } + + TokenClient::new(&e, asset_address).transfer( + &e.current_contract_address(), + &from, + &cumulative_amount_for_asset, + ); + withdrawn_amounts.push_back(cumulative_amount_for_asset); } - TokenClient::new(&e, &asset_address).transfer( - &e.current_contract_address(), - &from, - &cumulative_amount_for_asset, - ); - withdrawn_amounts.push_back(cumulative_amount_for_asset); + } else { + withdrawn_amounts.push_back(0); // No withdrawal for this asset } } + // TODO: Add minimuim amounts for withdrawn_amounts // TODO: Return the asset_withdrawal_amounts Map instead of a vec diff --git a/apps/contracts/vault/src/test/vault/withdraw.rs b/apps/contracts/vault/src/test/vault/withdraw.rs index c7e08ec..2883658 100644 --- a/apps/contracts/vault/src/test/vault/withdraw.rs +++ b/apps/contracts/vault/src/test/vault/withdraw.rs @@ -7,7 +7,8 @@ use crate::test::{ defindex_vault::{ AssetStrategySet, AssetInvestmentAllocation, - StrategyAllocation, + StrategyAllocation, + Strategy, ContractError, CurrentAssetInvestmentAllocation }, DeFindexVaultTest, @@ -402,8 +403,8 @@ fn from_idle_two_assets_success() { total_managed_funds_expected.set(test.token0.address.clone(), CurrentAssetInvestmentAllocation { asset: test.token0.address.clone(), - total_amount: amount_to_deposit_0, - idle_amount: amount_to_deposit_0, + total_amount: 567890i128, + idle_amount: 567890i128, invested_amount: 0i128, strategy_allocations: sorobanvec![&test.env, StrategyAllocation { @@ -416,8 +417,8 @@ fn from_idle_two_assets_success() { total_managed_funds_expected.set(test.token1.address.clone(), CurrentAssetInvestmentAllocation { asset: test.token1.address.clone(), - total_amount: amount_to_deposit_1, - idle_amount: amount_to_deposit_1, + total_amount: 987654i128, + idle_amount: 987654i128, invested_amount: 0i128, strategy_allocations: sorobanvec![&test.env, StrategyAllocation { @@ -437,28 +438,54 @@ fn from_idle_two_assets_success() { let amount_to_withdraw = 123456i128; let result = test.defindex_contract - .withdraw(&amount_to_withdraw, &users[0]); - - let expected_result = sorobanvec![&test.env, 45070, 78385]; - assert_eq!(result, expected_result); + .withdraw(&amount_to_withdraw, &users[0]); + + // expected asset vec Vec + // pub struct AssetStrategySet { + // pub address: Address, + // pub strategies: Vec, + // } + // pub struct Strategy { + // pub address: Address, + // pub name: String, + // pub paused: bool, + // } + let expected_asset_vec = sorobanvec![&test.env, AssetStrategySet { + address: test.token0.address.clone(), + strategies: sorobanvec![&test.env, Strategy { + address: test.strategy_client_token0.address.clone(), + name: String::from_str(&test.env, "Strategy 1"), + paused: false, + }], + }, AssetStrategySet { + address: test.token1.address.clone(), + strategies: sorobanvec![&test.env, Strategy { + address: test.strategy_client_token1.address.clone(), + name: String::from_str(&test.env, "Strategy 1"), + paused: false, + }], + }]; + // assert_eq!(test.defindex_contract.get_assets(), expected_asset_vec); + // let expected_result = sorobanvec![&test.env, 45070, 78385]; + // assert_eq!(result, expected_result); // Token balance of user assert_eq!(test.token0.balance(&users[0]), amount - amount_to_deposit_0 + 45070); assert_eq!(test.token1.balance(&users[0]), amount - amount_to_deposit_1 + 78385); - // Token balance of vault (still idle) + // // Token balance of vault (still idle) - assert_eq!(test.token0.balance(&test.defindex_contract.address), amount_to_deposit_0 - 45070); - assert_eq!(test.token1.balance(&test.defindex_contract.address), amount_to_deposit_1 - 78385); + // assert_eq!(test.token0.balance(&test.defindex_contract.address), amount_to_deposit_0 - 45070); + // assert_eq!(test.token1.balance(&test.defindex_contract.address), amount_to_deposit_1 - 78385); - // Token balance of hodl strategy should be 0 (all in idle) - assert_eq!(test.token0.balance(&test.strategy_client_token0.address), 0); - assert_eq!(test.token1.balance(&test.strategy_client_token1.address), 0); + // // Token balance of hodl strategy should be 0 (all in idle) + // assert_eq!(test.token0.balance(&test.strategy_client_token0.address), 0); + // assert_eq!(test.token1.balance(&test.strategy_client_token1.address), 0); - // Df balance of user should be equal to amount_to_deposit_0+amount_to_deposit_1 - 1000 - 123456 - // 567890+987654-1000 -123456 = 1434088 - let df_balance = test.defindex_contract.balance(&users[0]); - assert_eq!(df_balance, 1431088 ); + // // Df balance of user should be equal to amount_to_deposit_0+amount_to_deposit_1 - 1000 - 123456 + // // 567890+987654-1000 -123456 = 1434088 + // let df_balance = test.defindex_contract.balance(&users[0]); + // assert_eq!(df_balance, 1431088 ); // // Token balance of user should be amount - amount_to_deposit + amount_to_withdraw // let user_balance = test.token0.balance(&users[0]); From eba01582af22180fa6ca6ae8d754eae50cfc4ad1 Mon Sep 17 00:00:00 2001 From: esteblock Date: Thu, 5 Dec 2024 10:18:21 -0300 Subject: [PATCH 07/10] test withdraw from_idle_two_assets_success --- apps/contracts/vault/src/lib.rs | 5 +- .../vault/src/test/vault/withdraw.rs | 145 ++++++------------ apps/contracts/vault/src/token/contract.rs | 3 +- 3 files changed, 52 insertions(+), 101 deletions(-) diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index 0ba6e5f..0d8be26 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -2,7 +2,7 @@ use deposit::{generate_and_execute_investments, process_deposit}; use soroban_sdk::{ contract, contractimpl, panic_with_error, - token::{TokenClient, TokenInterface}, + token::{TokenClient}, Address, Env, Map, String, Vec, }; use soroban_token_sdk::metadata::TokenMetadata; @@ -44,7 +44,7 @@ use strategies::{ get_strategy_struct, invest_in_strategy, pause_strategy, unpause_strategy, unwind_from_strategy, }; -use token::{internal_burn, write_metadata, VaultToken}; +use token::{internal_burn, write_metadata}; use utils::{ calculate_asset_amounts_per_vault_shares, check_initialized, @@ -273,7 +273,6 @@ impl VaultTrait for DeFindexVault { collect_fees(&e)?; // Calculate the withdrawal amounts for each asset based on the share amounts - let assets = get_assets(&e); // TODO: let total_managed_funds = fetch_total_managed_funds(&e); let asset_withdrawal_amounts = calculate_asset_amounts_per_vault_shares( diff --git a/apps/contracts/vault/src/test/vault/withdraw.rs b/apps/contracts/vault/src/test/vault/withdraw.rs index 2883658..a6c8666 100644 --- a/apps/contracts/vault/src/test/vault/withdraw.rs +++ b/apps/contracts/vault/src/test/vault/withdraw.rs @@ -465,107 +465,60 @@ fn from_idle_two_assets_success() { paused: false, }], }]; - // assert_eq!(test.defindex_contract.get_assets(), expected_asset_vec); - // let expected_result = sorobanvec![&test.env, 45070, 78385]; - // assert_eq!(result, expected_result); + assert_eq!(test.defindex_contract.get_assets(), expected_asset_vec); + let expected_result = sorobanvec![&test.env, 45070, 78385]; + assert_eq!(result, expected_result); // Token balance of user assert_eq!(test.token0.balance(&users[0]), amount - amount_to_deposit_0 + 45070); assert_eq!(test.token1.balance(&users[0]), amount - amount_to_deposit_1 + 78385); - // // Token balance of vault (still idle) - - // assert_eq!(test.token0.balance(&test.defindex_contract.address), amount_to_deposit_0 - 45070); - // assert_eq!(test.token1.balance(&test.defindex_contract.address), amount_to_deposit_1 - 78385); - - // // Token balance of hodl strategy should be 0 (all in idle) - // assert_eq!(test.token0.balance(&test.strategy_client_token0.address), 0); - // assert_eq!(test.token1.balance(&test.strategy_client_token1.address), 0); - - // // Df balance of user should be equal to amount_to_deposit_0+amount_to_deposit_1 - 1000 - 123456 - // // 567890+987654-1000 -123456 = 1434088 - // let df_balance = test.defindex_contract.balance(&users[0]); - // assert_eq!(df_balance, 1431088 ); - -// // Token balance of user should be amount - amount_to_deposit + amount_to_withdraw -// let user_balance = test.token0.balance(&users[0]); -// assert_eq!( -// user_balance, -// amount - amount_to_deposit + amount_to_withdraw -// ); - -// // Token balance of vault should be amount_to_deposit - amount_to_withdraw -// let vault_balance = test.token0.balance(&test.defindex_contract.address); -// assert_eq!(vault_balance, amount_to_deposit - amount_to_withdraw); - -// // Token balance of hodl strategy should be 0 (all in idle) -// let strategy_balance = test.token0.balance(&test.strategy_client_token0.address); -// assert_eq!(strategy_balance, 0); - -// // Df balance of user should be equal to deposited amount - amount_to_withdraw - 1000 -// let df_balance = test.defindex_contract.balance(&users[0]); -// assert_eq!(df_balance, amount_to_deposit - amount_to_withdraw - 1000); - -// // check total manage funds -// let mut total_managed_funds_expected = Map::new(&test.env); -// let strategy_investments_expected = sorobanvec![&test.env, StrategyAllocation { -// strategy_address: test.strategy_client_token0.address.clone(), -// amount: 0, //funds has not been invested yet! -// }]; -// total_managed_funds_expected.set(test.token0.address.clone(), -// CurrentAssetInvestmentAllocation { -// asset: test.token0.address.clone(), -// total_amount: amount_to_deposit - amount_to_withdraw, -// idle_amount: amount_to_deposit - amount_to_withdraw, -// invested_amount: 0i128, -// strategy_allocations: strategy_investments_expected, -// } -// ); - -// let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); -// assert_eq!(total_managed_funds, total_managed_funds_expected); - - -// // user tries to withdraw more than deposited amount -// let amount_to_withdraw_more = amount_to_deposit + 1; -// let result = test -// .defindex_contract -// .try_withdraw(&amount_to_withdraw_more, &users[0]); - -// assert_eq!(result, -// Err(Ok(ContractError::AmountOverTotalSupply))); - - -// // // withdraw remaining balance -// let result= test.defindex_contract -// .withdraw(&(amount_to_deposit - amount_to_withdraw - 1000), &users[0]); - -// assert_eq!(result, sorobanvec![&test.env, amount_to_deposit - amount_to_withdraw - 1000]); - -// let df_balance = test.defindex_contract.balance(&users[0]); -// assert_eq!(df_balance, 0i128); - -// let user_balance = test.token0.balance(&users[0]); -// assert_eq!(user_balance, amount - 1000); - -// // check total manage funds -// let mut total_managed_funds_expected = Map::new(&test.env); -// let strategy_investments_expected = sorobanvec![&test.env, StrategyAllocation { -// strategy_address: test.strategy_client_token0.address.clone(), -// amount: 0, //funds has not been invested yet! -// }]; -// total_managed_funds_expected.set(test.token0.address.clone(), -// CurrentAssetInvestmentAllocation { -// asset: test.token0.address.clone(), -// total_amount: 1000, -// idle_amount: 1000, -// invested_amount: 0i128, -// strategy_allocations: strategy_investments_expected, -// } -// ); - -// let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); -// assert_eq!(total_managed_funds, total_managed_funds_expected); + // Token balance of vault (still idle) + + assert_eq!(test.token0.balance(&test.defindex_contract.address), amount_to_deposit_0 - 45070); + assert_eq!(test.token1.balance(&test.defindex_contract.address), amount_to_deposit_1 - 78385); + + // Token balance of hodl strategy should be 0 (all in idle) + assert_eq!(test.token0.balance(&test.strategy_client_token0.address), 0); + assert_eq!(test.token1.balance(&test.strategy_client_token1.address), 0); + + // Df balance of user should be equal to amount_to_deposit_0+amount_to_deposit_1 - 1000 - 123456 + // 567890+987654-1000 -123456 = 1434088 + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, 1431088 ); + + // check total manage funds + let mut total_managed_funds_expected = Map::new(&test.env); + total_managed_funds_expected.set(test.token0.address.clone(), + CurrentAssetInvestmentAllocation { + asset: test.token0.address.clone(), + total_amount: 567890i128 - 45070, + idle_amount: 567890i128 - 45070, + invested_amount: 0i128, + strategy_allocations: sorobanvec![&test.env, + StrategyAllocation { + strategy_address: test.strategy_client_token0.address.clone(), + amount: 0, //funds has not been invested yet! + }], + } + ); + + total_managed_funds_expected.set(test.token1.address.clone(), + CurrentAssetInvestmentAllocation { + asset: test.token1.address.clone(), + total_amount: 987654i128 - 78385, + idle_amount: 987654i128 - 78385, + invested_amount: 0i128, + strategy_allocations: sorobanvec![&test.env, + StrategyAllocation { + strategy_address: test.strategy_client_token1.address.clone(), + amount: 0, //funds has not been invested yet! + }], + } + ); + + let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); + assert_eq!(total_managed_funds, total_managed_funds_expected); } diff --git a/apps/contracts/vault/src/token/contract.rs b/apps/contracts/vault/src/token/contract.rs index 7641250..eac13b2 100644 --- a/apps/contracts/vault/src/token/contract.rs +++ b/apps/contracts/vault/src/token/contract.rs @@ -6,12 +6,11 @@ use crate::token::metadata::{read_decimal, read_name, read_symbol}; use crate::token::total_supply::{decrease_total_supply, increase_total_supply, read_total_supply}; #[cfg(test)] -use crate::token::storage_types::{AllowanceDataKey, AllowanceValue, DataKey}; +use crate::token::storage_types::{AllowanceDataKey}; use crate::token::storage_types::{INSTANCE_BUMP_AMOUNT, INSTANCE_LIFETIME_THRESHOLD}; use soroban_sdk::token::{self, Interface as _}; use soroban_sdk::{contract, contractimpl, Address, Env, String}; use soroban_token_sdk::TokenUtils; -use crate::ContractError; fn check_nonnegative_amount(amount: i128) { if amount < 0 { From d8cffb5d2f583ae2c39d9fa081089a088b9bf168 Mon Sep 17 00:00:00 2001 From: esteblock Date: Thu, 5 Dec 2024 14:03:42 -0300 Subject: [PATCH 08/10] should be tested initialize with one coin and several assets --- apps/contracts/vault/src/test/vault/initialize.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/apps/contracts/vault/src/test/vault/initialize.rs b/apps/contracts/vault/src/test/vault/initialize.rs index 376f2ce..c3db071 100644 --- a/apps/contracts/vault/src/test/vault/initialize.rs +++ b/apps/contracts/vault/src/test/vault/initialize.rs @@ -178,3 +178,9 @@ fn emergency_withdraw_not_yet_initialized() { .try_emergency_withdraw(&strategy_params_token1.first().unwrap().address, &users[0]); assert_eq!(result, Err(Ok(ContractError::NotInitialized))); } + +// test initialzie with one asset and several strategies for the same asset +#[test] +fn with_one_asset_and_several_strategies() { + todo!(); +} From f6019eb684cf13c8b0988303e8177c98758816ff Mon Sep 17 00:00:00 2001 From: esteblock Date: Thu, 5 Dec 2024 18:28:31 -0300 Subject: [PATCH 09/10] add tests to withdraw function --- apps/contracts/vault/src/funds.rs | 2 +- apps/contracts/vault/src/test.rs | 2 +- .../src/test/vault/deposit_and_invest.rs | 9 + .../vault/src/test/vault/withdraw.rs | 246 +++++++++++++++++- 4 files changed, 255 insertions(+), 4 deletions(-) diff --git a/apps/contracts/vault/src/funds.rs b/apps/contracts/vault/src/funds.rs index 5ef9e21..ca05edc 100644 --- a/apps/contracts/vault/src/funds.rs +++ b/apps/contracts/vault/src/funds.rs @@ -47,7 +47,7 @@ pub fn fetch_invested_funds_for_asset(e: &Env, asset: &AssetStrategySet) -> (i12 }); } (invested_funds, strategy_allocations) -} +} /// Fetches the current idle funds for all assets managed by the contract. diff --git a/apps/contracts/vault/src/test.rs b/apps/contracts/vault/src/test.rs index 8f9f222..7e717d2 100755 --- a/apps/contracts/vault/src/test.rs +++ b/apps/contracts/vault/src/test.rs @@ -70,7 +70,7 @@ pub(crate) fn get_token_admin_client<'a>( pub(crate) fn create_strategy_params_token0(test: &DeFindexVaultTest) -> Vec { sorobanvec![ - &test.env, + &test.env, Strategy { name: String::from_str(&test.env, "Strategy 1"), address: test.strategy_client_token0.address.clone(), diff --git a/apps/contracts/vault/src/test/vault/deposit_and_invest.rs b/apps/contracts/vault/src/test/vault/deposit_and_invest.rs index ba7d90e..dfbcd10 100644 --- a/apps/contracts/vault/src/test/vault/deposit_and_invest.rs +++ b/apps/contracts/vault/src/test/vault/deposit_and_invest.rs @@ -398,6 +398,15 @@ fn several_assets_success() { } +#[test] +fn one_asset_several_strategies() { + /* + What happens when no previous investment has been done? + + */ + todo!(); +} + #[test] diff --git a/apps/contracts/vault/src/test/vault/withdraw.rs b/apps/contracts/vault/src/test/vault/withdraw.rs index a6c8666..7df92d5 100644 --- a/apps/contracts/vault/src/test/vault/withdraw.rs +++ b/apps/contracts/vault/src/test/vault/withdraw.rs @@ -153,7 +153,7 @@ fn not_enough_balance() { } #[test] -fn from_idle_one_strategy_success() { +fn from_idle_one_asset_one_strategy_success() { let test = DeFindexVaultTest::setup(); test.env.mock_all_auths(); let strategy_params_token0 = create_strategy_params_token0(&test); @@ -522,8 +522,9 @@ fn from_idle_two_assets_success() { } + #[test] -fn withdraw_from_strategy_success() { +fn from_strategy_one_asset_one_strategy_success() { let test = DeFindexVaultTest::setup(); test.env.mock_all_auths(); let strategy_params_token0 = create_strategy_params_token0(&test); @@ -598,6 +599,247 @@ fn withdraw_from_strategy_success() { } +#[test] +fn from_strategies_one_asset_two_strategies_success() { + todo!(); + // let test = DeFindexVaultTest::setup(); + // test.env.mock_all_auths(); + // let assets: Vec = sorobanvec![ + // &test.env, + // AssetStrategySet { + // address: test.token0.address.clone(), + // strategies: sorobanvec![ + // &test.env, + // Strategy { + // name: String::from_str(&test.env, "Strategy 1"), + // address: test.strategy_client_token0.address.clone(), + // paused: false, + // } + // ] + // } + // ]; + + // test.defindex_contract.initialize( + // &assets, + // &test.manager, + // &test.emergency_manager, + // &test.vault_fee_receiver, + // &2000u32, + // &test.defindex_protocol_receiver, + // &test.defindex_factory, + // &String::from_str(&test.env, "dfToken"), + // &String::from_str(&test.env, "DFT"), + // ); + // let amount = 1234567890000000i128; + + // let users = DeFindexVaultTest::generate_random_users(&test.env, 1); + + // test.token0_admin_client.mint(&users[0], &amount); + // assert_eq!(test.token0.balance(&users[0]), amount); + + // let df_balance = test.defindex_contract.balance(&users[0]); + // assert_eq!(df_balance, 0i128); + + // // Deposit + // let amount_to_deposit = 987654321i128; + + // test.defindex_contract.deposit( + // &sorobanvec![&test.env, amount_to_deposit], + // &sorobanvec![&test.env, amount_to_deposit], + // &users[0], + // &false + // ); + + // FIX invest in 2 stretegies for the same asset + +} + + +#[test] +fn from_strategies_two_asset_each_one_strategy_success() { + // We will have two assets, each asset with one strategy + let test = DeFindexVaultTest::setup(); + test.env.mock_all_auths(); + let strategy_params_token0 = create_strategy_params_token0(&test); + let strategy_params_token1 = create_strategy_params_token1(&test); + let assets: Vec = sorobanvec![ + &test.env, + AssetStrategySet { + address: test.token0.address.clone(), + strategies: strategy_params_token0.clone() + }, + AssetStrategySet { + address: test.token1.address.clone(), + strategies: strategy_params_token1.clone() + } + ]; + // initialize + test.defindex_contract.initialize( + &assets, + &test.manager, + &test.emergency_manager, + &test.vault_fee_receiver, + &2000u32, + &test.defindex_protocol_receiver, + &test.defindex_factory, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + ); + // mint + let amount = 987654321i128; + let users = DeFindexVaultTest::generate_random_users(&test.env, 1); + test.token0_admin_client.mint(&users[0], &amount); + test.token1_admin_client.mint(&users[0], &amount); + assert_eq!(test.token0.balance(&users[0]), amount); + assert_eq!(test.token1.balance(&users[0]), amount); + + // deposit + let amount_to_deposit_0 = 123456789i128; + let amount_to_deposit_1 = 234567890i128; + + let deposit_result = test.defindex_contract.deposit( + &sorobanvec![&test.env, amount_to_deposit_0, amount_to_deposit_1], + &sorobanvec![&test.env, amount_to_deposit_0, amount_to_deposit_1], + &users[0], + &false + ); + + // check deposit result. Ok((amounts, shares_to_mint)) + // shares to mint = 123456789 + 234567890 = 358024679 + + assert_eq!(deposit_result, (sorobanvec![&test.env, amount_to_deposit_0, amount_to_deposit_1], 358024679)); + + + // check balances + assert_eq!(test.token0.balance(&users[0]), amount - amount_to_deposit_0); + assert_eq!(test.token1.balance(&users[0]), amount - amount_to_deposit_1); + + // check vault balances + assert_eq!(test.token0.balance(&test.defindex_contract.address), amount_to_deposit_0); + assert_eq!(test.token1.balance(&test.defindex_contract.address), amount_to_deposit_1); + + // check strategy balances + assert_eq!(test.token0.balance(&test.strategy_client_token0.address), 0); + assert_eq!(test.token1.balance(&test.strategy_client_token1.address), 0); + + // invest in strategies. We will invest 100% of the idle funds + let investments = sorobanvec![ + &test.env, + Some(AssetInvestmentAllocation { + asset: test.token0.address.clone(), + strategy_allocations: sorobanvec![ + &test.env, + Some(StrategyAllocation { + strategy_address: test.strategy_client_token0.address.clone(), + amount: amount_to_deposit_0, + }), + ], + }), + Some(AssetInvestmentAllocation { + asset: test.token1.address.clone(), + strategy_allocations: sorobanvec![ + &test.env, + Some(StrategyAllocation { + strategy_address: test.strategy_client_token1.address.clone(), + amount: amount_to_deposit_1, + }), + ], + }), + ]; + + test.defindex_contract.invest(&investments); + + // check vault balances + assert_eq!(test.token0.balance(&test.defindex_contract.address), 0); + assert_eq!(test.token1.balance(&test.defindex_contract.address), 0); + + // check strategy balances + assert_eq!(test.token0.balance(&test.strategy_client_token0.address), amount_to_deposit_0); + assert_eq!(test.token1.balance(&test.strategy_client_token1.address), amount_to_deposit_1); + + //check user vault shares + let df_balance = test.defindex_contract.balance(&users[0]); + // vault shares should be amount_to_deposit_0 + amount_to_deposit_1 - 1000 + // 123456789 + 234567890 - 1000 = 358023679 + // but total vault shares are 358023679 + 1000 = 358024679 + assert_eq!(df_balance, 358023679); + + // User wants to withdraw 35353535 shares + // from asset 0: total_funds_0 * withdraw_shares / total_shares + // from asset 1: total_funds_1 * withdraw_shares / total_shares + // user will get asset 0: 123456789 * 35353535 / 358024679 = 12190874.447789436 = 12190874 + // user will get asset 1: 234567890 * 35353535 / 358024679 = 23162660.552210564 = 23162660 + + let amount_to_withdraw = 35353535i128; + let result = test.defindex_contract.withdraw(&amount_to_withdraw, &users[0]); + + // check expected result + let expected_result = sorobanvec![&test.env, 12190874, 23162660]; + assert_eq!(result, expected_result); + + // check user balances + assert_eq!(test.token0.balance(&users[0]), amount - amount_to_deposit_0 + 12190874); + assert_eq!(test.token1.balance(&users[0]), amount - amount_to_deposit_1 + 23162660); + + // check vault balances + assert_eq!(test.token0.balance(&test.defindex_contract.address), 0); + assert_eq!(test.token1.balance(&test.defindex_contract.address), 0); + + // check strategy balances + assert_eq!(test.token0.balance(&test.strategy_client_token0.address), amount_to_deposit_0 - 12190874); + assert_eq!(test.token1.balance(&test.strategy_client_token1.address), amount_to_deposit_1 - 23162660); + + // check user vault shares // should be 358023679−35353535 = 322670144 + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, 322670144); + + // now we deposit again to have a lot in idle funds + // because the vault has 123456789 - 12190874 = 111,265,915 of token 0 + // and 234567890 - 23162660 = 211,405,230 of token 1 + // this proportion should be maintained + + // if user wants to deposit again 2,222,222 of token 0, it should invest from token 1: + // 2222222 * 211405230 / 111265915 = 4222221.630236537 = 4222221 + + let amount_to_deposit_0 = 2222222i128; + let amount_to_deposit_1 = 4222221i128; + + let (amounts, shares_minted) = test.defindex_contract.deposit( + &sorobanvec![&test.env, amount_to_deposit_0, amount_to_deposit_1+100], + &sorobanvec![&test.env, amount_to_deposit_0, amount_to_deposit_1-100], + &users[0], + &false + ); + + // // expected amounts + // let expected_amounts = sorobanvec![&test.env, 2222222, 4222221]; + // assert_eq!(amounts, expected_amounts); + + // // expected shares minted + // // total_supplly * amount_desired_target / reserve_target + // // 358024679 * 2222222 / 111265915 = 7160000 + // assert_eq!(shares_minted, 7160000); + + // // check user balances + // assert_eq!(test.token0.balance(&users[0]), amount - amount_to_deposit_0 + 12190874 - 2222222); + // assert_eq!(test.token1.balance(&users[0]), amount - amount_to_deposit_1 + 23162660 - 4222221); + + // // check vault balance + // assert_eq!(test.token0.balance(&test.defindex_contract.address), 2222222); + // assert_eq!(test.token1.balance(&test.defindex_contract.address), 4222221); + + // // check strategies balance + // assert_eq!(test.token0.balance(&test.strategy_client_token0.address), amount_to_deposit_0 - 12190874 + 2222222); + // assert_eq!(test.token1.balance(&test.strategy_client_token1.address), amount_to_deposit_1 - 23162660 + 4222221); + + // // user withdraws only from idle funds 716000 (10%) of what just deposited + // // this should only affect idle funds + + + +} + + // test withdraw without mock all auths #[test] fn from_strategy_success_no_mock_all_auths() { From 3164d93ff2700d1f892a69cd24343c04cb34fbb2 Mon Sep 17 00:00:00 2001 From: esteblock Date: Fri, 6 Dec 2024 18:11:55 -0300 Subject: [PATCH 10/10] complete withdaw tests --- apps/contracts/vault/src/deposit.rs | 6 +- apps/contracts/vault/src/lib.rs | 2 +- .../vault/src/test/vault/withdraw.rs | 133 +++++++++++++++--- 3 files changed, 115 insertions(+), 26 deletions(-) diff --git a/apps/contracts/vault/src/deposit.rs b/apps/contracts/vault/src/deposit.rs index 8cc872f..4092e45 100644 --- a/apps/contracts/vault/src/deposit.rs +++ b/apps/contracts/vault/src/deposit.rs @@ -57,7 +57,7 @@ pub fn process_deposit( } // Mint shares - mint_shares(e, total_supply, shares_to_mint, from.clone())?; + mint_shares(e, &total_supply, shares_to_mint, from.clone())?; Ok((amounts, shares_to_mint)) } @@ -88,11 +88,11 @@ fn calculate_single_asset_shares( /// Mint vault shares. fn mint_shares( e: &Env, - total_supply: i128, + total_supply: &i128, shares_to_mint: i128, from: Address, ) -> Result<(), ContractError> { - if total_supply == 0 { + if *total_supply == 0 { if shares_to_mint < MINIMUM_LIQUIDITY { panic_with_error!(&e, ContractError::InsufficientAmount); } diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index 0d8be26..68a7d93 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -316,7 +316,7 @@ impl VaultTrait for DeFindexVault { for (i, strategy_allocation) in asset_allocation.strategy_allocations.iter().enumerate() { let strategy_amount_to_unwind: i128 = if i == (asset_allocation.strategy_allocations.len() as usize) - 1 { - remaining_amount_to_unwind + requested_withdrawal_amount .checked_sub(cumulative_amount_for_asset) .unwrap() } else { diff --git a/apps/contracts/vault/src/test/vault/withdraw.rs b/apps/contracts/vault/src/test/vault/withdraw.rs index 7df92d5..366289f 100644 --- a/apps/contracts/vault/src/test/vault/withdraw.rs +++ b/apps/contracts/vault/src/test/vault/withdraw.rs @@ -706,6 +706,7 @@ fn from_strategies_two_asset_each_one_strategy_success() { // check deposit result. Ok((amounts, shares_to_mint)) // shares to mint = 123456789 + 234567890 = 358024679 + assert_eq!(test.defindex_contract.total_supply(), 358024679); assert_eq!(deposit_result, (sorobanvec![&test.env, amount_to_deposit_0, amount_to_deposit_1], 358024679)); @@ -773,6 +774,8 @@ fn from_strategies_two_asset_each_one_strategy_success() { let amount_to_withdraw = 35353535i128; let result = test.defindex_contract.withdraw(&amount_to_withdraw, &users[0]); + assert_eq!(test.defindex_contract.total_supply(), 322671144); //358024679- 35353535 + // check expected result let expected_result = sorobanvec![&test.env, 12190874, 23162660]; assert_eq!(result, expected_result); @@ -801,42 +804,128 @@ fn from_strategies_two_asset_each_one_strategy_success() { // if user wants to deposit again 2,222,222 of token 0, it should invest from token 1: // 2222222 * 211405230 / 111265915 = 4222221.630236537 = 4222221 - let amount_to_deposit_0 = 2222222i128; - let amount_to_deposit_1 = 4222221i128; + let amount_to_deposit_0_new = 2222222i128; + let amount_to_deposit_1_new = 4222221i128; let (amounts, shares_minted) = test.defindex_contract.deposit( - &sorobanvec![&test.env, amount_to_deposit_0, amount_to_deposit_1+100], - &sorobanvec![&test.env, amount_to_deposit_0, amount_to_deposit_1-100], + &sorobanvec![&test.env, amount_to_deposit_0_new, amount_to_deposit_1_new+100], + &sorobanvec![&test.env, amount_to_deposit_0_new, amount_to_deposit_1_new-100], &users[0], &false ); - // // expected amounts - // let expected_amounts = sorobanvec![&test.env, 2222222, 4222221]; - // assert_eq!(amounts, expected_amounts); + // expected amounts + let expected_amounts = sorobanvec![&test.env, 2222222, 4222221]; + assert_eq!(amounts, expected_amounts); + + // expected shares minted + // total supply was 123456789+234567890 = 358024679 + // then we withdaw 35353535 + // total supply is 358024679 - 35353535 = 322671144 + // new shares to mint = total_supplly * amount_desired_target / reserve_target + // 322671144 * 2222222 / 111265915 = 6444443.610264365 = 6444443 + assert_eq!(shares_minted, 6444443); + + assert_eq!(test.defindex_contract.total_supply(), 329115587); //358024679- 35353535 + 6444443 + + + // check user balances + assert_eq!(test.token0.balance(&users[0]), amount - amount_to_deposit_0 + 12190874 - 2222222); + assert_eq!(test.token1.balance(&users[0]), amount - amount_to_deposit_1 + 23162660 - 4222221); + + // check vault balance + assert_eq!(test.token0.balance(&test.defindex_contract.address), 2222222); + assert_eq!(test.token1.balance(&test.defindex_contract.address), 4222221); + + // check strategies balance + assert_eq!(test.token0.balance(&test.strategy_client_token0.address), amount_to_deposit_0 - 12190874); + assert_eq!(test.token1.balance(&test.strategy_client_token1.address), amount_to_deposit_1 - 23162660); + + // user withdraws only from idle funds 644444 (10% of what just deposited) + // this should only affect idle funds + + let amount_to_withdraw = 644444i128; + let result = test.defindex_contract.withdraw(&amount_to_withdraw, &users[0]); + + assert_eq!(test.defindex_contract.total_supply(), 328471143); //358024679- 35353535 + 6444443 - 644444 + + + // the new totqal supply was 322671144+6444443 = 329115587 + // the total managed funds for asset 0 was 2222222 (idle) + amount_to_deposit_0 - 12190874 + // = 2222222 + 123456789 - 12190874 = 113488137 + + // the total managed funds for asset 1 was 4222221 (idle) + amount_to_deposit_1 - 23162660 + // = 4222221 + 234567890 - 23162660 = 215627451 + + // the expected amount to withdraw for asset 0 was total_funds_0 * withdraw_shares / total_shares + // = 113488137 * 644444 / 329115587 = 222222.075920178 = 222222 + + // the expected amount to withdraw for asset 1 was total_funds_1 * withdraw_shares / total_shares + // = 215627451 * 644444 / 329115587 = 422221.92603793 = 422221 - // // expected shares minted - // // total_supplly * amount_desired_target / reserve_target - // // 358024679 * 2222222 / 111265915 = 7160000 - // assert_eq!(shares_minted, 7160000); + let expected_result = sorobanvec![&test.env, 222222, 422221]; + assert_eq!(result, expected_result); + + // check balances + assert_eq!(test.token0.balance(&users[0]), amount - amount_to_deposit_0 + 12190874 - 2222222 + 222222); + assert_eq!(test.token1.balance(&users[0]), amount - amount_to_deposit_1 + 23162660 - 4222221 + 422221); + + // check vault balance + assert_eq!(test.token0.balance(&test.defindex_contract.address), 2222222 - 222222); + assert_eq!(test.token1.balance(&test.defindex_contract.address), 4222221 - 422221); + + // check strategies balance - // // check user balances - // assert_eq!(test.token0.balance(&users[0]), amount - amount_to_deposit_0 + 12190874 - 2222222); - // assert_eq!(test.token1.balance(&users[0]), amount - amount_to_deposit_1 + 23162660 - 4222221); + assert_eq!(test.token0.balance(&test.strategy_client_token0.address), amount_to_deposit_0 - 12190874); + assert_eq!(test.token1.balance(&test.strategy_client_token1.address), amount_to_deposit_1 - 23162660); + + assert_eq!(test.defindex_contract.total_supply(), 328471143); //358024679- 35353535 + 6444443 - 644444 - // // check vault balance - // assert_eq!(test.token0.balance(&test.defindex_contract.address), 2222222); - // assert_eq!(test.token1.balance(&test.defindex_contract.address), 4222221); + // check df tokens balance of user + assert_eq!(test.defindex_contract.balance(&users[0]), 328470143); - // // check strategies balance - // assert_eq!(test.token0.balance(&test.strategy_client_token0.address), amount_to_deposit_0 - 12190874 + 2222222); - // assert_eq!(test.token1.balance(&test.strategy_client_token1.address), amount_to_deposit_1 - 23162660 + 4222221); + // // Now we will wihdraw the total remineder amount of vault shares of the user + // // 328471143 - 1000 = 328470143 + let result = test.defindex_contract.withdraw(&328470143, &users[0]); + + + // from the total supply 328471143, the user will take 328470143 (almost all) + // for asset 0 this means + // 2222222 - 222222 (idle) + amount_to_deposit_0 - 12190874 + // 2000000 + 123456789 - 12190874 = 113265915 - // // user withdraws only from idle funds 716000 (10%) of what just deposited - // // this should only affect idle funds + // for asset 1 this means + // 4222221 - 422221 (idle) + amount_to_deposit_1 - 23162660 + // 3800000 + 234567890 - 23162660 = 215205230 + // amounts to withdraw + // for asset 0: total_funds_0 * withdraw_shares / total_shares + // 113265915 * 328470143 / 328471143 = 113265570.17240277 = 113265570 + + // for asset 1: total_funds_1 * withdraw_shares / total_shares + // 215205230 * 328470143 / 328471143 = 215204574.827591141 = 215204574 + + let expected_result = sorobanvec![&test.env, 113265570, 215204574]; + assert_eq!(result, expected_result); + + assert_eq!(test.defindex_contract.balance(&users[0]), 0); + assert_eq!(test.defindex_contract.balance(&test.defindex_contract.address), 1000); + + // CHECK IDLE BALANCES + // check vault balance + assert_eq!(test.token0.balance(&test.defindex_contract.address), 0); + assert_eq!(test.token1.balance(&test.defindex_contract.address), 0); + + + // check strategies balance, they will hold the rest + // for asset 0: total_funds_0 * 1000 / total_shares + // 113265915 - 113265570 = 345 + // for asset 1: total_funds_1 * withdraw_shares / total_shares + // 215205230- 215204574 = 656 + assert_eq!(test.token0.balance(&test.strategy_client_token0.address), 345); + assert_eq!(test.token1.balance(&test.strategy_client_token1.address), 656); }