diff --git a/.github/workflows/test-contracts.yml b/.github/workflows/test-contracts.yml index 1ea9d90b..1a171c05 100644 --- a/.github/workflows/test-contracts.yml +++ b/.github/workflows/test-contracts.yml @@ -50,5 +50,5 @@ jobs: run: | set -e cd apps/contracts && - cargo test --release || exit 1 + cargo test --release -- --nocapture|| exit 1 continue-on-error: false \ No newline at end of file diff --git a/apps/contracts/integration-test/src/setup.rs b/apps/contracts/integration-test/src/setup.rs index d4a6282f..a801f4ac 100644 --- a/apps/contracts/integration-test/src/setup.rs +++ b/apps/contracts/integration-test/src/setup.rs @@ -5,7 +5,7 @@ use soroban_sdk::{ }; mod soroswap_setup; -use soroswap_setup::{ +pub use soroswap_setup::{ create_soroswap_pool, create_soroswap_factory, create_soroswap_router }; use crate::{blend_strategy::{create_blend_strategy_contract, BlendStrategyClient}, factory::{AssetStrategySet, Strategy}, test::EnvTestUtils}; diff --git a/apps/contracts/integration-test/src/test.rs b/apps/contracts/integration-test/src/test.rs index 7ce52e58..04acf6ce 100644 --- a/apps/contracts/integration-test/src/test.rs +++ b/apps/contracts/integration-test/src/test.rs @@ -121,3 +121,4 @@ impl<'a> IntegrationTest<'a> { mod vault_one_fixed_strategy; mod vault_one_hodl_strategy; mod vault_blend_strategy; +mod limits; diff --git a/apps/contracts/integration-test/src/test/limits/asset_n_strategies.rs b/apps/contracts/integration-test/src/test/limits/asset_n_strategies.rs new file mode 100644 index 00000000..faa0754c --- /dev/null +++ b/apps/contracts/integration-test/src/test/limits/asset_n_strategies.rs @@ -0,0 +1,931 @@ +use soroban_sdk::{testutils::{Address as _, Ledger, MockAuth, MockAuthInvoke}, vec as svec, xdr::ContractCostType, Address, BytesN, IntoVal, Map, String, Vec}; + +use crate::{blend_strategy::{create_blend_strategy_contract, BlendStrategyClient}, factory::{AssetStrategySet, Strategy}, fixed_strategy::{create_fixed_strategy_contract, FixedStrategyClient}, hodl_strategy::create_hodl_strategy_contract, setup::{blend_setup::{create_blend_pool, BlendFixture, BlendPoolClient, Request}, create_soroswap_factory, create_soroswap_pool, create_soroswap_router, create_vault_one_asset_hodl_strategy, mock_mint, VAULT_FEE}, test::{limits::check_limits, EnvTestUtils, IntegrationTest, DAY_IN_LEDGERS, ONE_YEAR_IN_SECONDS}, token::create_token, vault::{defindex_vault_contract::{Instruction, VaultContractClient}, MINIMUM_LIQUIDITY}}; + +// 26 strategies is the maximum number of strategies that can be added to a vault before exceeding the instructions limit IN RUST TESTS +// With 26 strategies withdrawals are not possible due to the instruction limit +// 13 strategies is the maximum including withdrawals +#[test] +fn asset_n_strategies_hodl() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + let num_strategies = 13; // CHANGE THIS IF U NEED TO TEST OTHER NUMBER OF STRATEGIES + + for i in 0..num_strategies { + let strategy_name = format!("Strategy_{}", i); + let strategy_contract = create_hodl_strategy_contract(&setup.env, &token.address); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, &strategy_name), + paused: false, + }); + } + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + setup.env.budget().reset_unlimited(); + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + check_limits(&setup.env, "Create Vault"); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + token_admin_client.mint(user, &user_starting_balance); + + let deposit_amount = 100000_0_000_000i128; + setup.env.budget().reset_unlimited(); + vault_contract.deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + &false, + ); + check_limits(&setup.env, "Deposit"); + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + for i in 0..num_strategies { + invest_instructions.push_back(Instruction::Invest( + strategies.get(i).unwrap().address.clone(), + deposit_amount / num_strategies as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + setup.env.jump(DAY_IN_LEDGERS * 7); + + // Simulate a user withdrawal touching all strategies + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&user); + vault_contract.withdraw(&balance, &user); + check_limits(&setup.env, "Withdraw"); +} + +#[test] +#[should_panic(expected = "Memory usage exceeded limit")] +fn asset_n_strategies_hodl_panic() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + let num_strategies = 14; // CHANGE THIS IF U NEED TO TEST OTHER NUMBER OF STRATEGIES + + for i in 0..num_strategies { + let strategy_name = format!("Strategy_{}", i); + let strategy_contract = create_hodl_strategy_contract(&setup.env, &token.address); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, &strategy_name), + paused: false, + }); + } + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + setup.env.budget().reset_unlimited(); + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + check_limits(&setup.env, "Create Vault"); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + token_admin_client.mint(user, &user_starting_balance); + + let deposit_amount = 100000_0_000_000i128; + setup.env.budget().reset_unlimited(); + vault_contract.deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + &false, + ); + check_limits(&setup.env, "Deposit"); + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + for i in 0..num_strategies { + invest_instructions.push_back(Instruction::Invest( + strategies.get(i).unwrap().address.clone(), + deposit_amount / num_strategies as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + setup.env.jump(DAY_IN_LEDGERS * 7); + + // Simulate a user withdrawal touching all strategies + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&user); + vault_contract.withdraw(&balance, &user); + check_limits(&setup.env, "Withdraw"); +} + +// FIXED Strategy limit is 10 including withdrawals in RUST +#[test] +fn asset_n_strategies_fixed() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + let num_strategies = 10; // CHANGE THIS IF U NEED TO TEST OTHER NUMBER OF STRATEGIES + + for i in 0..num_strategies { + let strategy_name = format!("Strategy_{}", i); + let strategy_contract = create_fixed_strategy_contract(&setup.env, &token.address, 1000u32, &token_admin_client); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, &strategy_name), + paused: false, + }); + } + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + setup.env.budget().reset_unlimited(); + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + check_limits(&setup.env, "Create Vault"); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + token_admin_client.mint(user, &user_starting_balance); + + let deposit_amount = 100000_0_000_000i128; + setup.env.budget().reset_unlimited(); + vault_contract.deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + &false, + ); + check_limits(&setup.env, "Deposit"); + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + for i in 0..num_strategies { + invest_instructions.push_back(Instruction::Invest( + strategies.get(i).unwrap().address.clone(), + deposit_amount / num_strategies as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + setup.env.jump(DAY_IN_LEDGERS * 7); + + // harvest on all strategies + for i in 0..num_strategies { + setup.env.budget().reset_unlimited(); + let temp_strategy_address = strategies.get(i).unwrap().address.clone(); + let temp_client = FixedStrategyClient::new(&setup.env, &temp_strategy_address); + + temp_client.harvest(&manager); + check_limits(&setup.env, "Harvest"); + } + + setup.env.budget().reset_unlimited(); + vault_contract.distribute_fees(&manager); + check_limits(&setup.env, "Distribute Fees"); + + // Simulate a user withdrawal touching all strategies + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&user); + vault_contract.withdraw(&balance, &user); + check_limits(&setup.env, "Withdraw"); +} + +#[test] +#[should_panic(expected = "Memory usage exceeded limit")] +fn asset_n_strategies_fixed_panic() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + let num_strategies = 12; // CHANGE THIS IF U NEED TO TEST OTHER NUMBER OF STRATEGIES + + for i in 0..num_strategies { + let strategy_name = format!("Strategy_{}", i); + let strategy_contract = create_fixed_strategy_contract(&setup.env, &token.address, 1000u32, &token_admin_client); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, &strategy_name), + paused: false, + }); + } + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + setup.env.budget().reset_unlimited(); + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + check_limits(&setup.env, "Create Vault"); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + token_admin_client.mint(user, &user_starting_balance); + + let deposit_amount = 100000_0_000_000i128; + setup.env.budget().reset_unlimited(); + vault_contract.deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + &false, + ); + check_limits(&setup.env, "Deposit"); + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + for i in 0..num_strategies { + invest_instructions.push_back(Instruction::Invest( + strategies.get(i).unwrap().address.clone(), + deposit_amount / num_strategies as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + setup.env.jump(DAY_IN_LEDGERS * 7); + + // harvest on all strategies + for i in 0..num_strategies { + setup.env.budget().reset_unlimited(); + let temp_strategy_address = strategies.get(i).unwrap().address.clone(); + let temp_client = FixedStrategyClient::new(&setup.env, &temp_strategy_address); + + temp_client.harvest(&manager); + check_limits(&setup.env, "Harvest"); + } + + setup.env.budget().reset_unlimited(); + vault_contract.distribute_fees(&manager); + check_limits(&setup.env, "Distribute Fees"); + + // Simulate a user withdrawal touching all strategies + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&user); + vault_contract.withdraw(&balance, &user); + check_limits(&setup.env, "Withdraw"); +} + +// 2 Strategies is the limit for 1 asset and 2 Blend strategies +#[test] +fn asset_n_strategies_blend() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let admin = Address::generate(&setup.env); + + let (blnd, blnd_client) = create_token(&setup.env, &admin); + let (usdc, usdc_client) = create_token(&setup.env, &admin); + let (_, xlm_client) = create_token(&setup.env, &admin); + + // Setting up soroswap pool + let pool_admin = Address::generate(&setup.env); + let amount_a = 100000000_0_000_000; + let amount_b = 50000000_0_000_000; + blnd_client.mint(&pool_admin, &amount_a); + usdc_client.mint(&pool_admin, &amount_b); + create_soroswap_pool( + &setup.env, + &soroswap_router, + &pool_admin, + &blnd.address, + &usdc.address, + &amount_a, + &amount_b, + ); + // End of setting up soroswap pool + + let blend_fixture = BlendFixture::deploy(&setup.env, &admin, &blnd.address, &usdc.address); + + let pool = create_blend_pool(&setup.env, &blend_fixture, &admin, &usdc_client, &xlm_client); + let pool_client = BlendPoolClient::new(&setup.env, &pool); + + let mut strategies = svec![&setup.env]; + let num_strategies = 2; // CHANGE THIS IF U NEED TO TEST OTHER NUMBER OF STRATEGIES + + for i in 0..num_strategies { + let strategy_name = format!("Blend_{}", i); + let strategy = create_blend_strategy_contract( + &setup.env, + &usdc.address, + &pool, + &0u32, + &blnd.address, + &soroswap_router.address, + svec![&setup.env, 0u32, 1u32, 2u32, 3u32] + ); + let strategy_contract = BlendStrategyClient::new(&setup.env, &strategy); + + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, &strategy_name), + paused: false, + }); + } + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "BlendVault"); + let vault_symbol = String::from_str(&setup.env, "BLNDVLT"); + let manager = Address::generate(&setup.env); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: usdc.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + setup.env.budget().reset_unlimited(); + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true + ); + check_limits(&setup.env, "Create Vault"); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + let users = IntegrationTest::generate_random_users(&setup.env, 3); + + let starting_balance = 300_0000000; + usdc_client.mint(&users[0], &starting_balance); + usdc_client.mint(&users[1], &starting_balance); + + setup.env.budget().reset_unlimited(); + vault_contract.deposit( + &svec!(&setup.env, starting_balance.clone()), + &svec!(&setup.env, starting_balance.clone()), + &users[0], + &false + ); + check_limits(&setup.env, "Deposit"); + + setup.env.budget().reset_unlimited(); + vault_contract.deposit( + &svec!(&setup.env, starting_balance.clone()), + &svec!(&setup.env, starting_balance.clone()), + &users[1], + &false + ); + check_limits(&setup.env, "Deposit"); + + let mut invest_instructions = svec![&setup.env]; + for i in 0..num_strategies { + invest_instructions.push_back(Instruction::Invest( + strategies.get(i).unwrap().address.clone(), + starting_balance * 2 / num_strategies as i128, + )); + } + + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + // user_2 deposit directly into pool + let user_2_starting_balance = 200_0000000; + usdc_client.mint(&users[2], &user_2_starting_balance); + pool_client.submit( + &users[2], + &users[2], + &users[2], + &svec![ + &setup.env, + Request { + request_type: 0, + address: usdc.address.clone(), + amount: user_2_starting_balance, + }, + ], + ); + + + // admin borrow back to 50% util rate + let borrow_amount = (user_2_starting_balance + starting_balance * 2) / 2; + pool_client.submit( + &admin, + &admin, + &admin, + &svec![ + &setup.env, + Request { + request_type: 4, + address: usdc.address.clone(), + amount: borrow_amount, + }, + ], + ); + + let report = vault_contract.report(); + println!("report = {:?}", report); + /* + * Allow 1 week to pass + */ + setup.env.jump(DAY_IN_LEDGERS * 7); + + pool_client.submit( + &users[2], + &users[2], + &users[2], + &svec![ + &setup.env, + Request { + request_type: 1, + address: usdc.address.clone(), + amount: user_2_starting_balance * 2, + }, + ], + ); + + std::println!("-- Harvesting --"); + // harvest on all strategies + for i in 0..num_strategies { + setup.env.budget().reset_unlimited(); + let temp_strategy_address = strategies.get(i).unwrap().address.clone(); + let temp_client = FixedStrategyClient::new(&setup.env, &temp_strategy_address); + + temp_client.harvest(&manager); + check_limits(&setup.env, "Harvest"); + } + + let report = vault_contract.report(); + println!("report = {:?}", report); + + let lock_fees = vault_contract.lock_fees(&None); + println!("locked_fees = {:?}", lock_fees); + + println!("-- Distributing Fees --"); + setup.env.budget().reset_unlimited(); + vault_contract.distribute_fees(&manager); + check_limits(&setup.env, "Distribute Fees"); + + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&users[0]); + vault_contract.withdraw(&balance, &users[0]); + check_limits(&setup.env, "Withdraw"); + + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&users[1]); + vault_contract.withdraw(&balance, &users[1]); + check_limits(&setup.env, "Withdraw"); +} + +#[test] +#[should_panic(expected = "CPU instructions exceeded limit")] +fn asset_n_strategies_blend_panic() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let admin = Address::generate(&setup.env); + + let (blnd, blnd_client) = create_token(&setup.env, &admin); + let (usdc, usdc_client) = create_token(&setup.env, &admin); + let (_, xlm_client) = create_token(&setup.env, &admin); + + // Setting up soroswap pool + let pool_admin = Address::generate(&setup.env); + let amount_a = 100000000_0_000_000; + let amount_b = 50000000_0_000_000; + blnd_client.mint(&pool_admin, &amount_a); + usdc_client.mint(&pool_admin, &amount_b); + create_soroswap_pool( + &setup.env, + &soroswap_router, + &pool_admin, + &blnd.address, + &usdc.address, + &amount_a, + &amount_b, + ); + // End of setting up soroswap pool + + let blend_fixture = BlendFixture::deploy(&setup.env, &admin, &blnd.address, &usdc.address); + + let pool = create_blend_pool(&setup.env, &blend_fixture, &admin, &usdc_client, &xlm_client); + let pool_client = BlendPoolClient::new(&setup.env, &pool); + + let mut strategies = svec![&setup.env]; + let num_strategies = 3; // CHANGE THIS IF U NEED TO TEST OTHER NUMBER OF STRATEGIES + + for i in 0..num_strategies { + let strategy_name = format!("Blend_{}", i); + let strategy = create_blend_strategy_contract( + &setup.env, + &usdc.address, + &pool, + &0u32, + &blnd.address, + &soroswap_router.address, + svec![&setup.env, 0u32, 1u32, 2u32, 3u32] + ); + let strategy_contract = BlendStrategyClient::new(&setup.env, &strategy); + + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, &strategy_name), + paused: false, + }); + } + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "BlendVault"); + let vault_symbol = String::from_str(&setup.env, "BLNDVLT"); + let manager = Address::generate(&setup.env); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: usdc.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + setup.env.budget().reset_unlimited(); + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true + ); + check_limits(&setup.env, "Create Vault"); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + let users = IntegrationTest::generate_random_users(&setup.env, 3); + + let starting_balance = 300_0000000; + usdc_client.mint(&users[0], &starting_balance); + usdc_client.mint(&users[1], &starting_balance); + + setup.env.budget().reset_unlimited(); + vault_contract.deposit( + &svec!(&setup.env, starting_balance.clone()), + &svec!(&setup.env, starting_balance.clone()), + &users[0], + &false + ); + check_limits(&setup.env, "Deposit"); + + setup.env.budget().reset_unlimited(); + vault_contract.deposit( + &svec!(&setup.env, starting_balance.clone()), + &svec!(&setup.env, starting_balance.clone()), + &users[1], + &false + ); + check_limits(&setup.env, "Deposit"); + + let mut invest_instructions = svec![&setup.env]; + for i in 0..num_strategies { + invest_instructions.push_back(Instruction::Invest( + strategies.get(i).unwrap().address.clone(), + starting_balance * 2 / num_strategies as i128, + )); + } + + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + // user_2 deposit directly into pool + let user_2_starting_balance = 200_0000000; + usdc_client.mint(&users[2], &user_2_starting_balance); + pool_client.submit( + &users[2], + &users[2], + &users[2], + &svec![ + &setup.env, + Request { + request_type: 0, + address: usdc.address.clone(), + amount: user_2_starting_balance, + }, + ], + ); + + + // admin borrow back to 50% util rate + let borrow_amount = (user_2_starting_balance + starting_balance * 2) / 2; + pool_client.submit( + &admin, + &admin, + &admin, + &svec![ + &setup.env, + Request { + request_type: 4, + address: usdc.address.clone(), + amount: borrow_amount, + }, + ], + ); + + let report = vault_contract.report(); + println!("report = {:?}", report); + /* + * Allow 1 week to pass + */ + setup.env.jump(DAY_IN_LEDGERS * 7); + + pool_client.submit( + &users[2], + &users[2], + &users[2], + &svec![ + &setup.env, + Request { + request_type: 1, + address: usdc.address.clone(), + amount: user_2_starting_balance * 2, + }, + ], + ); + + std::println!("-- Harvesting --"); + // harvest on all strategies + for i in 0..num_strategies { + setup.env.budget().reset_unlimited(); + let temp_strategy_address = strategies.get(i).unwrap().address.clone(); + let temp_client = FixedStrategyClient::new(&setup.env, &temp_strategy_address); + + temp_client.harvest(&manager); + check_limits(&setup.env, "Harvest"); + } + + let report = vault_contract.report(); + println!("report = {:?}", report); + + let lock_fees = vault_contract.lock_fees(&None); + println!("locked_fees = {:?}", lock_fees); + + println!("-- Distributing Fees --"); + setup.env.budget().reset_unlimited(); + vault_contract.distribute_fees(&manager); + check_limits(&setup.env, "Distribute Fees"); + + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&users[0]); + vault_contract.withdraw(&balance, &users[0]); + check_limits(&setup.env, "Withdraw"); + + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&users[1]); + vault_contract.withdraw(&balance, &users[1]); + check_limits(&setup.env, "Withdraw"); +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/limits/mod.rs b/apps/contracts/integration-test/src/test/limits/mod.rs new file mode 100644 index 00000000..d92f9fa6 --- /dev/null +++ b/apps/contracts/integration-test/src/test/limits/mod.rs @@ -0,0 +1,18 @@ +use soroban_sdk::Env; + +mod asset_n_strategies; +mod rebalance; +mod n_asset_one_strategy; + +pub const CPU_LIMIT: u64 = 100000000; +pub const MEM_LIMIT: u64 = 41943040; + +pub fn check_limits(e: &Env, message: &str) { + let cpu_used = e.budget().cpu_instruction_cost(); + let mem_used = e.budget().memory_bytes_cost(); + println!("{} CPU Instructions: {:?}", message, cpu_used); + println!("{} MEMORY: {:?}", message, mem_used); + println!("==========================================="); + assert!(cpu_used <= CPU_LIMIT, "CPU instructions exceeded limit"); + assert!(mem_used <= MEM_LIMIT, "Memory usage exceeded limit"); +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/limits/n_asset_one_strategy.rs b/apps/contracts/integration-test/src/test/limits/n_asset_one_strategy.rs new file mode 100644 index 00000000..51aa4493 --- /dev/null +++ b/apps/contracts/integration-test/src/test/limits/n_asset_one_strategy.rs @@ -0,0 +1,540 @@ +use soroban_sdk::{testutils::Address as _, vec as svec, Address, BytesN, Map, String}; +use crate::{blend_strategy::{create_blend_strategy_contract, BlendStrategyClient}, factory::{AssetStrategySet, Strategy}, fixed_strategy::{create_fixed_strategy_contract, FixedStrategyClient}, hodl_strategy::create_hodl_strategy_contract, setup::{blend_setup::{create_blend_pool, BlendFixture, BlendPoolClient}, create_soroswap_factory, create_soroswap_pool, create_soroswap_router, VAULT_FEE}, test::{limits::check_limits, EnvTestUtils, IntegrationTest, DAY_IN_LEDGERS}, token::create_token, vault::defindex_vault_contract::{Instruction, VaultContractClient}}; + +#[test] +fn n_assets_one_strategy_hodl() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let mut tokens = Vec::new(); + let mut token_clients = Vec::new(); + + let num_tokens = 12; + + for _ in 0..num_tokens { + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + tokens.push(token); + token_clients.push(token_admin_client); + } + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + + for i in 0..num_tokens { + let strategy_contract = create_hodl_strategy_contract(&setup.env, &tokens.get(i).unwrap().address); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "HodlStrategy"), + paused: false, + }); + } + + let mut assets = svec![&setup.env]; + for i in 0..num_tokens { + let token = tokens.get(i).unwrap(); + let strategy = strategies.get(i.try_into().unwrap()).unwrap(); + assets.push_back(AssetStrategySet { + address: token.address.clone(), + strategies: svec![&setup.env, strategy], + }); + } + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + setup.env.budget().reset_unlimited(); + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + check_limits(&setup.env, "Create Vault"); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + let mut amounts_desired = svec![&setup.env]; + let mut amounts_min = svec![&setup.env]; + + for i in 0..num_tokens { + let token_admin_client = token_clients.get(i).unwrap(); + token_admin_client.mint(user, &user_starting_balance); + + let desired_amount = 10000000_0_000_000i128; + + amounts_desired.push_back(desired_amount); + amounts_min.push_back(desired_amount); + } + + setup.env.budget().reset_unlimited(); + vault_contract.deposit(&amounts_desired, &amounts_min, user, &false); + check_limits(&setup.env, "Deposit"); + + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + let batch_size = num_tokens / 2; + + for i in 0..batch_size { + invest_instructions.push_back(Instruction::Invest( + strategies.get(i.try_into().unwrap()).unwrap().address.clone(), + user_starting_balance, + )); + } + + // Rebalance first batch + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest Batch 1"); + + let mut invest_instructions = svec![&setup.env]; + + for i in batch_size..num_tokens { + invest_instructions.push_back(Instruction::Invest( + strategies.get(i.try_into().unwrap()).unwrap().address.clone(), + user_starting_balance, + )); + } + + // Rebalance second batch + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest Batch 2"); + + for i in 0..num_tokens { + let token = tokens.get(i).unwrap(); + let strategy = strategies.get(i.try_into().unwrap()).unwrap(); + let balance = token.balance(&strategy.address); + println!("Strategy {} balance: {}", i, balance); + assert!(balance > 0, "Strategy {} has zero balance", i); + } + + // Deposit and Invest + let mut amounts_desired = svec![&setup.env]; + let mut amounts_min = svec![&setup.env]; + + for i in 0..num_tokens { + let token_admin_client = token_clients.get(i).unwrap(); + token_admin_client.mint(user, &user_starting_balance); + + let desired_amount = 10000000_0_000_000i128; + + amounts_desired.push_back(desired_amount); + amounts_min.push_back(desired_amount); + } + + setup.env.budget().reset_unlimited(); + vault_contract.deposit(&amounts_desired, &amounts_min, user, &true); + check_limits(&setup.env, "Deposit and Invest"); + + for i in 0..num_tokens { + let token = tokens.get(i).unwrap(); + let strategy = strategies.get(i.try_into().unwrap()).unwrap(); + let balance = token.balance(&strategy.address); + println!("Strategy {} balance: {}", i, balance); + assert!(balance > user_starting_balance, "Strategy {} has zero balance", i); + } + + setup.env.jump(DAY_IN_LEDGERS * 7); + + // Simulate a user withdrawal touching all strategies + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&user); + vault_contract.withdraw(&balance, &user); + check_limits(&setup.env, "Withdraw"); + + for i in 0..num_tokens { + let token = tokens.get(i).unwrap(); + let strategy = strategies.get(i.try_into().unwrap()).unwrap(); + let balance = token.balance(&strategy.address); + println!("Strategy {} balance after withdrawal: {}", i, balance); + assert!(balance < user_starting_balance, "Strategy {} balance did not decrease", i); + } +} + +#[test] +fn n_assets_one_strategy_fixed() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let mut tokens = Vec::new(); + let mut token_clients = Vec::new(); + + let num_tokens = 10; // CHANGE THIS TO CHECK THE LIMITS + + for _ in 0..num_tokens { + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + tokens.push(token); + token_clients.push(token_admin_client); + } + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + + for i in 0..num_tokens { + let token_admin_client = token_clients.get(i).unwrap(); + let strategy_contract = create_fixed_strategy_contract(&setup.env, &tokens.get(i).unwrap().address, 1000u32, &token_admin_client); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "FixedStrategy"), + paused: false, + }); + } + + let mut assets = svec![&setup.env]; + for i in 0..num_tokens { + let token = tokens.get(i).unwrap(); + let strategy = strategies.get(i.try_into().unwrap()).unwrap(); + assets.push_back(AssetStrategySet { + address: token.address.clone(), + strategies: svec![&setup.env, strategy], + }); + } + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + setup.env.budget().reset_unlimited(); + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + check_limits(&setup.env, "Create Vault"); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + let mut amounts_desired = svec![&setup.env]; + let mut amounts_min = svec![&setup.env]; + + for i in 0..num_tokens { + let token_admin_client = token_clients.get(i).unwrap(); + token_admin_client.mint(user, &user_starting_balance); + + let desired_amount = 10000000_0_000_000i128; + + amounts_desired.push_back(desired_amount); + amounts_min.push_back(desired_amount); + } + + setup.env.budget().reset_unlimited(); + vault_contract.deposit(&amounts_desired, &amounts_min, user, &false); + check_limits(&setup.env, "Deposit"); + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + + for i in 0..num_tokens { + invest_instructions.push_back(Instruction::Invest( + strategies.get(i.try_into().unwrap()).unwrap().address.clone(), + user_starting_balance, + )); + } + + // Rebalance first batch + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + for i in 0..num_tokens { + let token = tokens.get(i).unwrap(); + let strategy = strategies.get(i.try_into().unwrap()).unwrap(); + let balance = token.balance(&strategy.address); + println!("Strategy {} balance: {}", i, balance); + assert!(balance > 0, "Strategy {} has zero balance", i); + } + + // Deposit and Invest + let mut amounts_desired = svec![&setup.env]; + let mut amounts_min = svec![&setup.env]; + + for i in 0..num_tokens { + let token_admin_client = token_clients.get(i).unwrap(); + token_admin_client.mint(user, &user_starting_balance); + + let desired_amount = 10000000_0_000_000i128; + + amounts_desired.push_back(desired_amount); + amounts_min.push_back(desired_amount); + } + + setup.env.budget().reset_unlimited(); + vault_contract.deposit(&amounts_desired, &amounts_min, user, &true); + check_limits(&setup.env, "Deposit and Invest"); + + for i in 0..num_tokens { + let token = tokens.get(i).unwrap(); + let strategy = strategies.get(i.try_into().unwrap()).unwrap(); + let balance = token.balance(&strategy.address); + println!("Strategy {} balance: {}", i, balance); + assert!(balance > user_starting_balance, "Strategy {} has zero balance", i); + } + + setup.env.jump(DAY_IN_LEDGERS * 7); + + // Simulate a user withdrawal touching all strategies + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&user); + vault_contract.withdraw(&balance, &user); + check_limits(&setup.env, "Withdraw"); +} + +#[test] +fn n_assets_one_strategy_blend() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let admin = Address::generate(&setup.env); + + let (blnd, blnd_client) = create_token(&setup.env, &admin); + let (usdc, usdc_client) = create_token(&setup.env, &admin); + let (xlm, xlm_client) = create_token(&setup.env, &admin); + + // Setting up soroswap pool + let pool_admin = Address::generate(&setup.env); + let amount_a = 100000000_0_000_000; + let amount_b = 50000000_0_000_000; + blnd_client.mint(&pool_admin, &amount_a); + usdc_client.mint(&pool_admin, &amount_b); + create_soroswap_pool( + &setup.env, + &soroswap_router, + &pool_admin, + &blnd.address, + &usdc.address, + &amount_a, + &amount_b, + ); + + let blend_fixture = BlendFixture::deploy(&setup.env, &admin, &blnd.address, &usdc.address); + + let pool = create_blend_pool(&setup.env, &blend_fixture, &admin, &usdc_client, &xlm_client); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "BlendVault"); + let vault_symbol = String::from_str(&setup.env, "BLNDVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let usdc_strategy = create_blend_strategy_contract( + &setup.env, + &usdc.address, + &pool, + &0u32, + &blnd.address, + &soroswap_router.address, + svec![&setup.env, 0u32, 1u32, 2u32, 3u32] + ); + let usdc_strategy_contract = BlendStrategyClient::new(&setup.env, &usdc_strategy); + + let xlm_strategy = create_blend_strategy_contract( + &setup.env, + &xlm.address, + &pool, + &1u32, + &blnd.address, + &soroswap_router.address, + svec![&setup.env, 0u32, 1u32, 2u32, 3u32] + ); + let xlm_strategy_contract = BlendStrategyClient::new(&setup.env, &xlm_strategy); + + let num_tokens = 2; + + let mut assets = svec![&setup.env]; + + assets.push_back(AssetStrategySet { + address: usdc.address.clone(), + strategies: svec![&setup.env, Strategy { + address: usdc_strategy_contract.address.clone(), + name: String::from_str(&setup.env, "BlendUSDC"), + paused: false, + }], + }); + assets.push_back(AssetStrategySet { + address: xlm.address.clone(), + strategies: svec![&setup.env, Strategy { + address: xlm_strategy_contract.address.clone(), + name: String::from_str(&setup.env, "BlendXLM"), + paused: false, + }], + }); + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + setup.env.budget().reset_unlimited(); + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + check_limits(&setup.env, "Create Vault"); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + let mut amounts_desired = svec![&setup.env]; + let mut amounts_min = svec![&setup.env]; + + for _ in 0..num_tokens { + usdc_client.mint(user, &user_starting_balance); + xlm_client.mint(user, &user_starting_balance); + + let desired_amount = 10000000_0_000_000i128; + + amounts_desired.push_back(desired_amount); + amounts_min.push_back(desired_amount); + } + + setup.env.budget().reset_unlimited(); + vault_contract.deposit(&amounts_desired, &amounts_min, user, &false); + check_limits(&setup.env, "Deposit"); + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + let batch_size = num_tokens; + + for i in 0..batch_size { + let asset = assets.get(i.try_into().unwrap()).unwrap(); + let strategy = asset.strategies.get(0).unwrap(); + invest_instructions.push_back(Instruction::Invest( + strategy.address.clone(), + user_starting_balance, + )); + } + + // Rebalance first batch + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + // let mut invest_instructions = svec![&setup.env]; + + // for i in batch_size..num_tokens { + // invest_instructions.push_back(Instruction::Invest( + // strategies.get(i.try_into().unwrap()).unwrap().address.clone(), + // user_starting_balance, + // )); + // } + + // // Rebalance second batch + // setup.env.budget().reset_unlimited(); + // vault_contract.rebalance(&manager, &invest_instructions); + // check_limits(&setup.env, "Invest Batch 2"); + + let mut amounts_desired = svec![&setup.env]; + let mut amounts_min = svec![&setup.env]; + + for _ in 0..num_tokens { + usdc_client.mint(user, &user_starting_balance); + xlm_client.mint(user, &user_starting_balance); + + let desired_amount = 10000000_0_000_000i128; + + amounts_desired.push_back(desired_amount); + amounts_min.push_back(desired_amount); + } + + setup.env.budget().reset_unlimited(); + vault_contract.deposit(&amounts_desired, &amounts_min, user, &true); + check_limits(&setup.env, "Deposit and Invest"); + + setup.env.jump(DAY_IN_LEDGERS * 7); + + // Simulate a user withdrawal touching all strategies + setup.env.budget().reset_unlimited(); + let balance = vault_contract.balance(&user); + vault_contract.withdraw(&(balance/2), &user); + check_limits(&setup.env, "Withdraw"); +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/limits/rebalance.rs b/apps/contracts/integration-test/src/test/limits/rebalance.rs new file mode 100644 index 00000000..dab0462e --- /dev/null +++ b/apps/contracts/integration-test/src/test/limits/rebalance.rs @@ -0,0 +1,1492 @@ +use soroban_sdk::{testutils::Address as _, vec as svec, Address, BytesN, Map, String}; + +use crate::{blend_strategy::{create_blend_strategy_contract, BlendStrategyClient}, factory::{AssetStrategySet, Strategy}, fixed_strategy::create_fixed_strategy_contract, hodl_strategy::create_hodl_strategy_contract, setup::{blend_setup::{create_blend_pool, BlendFixture, BlendPoolClient}, create_soroswap_factory, create_soroswap_pool, create_soroswap_router, VAULT_FEE}, test::{EnvTestUtils, IntegrationTest, DAY_IN_LEDGERS}, token::create_token, vault::defindex_vault_contract::{Instruction, VaultContractClient}}; + +use super::check_limits; + +#[test] +fn asset_one_strategy_hodl_rebalance() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + + let strategy_contract = create_hodl_strategy_contract(&setup.env, &token.address); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "strategy_name"), + paused: false, + }); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + token_admin_client.mint(user, &user_starting_balance); + + let deposit_amount = 100000_0_000_000i128; + + vault_contract.deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + &false, + ); + + let num_investments = 29; + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + for _ in 0..num_investments { + invest_instructions.push_back(Instruction::Invest( + strategies.first().unwrap().address.clone(), + (deposit_amount - 1000) / num_investments as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + // Checking unwind limit + let balance_on_strategy = strategy_contract.balance(&vault_contract.address); + let num_unwinds = 29; + + let mut unwind_instructions = svec![&setup.env]; + for _ in 0..num_unwinds { + unwind_instructions.push_back(Instruction::Unwind( + strategies.first().unwrap().address.clone(), + (balance_on_strategy - 1000) / num_unwinds as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &unwind_instructions); + check_limits(&setup.env, "Unwind"); +} + +#[test] +#[should_panic(expected = "Memory usage exceeded limit")] +fn asset_one_strategy_hodl_rebalance_panic_invest() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + + let strategy_contract = create_hodl_strategy_contract(&setup.env, &token.address); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "strategy_name"), + paused: false, + }); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + token_admin_client.mint(user, &user_starting_balance); + + let deposit_amount = 100000_0_000_000i128; + + vault_contract.deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + &false, + ); + + let num_investments = 30; + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + for _ in 0..num_investments { + invest_instructions.push_back(Instruction::Invest( + strategies.first().unwrap().address.clone(), + (deposit_amount - 1000) / num_investments as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); +} + +#[test] +#[should_panic(expected = "Memory usage exceeded limit")] +fn asset_one_strategy_hodl_rebalance_panic_unwind() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + + let strategy_contract = create_hodl_strategy_contract(&setup.env, &token.address); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "strategy_name"), + paused: false, + }); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + token_admin_client.mint(user, &user_starting_balance); + + let deposit_amount = 100000_0_000_000i128; + + vault_contract.deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + &false, + ); + + let num_investments = 30; + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + for _ in 0..num_investments { + invest_instructions.push_back(Instruction::Invest( + strategies.first().unwrap().address.clone(), + (deposit_amount - 1000) / num_investments as i128, + )); + } + + // Rebalance + vault_contract.rebalance(&manager, &invest_instructions); + + // Checking unwind limit + let balance_on_strategy = strategy_contract.balance(&vault_contract.address); + let num_unwinds = 30; + + let mut unwind_instructions = svec![&setup.env]; + for _ in 0..num_unwinds { + unwind_instructions.push_back(Instruction::Unwind( + strategies.first().unwrap().address.clone(), + (balance_on_strategy - 1000) / num_unwinds as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &unwind_instructions); + check_limits(&setup.env, "Unwind"); +} + +#[test] +fn asset_one_strategy_fixed_rebalance() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + + let strategy_contract = create_fixed_strategy_contract(&setup.env, &token.address, 1000u32, &token_admin_client); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "strategy_name"), + paused: false, + }); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + token_admin_client.mint(user, &user_starting_balance); + + let deposit_amount = 100000_0_000_000i128; + vault_contract.deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + &false, + ); + + let num_investments = 24; + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + for _ in 0..num_investments { + invest_instructions.push_back(Instruction::Invest( + strategies.first().unwrap().address.clone(), + (deposit_amount - 1000) / num_investments as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + setup.env.jump(DAY_IN_LEDGERS * 7); + + // harvest on strategy + strategy_contract.harvest(&manager); + + vault_contract.report(); + vault_contract.lock_fees(&None); + vault_contract.distribute_fees(&manager); + + let balance_on_strategy = strategy_contract.balance(&vault_contract.address); + let num_unwinds = 25; + + // Prepare rebalance instructions for all strategies + let mut unwind_instructions = svec![&setup.env]; + for _ in 0..num_unwinds { + unwind_instructions.push_back(Instruction::Unwind( + strategies.first().unwrap().address.clone(), + (balance_on_strategy - 1000) / num_unwinds as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &unwind_instructions); + check_limits(&setup.env, "Unwind"); +} + +#[test] +#[should_panic(expected = "Memory usage exceeded limit")] +fn asset_one_strategy_fixed_rebalance_panic_invest() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + + let strategy_contract = create_fixed_strategy_contract(&setup.env, &token.address, 1000u32, &token_admin_client); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "strategy_name"), + paused: false, + }); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + token_admin_client.mint(user, &user_starting_balance); + + let deposit_amount = 100000_0_000_000i128; + vault_contract.deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + &false, + ); + + let num_investments = 25; + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + for _ in 0..num_investments { + invest_instructions.push_back(Instruction::Invest( + strategies.first().unwrap().address.clone(), + (deposit_amount - 1000) / num_investments as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); +} + +#[test] +#[should_panic(expected = "Memory usage exceeded limit")] +fn asset_one_strategy_fixed_rebalance_panic_unwind() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let mut strategies = svec![&setup.env]; + + let strategy_contract = create_fixed_strategy_contract(&setup.env, &token.address, 1000u32, &token_admin_client); + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "strategy_name"), + paused: false, + }); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let user_starting_balance = 10000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + token_admin_client.mint(user, &user_starting_balance); + + let deposit_amount = 100000_0_000_000i128; + vault_contract.deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + &false, + ); + + let num_investments = 24; + + // Prepare rebalance instructions for all strategies + let mut invest_instructions = svec![&setup.env]; + for _ in 0..num_investments { + invest_instructions.push_back(Instruction::Invest( + strategies.first().unwrap().address.clone(), + (deposit_amount - 1000) / num_investments as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + setup.env.jump(DAY_IN_LEDGERS * 7); + + // harvest on strategy + strategy_contract.harvest(&manager); + + vault_contract.report(); + vault_contract.lock_fees(&None); + vault_contract.distribute_fees(&manager); + + let balance_on_strategy = strategy_contract.balance(&vault_contract.address); + let num_unwinds = 26; + + // Prepare rebalance instructions for all strategies + let mut unwind_instructions = svec![&setup.env]; + for _ in 0..num_unwinds { + unwind_instructions.push_back(Instruction::Unwind( + strategies.first().unwrap().address.clone(), + (balance_on_strategy - 1000) / num_unwinds as i128, + )); + } + + // Rebalance + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &unwind_instructions); + check_limits(&setup.env, "Unwind"); +} + +#[test] +fn asset_one_strategy_blend_rebalance() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let admin = Address::generate(&setup.env); + + let (blnd, blnd_client) = create_token(&setup.env, &admin); + let (usdc, usdc_client) = create_token(&setup.env, &admin); + let (_, xlm_client) = create_token(&setup.env, &admin); + + // Setting up soroswap pool + let pool_admin = Address::generate(&setup.env); + let amount_a = 100000000_0_000_000; + let amount_b = 50000000_0_000_000; + blnd_client.mint(&pool_admin, &amount_a); + usdc_client.mint(&pool_admin, &amount_b); + create_soroswap_pool( + &setup.env, + &soroswap_router, + &pool_admin, + &blnd.address, + &usdc.address, + &amount_a, + &amount_b, + ); + // End of setting up soroswap pool + + let blend_fixture = BlendFixture::deploy(&setup.env, &admin, &blnd.address, &usdc.address); + + let pool = create_blend_pool(&setup.env, &blend_fixture, &admin, &usdc_client, &xlm_client); + + let mut strategies = svec![&setup.env]; + + let strategy = create_blend_strategy_contract( + &setup.env, + &usdc.address, + &pool, + &0u32, + &blnd.address, + &soroswap_router.address, + svec![&setup.env, 0u32, 1u32, 2u32, 3u32] + ); + let strategy_contract = BlendStrategyClient::new(&setup.env, &strategy); + + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "strategy_name"), + paused: false, + }); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "BlendVault"); + let vault_symbol = String::from_str(&setup.env, "BLNDVLT"); + let manager = Address::generate(&setup.env); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: usdc.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + let users = IntegrationTest::generate_random_users(&setup.env, 3); + + let starting_balance = 300_0000000; + usdc_client.mint(&users[0], &starting_balance); + usdc_client.mint(&users[1], &starting_balance); + + vault_contract.deposit( + &svec!(&setup.env, starting_balance.clone()), + &svec!(&setup.env, starting_balance.clone()), + &users[0], + &false + ); + + vault_contract.deposit( + &svec!(&setup.env, starting_balance.clone()), + &svec!(&setup.env, starting_balance.clone()), + &users[1], + &false + ); + + let num_investments = 4; + + let mut invest_instructions = svec![&setup.env]; + for _ in 0..num_investments { + invest_instructions.push_back(Instruction::Invest( + strategies.first().unwrap().address.clone(), + (starting_balance * 2 - 1000) / num_investments as i128, + )); + } + + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); + + let balance_on_strategy = strategy_contract.balance(&vault_contract.address); + let num_unwinds = 5; + + let mut unwinds_instructions = svec![&setup.env]; + for _ in 0..num_unwinds { + unwinds_instructions.push_back(Instruction::Unwind( + strategies.first().unwrap().address.clone(), + (balance_on_strategy - 1000) / num_unwinds as i128, + )); + } + + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &unwinds_instructions); + check_limits(&setup.env, "Unwind"); +} + +#[test] +#[should_panic(expected = "CPU instructions exceeded limit")] +fn asset_one_strategy_blend_rebalance_panic_invest() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let admin = Address::generate(&setup.env); + + let (blnd, blnd_client) = create_token(&setup.env, &admin); + let (usdc, usdc_client) = create_token(&setup.env, &admin); + let (_, xlm_client) = create_token(&setup.env, &admin); + + // Setting up soroswap pool + let pool_admin = Address::generate(&setup.env); + let amount_a = 100000000_0_000_000; + let amount_b = 50000000_0_000_000; + blnd_client.mint(&pool_admin, &amount_a); + usdc_client.mint(&pool_admin, &amount_b); + create_soroswap_pool( + &setup.env, + &soroswap_router, + &pool_admin, + &blnd.address, + &usdc.address, + &amount_a, + &amount_b, + ); + // End of setting up soroswap pool + + let blend_fixture = BlendFixture::deploy(&setup.env, &admin, &blnd.address, &usdc.address); + + let pool = create_blend_pool(&setup.env, &blend_fixture, &admin, &usdc_client, &xlm_client); + + let mut strategies = svec![&setup.env]; + + let strategy = create_blend_strategy_contract( + &setup.env, + &usdc.address, + &pool, + &0u32, + &blnd.address, + &soroswap_router.address, + svec![&setup.env, 0u32, 1u32, 2u32, 3u32] + ); + let strategy_contract = BlendStrategyClient::new(&setup.env, &strategy); + + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "strategy_name"), + paused: false, + }); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "BlendVault"); + let vault_symbol = String::from_str(&setup.env, "BLNDVLT"); + let manager = Address::generate(&setup.env); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: usdc.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + let users = IntegrationTest::generate_random_users(&setup.env, 3); + + let starting_balance = 300_0000000; + usdc_client.mint(&users[0], &starting_balance); + usdc_client.mint(&users[1], &starting_balance); + + vault_contract.deposit( + &svec!(&setup.env, starting_balance.clone()), + &svec!(&setup.env, starting_balance.clone()), + &users[0], + &false + ); + + vault_contract.deposit( + &svec!(&setup.env, starting_balance.clone()), + &svec!(&setup.env, starting_balance.clone()), + &users[1], + &false + ); + + let num_investments = 5; + + let mut invest_instructions = svec![&setup.env]; + for _ in 0..num_investments { + invest_instructions.push_back(Instruction::Invest( + strategies.first().unwrap().address.clone(), + (starting_balance * 2 - 1000) / num_investments as i128, + )); + } + + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &invest_instructions); + check_limits(&setup.env, "Invest"); +} + +#[test] +#[should_panic(expected = "CPU instructions exceeded limit")] +fn asset_one_strategy_blend_rebalance_panic_unwind() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + let admin = Address::generate(&setup.env); + + let (blnd, blnd_client) = create_token(&setup.env, &admin); + let (usdc, usdc_client) = create_token(&setup.env, &admin); + let (_, xlm_client) = create_token(&setup.env, &admin); + + // Setting up soroswap pool + let pool_admin = Address::generate(&setup.env); + let amount_a = 100000000_0_000_000; + let amount_b = 50000000_0_000_000; + blnd_client.mint(&pool_admin, &amount_a); + usdc_client.mint(&pool_admin, &amount_b); + create_soroswap_pool( + &setup.env, + &soroswap_router, + &pool_admin, + &blnd.address, + &usdc.address, + &amount_a, + &amount_b, + ); + // End of setting up soroswap pool + + let blend_fixture = BlendFixture::deploy(&setup.env, &admin, &blnd.address, &usdc.address); + + let pool = create_blend_pool(&setup.env, &blend_fixture, &admin, &usdc_client, &xlm_client); + + let mut strategies = svec![&setup.env]; + + let strategy = create_blend_strategy_contract( + &setup.env, + &usdc.address, + &pool, + &0u32, + &blnd.address, + &soroswap_router.address, + svec![&setup.env, 0u32, 1u32, 2u32, 3u32] + ); + let strategy_contract = BlendStrategyClient::new(&setup.env, &strategy); + + strategies.push_back(Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "strategy_name"), + paused: false, + }); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "BlendVault"); + let vault_symbol = String::from_str(&setup.env, "BLNDVLT"); + let manager = Address::generate(&setup.env); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: usdc.address.clone(), + strategies: strategies.clone(), + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + let users = IntegrationTest::generate_random_users(&setup.env, 3); + + let starting_balance = 300_0000000; + usdc_client.mint(&users[0], &starting_balance); + usdc_client.mint(&users[1], &starting_balance); + + vault_contract.deposit( + &svec!(&setup.env, starting_balance.clone()), + &svec!(&setup.env, starting_balance.clone()), + &users[0], + &false + ); + + vault_contract.deposit( + &svec!(&setup.env, starting_balance.clone()), + &svec!(&setup.env, starting_balance.clone()), + &users[1], + &false + ); + + let num_investments = 4; + + let mut invest_instructions = svec![&setup.env]; + for _ in 0..num_investments { + invest_instructions.push_back(Instruction::Invest( + strategies.first().unwrap().address.clone(), + (starting_balance * 2 - 1000) / num_investments as i128, + )); + } + + vault_contract.rebalance(&manager, &invest_instructions); + + let balance_on_strategy = strategy_contract.balance(&vault_contract.address); + let num_unwinds = 6; + + let mut unwinds_instructions = svec![&setup.env]; + for _ in 0..num_unwinds { + unwinds_instructions.push_back(Instruction::Unwind( + strategies.first().unwrap().address.clone(), + (balance_on_strategy - 1000) / num_unwinds as i128, + )); + } + + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &unwinds_instructions); + check_limits(&setup.env, "Unwind"); +} + +#[test] +fn two_assets_swap_limits_rebalance() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (xlm, xlm_client) = create_token(&setup.env, &token_admin); + let (usdc, usdc_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + // Setting up soroswap pool + let pool_admin = Address::generate(&setup.env); + let amount_a = 100000000000_0_000_000; + let amount_b = 50000000000_0_000_000; + xlm_client.mint(&pool_admin, &amount_a); + usdc_client.mint(&pool_admin, &amount_b); + create_soroswap_pool( + &setup.env, + &soroswap_router, + &pool_admin, + &xlm.address, + &usdc.address, + &amount_a, + &amount_b, + ); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let xlm_strategy_contract = create_hodl_strategy_contract(&setup.env, &xlm.address); + let usdc_strategy_contract = create_hodl_strategy_contract(&setup.env, &usdc.address); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: xlm.address.clone(), + strategies: svec![&setup.env, Strategy { + address: xlm_strategy_contract.address.clone(), + name: String::from_str(&setup.env, "xlmStrat"), + paused: false, + }], + }, + AssetStrategySet { + address: usdc.address.clone(), + strategies: svec![&setup.env, Strategy { + address: usdc_strategy_contract.address.clone(), + name: String::from_str(&setup.env, "usdcStrat"), + paused: false, + }], + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let xlm_starting_balance = 10000000_0_000_000i128; + let usdc_starting_balance = 5000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + xlm_client.mint(user, &xlm_starting_balance); + usdc_client.mint(user, &usdc_starting_balance); + + vault_contract.deposit( + &svec![&setup.env, xlm_starting_balance, usdc_starting_balance], + &svec![&setup.env, xlm_starting_balance, usdc_starting_balance], + &user, + &false, + ); + + // Checking SWAP Limits + let usdc_balance_on_vault = usdc.balance(&vault_contract.address); + let num_exact_in = 5; + + let mut exact_in_instructions = svec![&setup.env]; + for _ in 0..num_exact_in { + exact_in_instructions.push_back(Instruction::SwapExactIn( + usdc.address.clone(), + xlm.address.clone(), + usdc_balance_on_vault / num_exact_in as i128, + 0, + setup.env.ledger().timestamp() + 3600u64, + )); + } + + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &exact_in_instructions); + check_limits(&setup.env, "SwapExactIn"); + + let num_exact_out = 5; + + let mut exact_out_instructions = svec![&setup.env]; + for _ in 0..num_exact_out { + exact_out_instructions.push_back(Instruction::SwapExactIn( + xlm.address.clone(), + usdc.address.clone(), + usdc_balance_on_vault / num_exact_out as i128, + 0, + setup.env.ledger().timestamp() + 3600u64, + )); + } + + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &exact_out_instructions); + check_limits(&setup.env, "SwapExactOut"); +} + +#[test] +#[should_panic(expected = "Memory usage exceeded limit")] +fn two_assets_swap_limits_rebalance_panic_exact_in() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (xlm, xlm_client) = create_token(&setup.env, &token_admin); + let (usdc, usdc_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + // Setting up soroswap pool + let pool_admin = Address::generate(&setup.env); + let amount_a = 100000000000_0_000_000; + let amount_b = 50000000000_0_000_000; + xlm_client.mint(&pool_admin, &amount_a); + usdc_client.mint(&pool_admin, &amount_b); + create_soroswap_pool( + &setup.env, + &soroswap_router, + &pool_admin, + &xlm.address, + &usdc.address, + &amount_a, + &amount_b, + ); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let xlm_strategy_contract = create_hodl_strategy_contract(&setup.env, &xlm.address); + let usdc_strategy_contract = create_hodl_strategy_contract(&setup.env, &usdc.address); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: xlm.address.clone(), + strategies: svec![&setup.env, Strategy { + address: xlm_strategy_contract.address.clone(), + name: String::from_str(&setup.env, "xlmStrat"), + paused: false, + }], + }, + AssetStrategySet { + address: usdc.address.clone(), + strategies: svec![&setup.env, Strategy { + address: usdc_strategy_contract.address.clone(), + name: String::from_str(&setup.env, "usdcStrat"), + paused: false, + }], + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let xlm_starting_balance = 10000000_0_000_000i128; + let usdc_starting_balance = 5000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + xlm_client.mint(user, &xlm_starting_balance); + usdc_client.mint(user, &usdc_starting_balance); + + vault_contract.deposit( + &svec![&setup.env, xlm_starting_balance, usdc_starting_balance], + &svec![&setup.env, xlm_starting_balance, usdc_starting_balance], + &user, + &false, + ); + + // Checking SWAP Limits + let usdc_balance_on_vault = usdc.balance(&vault_contract.address); + let num_exact_in = 6; + + let mut exact_in_instructions = svec![&setup.env]; + for _ in 0..num_exact_in { + exact_in_instructions.push_back(Instruction::SwapExactIn( + usdc.address.clone(), + xlm.address.clone(), + usdc_balance_on_vault / num_exact_in as i128, + 0, + setup.env.ledger().timestamp() + 3600u64, + )); + } + + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &exact_in_instructions); + check_limits(&setup.env, "SwapExactIn"); +} + +#[test] +#[should_panic(expected = "Memory usage exceeded limit")] +fn two_assets_swap_limits_rebalance_panic_exact_out() { + let setup = IntegrationTest::setup(); + setup.env.mock_all_auths(); + setup.env.budget().reset_unlimited(); + + let token_admin = Address::generate(&setup.env); + let (xlm, xlm_client) = create_token(&setup.env, &token_admin); + let (usdc, usdc_client) = create_token(&setup.env, &token_admin); + + // Soroswap Setup + let soroswap_admin = Address::generate(&setup.env); + let soroswap_factory = create_soroswap_factory(&setup.env, &soroswap_admin); + let soroswap_router = create_soroswap_router(&setup.env, &soroswap_factory.address); + + // Setting up soroswap pool + let pool_admin = Address::generate(&setup.env); + let amount_a = 100000000000_0_000_000; + let amount_b = 50000000000_0_000_000; + xlm_client.mint(&pool_admin, &amount_a); + usdc_client.mint(&pool_admin, &amount_b); + create_soroswap_pool( + &setup.env, + &soroswap_router, + &pool_admin, + &xlm.address, + &usdc.address, + &amount_a, + &amount_b, + ); + + let emergency_manager = Address::generate(&setup.env); + let rebalance_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let manager = Address::generate(&setup.env); + + let vault_fee = VAULT_FEE; + let vault_name = String::from_str(&setup.env, "MultiStrategyVault"); + let vault_symbol = String::from_str(&setup.env, "MSVLT"); + + let mut roles: Map = Map::new(&setup.env); + roles.set(0u32, emergency_manager.clone()); // EmergencyManager enum = 0 + roles.set(1u32, fee_receiver.clone()); // VaultFeeReceiver enum = 1 + roles.set(2u32, manager.clone()); // Manager enum = 2 + roles.set(3u32, rebalance_manager.clone()); // RebalanceManager enum = 3 + + let mut name_symbol: Map = Map::new(&setup.env); + name_symbol.set(String::from_str(&setup.env, "name"), vault_name); + name_symbol.set(String::from_str(&setup.env, "symbol"), vault_symbol); + + let xlm_strategy_contract = create_hodl_strategy_contract(&setup.env, &xlm.address); + let usdc_strategy_contract = create_hodl_strategy_contract(&setup.env, &usdc.address); + + let assets = svec![ + &setup.env, + AssetStrategySet { + address: xlm.address.clone(), + strategies: svec![&setup.env, Strategy { + address: xlm_strategy_contract.address.clone(), + name: String::from_str(&setup.env, "xlmStrat"), + paused: false, + }], + }, + AssetStrategySet { + address: usdc.address.clone(), + strategies: svec![&setup.env, Strategy { + address: usdc_strategy_contract.address.clone(), + name: String::from_str(&setup.env, "usdcStrat"), + paused: false, + }], + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &roles, + &vault_fee, + &assets, + &salt, + &soroswap_router.address, + &name_symbol, + &true, + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + // User deposit + let xlm_starting_balance = 10000000_0_000_000i128; + let usdc_starting_balance = 5000000_0_000_000i128; + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + xlm_client.mint(user, &xlm_starting_balance); + usdc_client.mint(user, &usdc_starting_balance); + + vault_contract.deposit( + &svec![&setup.env, xlm_starting_balance, usdc_starting_balance], + &svec![&setup.env, xlm_starting_balance, usdc_starting_balance], + &user, + &false, + ); + + // Checking SWAP Limits + let usdc_balance_on_vault = usdc.balance(&vault_contract.address); + let num_exact_in = 5; + + let mut exact_in_instructions = svec![&setup.env]; + for _ in 0..num_exact_in { + exact_in_instructions.push_back(Instruction::SwapExactIn( + usdc.address.clone(), + xlm.address.clone(), + usdc_balance_on_vault / num_exact_in as i128, + 0, + setup.env.ledger().timestamp() + 3600u64, + )); + } + + vault_contract.rebalance(&manager, &exact_in_instructions); + + let num_exact_out = 6; + + let mut exact_out_instructions = svec![&setup.env]; + for _ in 0..num_exact_out { + exact_out_instructions.push_back(Instruction::SwapExactIn( + xlm.address.clone(), + usdc.address.clone(), + usdc_balance_on_vault / num_exact_out as i128, + 0, + setup.env.ledger().timestamp() + 3600u64, + )); + } + + setup.env.budget().reset_unlimited(); + vault_contract.rebalance(&manager, &exact_out_instructions); + check_limits(&setup.env, "SwapExactOut"); +} \ No newline at end of file