From 48c3f8097c0a7e4552659fa6e7e7c2ffa58041aa Mon Sep 17 00:00:00 2001 From: coderipper Date: Tue, 5 Nov 2024 20:53:38 -0300 Subject: [PATCH 01/50] integration test, all flow tests --- apps/contracts/factory/src/test/all_flow.rs | 191 +++++++++++++++++--- 1 file changed, 170 insertions(+), 21 deletions(-) diff --git a/apps/contracts/factory/src/test/all_flow.rs b/apps/contracts/factory/src/test/all_flow.rs index fa0e5e9b..8b43c510 100644 --- a/apps/contracts/factory/src/test/all_flow.rs +++ b/apps/contracts/factory/src/test/all_flow.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{vec, BytesN, Map, String, Vec}; +use soroban_sdk::{testutils::Ledger, vec, BytesN, Map, String, Vec}; use crate::test::{create_asset_params, defindex_vault_contract::{self, Investment}, DeFindexFactoryTest}; @@ -7,7 +7,7 @@ fn test_deposit_success() { let test = DeFindexFactoryTest::setup(); test.env.mock_all_auths(); - test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &100u32, &test.defindex_wasm_hash); + test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &50u32, &test.defindex_wasm_hash); let asset_params = create_asset_params(&test); @@ -16,7 +16,7 @@ fn test_deposit_success() { test.factory_contract.create_defindex_vault( &test.emergency_manager, &test.fee_receiver, - &2000u32, + &100u32, &String::from_str(&test.env, "dfToken"), &String::from_str(&test.env, "DFT"), &test.manager, @@ -34,32 +34,31 @@ fn test_deposit_success() { let amount_token1 = 12_000i128; let users = DeFindexFactoryTest::generate_random_users(&test.env, 1); - + + // Minting Token 0 to user test.token0_admin_client.mint(&users[0], &amount_token0); let user_balance = test.token0.balance(&users[0]); assert_eq!(user_balance, amount_token0); + // Minting Token 1 to user test.token1_admin_client.mint(&users[0], &amount_token1); let user_balance = test.token1.balance(&users[0]); assert_eq!(user_balance, amount_token1); + // Checking user balance of dfTokens let df_balance = defindex_contract.balance(&users[0]); assert_eq!(df_balance, 0i128); + // Depositing Token 0 and Token 1 to defindex defindex_contract.deposit(&vec![&test.env, amount_token0, amount_token1], &vec![&test.env, 0, 0], &users[0]); + // Checking user balance of dfTokens let df_balance = defindex_contract.balance(&users[0]); - assert_eq!(df_balance, amount_token0 + amount_token1); // TODO: The amount of dfTokens minted is the sum of both asset deposited? - - - // defindex_contract.withdraw(&df_balance, &users[0]); - - // let df_balance = defindex_contract.user_balance(&users[0]); - // assert_eq!(df_balance, 0i128); - - // let user_balance = test.token0.balance(&users[0]); - // assert_eq!(user_balance, amount); + assert_eq!(df_balance, amount_token0 + amount_token1); + // Since this is the first deposit, no fees should be minted + let total_supply = defindex_contract.total_supply(); + assert_eq!(total_supply, amount_token0 + amount_token1); } #[test] @@ -67,16 +66,17 @@ fn test_withdraw_success() { let test = DeFindexFactoryTest::setup(); test.env.mock_all_auths(); - test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &100u32, &test.defindex_wasm_hash); + test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &50u32, &test.defindex_wasm_hash); let asset_params = create_asset_params(&test); let salt = BytesN::from_array(&test.env, &[0; 32]); + // Create a new DeFindex vault test.factory_contract.create_defindex_vault( &test.emergency_manager, &test.fee_receiver, - &2000u32, + &100u32, &String::from_str(&test.env, "dfToken"), &String::from_str(&test.env, "DFT"), &test.manager, @@ -84,39 +84,50 @@ fn test_withdraw_success() { &salt ); + // Verify that the vault was created let deployed_defindexes = test.factory_contract.deployed_defindexes(); assert_eq!(deployed_defindexes.len(), 1); + // Get the address of the created vault and create a client for it let defindex_address = deployed_defindexes.get(0).unwrap(); let defindex_contract = defindex_vault_contract::Client::new(&test.env, &defindex_address); + // Define the amounts to be deposited let amount_token0 = 1_000i128; let amount_token1 = 12_000i128; + // Generate random users for the test let users = DeFindexFactoryTest::generate_random_users(&test.env, 1); + // Mint Token 0 to the user and verify the balance test.token0_admin_client.mint(&users[0], &amount_token0); let user_balance = test.token0.balance(&users[0]); assert_eq!(user_balance, amount_token0); + // Mint Token 1 to the user and verify the balance test.token1_admin_client.mint(&users[0], &amount_token1); let user_balance = test.token1.balance(&users[0]); assert_eq!(user_balance, amount_token1); + // Verify the initial balance of dfTokens for the user let df_balance = defindex_contract.balance(&users[0]); assert_eq!(df_balance, 0i128); + // Deposit Token 0 and Token 1 into the vault defindex_contract.deposit(&vec![&test.env, amount_token0, amount_token1], &vec![&test.env, 0, 0], &users[0]); + // Verify the balance of dfTokens after deposit let df_balance = defindex_contract.balance(&users[0]); - assert_eq!(df_balance.clone(), amount_token0 + amount_token1); // TODO: The amount of dfTokens minted is the sum of both asset deposited? + assert_eq!(df_balance.clone(), amount_token0 + amount_token1); + // Verify the vault's balance of Token 0 and Token 1 after deposit let vault_token0_balance = test.token0.balance(&defindex_contract.address); assert_eq!(vault_token0_balance, amount_token0); let vault_token1_balance = test.token1.balance(&defindex_contract.address); assert_eq!(vault_token1_balance, amount_token1); + // Create investment strategies for the deposited tokens let investments = vec![ &test.env, Investment { @@ -128,46 +139,184 @@ fn test_withdraw_success() { strategy: test.strategy_contract_token1.address.clone() }]; - + // Invest the tokens into the strategies defindex_contract.invest(&investments); + // Verify the vault's balance of Token 0 and Token 1 after investment let vault_token0_balance = test.token0.balance(&defindex_contract.address); assert_eq!(vault_token0_balance, 0i128); let vault_token1_balance = test.token1.balance(&defindex_contract.address); assert_eq!(vault_token1_balance, 0i128); + // Verify the strategy's balance of Token 0 and Token 1 after investment let strategy_token0_balance = test.token0.balance(&test.strategy_contract_token0.address); assert_eq!(strategy_token0_balance, amount_token0); let strategy_token1_balance = test.token1.balance(&test.strategy_contract_token1.address); assert_eq!(strategy_token1_balance, amount_token1); - // let test_fee = defindex_contract.asses_fees(); - // assert_eq!(test_fee, 0i128); - + // Withdraw the dfTokens and verify the result let withdraw_result = defindex_contract.withdraw(&df_balance, &users[0]); assert_eq!(withdraw_result, vec![&test.env, 12000i128, 1000i128]); + // Verify the balance of dfTokens after withdrawal let df_balance = defindex_contract.balance(&users[0]); assert_eq!(df_balance, 0i128); + // Verify the user's balance of Token 0 and Token 1 after withdrawal let user_balance = test.token0.balance(&users[0]); assert_eq!(user_balance, amount_token0); let user_balance = test.token1.balance(&users[0]); assert_eq!(user_balance, amount_token1); + // Verify the vault's balance of Token 0 and Token 1 after withdrawal let vault_token0_balance = test.token0.balance(&defindex_contract.address); assert_eq!(vault_token0_balance, 0i128); let vault_token1_balance = test.token1.balance(&defindex_contract.address); assert_eq!(vault_token1_balance, 0i128); + // Verify the strategy's balance of Token 0 and Token 1 after withdrawal let strategy_token0_balance = test.token0.balance(&test.strategy_contract_token0.address); assert_eq!(strategy_token0_balance, 0i128); let strategy_token1_balance = test.token1.balance(&test.strategy_contract_token1.address); assert_eq!(strategy_token1_balance, 0i128); +} +#[test] +fn test_consecutive_deposits_and_partial_withdrawal() { + let test = DeFindexFactoryTest::setup(); + test.env.mock_all_auths(); + + test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &50u32, &test.defindex_wasm_hash); + + let asset_params = create_asset_params(&test); + + let salt = BytesN::from_array(&test.env, &[0; 32]); + + // Create a new DeFindex vault + test.factory_contract.create_defindex_vault( + &test.emergency_manager, + &test.fee_receiver, + &100u32, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + &test.manager, + &asset_params, + &salt + ); + + // Verify that the vault was created + let deployed_defindexes = test.factory_contract.deployed_defindexes(); + assert_eq!(deployed_defindexes.len(), 1); + + // Get the address of the created vault and create a client for it + let defindex_address = deployed_defindexes.get(0).unwrap(); + let defindex_contract = defindex_vault_contract::Client::new(&test.env, &defindex_address); + + // Define the amounts to be deposited + let amount_token0_user1 = 1_000i128; + let amount_token1_user1 = 12_000i128; + let amount_token0_user2 = 500i128; + let amount_token1_user2 = 6_000i128; + + // Generate random users for the test + let users = DeFindexFactoryTest::generate_random_users(&test.env, 2); + + // Mint Token 0 and Token 1 to user 1 and verify the balance + test.token0_admin_client.mint(&users[0], &amount_token0_user1); + test.token1_admin_client.mint(&users[0], &amount_token1_user1); + assert_eq!(test.token0.balance(&users[0]), amount_token0_user1); + assert_eq!(test.token1.balance(&users[0]), amount_token1_user1); + + // Mint Token 0 and Token 1 to user 2 and verify the balance + test.token0_admin_client.mint(&users[1], &amount_token0_user2); + test.token1_admin_client.mint(&users[1], &amount_token1_user2); + assert_eq!(test.token0.balance(&users[1]), amount_token0_user2); + assert_eq!(test.token1.balance(&users[1]), amount_token1_user2); + + // Verify the initial balance of dfTokens for both users + assert_eq!(defindex_contract.balance(&users[0]), 0i128); + assert_eq!(defindex_contract.balance(&users[1]), 0i128); + + // User 1 deposits Token 0 and Token 1 into the vault + defindex_contract.deposit(&vec![&test.env, amount_token0_user1, amount_token1_user1], &vec![&test.env, 0, 0], &users[0]); + assert_eq!(defindex_contract.balance(&users[0]), amount_token0_user1 + amount_token1_user1); + + // User 1 should have deposited all their tokens into the vault + assert_eq!(test.token0.balance(&users[0]), 0); + assert_eq!(test.token1.balance(&users[0]), 0); + assert_eq!(test.token0.balance(&defindex_contract.address), amount_token0_user1); + assert_eq!(test.token1.balance(&defindex_contract.address), amount_token1_user1); + + // Since this is the first deposit, no fees should be minted + let total_supply = defindex_contract.total_supply(); + assert_eq!(total_supply, amount_token0_user1 + amount_token1_user1); + + let mut ledger_info = test.env.ledger().get(); + ledger_info.timestamp += 31_536_000; + test.env.ledger().set(ledger_info); + + // User 2 deposits Token 0 and Token 1 into the vault + defindex_contract.deposit(&vec![&test.env, amount_token0_user2, amount_token1_user2], &vec![&test.env, 0, 0], &users[1]); + // tvl = 13000 = 13195 dfTokens + // new_tvl = 13000 + 6500 = 19792 dfTokens + + assert_eq!(defindex_contract.balance(&users[1]), 6597i128); + // User 2 should have deposited all their tokens into the vault + assert_eq!(test.token0.balance(&users[1]), 0); + assert_eq!(test.token1.balance(&users[1]), 0); + + assert_eq!(test.token0.balance(&defindex_contract.address), amount_token0_user1 + amount_token0_user2); + assert_eq!(test.token1.balance(&defindex_contract.address), amount_token1_user1 + amount_token1_user2); + + let total_supply = defindex_contract.total_supply(); + assert_eq!(total_supply, (amount_token0_user1 + amount_token1_user1) + ((amount_token0_user1 + amount_token1_user1) * 150) / 10000); + + // // Create investment strategies for the deposited tokens + // let investments = vec![ + // &test.env, + // Investment { + // amount: amount_token0_user1 + amount_token0_user2, + // strategy: test.strategy_contract_token0.address.clone() + // }, + // Investment { + // amount: amount_token1_user1 + amount_token1_user2, + // strategy: test.strategy_contract_token1.address.clone() + // }]; + + // // Invest the tokens into the strategies + // defindex_contract.invest(&investments); + + // // Verify the vault's balance of Token 0 and Token 1 after investment + // assert_eq!(test.token0.balance(&defindex_contract.address), 0i128); + // assert_eq!(test.token1.balance(&defindex_contract.address), 0i128); + + // // Verify the strategy's balance of Token 0 and Token 1 after investment + // assert_eq!(test.token0.balance(&test.strategy_contract_token0.address), amount_token0_user1 + amount_token0_user2); + // assert_eq!(test.token1.balance(&test.strategy_contract_token1.address), amount_token1_user1 + amount_token1_user2); + + // // User 1 withdraws a part of their dfTokens + // let partial_withdraw_amount = 5_000i128; + // let withdraw_result = defindex_contract.withdraw(&partial_withdraw_amount, &users[0]); + // assert_eq!(withdraw_result, vec![&test.env, 5000i128, 0i128]); + + // // Verify the balance of dfTokens after partial withdrawal + // assert_eq!(defindex_contract.balance(&users[0]), (amount_token0_user1 + amount_token1_user1) - partial_withdraw_amount); + // assert_eq!(defindex_contract.balance(&users[1]), amount_token0_user2 + amount_token1_user2); + + // // Verify the user's balance of Token 0 and Token 1 after partial withdrawal + // assert_eq!(test.token0.balance(&users[0]), partial_withdraw_amount); + // assert_eq!(test.token1.balance(&users[0]), 0i128); + + // // Verify the vault's balance of Token 0 and Token 1 after partial withdrawal + // assert_eq!(test.token0.balance(&defindex_contract.address), 0i128); + // assert_eq!(test.token1.balance(&defindex_contract.address), 0i128); + + // // Verify the strategy's balance of Token 0 and Token 1 after partial withdrawal + // assert_eq!(test.token0.balance(&test.strategy_contract_token0.address), (amount_token0_user1 + amount_token0_user2) - partial_withdraw_amount); + // assert_eq!(test.token1.balance(&test.strategy_contract_token1.address), amount_token1_user1 + amount_token1_user2); } \ No newline at end of file From 7ab6e80729a7456b30c05716b5605b435d5e0801 Mon Sep 17 00:00:00 2001 From: coderipper Date: Tue, 5 Nov 2024 20:55:08 -0300 Subject: [PATCH 02/50] added comment --- apps/contracts/factory/src/test/all_flow.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/contracts/factory/src/test/all_flow.rs b/apps/contracts/factory/src/test/all_flow.rs index 8b43c510..d18596bd 100644 --- a/apps/contracts/factory/src/test/all_flow.rs +++ b/apps/contracts/factory/src/test/all_flow.rs @@ -268,6 +268,7 @@ fn test_consecutive_deposits_and_partial_withdrawal() { assert_eq!(defindex_contract.balance(&users[1]), 6597i128); // User 2 should have deposited all their tokens into the vault assert_eq!(test.token0.balance(&users[1]), 0); + // TODO: There is an error with the deposit, since depositing only 500 of the token1 when the user is trying to deposit 6000 assert_eq!(test.token1.balance(&users[1]), 0); assert_eq!(test.token0.balance(&defindex_contract.address), amount_token0_user1 + amount_token0_user2); From a1e16975f5e1be6e0d280f4a03517f3193c0c2c8 Mon Sep 17 00:00:00 2001 From: coderipper Date: Thu, 7 Nov 2024 17:20:50 -0300 Subject: [PATCH 03/50] WIP fixing test --- apps/contracts/factory/src/test/all_flow.rs | 24 ++++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/apps/contracts/factory/src/test/all_flow.rs b/apps/contracts/factory/src/test/all_flow.rs index d18596bd..5a63df1e 100644 --- a/apps/contracts/factory/src/test/all_flow.rs +++ b/apps/contracts/factory/src/test/all_flow.rs @@ -2,12 +2,16 @@ use soroban_sdk::{testutils::Ledger, vec, BytesN, Map, String, Vec}; use crate::test::{create_asset_params, defindex_vault_contract::{self, Investment}, DeFindexFactoryTest}; +pub(crate) const DEFINDEX_FEE: u32 = 50u32; +pub(crate) const VAULT_FEE: u32 = 100u32; +pub(crate) const MAX_BPS: u32 = 10_000u32; + #[test] fn test_deposit_success() { let test = DeFindexFactoryTest::setup(); test.env.mock_all_auths(); - test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &50u32, &test.defindex_wasm_hash); + test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &DEFINDEX_FEE, &test.defindex_wasm_hash); let asset_params = create_asset_params(&test); @@ -16,7 +20,7 @@ fn test_deposit_success() { test.factory_contract.create_defindex_vault( &test.emergency_manager, &test.fee_receiver, - &100u32, + &VAULT_FEE, &String::from_str(&test.env, "dfToken"), &String::from_str(&test.env, "DFT"), &test.manager, @@ -66,7 +70,7 @@ fn test_withdraw_success() { let test = DeFindexFactoryTest::setup(); test.env.mock_all_auths(); - test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &50u32, &test.defindex_wasm_hash); + test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &DEFINDEX_FEE, &test.defindex_wasm_hash); let asset_params = create_asset_params(&test); @@ -76,7 +80,7 @@ fn test_withdraw_success() { test.factory_contract.create_defindex_vault( &test.emergency_manager, &test.fee_receiver, - &100u32, + &VAULT_FEE, &String::from_str(&test.env, "dfToken"), &String::from_str(&test.env, "DFT"), &test.manager, @@ -201,7 +205,7 @@ fn test_consecutive_deposits_and_partial_withdrawal() { test.factory_contract.create_defindex_vault( &test.emergency_manager, &test.fee_receiver, - &100u32, + &VAULT_FEE, &String::from_str(&test.env, "dfToken"), &String::from_str(&test.env, "DFT"), &test.manager, @@ -261,11 +265,15 @@ fn test_consecutive_deposits_and_partial_withdrawal() { test.env.ledger().set(ledger_info); // User 2 deposits Token 0 and Token 1 into the vault + // total_fees = (fee_rate as i128 * total_supply * time_elapsed) / ((SECONDS_PER_YEAR * MAX_BPS) - (fee_rate as i128 * time_elapsed)); + let fee_rate = DEFINDEX_FEE + VAULT_FEE; + let expected_minted_fee: i128 = (fee_rate as i128).checked_mul(total_supply).unwrap().checked_mul(31_536_000i128).unwrap().checked_div(31_536_000i128.checked_mul(MAX_BPS as i128).unwrap().checked_sub((fee_rate as i128).checked_mul(31_536_000i128).unwrap()).unwrap()).unwrap(); + defindex_contract.deposit(&vec![&test.env, amount_token0_user2, amount_token1_user2], &vec![&test.env, 0, 0], &users[1]); - // tvl = 13000 = 13195 dfTokens - // new_tvl = 13000 + 6500 = 19792 dfTokens + // tvl = 13000 = 13197 dfTokens + // new_tvl = 13000 + 6500 = 19500 dfTokens - assert_eq!(defindex_contract.balance(&users[1]), 6597i128); + assert_eq!(defindex_contract.balance(&users[1]), 6598i128); // User 2 should have deposited all their tokens into the vault assert_eq!(test.token0.balance(&users[1]), 0); // TODO: There is an error with the deposit, since depositing only 500 of the token1 when the user is trying to deposit 6000 From 6ec5d4d4852b24c0e71fed9583a21879730c021a Mon Sep 17 00:00:00 2001 From: coderipper Date: Tue, 12 Nov 2024 10:37:03 -0300 Subject: [PATCH 04/50] integration-test directory --- apps/contracts/Cargo.lock | 10 ++++++++++ apps/contracts/Cargo.toml | 2 +- apps/contracts/integration-test/Cargo.toml | 13 +++++++++++++ apps/contracts/integration-test/Makefile | 17 +++++++++++++++++ apps/contracts/integration-test/src/lib.rs | 0 apps/contracts/vault/src/token/contract.rs | 7 ------- 6 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 apps/contracts/integration-test/Cargo.toml create mode 100644 apps/contracts/integration-test/Makefile create mode 100644 apps/contracts/integration-test/src/lib.rs diff --git a/apps/contracts/Cargo.lock b/apps/contracts/Cargo.lock index 889ab1d3..dc557506 100644 --- a/apps/contracts/Cargo.lock +++ b/apps/contracts/Cargo.lock @@ -587,6 +587,16 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" +[[package]] +name = "integration-test" +version = "0.0.0" +dependencies = [ + "defindex-factory", + "defindex-vault", + "hodl_strategy", + "soroban-sdk", +] + [[package]] name = "itertools" version = "0.11.0" diff --git a/apps/contracts/Cargo.toml b/apps/contracts/Cargo.toml index 9dee20c8..c1aafc48 100644 --- a/apps/contracts/Cargo.toml +++ b/apps/contracts/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["strategies/*", "vault", "factory"] +members = ["strategies/*", "vault", "factory", "integration-test"] exclude = [ "strategies/external_wasms", ] diff --git a/apps/contracts/integration-test/Cargo.toml b/apps/contracts/integration-test/Cargo.toml new file mode 100644 index 00000000..4ef2117f --- /dev/null +++ b/apps/contracts/integration-test/Cargo.toml @@ -0,0 +1,13 @@ + +[package] +name = "integration-test" +version = "0.0.0" +authors = ["coderipper "] +edition = "2021" +publish = false + +[dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } +factory = { path = "../factory", package = "defindex-factory" } +vault = { path = "../vault", package = "defindex-vault"} +hodl-strategy={ path="../strategies/hodl", package="hodl_strategy"} \ No newline at end of file diff --git a/apps/contracts/integration-test/Makefile b/apps/contracts/integration-test/Makefile new file mode 100644 index 00000000..6eb00e2c --- /dev/null +++ b/apps/contracts/integration-test/Makefile @@ -0,0 +1,17 @@ +default: build + +all: test + +test: build + cargo test + +build: + cargo build --target wasm32-unknown-unknown --release + soroban contract optimize --wasm ../../target/wasm32-unknown-unknown/release/hodl_strategy.wasm + @rm ../../target/wasm32-unknown-unknown/release/hodl_strategy.wasm + +fmt: + cargo fmt --all --check + +clean: + cargo clean \ No newline at end of file diff --git a/apps/contracts/integration-test/src/lib.rs b/apps/contracts/integration-test/src/lib.rs new file mode 100644 index 00000000..e69de29b diff --git a/apps/contracts/vault/src/token/contract.rs b/apps/contracts/vault/src/token/contract.rs index 98d09427..430cf746 100644 --- a/apps/contracts/vault/src/token/contract.rs +++ b/apps/contracts/vault/src/token/contract.rs @@ -54,13 +54,6 @@ impl VaultToken { pub fn total_supply(e: Env) -> i128 { read_total_supply(&e) } - - #[cfg(test)] - pub fn get_allowance(e: Env, from: Address, spender: Address) -> Option { - let key = DataKey::Allowance(AllowanceDataKey { from, spender }); - let allowance = e.storage().temporary().get::<_, AllowanceValue>(&key); - allowance - } } #[contractimpl] From 93c520233a43421f1cc2291efba6be9762848740 Mon Sep 17 00:00:00 2001 From: coderipper Date: Tue, 12 Nov 2024 16:24:39 -0300 Subject: [PATCH 05/50] fixed aggregator args --- apps/contracts/vault/src/aggregator.rs | 36 +++++++++++++------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/apps/contracts/vault/src/aggregator.rs b/apps/contracts/vault/src/aggregator.rs index c10e34db..6923e188 100644 --- a/apps/contracts/vault/src/aggregator.rs +++ b/apps/contracts/vault/src/aggregator.rs @@ -37,19 +37,19 @@ pub fn internal_swap_exact_tokens_for_tokens( return Err(ContractError::UnsupportedAsset); } - let mut init_args: Vec = vec![&e]; - init_args.push_back(token_in.to_val()); - init_args.push_back(token_out.to_val()); - init_args.push_back(amount_in.into_val(e)); - init_args.push_back(amount_out_min.into_val(e)); - init_args.push_back(distribution.into_val(e)); - init_args.push_back(e.current_contract_address().to_val()); - init_args.push_back(deadline.into_val(e)); + let mut swap_args: Vec = vec![&e]; + swap_args.push_back(token_in.to_val()); + swap_args.push_back(token_out.to_val()); + swap_args.push_back(amount_in.into_val(e)); + swap_args.push_back(amount_out_min.into_val(e)); + swap_args.push_back(distribution.into_val(e)); + swap_args.push_back(e.current_contract_address().to_val()); + swap_args.push_back(deadline.into_val(e)); e.invoke_contract( &aggregator_address, &Symbol::new(&e, "swap_exact_tokens_for_tokens"), - Vec::new(&e), + swap_args, ) } @@ -69,18 +69,18 @@ pub fn internal_swap_tokens_for_exact_tokens( return Err(ContractError::UnsupportedAsset); } - let mut init_args: Vec = vec![&e]; - init_args.push_back(token_in.to_val()); - init_args.push_back(token_out.to_val()); - init_args.push_back(amount_out.into_val(e)); - init_args.push_back(amount_in_max.into_val(e)); - init_args.push_back(distribution.into_val(e)); - init_args.push_back(e.current_contract_address().to_val()); - init_args.push_back(deadline.into_val(e)); + let mut swap_args: Vec = vec![&e]; + swap_args.push_back(token_in.to_val()); + swap_args.push_back(token_out.to_val()); + swap_args.push_back(amount_out.into_val(e)); + swap_args.push_back(amount_in_max.into_val(e)); + swap_args.push_back(distribution.into_val(e)); + swap_args.push_back(e.current_contract_address().to_val()); + swap_args.push_back(deadline.into_val(e)); e.invoke_contract( &aggregator_address, &Symbol::new(&e, "swap_tokens_for_exact_tokens"), - Vec::new(&e), + swap_args, ) } From 54aae195af1517679d18f2727eb1b64e51345c96 Mon Sep 17 00:00:00 2001 From: coderipper Date: Tue, 12 Nov 2024 16:49:36 -0300 Subject: [PATCH 06/50] integration tests --- apps/contracts/integration-test/src/lib.rs | 4 + apps/contracts/integration-test/src/setup.rs | 204 +++++++++++++++++++ apps/contracts/integration-test/src/test.rs | 151 ++++++++++++++ apps/contracts/integration-test/src/token.rs | 20 ++ 4 files changed, 379 insertions(+) create mode 100644 apps/contracts/integration-test/src/setup.rs create mode 100644 apps/contracts/integration-test/src/test.rs create mode 100644 apps/contracts/integration-test/src/token.rs diff --git a/apps/contracts/integration-test/src/lib.rs b/apps/contracts/integration-test/src/lib.rs index e69de29b..b18c1a35 100644 --- a/apps/contracts/integration-test/src/lib.rs +++ b/apps/contracts/integration-test/src/lib.rs @@ -0,0 +1,4 @@ +#![allow(clippy::all)] +pub mod test; +pub mod token; +// pub mod setup; diff --git a/apps/contracts/integration-test/src/setup.rs b/apps/contracts/integration-test/src/setup.rs new file mode 100644 index 00000000..48a99532 --- /dev/null +++ b/apps/contracts/integration-test/src/setup.rs @@ -0,0 +1,204 @@ +use soroban_sdk::{testutils::Address as _, vec as svec, Address, String, Vec as SVec}; + +mod hodl_strategy { + soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/hodl_strategy.optimized.wasm"); + pub type StrategyContractClient<'a> = Client<'a>; +} + +/// Create a test fixture with a pool and a whale depositing and borrowing all assets +pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> { + // let mut fixture = TestFixture::create(wasm); + + // // mint whale tokens + // let frodo = fixture.users[0].clone(); + // fixture.tokens[TokenIndex::STABLE].mint(&frodo, &(100_000 * 10i128.pow(6))); + // fixture.tokens[TokenIndex::XLM].mint(&frodo, &(1_000_000 * SCALAR_7)); + // fixture.tokens[TokenIndex::WETH].mint(&frodo, &(100 * 10i128.pow(9))); + + // // mint LP tokens with whale + // // frodo has 40m BLND from drop + // fixture.tokens[TokenIndex::BLND].mint(&frodo, &(70_000_000 * SCALAR_7)); + // fixture.tokens[TokenIndex::USDC].mint(&frodo, &(2_600_000 * SCALAR_7)); + // fixture.lp.join_pool( + // &(10_000_000 * SCALAR_7), + // &svec![&fixture.env, 110_000_000 * SCALAR_7, 2_600_000 * SCALAR_7,], + // &frodo, + // ); + + // // create pool + // fixture.create_pool(String::from_str(&fixture.env, "Teapot"), 0_1000000, 6); + + // let mut stable_config = default_reserve_metadata(); + // stable_config.decimals = 6; + // stable_config.c_factor = 0_900_0000; + // stable_config.l_factor = 0_950_0000; + // stable_config.util = 0_850_0000; + // fixture.create_pool_reserve(0, TokenIndex::STABLE, &stable_config); + + // let mut xlm_config = default_reserve_metadata(); + // xlm_config.c_factor = 0_750_0000; + // xlm_config.l_factor = 0_750_0000; + // xlm_config.util = 0_500_0000; + // fixture.create_pool_reserve(0, TokenIndex::XLM, &xlm_config); + + // let mut weth_config = default_reserve_metadata(); + // weth_config.decimals = 9; + // weth_config.c_factor = 0_800_0000; + // weth_config.l_factor = 0_800_0000; + // weth_config.util = 0_700_0000; + // fixture.create_pool_reserve(0, TokenIndex::WETH, &weth_config); + + // // enable emissions for pool + // let pool_fixture = &fixture.pools[0]; + + // let reserve_emissions: soroban_sdk::Vec = soroban_sdk::vec![ + // &fixture.env, + // ReserveEmissionMetadata { + // res_index: 0, // STABLE + // res_type: 0, // d_token + // share: 0_600_0000 + // }, + // ReserveEmissionMetadata { + // res_index: 1, // XLM + // res_type: 1, // b_token + // share: 0_400_0000 + // }, + // ]; + // pool_fixture.pool.set_emissions_config(&reserve_emissions); + + // // deposit into backstop, add to reward zone + // fixture + // .backstop + // .deposit(&frodo, &pool_fixture.pool.address, &(50_000 * SCALAR_7)); + // fixture.backstop.update_tkn_val(); + // fixture + // .backstop + // .add_reward(&pool_fixture.pool.address, &Address::generate(&fixture.env)); + // pool_fixture.pool.set_status(&3); + // pool_fixture.pool.update_status(); + + // // enable emissions + // fixture.emitter.distribute(); + // fixture.backstop.gulp_emissions(); + // pool_fixture.pool.gulp_emissions(); + + // fixture.jump(60); + + // // supply and borrow STABLE for 80% utilization (close to target) + // let requests: SVec = svec![ + // &fixture.env, + // Request { + // request_type: RequestType::SupplyCollateral as u32, + // address: fixture.tokens[TokenIndex::STABLE].address.clone(), + // amount: 10_000 * 10i128.pow(6), + // }, + // Request { + // request_type: RequestType::Borrow as u32, + // address: fixture.tokens[TokenIndex::STABLE].address.clone(), + // amount: 8_000 * 10i128.pow(6), + // }, + // ]; + // pool_fixture.pool.submit(&frodo, &frodo, &frodo, &requests); + + // // supply and borrow WETH for 50% utilization (below target) + // let requests: SVec = svec![ + // &fixture.env, + // Request { + // request_type: RequestType::SupplyCollateral as u32, + // address: fixture.tokens[TokenIndex::WETH].address.clone(), + // amount: 10 * 10i128.pow(9), + // }, + // Request { + // request_type: RequestType::Borrow as u32, + // address: fixture.tokens[TokenIndex::WETH].address.clone(), + // amount: 5 * 10i128.pow(9), + // }, + // ]; + // pool_fixture.pool.submit(&frodo, &frodo, &frodo, &requests); + + // // supply and borrow XLM for 65% utilization (above target) + // let requests: SVec = svec![ + // &fixture.env, + // Request { + // request_type: RequestType::SupplyCollateral as u32, + // address: fixture.tokens[TokenIndex::XLM].address.clone(), + // amount: 100_000 * SCALAR_7, + // }, + // Request { + // request_type: RequestType::Borrow as u32, + // address: fixture.tokens[TokenIndex::XLM].address.clone(), + // amount: 65_000 * SCALAR_7, + // }, + // ]; + // pool_fixture.pool.submit(&frodo, &frodo, &frodo, &requests); + + // fixture.jump(60 * 60); // 1 hr + + // fixture.env.budget().reset_unlimited(); + // fixture +} + +#[cfg(test)] +mod tests { + + // use crate::test_fixture::PoolFixture; + + // use super::*; + + #[test] + fn test_create_fixture_with_data_wasm() { + // let fixture: TestFixture<'_> = create_fixture_with_data(true); + // let frodo = fixture.users.get(0).unwrap(); + // let pool_fixture: &PoolFixture = fixture.pools.get(0).unwrap(); + + // // validate backstop deposit and drop + // assert_eq!( + // 50_000 * SCALAR_7, + // fixture.lp.balance(&fixture.backstop.address) + // ); + // assert_eq!( + // 10_000_000 * SCALAR_7, + // fixture.tokens[TokenIndex::BLND].balance(&fixture.bombadil) + // ); + + // // validate pool actions + // assert_eq!( + // 2_000 * 10i128.pow(6), + // fixture.tokens[TokenIndex::STABLE].balance(&pool_fixture.pool.address) + // ); + // assert_eq!( + // 35_000 * SCALAR_7, + // fixture.tokens[TokenIndex::XLM].balance(&pool_fixture.pool.address) + // ); + // assert_eq!( + // 5 * 10i128.pow(9), + // fixture.tokens[TokenIndex::WETH].balance(&pool_fixture.pool.address) + // ); + + // assert_eq!( + // 98_000 * 10i128.pow(6), + // fixture.tokens[TokenIndex::STABLE].balance(&frodo) + // ); + // assert_eq!( + // 965_000 * SCALAR_7, + // fixture.tokens[TokenIndex::XLM].balance(&frodo) + // ); + // assert_eq!( + // 95 * 10i128.pow(9), + // fixture.tokens[TokenIndex::WETH].balance(&frodo) + // ); + + // // validate emissions are turned on + // let (emis_config, emis_data) = fixture.read_reserve_emissions(0, TokenIndex::STABLE, 0); + // assert_eq!( + // emis_data.last_time, + // fixture.env.ledger().timestamp() - 60 * 61 + // ); + // assert_eq!(emis_data.index, 0); + // assert_eq!(0_180_0000, emis_config.eps); + // assert_eq!( + // fixture.env.ledger().timestamp() + 7 * 24 * 60 * 60 - 60 * 61, + // emis_config.expiration + // ) + } +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test.rs b/apps/contracts/integration-test/src/test.rs new file mode 100644 index 00000000..f1018030 --- /dev/null +++ b/apps/contracts/integration-test/src/test.rs @@ -0,0 +1,151 @@ +#![cfg(test)] +extern crate std; +use soroban_sdk::token::{ + StellarAssetClient as SorobanTokenAdminClient, TokenClient as SorobanTokenClient, +}; +use soroban_sdk::{BytesN, Val}; +use soroban_sdk::{ + Env, + Address, + testutils::Address as _, + Vec, + vec as sorobanvec, + String +}; +use std::vec; + +// DeFindex Hodl Strategy Contract +mod hodl_strategy { + soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/hodl_strategy.optimized.wasm"); + pub type StrategyContractClient<'a> = Client<'a>; +} + +use hodl_strategy::StrategyContractClient; + +use crate::token; + +fn create_strategy_contract<'a>(e: &Env, asset: &Address, init_args: &Vec) -> StrategyContractClient<'a> { + let address = &e.register_contract_wasm(None, hodl_strategy::WASM); + let strategy = StrategyContractClient::new(e, address); + strategy.initialize(asset, init_args); + strategy +} + +// // DeFindex Vault Contract +// mod defindex_vault_contract { +// soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/defindex_vault.optimized.wasm"); +// } + +mod factory { + soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/defindex_factory.optimized.wasm"); + pub type DeFindexFactoryClient<'a> = Client<'a>; +} + +// use factory::DeFindexFactoryClient; + +// DeFindex Vault Contract +// fn create_defindex_factory<'a>(e: &Env) -> DeFindexFactoryClient<'a> { +// DeFindexFactoryClient::new(e, &e.register_contract(None, DeFindexFactory {})) +// } + +// pub(crate) fn create_asset_params(test: &DeFindexFactoryTest) -> Vec { +// sorobanvec![ +// &test.env, +// AssetStrategySet { +// address: test.token0.address.clone(), +// strategies: sorobanvec![ +// &test.env, +// Strategy { +// address: test.strategy_contract_token0.address.clone(), +// name: String::from_str(&test.env, "Strategy 1"), +// paused: false, +// } +// ], +// }, +// AssetStrategySet { +// address: test.token1.address.clone(), +// strategies: sorobanvec![ +// &test.env, +// Strategy { +// address: test.strategy_contract_token1.address.clone(), +// name: String::from_str(&test.env, "Strategy 1"), +// paused: false, +// } +// ], +// } +// ] +// } + +pub struct DeFindexFactoryTest<'a> { + env: Env, + // factory_contract: DeFindexFactoryClient<'a>, + // admin: Address, + // defindex_receiver: Address, + // defindex_wasm_hash: BytesN<32>, + // emergency_manager: Address, + // fee_receiver: Address, + // manager: Address, + // token0_admin_client: SorobanTokenAdminClient<'a>, + // token0: SorobanTokenClient<'a>, + token_admin_client: SorobanTokenAdminClient<'a>, + // token1: SorobanTokenClient<'a>, + strategy_contract: StrategyContractClient<'a>, + // strategy_contract_token1: StrategyContractClient<'a>, +} + +impl<'a> DeFindexFactoryTest<'a> { + fn setup() -> Self { + let env = Env::default(); + env.budget().reset_unlimited(); + // env.mock_all_auths(); + // let factory_contract = create_defindex_factory(&env); + + // let admin = Address::generate(&env); + // let defindex_receiver = Address::generate(&env); + + // let defindex_wasm_hash = env.deployer().upload_contract_wasm(defindex_vault_contract::WASM); + + // let emergency_manager = Address::generate(&env); + // let fee_receiver = Address::generate(&env); + // let manager = Address::generate(&env); + + // let token0_admin = Address::generate(&env); + // let token0 = create_token_contract(&env, &token0_admin); + + let token_admin = Address::generate(&env); + // let token1 = create_token_contract(&env, &token1_admin); + + // let token0_admin_client = get_token_admin_client(&env, &token0.address.clone()); + // let token1_admin_client = get_token_admin_client(&env, &token1.address.clone()); + let (token, token_admin_client) = token::create_token(&env, &token_admin); + + // // TODO: Add a strategy adapter, this is a mockup + let strategy_contract = create_strategy_contract(&env, &token.address, &Vec::new(&env)); + // let strategy_contract_token1 = create_strategy_contract(&env, &token1.address, &Vec::new(&env)); + + DeFindexFactoryTest { + env, + // factory_contract, + // admin, + // defindex_receiver, + // defindex_wasm_hash, + // emergency_manager, + // fee_receiver, + // manager, + // token0_admin_client, + // token0, + token_admin_client, + // token1, + strategy_contract, + // strategy_contract_token1 + } + } + + pub(crate) fn generate_random_users(e: &Env, users_count: u32) -> vec::Vec
{ + let mut users = vec![]; + for _c in 0..users_count { + users.push(Address::generate(e)); + } + users + } +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/token.rs b/apps/contracts/integration-test/src/token.rs new file mode 100644 index 00000000..ef729253 --- /dev/null +++ b/apps/contracts/integration-test/src/token.rs @@ -0,0 +1,20 @@ +use soroban_sdk::{token::{TokenClient as SorobanTokenClient, StellarAssetClient as SorobanTokenAdminClient}, Address, Env}; + +fn create_token_contract<'a>(e: &Env, admin: &Address) -> SorobanTokenClient<'a> { + SorobanTokenClient::new(e, &e.register_stellar_asset_contract_v2(admin.clone()).address()) +} + +fn get_token_admin_client<'a>( + e: &Env, + address: &Address, +) -> SorobanTokenAdminClient<'a> { + SorobanTokenAdminClient::new(e, address) +} + +pub fn create_token<'a>(e: &Env, admin: &Address) -> (SorobanTokenClient<'a>, SorobanTokenAdminClient<'a>) { + let token = create_token_contract(e, admin); + + let token_admin_client = get_token_admin_client(e, &token.address); + + (token, token_admin_client) +} \ No newline at end of file From 2123382b81483cbd57a280abe870010a3f968318 Mon Sep 17 00:00:00 2001 From: coderipper Date: Tue, 12 Nov 2024 18:33:40 -0300 Subject: [PATCH 07/50] using common for AssetStrategy Structs --- apps/contracts/Cargo.lock | 10 +++ apps/contracts/Cargo.toml | 3 +- apps/contracts/common/Cargo.toml | 14 +++++ apps/contracts/common/src/lib.rs | 3 + apps/contracts/common/src/models.rs | 16 +++++ apps/contracts/factory/Cargo.toml | 2 + apps/contracts/factory/src/defindex.rs | 4 -- apps/contracts/factory/src/events.rs | 2 +- apps/contracts/factory/src/lib.rs | 62 ++++++++++--------- apps/contracts/factory/src/test.rs | 2 +- apps/contracts/integration-test/Makefile | 17 ----- .../contracts/integration-test/src/factory.rs | 14 +++++ .../integration-test/src/hodl_strategy.rs | 15 +++++ apps/contracts/integration-test/src/lib.rs | 3 + apps/contracts/integration-test/src/test.rs | 33 +--------- apps/contracts/integration-test/src/vault.rs | 4 ++ apps/contracts/vault/Cargo.toml | 1 + apps/contracts/vault/src/events.rs | 2 +- apps/contracts/vault/src/funds.rs | 2 +- apps/contracts/vault/src/interface.rs | 3 +- apps/contracts/vault/src/investment.rs | 3 +- apps/contracts/vault/src/lib.rs | 3 +- apps/contracts/vault/src/models.rs | 15 ----- apps/contracts/vault/src/storage.rs | 2 +- apps/contracts/vault/src/strategies.rs | 3 +- apps/contracts/vault/src/utils.rs | 2 +- 26 files changed, 131 insertions(+), 109 deletions(-) create mode 100644 apps/contracts/common/Cargo.toml create mode 100644 apps/contracts/common/src/lib.rs create mode 100644 apps/contracts/common/src/models.rs delete mode 100644 apps/contracts/integration-test/Makefile create mode 100644 apps/contracts/integration-test/src/factory.rs create mode 100644 apps/contracts/integration-test/src/hodl_strategy.rs create mode 100644 apps/contracts/integration-test/src/vault.rs diff --git a/apps/contracts/Cargo.lock b/apps/contracts/Cargo.lock index 25ce9fc2..64bb7b90 100644 --- a/apps/contracts/Cargo.lock +++ b/apps/contracts/Cargo.lock @@ -144,6 +144,13 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "common" +version = "1.0.0" +dependencies = [ + "soroban-sdk", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -275,6 +282,8 @@ dependencies = [ name = "defindex-factory" version = "1.0.0" dependencies = [ + "common", + "defindex-strategy-core", "soroban-sdk", ] @@ -289,6 +298,7 @@ dependencies = [ name = "defindex-vault" version = "1.0.0" dependencies = [ + "common", "defindex-strategy-core", "soroban-sdk", "soroban-token-sdk", diff --git a/apps/contracts/Cargo.toml b/apps/contracts/Cargo.toml index c1aafc48..6d1a583f 100644 --- a/apps/contracts/Cargo.toml +++ b/apps/contracts/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["strategies/*", "vault", "factory", "integration-test"] +members = ["common", "strategies/*", "vault", "factory", "integration-test"] exclude = [ "strategies/external_wasms", ] @@ -16,6 +16,7 @@ soroban-sdk = "21.7.6" soroban-token-sdk = { version = "21.0.1-preview.3" } # soroswap-library = "0.3.0" defindex-strategy-core={ path="./strategies/core", package="defindex-strategy-core" } +common={ path="./common", package="common" } [profile.release] opt-level = "z" diff --git a/apps/contracts/common/Cargo.toml b/apps/contracts/common/Cargo.toml new file mode 100644 index 00000000..37476ab1 --- /dev/null +++ b/apps/contracts/common/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "common" +version = { workspace = true } +authors = ["coderipper "] +license = { workspace = true } +edition = { workspace = true } +publish = false +repository = { workspace = true } + +[dependencies] +soroban-sdk = { workspace = true } + +[dev-dependencies] +soroban-sdk = { workspace = true, features = ["testutils"] } \ No newline at end of file diff --git a/apps/contracts/common/src/lib.rs b/apps/contracts/common/src/lib.rs new file mode 100644 index 00000000..bc7fe786 --- /dev/null +++ b/apps/contracts/common/src/lib.rs @@ -0,0 +1,3 @@ +#![no_std] + +pub mod models; \ No newline at end of file diff --git a/apps/contracts/common/src/models.rs b/apps/contracts/common/src/models.rs new file mode 100644 index 00000000..401e620a --- /dev/null +++ b/apps/contracts/common/src/models.rs @@ -0,0 +1,16 @@ +use soroban_sdk::{contracttype, Address, String, Vec}; + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Strategy { + pub address: Address, + pub name: String, + pub paused: bool, +} + +#[contracttype] +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct AssetStrategySet { + pub address: Address, + pub strategies: Vec, +} \ No newline at end of file diff --git a/apps/contracts/factory/Cargo.toml b/apps/contracts/factory/Cargo.toml index 21f2a8b5..a4b2ad8c 100644 --- a/apps/contracts/factory/Cargo.toml +++ b/apps/contracts/factory/Cargo.toml @@ -12,6 +12,8 @@ crate-type = ["cdylib"] [dependencies] soroban-sdk = { workspace = true } +defindex-strategy-core = { workspace = true } +common = { workspace = true } [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/apps/contracts/factory/src/defindex.rs b/apps/contracts/factory/src/defindex.rs index 1b6a2d19..96d13b78 100644 --- a/apps/contracts/factory/src/defindex.rs +++ b/apps/contracts/factory/src/defindex.rs @@ -2,10 +2,6 @@ #![allow(unused)] use soroban_sdk::{contracttype, contracterror, xdr::ToXdr, Address, Bytes, BytesN, Env, Vec}; -soroban_sdk::contractimport!( - file = "../target/wasm32-unknown-unknown/release/defindex_vault.optimized.wasm" -); - // Define a function to create a new contract instance pub fn create_contract( e: &Env, // Pass in the current environment as an argument diff --git a/apps/contracts/factory/src/events.rs b/apps/contracts/factory/src/events.rs index 92eaa54c..baa1e041 100644 --- a/apps/contracts/factory/src/events.rs +++ b/apps/contracts/factory/src/events.rs @@ -1,6 +1,6 @@ //! Definition of the Events used in the contract +use common::models::AssetStrategySet; use soroban_sdk::{contracttype, symbol_short, Address, Env, Vec}; -use crate::defindex::AssetStrategySet; // INITIALIZED #[contracttype] diff --git a/apps/contracts/factory/src/lib.rs b/apps/contracts/factory/src/lib.rs index 033908be..12d5ece9 100644 --- a/apps/contracts/factory/src/lib.rs +++ b/apps/contracts/factory/src/lib.rs @@ -5,11 +5,12 @@ mod events; mod storage; mod error; +use common::models::AssetStrategySet; use soroban_sdk::{ - contract, contractimpl, Address, BytesN, Env, Map, String, Vec + contract, contractimpl, vec, Address, BytesN, Env, Map, String, Symbol, Val, Vec, IntoVal }; use error::FactoryError; -use defindex::{create_contract, AssetStrategySet}; +pub use defindex::create_contract; use storage::{ add_new_defindex, extend_instance_ttl, get_admin, get_defi_wasm_hash, get_defindex_receiver, get_deployed_defindexes, get_fee_rate, has_admin, put_admin, put_defi_wasm_hash, put_defindex_receiver, put_defindex_fee }; fn check_initialized(e: &Env) -> Result<(), FactoryError> { @@ -238,17 +239,18 @@ impl FactoryTrait for DeFindexFactory { let defindex_receiver = get_defindex_receiver(&e); - defindex::Client::new(&e, &defindex_address).initialize( - &assets, - &manager, - &emergency_manager, - &fee_receiver, - &vault_fee, - &defindex_receiver, - ¤t_contract, - &vault_name, - &vault_symbol, - ); + let mut init_args: Vec = vec![&e]; + init_args.push_back(assets.to_val()); + init_args.push_back(manager.to_val()); + init_args.push_back(emergency_manager.to_val()); + init_args.push_back(fee_receiver.to_val()); + init_args.push_back(vault_fee.into_val(&e)); + init_args.push_back(defindex_receiver.to_val()); + init_args.push_back(current_contract.to_val()); + init_args.push_back(vault_name.to_val()); + init_args.push_back(vault_symbol.to_val()); + + e.invoke_contract::(&defindex_address, &Symbol::new(&e, "initialize"), init_args); add_new_defindex(&e, defindex_address.clone()); events::emit_create_defindex_vault(&e, emergency_manager, fee_receiver, manager, vault_fee, assets); @@ -298,30 +300,30 @@ impl FactoryTrait for DeFindexFactory { let defindex_receiver = get_defindex_receiver(&e); - let defindex_client = defindex::Client::new(&e, &defindex_address); + let mut init_args: Vec = vec![&e]; + init_args.push_back(assets.to_val()); + init_args.push_back(manager.to_val()); + init_args.push_back(emergency_manager.to_val()); + init_args.push_back(fee_receiver.to_val()); + init_args.push_back(vault_fee.into_val(&e)); + init_args.push_back(defindex_receiver.to_val()); + init_args.push_back(current_contract.to_val()); + init_args.push_back(vault_name.to_val()); + init_args.push_back(vault_symbol.to_val()); - defindex_client.initialize( - &assets, - &manager, - &emergency_manager, - &fee_receiver, - &vault_fee, - &defindex_receiver, - ¤t_contract, - &vault_name, - &vault_symbol, - ); + e.invoke_contract::(&defindex_address, &Symbol::new(&e, "initialize"), init_args); let mut amounts_min = Vec::new(&e); for _ in 0..amounts.len() { amounts_min.push_back(0i128); } - defindex_client.deposit( - &amounts, - &amounts_min, - &caller - ); + let mut deposit_args: Vec = vec![&e]; + deposit_args.push_back(amounts.to_val()); + deposit_args.push_back(amounts_min.to_val()); + deposit_args.push_back(caller.to_val()); + + e.invoke_contract::(&defindex_address, &Symbol::new(&e, "deposit"), deposit_args); add_new_defindex(&e, defindex_address.clone()); events::emit_create_defindex_vault(&e, emergency_manager, fee_receiver, manager, vault_fee, assets); diff --git a/apps/contracts/factory/src/test.rs b/apps/contracts/factory/src/test.rs index bf5f7980..5eda131e 100644 --- a/apps/contracts/factory/src/test.rs +++ b/apps/contracts/factory/src/test.rs @@ -1,7 +1,7 @@ #![cfg(test)] extern crate std; -use crate::defindex::{AssetStrategySet, Strategy}; use crate::{DeFindexFactory, DeFindexFactoryClient}; +use common::models::{AssetStrategySet, Strategy}; use soroban_sdk::token::{ StellarAssetClient as SorobanTokenAdminClient, TokenClient as SorobanTokenClient, }; diff --git a/apps/contracts/integration-test/Makefile b/apps/contracts/integration-test/Makefile deleted file mode 100644 index 6eb00e2c..00000000 --- a/apps/contracts/integration-test/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -default: build - -all: test - -test: build - cargo test - -build: - cargo build --target wasm32-unknown-unknown --release - soroban contract optimize --wasm ../../target/wasm32-unknown-unknown/release/hodl_strategy.wasm - @rm ../../target/wasm32-unknown-unknown/release/hodl_strategy.wasm - -fmt: - cargo fmt --all --check - -clean: - cargo clean \ No newline at end of file diff --git a/apps/contracts/integration-test/src/factory.rs b/apps/contracts/integration-test/src/factory.rs new file mode 100644 index 00000000..f89f5862 --- /dev/null +++ b/apps/contracts/integration-test/src/factory.rs @@ -0,0 +1,14 @@ +mod factory_contract { + soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/defindex_factory.optimized.wasm"); + pub type DeFindexFactoryClient<'a> = Client<'a>; +} + +use factory_contract::DeFindexFactoryClient; +use soroban_sdk::Env; + +// DeFindex Factory Contract +pub fn create_factory_contract<'a>(e: &Env) -> DeFindexFactoryClient<'a> { + let address = &e.register_contract_wasm(None, factory_contract::WASM); + let factory = DeFindexFactoryClient::new(e, address); + factory +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/hodl_strategy.rs b/apps/contracts/integration-test/src/hodl_strategy.rs new file mode 100644 index 00000000..635cade5 --- /dev/null +++ b/apps/contracts/integration-test/src/hodl_strategy.rs @@ -0,0 +1,15 @@ +// DeFindex Hodl Strategy Contract +mod hodl_strategy { + soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/hodl_strategy.optimized.wasm"); + pub type StrategyContractClient<'a> = Client<'a>; +} + +pub use hodl_strategy::StrategyContractClient; +use soroban_sdk::{Address, Env, Val, Vec}; + +pub fn create_strategy_contract<'a>(e: &Env, asset: &Address, init_args: &Vec) -> StrategyContractClient<'a> { + let address = &e.register_contract_wasm(None, hodl_strategy::WASM); + let strategy = StrategyContractClient::new(e, address); + strategy.initialize(asset, init_args); + strategy +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/lib.rs b/apps/contracts/integration-test/src/lib.rs index b18c1a35..9b22eab3 100644 --- a/apps/contracts/integration-test/src/lib.rs +++ b/apps/contracts/integration-test/src/lib.rs @@ -1,4 +1,7 @@ #![allow(clippy::all)] pub mod test; pub mod token; +pub mod hodl_strategy; +pub mod vault; +pub mod factory; // pub mod setup; diff --git a/apps/contracts/integration-test/src/test.rs b/apps/contracts/integration-test/src/test.rs index f1018030..d6f45d62 100644 --- a/apps/contracts/integration-test/src/test.rs +++ b/apps/contracts/integration-test/src/test.rs @@ -14,40 +14,9 @@ use soroban_sdk::{ }; use std::vec; -// DeFindex Hodl Strategy Contract -mod hodl_strategy { - soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/hodl_strategy.optimized.wasm"); - pub type StrategyContractClient<'a> = Client<'a>; -} - -use hodl_strategy::StrategyContractClient; - +use crate::hodl_strategy::{create_strategy_contract, StrategyContractClient}; use crate::token; -fn create_strategy_contract<'a>(e: &Env, asset: &Address, init_args: &Vec) -> StrategyContractClient<'a> { - let address = &e.register_contract_wasm(None, hodl_strategy::WASM); - let strategy = StrategyContractClient::new(e, address); - strategy.initialize(asset, init_args); - strategy -} - -// // DeFindex Vault Contract -// mod defindex_vault_contract { -// soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/defindex_vault.optimized.wasm"); -// } - -mod factory { - soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/defindex_factory.optimized.wasm"); - pub type DeFindexFactoryClient<'a> = Client<'a>; -} - -// use factory::DeFindexFactoryClient; - -// DeFindex Vault Contract -// fn create_defindex_factory<'a>(e: &Env) -> DeFindexFactoryClient<'a> { -// DeFindexFactoryClient::new(e, &e.register_contract(None, DeFindexFactory {})) -// } - // pub(crate) fn create_asset_params(test: &DeFindexFactoryTest) -> Vec { // sorobanvec![ // &test.env, diff --git a/apps/contracts/integration-test/src/vault.rs b/apps/contracts/integration-test/src/vault.rs new file mode 100644 index 00000000..3d2b552a --- /dev/null +++ b/apps/contracts/integration-test/src/vault.rs @@ -0,0 +1,4 @@ +// DeFindex Vault Contract +pub mod defindex_vault_contract { + soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/defindex_vault.optimized.wasm"); +} \ No newline at end of file diff --git a/apps/contracts/vault/Cargo.toml b/apps/contracts/vault/Cargo.toml index ccdcb113..a2c4b93c 100755 --- a/apps/contracts/vault/Cargo.toml +++ b/apps/contracts/vault/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["cdylib"] soroban-sdk = { workspace = true } soroban-token-sdk = { workspace = true } defindex-strategy-core = { workspace = true } +common = { workspace = true } [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } diff --git a/apps/contracts/vault/src/events.rs b/apps/contracts/vault/src/events.rs index 2d48293e..755d4408 100644 --- a/apps/contracts/vault/src/events.rs +++ b/apps/contracts/vault/src/events.rs @@ -1,5 +1,5 @@ //! Definition of the Events used in the DeFindex Vault contract -use crate::models::AssetStrategySet; +use common::models::AssetStrategySet; use soroban_sdk::{contracttype, symbol_short, Address, Env, Vec}; // INITIALIZED VAULT EVENT diff --git a/apps/contracts/vault/src/funds.rs b/apps/contracts/vault/src/funds.rs index 8d19cee9..d34c4efb 100644 --- a/apps/contracts/vault/src/funds.rs +++ b/apps/contracts/vault/src/funds.rs @@ -1,7 +1,7 @@ use soroban_sdk::token::TokenClient; use soroban_sdk::{Address, Env, Map}; -use crate::models::AssetStrategySet; +use common::models::AssetStrategySet; use crate::storage::get_assets; use crate::strategies::get_strategy_client; diff --git a/apps/contracts/vault/src/interface.rs b/apps/contracts/vault/src/interface.rs index 7dd40f1f..78fb1087 100644 --- a/apps/contracts/vault/src/interface.rs +++ b/apps/contracts/vault/src/interface.rs @@ -1,9 +1,10 @@ use soroban_sdk::{Address, Env, Map, String, Vec}; use crate::{ - models::{AssetStrategySet, Instruction, AssetInvestmentAllocation}, + models::{Instruction, AssetInvestmentAllocation}, ContractError, }; +use common::models::AssetStrategySet; pub trait VaultTrait { /// Initializes the DeFindex Vault contract with the required parameters. diff --git a/apps/contracts/vault/src/investment.rs b/apps/contracts/vault/src/investment.rs index 840c2e9f..3d2d8ffb 100644 --- a/apps/contracts/vault/src/investment.rs +++ b/apps/contracts/vault/src/investment.rs @@ -1,11 +1,12 @@ use soroban_sdk::{Env, Vec, panic_with_error}; use crate::{ - models::{AssetStrategySet, AssetInvestmentAllocation}, + models::AssetInvestmentAllocation, strategies::invest_in_strategy, utils::{check_nonnegative_amount}, ContractError, }; +use common::models::AssetStrategySet; /// Checks and executes the investments for each asset based on provided allocations. /// The function iterates through the specified assets and asset investments to ensure validity diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index ece2aec1..2256a2fe 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -29,7 +29,7 @@ use funds::{fetch_current_idle_funds, fetch_current_invested_funds, fetch_total_ use interface::{AdminInterfaceTrait, VaultManagementTrait, VaultTrait}; use investment::{check_and_execute_investments}; use models::{ - ActionType, AssetStrategySet, Instruction, AssetInvestmentAllocation, OptionalSwapDetailsExactIn, + ActionType, Instruction, AssetInvestmentAllocation, OptionalSwapDetailsExactIn, OptionalSwapDetailsExactOut, }; use storage::{ @@ -48,6 +48,7 @@ use utils::{ }; use defindex_strategy_core::DeFindexStrategyClient; +use common::models::AssetStrategySet; static MINIMUM_LIQUIDITY: i128 = 1000; diff --git a/apps/contracts/vault/src/models.rs b/apps/contracts/vault/src/models.rs index c0eb3db3..28d6b3d4 100644 --- a/apps/contracts/vault/src/models.rs +++ b/apps/contracts/vault/src/models.rs @@ -1,20 +1,5 @@ use soroban_sdk::{contracttype, Address, String, Vec}; -#[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct Strategy { - pub address: Address, - pub name: String, - pub paused: bool, -} - -#[contracttype] -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct AssetStrategySet { - pub address: Address, - pub strategies: Vec, -} - // Investment Allocation in Strategies #[contracttype] #[derive(Clone, Debug, Eq, PartialEq)] diff --git a/apps/contracts/vault/src/storage.rs b/apps/contracts/vault/src/storage.rs index 0cafff50..8dfb2ee3 100644 --- a/apps/contracts/vault/src/storage.rs +++ b/apps/contracts/vault/src/storage.rs @@ -1,6 +1,6 @@ use soroban_sdk::{contracttype, Address, Env, Vec}; -use crate::models::AssetStrategySet; +use common::models::AssetStrategySet; const DAY_IN_LEDGERS: u32 = 17280; const INSTANCE_BUMP_AMOUNT: u32 = 30 * DAY_IN_LEDGERS; diff --git a/apps/contracts/vault/src/strategies.rs b/apps/contracts/vault/src/strategies.rs index 7589a90d..51046e02 100644 --- a/apps/contracts/vault/src/strategies.rs +++ b/apps/contracts/vault/src/strategies.rs @@ -4,11 +4,12 @@ use soroban_sdk::auth::{ContractContext, InvokerContractAuthEntry, SubContractIn use crate::{ - models::{AssetStrategySet, Strategy}, storage::{get_asset, get_assets, get_total_assets, set_asset}, ContractError, }; +use common::models::{AssetStrategySet, Strategy}; + pub fn get_strategy_client(e: &Env, address: Address) -> DeFindexStrategyClient { DeFindexStrategyClient::new(&e, &address) } diff --git a/apps/contracts/vault/src/utils.rs b/apps/contracts/vault/src/utils.rs index db1360ed..985e2762 100644 --- a/apps/contracts/vault/src/utils.rs +++ b/apps/contracts/vault/src/utils.rs @@ -6,10 +6,10 @@ use crate::{ fetch_invested_funds_for_asset, fetch_invested_funds_for_strategy, fetch_total_managed_funds, }, - models::AssetStrategySet, token::VaultToken, ContractError, }; +use common::models::AssetStrategySet; pub const DAY_IN_LEDGERS: u32 = 17280; From e368682805d6a8a084f848978447ca8bfaf42b2b Mon Sep 17 00:00:00 2001 From: coderipper Date: Tue, 12 Nov 2024 18:45:11 -0300 Subject: [PATCH 08/50] renamed defindex.rs to vault.rs --- apps/contracts/factory/src/defindex.rs | 22 ---------------------- apps/contracts/factory/src/lib.rs | 4 ++-- apps/contracts/factory/src/vault.rs | 14 ++++++++++++++ 3 files changed, 16 insertions(+), 24 deletions(-) delete mode 100644 apps/contracts/factory/src/defindex.rs create mode 100644 apps/contracts/factory/src/vault.rs diff --git a/apps/contracts/factory/src/defindex.rs b/apps/contracts/factory/src/defindex.rs deleted file mode 100644 index 96d13b78..00000000 --- a/apps/contracts/factory/src/defindex.rs +++ /dev/null @@ -1,22 +0,0 @@ -// Import necessary types from the Soroban SDK -#![allow(unused)] -use soroban_sdk::{contracttype, contracterror, xdr::ToXdr, Address, Bytes, BytesN, Env, Vec}; - -// Define a function to create a new contract instance -pub fn create_contract( - e: &Env, // Pass in the current environment as an argument - defindex_wasm_hash: BytesN<32>, // Pass in the hash of the token contract's WASM file - salt: BytesN<32>, -) -> Address { - - // Append the bytes of the address and name to the salt - // salt.append(&adapters.clone().to_xdr(e)); - // let mut value = [0u8; 32]; - // e.prng().fill(&mut value); - // salt = Bytes::from_array(&e, &value); - - // Use the deployer() method of the current environment to create a new contract instance - e.deployer() - .with_current_contract(e.crypto().sha256(&salt.into())) // Use the salt as a unique identifier for the new contract instance - .deploy(defindex_wasm_hash) // Deploy the new contract instance using the given pair_wasm_hash value -} \ No newline at end of file diff --git a/apps/contracts/factory/src/lib.rs b/apps/contracts/factory/src/lib.rs index 12d5ece9..bc841f71 100644 --- a/apps/contracts/factory/src/lib.rs +++ b/apps/contracts/factory/src/lib.rs @@ -1,6 +1,6 @@ #![no_std] -mod defindex; +mod vault; mod events; mod storage; mod error; @@ -10,7 +10,7 @@ use soroban_sdk::{ contract, contractimpl, vec, Address, BytesN, Env, Map, String, Symbol, Val, Vec, IntoVal }; use error::FactoryError; -pub use defindex::create_contract; +pub use vault::create_contract; use storage::{ add_new_defindex, extend_instance_ttl, get_admin, get_defi_wasm_hash, get_defindex_receiver, get_deployed_defindexes, get_fee_rate, has_admin, put_admin, put_defi_wasm_hash, put_defindex_receiver, put_defindex_fee }; fn check_initialized(e: &Env) -> Result<(), FactoryError> { diff --git a/apps/contracts/factory/src/vault.rs b/apps/contracts/factory/src/vault.rs new file mode 100644 index 00000000..2df724f3 --- /dev/null +++ b/apps/contracts/factory/src/vault.rs @@ -0,0 +1,14 @@ +#![allow(unused)] +use soroban_sdk::{contracttype, contracterror, xdr::ToXdr, Address, Bytes, BytesN, Env, Vec}; + +// Define a function to create a new contract instance +pub fn create_contract( + e: &Env, // Pass in the current environment as an argument + defindex_wasm_hash: BytesN<32>, // Pass in the hash of the token contract's WASM file + salt: BytesN<32>, +) -> Address { + + e.deployer() + .with_current_contract(e.crypto().sha256(&salt.into())) + .deploy(defindex_wasm_hash) +} \ No newline at end of file From afec3badd669b7c8881d54f3ddc9cccf3498940b Mon Sep 17 00:00:00 2001 From: coderipper Date: Wed, 13 Nov 2024 10:02:39 -0300 Subject: [PATCH 09/50] test.rs --- apps/contracts/integration-test/src/test.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apps/contracts/integration-test/src/test.rs b/apps/contracts/integration-test/src/test.rs index d6f45d62..d80fea42 100644 --- a/apps/contracts/integration-test/src/test.rs +++ b/apps/contracts/integration-test/src/test.rs @@ -65,8 +65,6 @@ pub struct DeFindexFactoryTest<'a> { impl<'a> DeFindexFactoryTest<'a> { fn setup() -> Self { let env = Env::default(); - env.budget().reset_unlimited(); - // env.mock_all_auths(); // let factory_contract = create_defindex_factory(&env); // let admin = Address::generate(&env); @@ -91,6 +89,7 @@ impl<'a> DeFindexFactoryTest<'a> { // // TODO: Add a strategy adapter, this is a mockup let strategy_contract = create_strategy_contract(&env, &token.address, &Vec::new(&env)); // let strategy_contract_token1 = create_strategy_contract(&env, &token1.address, &Vec::new(&env)); + env.budget().reset_unlimited(); DeFindexFactoryTest { env, From 8bf02a96807d1ea4881f781f12390ca2c5b8afa8 Mon Sep 17 00:00:00 2001 From: coderipper Date: Wed, 13 Nov 2024 20:59:55 -0300 Subject: [PATCH 10/50] setup vault_one_asset_hodl_strategy --- apps/contracts/Cargo.lock | 1 + apps/contracts/factory/src/lib.rs | 20 +- apps/contracts/factory/src/storage.rs | 6 +- apps/contracts/integration-test/Cargo.toml | 3 +- .../contracts/integration-test/src/factory.rs | 8 +- .../integration-test/src/hodl_strategy.rs | 8 +- apps/contracts/integration-test/src/lib.rs | 2 +- apps/contracts/integration-test/src/setup.rs | 311 +++++++----------- apps/contracts/integration-test/src/test.rs | 116 ++----- apps/contracts/integration-test/src/vault.rs | 6 +- 10 files changed, 173 insertions(+), 308 deletions(-) diff --git a/apps/contracts/Cargo.lock b/apps/contracts/Cargo.lock index 64bb7b90..edad11d9 100644 --- a/apps/contracts/Cargo.lock +++ b/apps/contracts/Cargo.lock @@ -609,6 +609,7 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" name = "integration-test" version = "0.0.0" dependencies = [ + "common", "defindex-factory", "defindex-vault", "hodl_strategy", diff --git a/apps/contracts/factory/src/lib.rs b/apps/contracts/factory/src/lib.rs index bc841f71..43175e34 100644 --- a/apps/contracts/factory/src/lib.rs +++ b/apps/contracts/factory/src/lib.rs @@ -11,7 +11,7 @@ use soroban_sdk::{ }; use error::FactoryError; pub use vault::create_contract; -use storage::{ add_new_defindex, extend_instance_ttl, get_admin, get_defi_wasm_hash, get_defindex_receiver, get_deployed_defindexes, get_fee_rate, has_admin, put_admin, put_defi_wasm_hash, put_defindex_receiver, put_defindex_fee }; +use storage::{ add_new_defindex, extend_instance_ttl, get_admin, get_vault_wasm_hash, get_defindex_receiver, get_deployed_defindexes, get_fee_rate, has_admin, put_admin, put_vault_wasm_hash, put_defindex_receiver, put_defindex_fee }; fn check_initialized(e: &Env) -> Result<(), FactoryError> { if !has_admin(e) { @@ -28,7 +28,7 @@ pub trait FactoryTrait { /// * `admin` - The address of the contract administrator, who can manage settings. /// * `defindex_receiver` - The default address designated to receive a portion of fees. /// * `defindex_fee` - The initial annual fee rate (in basis points). - /// * `defindex_wasm_hash` - The hash of the DeFindex Vault's WASM file for deploying new vaults. + /// * `vault_wasm_hash` - The hash of the DeFindex Vault's WASM file for deploying new vaults. /// /// # Returns /// * `Result<(), FactoryError>` - Returns Ok(()) if successful, otherwise an error. @@ -37,7 +37,7 @@ pub trait FactoryTrait { admin: Address, defindex_receiver: Address, defindex_fee: u32, - defindex_wasm_hash: BytesN<32> + vault_wasm_hash: BytesN<32> ) -> Result<(), FactoryError>; /// Creates a new DeFindex Vault with specified parameters. @@ -181,7 +181,7 @@ impl FactoryTrait for DeFindexFactory { /// * `admin` - The address of the contract administrator, who can manage settings. /// * `defindex_receiver` - The default address designated to receive a portion of fees. /// * `defindex_fee` - The initial annual fee rate (in basis points). - /// * `defindex_wasm_hash` - The hash of the DeFindex Vault's WASM file for deploying new vaults. + /// * `vault_wasm_hash` - The hash of the DeFindex Vault's WASM file for deploying new vaults. /// /// # Returns /// * `Result<(), FactoryError>` - Returns Ok(()) if successful, otherwise an error. @@ -190,7 +190,7 @@ impl FactoryTrait for DeFindexFactory { admin: Address, defindex_receiver: Address, defindex_fee: u32, - defi_wasm_hash: BytesN<32> + vault_wasm_hash: BytesN<32> ) -> Result<(), FactoryError> { if has_admin(&e) { return Err(FactoryError::AlreadyInitialized); @@ -198,7 +198,7 @@ impl FactoryTrait for DeFindexFactory { put_admin(&e, &admin); put_defindex_receiver(&e, &defindex_receiver); - put_defi_wasm_hash(&e, defi_wasm_hash); + put_vault_wasm_hash(&e, vault_wasm_hash); put_defindex_fee(&e, &defindex_fee); events::emit_initialized(&e, admin, defindex_receiver, defindex_fee); @@ -234,8 +234,8 @@ impl FactoryTrait for DeFindexFactory { let current_contract = e.current_contract_address(); - let defi_wasm_hash = get_defi_wasm_hash(&e)?; - let defindex_address = create_contract(&e, defi_wasm_hash, salt); + let vault_wasm_hash = get_vault_wasm_hash(&e)?; + let defindex_address = create_contract(&e, vault_wasm_hash, salt); let defindex_receiver = get_defindex_receiver(&e); @@ -295,8 +295,8 @@ impl FactoryTrait for DeFindexFactory { let current_contract = e.current_contract_address(); - let defi_wasm_hash = get_defi_wasm_hash(&e)?; - let defindex_address = create_contract(&e, defi_wasm_hash, salt); + let vault_wasm_hash = get_vault_wasm_hash(&e)?; + let defindex_address = create_contract(&e, vault_wasm_hash, salt); let defindex_receiver = get_defindex_receiver(&e); diff --git a/apps/contracts/factory/src/storage.rs b/apps/contracts/factory/src/storage.rs index bde91cd0..7dca9473 100644 --- a/apps/contracts/factory/src/storage.rs +++ b/apps/contracts/factory/src/storage.rs @@ -42,14 +42,14 @@ fn get_persistent_extend_or_error>( } } -pub fn get_defi_wasm_hash(e: &Env) -> Result, FactoryError>{ +pub fn get_vault_wasm_hash(e: &Env) -> Result, FactoryError>{ let key = DataKey::DeFindexWasmHash; get_persistent_extend_or_error(&e, &key, FactoryError::NotInitialized) } -pub fn put_defi_wasm_hash(e: &Env, pair_wasm_hash: BytesN<32>) { +pub fn put_vault_wasm_hash(e: &Env, vault_wasm_hash: BytesN<32>) { let key = DataKey::DeFindexWasmHash; - e.storage().persistent().set(&key, &pair_wasm_hash); + e.storage().persistent().set(&key, &vault_wasm_hash); e.storage() .persistent() .extend_ttl(&key, PERSISTENT_LIFETIME_THRESHOLD, PERSISTENT_BUMP_AMOUNT) diff --git a/apps/contracts/integration-test/Cargo.toml b/apps/contracts/integration-test/Cargo.toml index 4ef2117f..4debd7cf 100644 --- a/apps/contracts/integration-test/Cargo.toml +++ b/apps/contracts/integration-test/Cargo.toml @@ -10,4 +10,5 @@ publish = false soroban-sdk = { workspace = true, features = ["testutils"] } factory = { path = "../factory", package = "defindex-factory" } vault = { path = "../vault", package = "defindex-vault"} -hodl-strategy={ path="../strategies/hodl", package="hodl_strategy"} \ No newline at end of file +hodl-strategy={ path="../strategies/hodl", package="hodl_strategy"} +common = { workspace = true } \ No newline at end of file diff --git a/apps/contracts/integration-test/src/factory.rs b/apps/contracts/integration-test/src/factory.rs index f89f5862..c69fddd0 100644 --- a/apps/contracts/integration-test/src/factory.rs +++ b/apps/contracts/integration-test/src/factory.rs @@ -3,12 +3,14 @@ mod factory_contract { pub type DeFindexFactoryClient<'a> = Client<'a>; } -use factory_contract::DeFindexFactoryClient; -use soroban_sdk::Env; +pub use factory_contract::{AssetStrategySet, Strategy, DeFindexFactoryClient}; +use soroban_sdk::{Address, BytesN, Env}; // DeFindex Factory Contract -pub fn create_factory_contract<'a>(e: &Env) -> DeFindexFactoryClient<'a> { +pub fn create_factory_contract<'a>(e: &Env, admin: &Address, defindex_receiver: &Address, defindex_fee: &u32, vault_wasm_hash: &BytesN<32>) -> DeFindexFactoryClient<'a> { let address = &e.register_contract_wasm(None, factory_contract::WASM); let factory = DeFindexFactoryClient::new(e, address); + + factory.initialize(admin, defindex_receiver, defindex_fee, vault_wasm_hash); factory } \ No newline at end of file diff --git a/apps/contracts/integration-test/src/hodl_strategy.rs b/apps/contracts/integration-test/src/hodl_strategy.rs index 635cade5..bea4c737 100644 --- a/apps/contracts/integration-test/src/hodl_strategy.rs +++ b/apps/contracts/integration-test/src/hodl_strategy.rs @@ -1,15 +1,15 @@ // DeFindex Hodl Strategy Contract mod hodl_strategy { soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/hodl_strategy.optimized.wasm"); - pub type StrategyContractClient<'a> = Client<'a>; + pub type HodlStrategyClient<'a> = Client<'a>; } -pub use hodl_strategy::StrategyContractClient; +pub use hodl_strategy::HodlStrategyClient; use soroban_sdk::{Address, Env, Val, Vec}; -pub fn create_strategy_contract<'a>(e: &Env, asset: &Address, init_args: &Vec) -> StrategyContractClient<'a> { +pub fn create_hodl_strategy_contract<'a>(e: &Env, asset: &Address, init_args: &Vec) -> HodlStrategyClient<'a> { let address = &e.register_contract_wasm(None, hodl_strategy::WASM); - let strategy = StrategyContractClient::new(e, address); + let strategy = HodlStrategyClient::new(e, address); strategy.initialize(asset, init_args); strategy } \ No newline at end of file diff --git a/apps/contracts/integration-test/src/lib.rs b/apps/contracts/integration-test/src/lib.rs index 9b22eab3..3012abff 100644 --- a/apps/contracts/integration-test/src/lib.rs +++ b/apps/contracts/integration-test/src/lib.rs @@ -4,4 +4,4 @@ pub mod token; pub mod hodl_strategy; pub mod vault; pub mod factory; -// pub mod setup; +pub mod setup; \ 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 48a99532..75e6badb 100644 --- a/apps/contracts/integration-test/src/setup.rs +++ b/apps/contracts/integration-test/src/setup.rs @@ -1,204 +1,129 @@ -use soroban_sdk::{testutils::Address as _, vec as svec, Address, String, Vec as SVec}; - -mod hodl_strategy { - soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/hodl_strategy.optimized.wasm"); - pub type StrategyContractClient<'a> = Client<'a>; +use soroban_sdk::token::{StellarAssetClient, TokenClient}; +use soroban_sdk::BytesN; +use soroban_sdk::{testutils::Address as _, vec as sorobanvec, Address, String}; + +use crate::hodl_strategy::{create_hodl_strategy_contract, HodlStrategyClient}; +use crate::test::IntegrationTest; +use crate::token::create_token; +use crate::factory::{AssetStrategySet, Strategy}; +use crate::vault::defindex_vault_contract::VaultContractClient; + +pub struct VaultOneAseetHodl<'a> { + pub setup: IntegrationTest<'a>, + pub token: TokenClient<'a>, + pub token_admin_client: StellarAssetClient<'a>, + pub strategy_contract: HodlStrategyClient<'a>, + pub vault_contract: VaultContractClient<'a>, + pub manager: Address, + pub emergency_manager: Address, + pub fee_receiver: Address, + pub vault_fee: u32, } -/// Create a test fixture with a pool and a whale depositing and borrowing all assets -pub fn create_fixture_with_data<'a>(wasm: bool) -> TestFixture<'a> { - // let mut fixture = TestFixture::create(wasm); - - // // mint whale tokens - // let frodo = fixture.users[0].clone(); - // fixture.tokens[TokenIndex::STABLE].mint(&frodo, &(100_000 * 10i128.pow(6))); - // fixture.tokens[TokenIndex::XLM].mint(&frodo, &(1_000_000 * SCALAR_7)); - // fixture.tokens[TokenIndex::WETH].mint(&frodo, &(100 * 10i128.pow(9))); - - // // mint LP tokens with whale - // // frodo has 40m BLND from drop - // fixture.tokens[TokenIndex::BLND].mint(&frodo, &(70_000_000 * SCALAR_7)); - // fixture.tokens[TokenIndex::USDC].mint(&frodo, &(2_600_000 * SCALAR_7)); - // fixture.lp.join_pool( - // &(10_000_000 * SCALAR_7), - // &svec![&fixture.env, 110_000_000 * SCALAR_7, 2_600_000 * SCALAR_7,], - // &frodo, - // ); - - // // create pool - // fixture.create_pool(String::from_str(&fixture.env, "Teapot"), 0_1000000, 6); - - // let mut stable_config = default_reserve_metadata(); - // stable_config.decimals = 6; - // stable_config.c_factor = 0_900_0000; - // stable_config.l_factor = 0_950_0000; - // stable_config.util = 0_850_0000; - // fixture.create_pool_reserve(0, TokenIndex::STABLE, &stable_config); - - // let mut xlm_config = default_reserve_metadata(); - // xlm_config.c_factor = 0_750_0000; - // xlm_config.l_factor = 0_750_0000; - // xlm_config.util = 0_500_0000; - // fixture.create_pool_reserve(0, TokenIndex::XLM, &xlm_config); - - // let mut weth_config = default_reserve_metadata(); - // weth_config.decimals = 9; - // weth_config.c_factor = 0_800_0000; - // weth_config.l_factor = 0_800_0000; - // weth_config.util = 0_700_0000; - // fixture.create_pool_reserve(0, TokenIndex::WETH, &weth_config); - - // // enable emissions for pool - // let pool_fixture = &fixture.pools[0]; - - // let reserve_emissions: soroban_sdk::Vec = soroban_sdk::vec![ - // &fixture.env, - // ReserveEmissionMetadata { - // res_index: 0, // STABLE - // res_type: 0, // d_token - // share: 0_600_0000 - // }, - // ReserveEmissionMetadata { - // res_index: 1, // XLM - // res_type: 1, // b_token - // share: 0_400_0000 - // }, - // ]; - // pool_fixture.pool.set_emissions_config(&reserve_emissions); - - // // deposit into backstop, add to reward zone - // fixture - // .backstop - // .deposit(&frodo, &pool_fixture.pool.address, &(50_000 * SCALAR_7)); - // fixture.backstop.update_tkn_val(); - // fixture - // .backstop - // .add_reward(&pool_fixture.pool.address, &Address::generate(&fixture.env)); - // pool_fixture.pool.set_status(&3); - // pool_fixture.pool.update_status(); - - // // enable emissions - // fixture.emitter.distribute(); - // fixture.backstop.gulp_emissions(); - // pool_fixture.pool.gulp_emissions(); - - // fixture.jump(60); - - // // supply and borrow STABLE for 80% utilization (close to target) - // let requests: SVec = svec![ - // &fixture.env, - // Request { - // request_type: RequestType::SupplyCollateral as u32, - // address: fixture.tokens[TokenIndex::STABLE].address.clone(), - // amount: 10_000 * 10i128.pow(6), - // }, - // Request { - // request_type: RequestType::Borrow as u32, - // address: fixture.tokens[TokenIndex::STABLE].address.clone(), - // amount: 8_000 * 10i128.pow(6), - // }, - // ]; - // pool_fixture.pool.submit(&frodo, &frodo, &frodo, &requests); - - // // supply and borrow WETH for 50% utilization (below target) - // let requests: SVec = svec![ - // &fixture.env, - // Request { - // request_type: RequestType::SupplyCollateral as u32, - // address: fixture.tokens[TokenIndex::WETH].address.clone(), - // amount: 10 * 10i128.pow(9), - // }, - // Request { - // request_type: RequestType::Borrow as u32, - // address: fixture.tokens[TokenIndex::WETH].address.clone(), - // amount: 5 * 10i128.pow(9), - // }, - // ]; - // pool_fixture.pool.submit(&frodo, &frodo, &frodo, &requests); - - // // supply and borrow XLM for 65% utilization (above target) - // let requests: SVec = svec![ - // &fixture.env, - // Request { - // request_type: RequestType::SupplyCollateral as u32, - // address: fixture.tokens[TokenIndex::XLM].address.clone(), - // amount: 100_000 * SCALAR_7, - // }, - // Request { - // request_type: RequestType::Borrow as u32, - // address: fixture.tokens[TokenIndex::XLM].address.clone(), - // amount: 65_000 * SCALAR_7, - // }, - // ]; - // pool_fixture.pool.submit(&frodo, &frodo, &frodo, &requests); - - // fixture.jump(60 * 60); // 1 hr - - // fixture.env.budget().reset_unlimited(); - // fixture +pub fn create_vault_one_asset_hodl_strategy<'a>() -> VaultOneAseetHodl<'a> { + let setup = IntegrationTest::setup(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + let strategy_contract = create_hodl_strategy_contract(&setup.env, &token.address, &sorobanvec![&setup.env]); + + let emergency_manager = Address::generate(&setup.env); + let fee_receiver = Address::generate(&setup.env); + let vault_fee = 100u32; + let vault_name = String::from_str(&setup.env, "HodlVault"); + let vault_symbol = String::from_str(&setup.env, "HVLT"); + let manager = Address::generate(&setup.env); + + let assets = sorobanvec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: sorobanvec![ + &setup.env, + Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "Hodl Strategy"), + paused: false, + } + ], + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &emergency_manager, + &fee_receiver, + &vault_fee, + &vault_name, + &vault_symbol, + &manager, + &assets, + &salt + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + VaultOneAseetHodl { + setup, + token, + token_admin_client, + strategy_contract, + vault_contract, + manager, + emergency_manager, + fee_receiver, + vault_fee, + } } #[cfg(test)] mod tests { + use crate::vault::{VaultAssetStrategySet, VaultStrategy}; - // use crate::test_fixture::PoolFixture; - - // use super::*; + use super::*; #[test] - fn test_create_fixture_with_data_wasm() { - // let fixture: TestFixture<'_> = create_fixture_with_data(true); - // let frodo = fixture.users.get(0).unwrap(); - // let pool_fixture: &PoolFixture = fixture.pools.get(0).unwrap(); - - // // validate backstop deposit and drop - // assert_eq!( - // 50_000 * SCALAR_7, - // fixture.lp.balance(&fixture.backstop.address) - // ); - // assert_eq!( - // 10_000_000 * SCALAR_7, - // fixture.tokens[TokenIndex::BLND].balance(&fixture.bombadil) - // ); - - // // validate pool actions - // assert_eq!( - // 2_000 * 10i128.pow(6), - // fixture.tokens[TokenIndex::STABLE].balance(&pool_fixture.pool.address) - // ); - // assert_eq!( - // 35_000 * SCALAR_7, - // fixture.tokens[TokenIndex::XLM].balance(&pool_fixture.pool.address) - // ); - // assert_eq!( - // 5 * 10i128.pow(9), - // fixture.tokens[TokenIndex::WETH].balance(&pool_fixture.pool.address) - // ); - - // assert_eq!( - // 98_000 * 10i128.pow(6), - // fixture.tokens[TokenIndex::STABLE].balance(&frodo) - // ); - // assert_eq!( - // 965_000 * SCALAR_7, - // fixture.tokens[TokenIndex::XLM].balance(&frodo) - // ); - // assert_eq!( - // 95 * 10i128.pow(9), - // fixture.tokens[TokenIndex::WETH].balance(&frodo) - // ); - - // // validate emissions are turned on - // let (emis_config, emis_data) = fixture.read_reserve_emissions(0, TokenIndex::STABLE, 0); - // assert_eq!( - // emis_data.last_time, - // fixture.env.ledger().timestamp() - 60 * 61 - // ); - // assert_eq!(emis_data.index, 0); - // assert_eq!(0_180_0000, emis_config.eps); - // assert_eq!( - // fixture.env.ledger().timestamp() + 7 * 24 * 60 * 60 - 60 * 61, - // emis_config.expiration - // ) + fn test_create_vault_one_asset_hodl_strategy() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + assert_eq!(setup.factory_contract.deployed_defindexes().len(), 1); + + let strategy_token = enviroment.strategy_contract.asset(); + assert_eq!(strategy_token, enviroment.token.address); + + let assets = sorobanvec![ + &setup.env, + VaultAssetStrategySet { + address: enviroment.token.address.clone(), + strategies: sorobanvec![ + &setup.env, + VaultStrategy { + address: enviroment.strategy_contract.address.clone(), + name: String::from_str(&setup.env, "Hodl Strategy"), + paused: false, + } + ], + } + ]; + + let vault_assets = enviroment.vault_contract.get_assets(); + assert_eq!(vault_assets, assets); + + let vault_emergency_manager = enviroment.vault_contract.get_emergency_manager(); + assert_eq!(vault_emergency_manager, enviroment.emergency_manager); + + let vault_fee_receiver = enviroment.vault_contract.get_fee_receiver(); + assert_eq!(vault_fee_receiver, enviroment.fee_receiver); + + let vault_manager = enviroment.vault_contract.get_manager(); + assert_eq!(vault_manager, enviroment.manager); + + let vault_name = enviroment.vault_contract.name(); + assert_eq!(vault_name, String::from_str(&setup.env, "HodlVault")); + + let vault_symbol = enviroment.vault_contract.symbol(); + assert_eq!(vault_symbol, String::from_str(&setup.env, "HVLT")); } } \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test.rs b/apps/contracts/integration-test/src/test.rs index d80fea42..08155317 100644 --- a/apps/contracts/integration-test/src/test.rs +++ b/apps/contracts/integration-test/src/test.rs @@ -1,116 +1,48 @@ -#![cfg(test)] extern crate std; -use soroban_sdk::token::{ - StellarAssetClient as SorobanTokenAdminClient, TokenClient as SorobanTokenClient, -}; -use soroban_sdk::{BytesN, Val}; +use crate::factory::DeFindexFactoryClient; use soroban_sdk::{ Env, Address, testutils::Address as _, - Vec, - vec as sorobanvec, - String }; -use std::vec; - -use crate::hodl_strategy::{create_strategy_contract, StrategyContractClient}; -use crate::token; +use std::vec as std_vec; -// pub(crate) fn create_asset_params(test: &DeFindexFactoryTest) -> Vec { -// sorobanvec![ -// &test.env, -// AssetStrategySet { -// address: test.token0.address.clone(), -// strategies: sorobanvec![ -// &test.env, -// Strategy { -// address: test.strategy_contract_token0.address.clone(), -// name: String::from_str(&test.env, "Strategy 1"), -// paused: false, -// } -// ], -// }, -// AssetStrategySet { -// address: test.token1.address.clone(), -// strategies: sorobanvec![ -// &test.env, -// Strategy { -// address: test.strategy_contract_token1.address.clone(), -// name: String::from_str(&test.env, "Strategy 1"), -// paused: false, -// } -// ], -// } -// ] -// } +use crate::factory::create_factory_contract; +use crate::vault::defindex_vault_contract; -pub struct DeFindexFactoryTest<'a> { - env: Env, - // factory_contract: DeFindexFactoryClient<'a>, - // admin: Address, - // defindex_receiver: Address, - // defindex_wasm_hash: BytesN<32>, - // emergency_manager: Address, - // fee_receiver: Address, - // manager: Address, - // token0_admin_client: SorobanTokenAdminClient<'a>, - // token0: SorobanTokenClient<'a>, - token_admin_client: SorobanTokenAdminClient<'a>, - // token1: SorobanTokenClient<'a>, - strategy_contract: StrategyContractClient<'a>, - // strategy_contract_token1: StrategyContractClient<'a>, +pub struct IntegrationTest<'a> { + pub env: Env, + pub factory_contract: DeFindexFactoryClient<'a>, + pub admin: Address, + pub defindex_receiver: Address, + pub defindex_fee: u32 } -impl<'a> DeFindexFactoryTest<'a> { - fn setup() -> Self { +impl<'a> IntegrationTest<'a> { + pub fn setup() -> Self { let env = Env::default(); - // let factory_contract = create_defindex_factory(&env); - // let admin = Address::generate(&env); - // let defindex_receiver = Address::generate(&env); - - // let defindex_wasm_hash = env.deployer().upload_contract_wasm(defindex_vault_contract::WASM); + let admin = Address::generate(&env); + let defindex_receiver = Address::generate(&env); - // let emergency_manager = Address::generate(&env); - // let fee_receiver = Address::generate(&env); - // let manager = Address::generate(&env); + let vault_wasm_hash = env.deployer().upload_contract_wasm(defindex_vault_contract::WASM); + let defindex_fee = 50u32; - // let token0_admin = Address::generate(&env); - // let token0 = create_token_contract(&env, &token0_admin); - - let token_admin = Address::generate(&env); - // let token1 = create_token_contract(&env, &token1_admin); - - // let token0_admin_client = get_token_admin_client(&env, &token0.address.clone()); - // let token1_admin_client = get_token_admin_client(&env, &token1.address.clone()); - let (token, token_admin_client) = token::create_token(&env, &token_admin); + let factory_contract = create_factory_contract(&env, &admin, &defindex_receiver, &defindex_fee, &vault_wasm_hash); - // // TODO: Add a strategy adapter, this is a mockup - let strategy_contract = create_strategy_contract(&env, &token.address, &Vec::new(&env)); - // let strategy_contract_token1 = create_strategy_contract(&env, &token1.address, &Vec::new(&env)); env.budget().reset_unlimited(); - DeFindexFactoryTest { + IntegrationTest { env, - // factory_contract, - // admin, - // defindex_receiver, - // defindex_wasm_hash, - // emergency_manager, - // fee_receiver, - // manager, - // token0_admin_client, - // token0, - token_admin_client, - // token1, - strategy_contract, - // strategy_contract_token1 + factory_contract, + admin, + defindex_receiver, + defindex_fee } } - pub(crate) fn generate_random_users(e: &Env, users_count: u32) -> vec::Vec
{ - let mut users = vec![]; + pub(crate) fn generate_random_users(e: &Env, users_count: u32) -> std_vec::Vec
{ + let mut users = std_vec![]; for _c in 0..users_count { users.push(Address::generate(e)); } diff --git a/apps/contracts/integration-test/src/vault.rs b/apps/contracts/integration-test/src/vault.rs index 3d2b552a..6724e6f5 100644 --- a/apps/contracts/integration-test/src/vault.rs +++ b/apps/contracts/integration-test/src/vault.rs @@ -1,4 +1,8 @@ // DeFindex Vault Contract pub mod defindex_vault_contract { soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/defindex_vault.optimized.wasm"); -} \ No newline at end of file + + pub type VaultContractClient<'a> = Client<'a>; +} + +pub use defindex_vault_contract::{AssetStrategySet as VaultAssetStrategySet, Strategy as VaultStrategy}; From 985530e641f0f3385203fcf36c8dcd66d8ec80cf Mon Sep 17 00:00:00 2001 From: coderipper Date: Thu, 14 Nov 2024 12:29:17 -0300 Subject: [PATCH 11/50] withdraw tests on hodl strategy --- apps/contracts/integration-test/src/setup.rs | 7 +- apps/contracts/integration-test/src/test.rs | 12 +- .../src/test/test_vault_one_strategy/mod.rs | 2 + .../test_vault_one_strategy/test_deposit.rs | 205 ++++++++++ .../test_vault_one_strategy/test_withdraw.rs | 385 ++++++++++++++++++ apps/contracts/integration-test/src/vault.rs | 5 +- 6 files changed, 611 insertions(+), 5 deletions(-) create mode 100644 apps/contracts/integration-test/src/test/test_vault_one_strategy/mod.rs create mode 100644 apps/contracts/integration-test/src/test/test_vault_one_strategy/test_deposit.rs create mode 100644 apps/contracts/integration-test/src/test/test_vault_one_strategy/test_withdraw.rs diff --git a/apps/contracts/integration-test/src/setup.rs b/apps/contracts/integration-test/src/setup.rs index 75e6badb..3f44050e 100644 --- a/apps/contracts/integration-test/src/setup.rs +++ b/apps/contracts/integration-test/src/setup.rs @@ -11,6 +11,7 @@ use crate::vault::defindex_vault_contract::VaultContractClient; pub struct VaultOneAseetHodl<'a> { pub setup: IntegrationTest<'a>, pub token: TokenClient<'a>, + pub token_admin: Address, pub token_admin_client: StellarAssetClient<'a>, pub strategy_contract: HodlStrategyClient<'a>, pub vault_contract: VaultContractClient<'a>, @@ -20,6 +21,8 @@ pub struct VaultOneAseetHodl<'a> { pub vault_fee: u32, } +pub static VAULT_FEE: u32 = 100; + pub fn create_vault_one_asset_hodl_strategy<'a>() -> VaultOneAseetHodl<'a> { let setup = IntegrationTest::setup(); @@ -30,7 +33,7 @@ pub fn create_vault_one_asset_hodl_strategy<'a>() -> VaultOneAseetHodl<'a> { let emergency_manager = Address::generate(&setup.env); let fee_receiver = Address::generate(&setup.env); - let vault_fee = 100u32; + let vault_fee = VAULT_FEE; let vault_name = String::from_str(&setup.env, "HodlVault"); let vault_symbol = String::from_str(&setup.env, "HVLT"); let manager = Address::generate(&setup.env); @@ -68,6 +71,7 @@ pub fn create_vault_one_asset_hodl_strategy<'a>() -> VaultOneAseetHodl<'a> { VaultOneAseetHodl { setup, token, + token_admin, token_admin_client, strategy_contract, vault_contract, @@ -126,4 +130,5 @@ mod tests { let vault_symbol = enviroment.vault_contract.symbol(); assert_eq!(vault_symbol, String::from_str(&setup.env, "HVLT")); } + } \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test.rs b/apps/contracts/integration-test/src/test.rs index 08155317..433c7de5 100644 --- a/apps/contracts/integration-test/src/test.rs +++ b/apps/contracts/integration-test/src/test.rs @@ -10,6 +10,9 @@ use std::vec as std_vec; use crate::factory::create_factory_contract; use crate::vault::defindex_vault_contract; +pub static ONE_YEAR_IN_SECONDS: u64 = 31_536_000; +pub static DEFINDEX_FEE: u32 = 50; + pub struct IntegrationTest<'a> { pub env: Env, pub factory_contract: DeFindexFactoryClient<'a>, @@ -26,7 +29,7 @@ impl<'a> IntegrationTest<'a> { let defindex_receiver = Address::generate(&env); let vault_wasm_hash = env.deployer().upload_contract_wasm(defindex_vault_contract::WASM); - let defindex_fee = 50u32; + let defindex_fee = DEFINDEX_FEE; let factory_contract = create_factory_contract(&env, &admin, &defindex_receiver, &defindex_fee, &vault_wasm_hash); @@ -41,11 +44,14 @@ impl<'a> IntegrationTest<'a> { } } - pub(crate) fn generate_random_users(e: &Env, users_count: u32) -> std_vec::Vec
{ + pub fn generate_random_users(e: &Env, users_count: u32) -> std_vec::Vec
{ let mut users = std_vec![]; for _c in 0..users_count { users.push(Address::generate(e)); } users } -} \ No newline at end of file +} + +#[cfg(test)] +mod test_vault_one_strategy; \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/mod.rs b/apps/contracts/integration-test/src/test/test_vault_one_strategy/mod.rs new file mode 100644 index 00000000..3f2289d7 --- /dev/null +++ b/apps/contracts/integration-test/src/test/test_vault_one_strategy/mod.rs @@ -0,0 +1,2 @@ +mod test_deposit; +mod test_withdraw; \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_deposit.rs b/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_deposit.rs new file mode 100644 index 00000000..6769c13d --- /dev/null +++ b/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_deposit.rs @@ -0,0 +1,205 @@ +use crate::{setup::create_vault_one_asset_hodl_strategy, test::IntegrationTest, vault::MINIMUM_LIQUIDITY}; +use soroban_sdk::{testutils::{MockAuth, MockAuthInvoke}, vec as svec, IntoVal, Vec}; + +#[test] +fn test_deposit_success() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); + + let vault_balance = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance, deposit_amount); + + let user_balance_after_deposit = enviroment.token.balance(user); + assert_eq!(user_balance_after_deposit, user_starting_balance - deposit_amount); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, deposit_amount - MINIMUM_LIQUIDITY); + + let total_supply = enviroment.vault_contract.total_supply(); + assert_eq!(total_supply, deposit_amount); +} + +// #[test] +// fn test_consecutive_deposits_and_partial_withdrawal() { +// let test = DeFindexFactoryTest::setup(); +// test.env.mock_all_auths(); + +// test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &50u32, &test.defindex_wasm_hash); + +// let asset_params = create_asset_params(&test); + +// let salt = BytesN::from_array(&test.env, &[0; 32]); + +// // Create a new DeFindex vault +// test.factory_contract.create_defindex_vault( +// &test.emergency_manager, +// &test.fee_receiver, +// &VAULT_FEE, +// &String::from_str(&test.env, "dfToken"), +// &String::from_str(&test.env, "DFT"), +// &test.manager, +// &asset_params, +// &salt +// ); + +// // Verify that the vault was created +// let deployed_defindexes = test.factory_contract.deployed_defindexes(); +// assert_eq!(deployed_defindexes.len(), 1); + +// // Get the address of the created vault and create a client for it +// let defindex_address = deployed_defindexes.get(0).unwrap(); +// let defindex_contract = defindex_vault_contract::Client::new(&test.env, &defindex_address); + +// // Define the amounts to be deposited +// let amount_token0_user1 = 1_000i128; +// let amount_token1_user1 = 12_000i128; +// let amount_token0_user2 = 500i128; +// let amount_token1_user2 = 6_000i128; + +// // Generate random users for the test +// let users = DeFindexFactoryTest::generate_random_users(&test.env, 2); + +// // Mint Token 0 and Token 1 to user 1 and verify the balance +// test.token0_admin_client.mint(&users[0], &amount_token0_user1); +// test.token1_admin_client.mint(&users[0], &amount_token1_user1); +// assert_eq!(test.token0.balance(&users[0]), amount_token0_user1); +// assert_eq!(test.token1.balance(&users[0]), amount_token1_user1); + +// // Mint Token 0 and Token 1 to user 2 and verify the balance +// test.token0_admin_client.mint(&users[1], &amount_token0_user2); +// test.token1_admin_client.mint(&users[1], &amount_token1_user2); +// assert_eq!(test.token0.balance(&users[1]), amount_token0_user2); +// assert_eq!(test.token1.balance(&users[1]), amount_token1_user2); + +// // Verify the initial balance of dfTokens for both users +// assert_eq!(defindex_contract.balance(&users[0]), 0i128); +// assert_eq!(defindex_contract.balance(&users[1]), 0i128); + +// // User 1 deposits Token 0 and Token 1 into the vault +// defindex_contract.deposit(&vec![&test.env, amount_token0_user1, amount_token1_user1], &vec![&test.env, 0, 0], &users[0]); +// assert_eq!(defindex_contract.balance(&users[0]), amount_token0_user1 + amount_token1_user1); + +// // User 1 should have deposited all their tokens into the vault +// assert_eq!(test.token0.balance(&users[0]), 0); +// assert_eq!(test.token1.balance(&users[0]), 0); +// assert_eq!(test.token0.balance(&defindex_contract.address), amount_token0_user1); +// assert_eq!(test.token1.balance(&defindex_contract.address), amount_token1_user1); + +// // Since this is the first deposit, no fees should be minted +// let total_supply = defindex_contract.total_supply(); +// assert_eq!(total_supply, amount_token0_user1 + amount_token1_user1); + +// let mut ledger_info = test.env.ledger().get(); +// ledger_info.timestamp += 31_536_000; +// test.env.ledger().set(ledger_info); + +// // User 2 deposits Token 0 and Token 1 into the vault +// // total_fees = (fee_rate as i128 * total_supply * time_elapsed) / ((SECONDS_PER_YEAR * MAX_BPS) - (fee_rate as i128 * time_elapsed)); +// let fee_rate = DEFINDEX_FEE + VAULT_FEE; +// let expected_minted_fee: i128 = (fee_rate as i128).checked_mul(total_supply).unwrap().checked_mul(31_536_000i128).unwrap().checked_div(31_536_000i128.checked_mul(MAX_BPS as i128).unwrap().checked_sub((fee_rate as i128).checked_mul(31_536_000i128).unwrap()).unwrap()).unwrap(); + +// defindex_contract.deposit(&vec![&test.env, amount_token0_user2, amount_token1_user2], &vec![&test.env, 0, 0], &users[1]); +// // tvl = 13000 = 13197 dfTokens +// // new_tvl = 13000 + 6500 = 19500 dfTokens + +// assert_eq!(defindex_contract.balance(&users[1]), 6598i128); +// // User 2 should have deposited all their tokens into the vault +// assert_eq!(test.token0.balance(&users[1]), 0); +// // TODO: There is an error with the deposit, since depositing only 500 of the token1 when the user is trying to deposit 6000 +// assert_eq!(test.token1.balance(&users[1]), 0); + +// assert_eq!(test.token0.balance(&defindex_contract.address), amount_token0_user1 + amount_token0_user2); +// assert_eq!(test.token1.balance(&defindex_contract.address), amount_token1_user1 + amount_token1_user2); + +// let total_supply = defindex_contract.total_supply(); +// assert_eq!(total_supply, (amount_token0_user1 + amount_token1_user1) + ((amount_token0_user1 + amount_token1_user1) * 150) / 10000); + +// // // Create investment strategies for the deposited tokens +// // let investments = vec![ +// // &test.env, +// // Investment { +// // amount: amount_token0_user1 + amount_token0_user2, +// // strategy: test.strategy_contract_token0.address.clone() +// // }, +// // Investment { +// // amount: amount_token1_user1 + amount_token1_user2, +// // strategy: test.strategy_contract_token1.address.clone() +// // }]; + +// // // Invest the tokens into the strategies +// // defindex_contract.invest(&investments); + +// // // Verify the vault's balance of Token 0 and Token 1 after investment +// // assert_eq!(test.token0.balance(&defindex_contract.address), 0i128); +// // assert_eq!(test.token1.balance(&defindex_contract.address), 0i128); + +// // // Verify the strategy's balance of Token 0 and Token 1 after investment +// // assert_eq!(test.token0.balance(&test.strategy_contract_token0.address), amount_token0_user1 + amount_token0_user2); +// // assert_eq!(test.token1.balance(&test.strategy_contract_token1.address), amount_token1_user1 + amount_token1_user2); + +// // // User 1 withdraws a part of their dfTokens +// // let partial_withdraw_amount = 5_000i128; +// // let withdraw_result = defindex_contract.withdraw(&partial_withdraw_amount, &users[0]); +// // assert_eq!(withdraw_result, vec![&test.env, 5000i128, 0i128]); + +// // // Verify the balance of dfTokens after partial withdrawal +// // assert_eq!(defindex_contract.balance(&users[0]), (amount_token0_user1 + amount_token1_user1) - partial_withdraw_amount); +// // assert_eq!(defindex_contract.balance(&users[1]), amount_token0_user2 + amount_token1_user2); + +// // // Verify the user's balance of Token 0 and Token 1 after partial withdrawal +// // assert_eq!(test.token0.balance(&users[0]), partial_withdraw_amount); +// // assert_eq!(test.token1.balance(&users[0]), 0i128); + +// // // Verify the vault's balance of Token 0 and Token 1 after partial withdrawal +// // assert_eq!(test.token0.balance(&defindex_contract.address), 0i128); +// // assert_eq!(test.token1.balance(&defindex_contract.address), 0i128); + +// // // Verify the strategy's balance of Token 0 and Token 1 after partial withdrawal +// // assert_eq!(test.token0.balance(&test.strategy_contract_token0.address), (amount_token0_user1 + amount_token0_user2) - partial_withdraw_amount); +// // assert_eq!(test.token1.balance(&test.strategy_contract_token1.address), amount_token1_user1 + amount_token1_user2); +// } \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_withdraw.rs b/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_withdraw.rs new file mode 100644 index 00000000..ba966989 --- /dev/null +++ b/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_withdraw.rs @@ -0,0 +1,385 @@ +use crate::{setup::{create_vault_one_asset_hodl_strategy, VAULT_FEE}, test::{IntegrationTest, DEFINDEX_FEE, ONE_YEAR_IN_SECONDS}, vault::{defindex_vault_contract::{AssetInvestmentAllocation, StrategyInvestment}, VaultContractError, MINIMUM_LIQUIDITY}}; +use soroban_sdk::{testutils::{Ledger, MockAuth, MockAuthInvoke}, vec as svec, IntoVal, Vec}; + +#[test] +fn test_withdraw_no_invest_success() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, deposit_amount - MINIMUM_LIQUIDITY); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "withdraw", + args: ( + df_balance.clone(), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .withdraw(&df_balance, &user); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, 0); + + let vault_balance_after_withdraw = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance_after_withdraw, MINIMUM_LIQUIDITY); + + let user_balance_after_withdraw = enviroment.token.balance(user); + assert_eq!(user_balance_after_withdraw, user_starting_balance - MINIMUM_LIQUIDITY); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, 0); + + let total_supply = enviroment.vault_contract.total_supply(); + assert_eq!(total_supply, MINIMUM_LIQUIDITY); +} + +#[test] +fn test_withdraw_partial_success() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, deposit_amount - MINIMUM_LIQUIDITY); + + let withdraw_amount = df_balance / 2; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "withdraw", + args: ( + withdraw_amount.clone(), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .withdraw(&withdraw_amount, &user); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, withdraw_amount); + + let vault_balance_after_withdraw = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance_after_withdraw, deposit_amount - withdraw_amount); + + let user_balance_after_withdraw = enviroment.token.balance(user); + assert_eq!(user_balance_after_withdraw, user_starting_balance - (deposit_amount - withdraw_amount)); +} + +#[test] +fn test_withdraw_insufficient_balance() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, deposit_amount - MINIMUM_LIQUIDITY); + + let withdraw_amount = df_balance + 1; // Attempt to withdraw more than the balance + let result = enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "withdraw", + args: ( + withdraw_amount.clone(), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .try_withdraw(&withdraw_amount, &user); + assert_eq!(result, Err(Ok(VaultContractError::InsufficientBalance))); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, deposit_amount - MINIMUM_LIQUIDITY); + + let vault_balance_after_withdraw = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance_after_withdraw, deposit_amount); + + let user_balance_after_withdraw = enviroment.token.balance(user); + assert_eq!(user_balance_after_withdraw, user_starting_balance - deposit_amount); +} + +#[test] +fn test_withdraw_after_invest() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 10_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); + + let user_balance_after_deposit = enviroment.token.balance(user); + assert_eq!(user_balance_after_deposit, 0); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, deposit_amount - MINIMUM_LIQUIDITY); + + // Create investment strategies for the deposited tokens + let investments = svec![ + &setup.env, + Some(AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: svec![ + &setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount, + }), + ], + }), + ]; + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &enviroment.manager.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "invest", + args: ( + Vec::from_array(&setup.env,[ + Some( + AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: + svec![&setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount, + }) + ] + } + ) + ]), + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .invest(&investments); + + setup.env.ledger().set_timestamp(setup.env.ledger().timestamp() + ONE_YEAR_IN_SECONDS); + + let token_balance_after_invest = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(token_balance_after_invest, 0); + + let strategy_balance = enviroment.strategy_contract.balance(&enviroment.vault_contract.address); + assert_eq!(strategy_balance, deposit_amount); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "withdraw", + args: ( + df_balance.clone(), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .withdraw(&df_balance, &user); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, 0); + + let token_balance_after_withdraw = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(token_balance_after_withdraw, 0); + + let charged_fee = (deposit_amount - MINIMUM_LIQUIDITY) * (DEFINDEX_FEE as i128 + VAULT_FEE as i128) / 10000; + let expected_amount = deposit_amount - MINIMUM_LIQUIDITY - charged_fee; + + let user_balance_after_withdraw = enviroment.token.balance(user); + assert_eq!(user_balance_after_withdraw, expected_amount); + + let strategy_balance = enviroment.strategy_contract.balance(&enviroment.vault_contract.address); + assert_eq!(strategy_balance, charged_fee + MINIMUM_LIQUIDITY); + + let vault_balance_after_withdraw = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance_after_withdraw, 0); +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/vault.rs b/apps/contracts/integration-test/src/vault.rs index 6724e6f5..13987ab1 100644 --- a/apps/contracts/integration-test/src/vault.rs +++ b/apps/contracts/integration-test/src/vault.rs @@ -5,4 +5,7 @@ pub mod defindex_vault_contract { pub type VaultContractClient<'a> = Client<'a>; } -pub use defindex_vault_contract::{AssetStrategySet as VaultAssetStrategySet, Strategy as VaultStrategy}; +pub use defindex_vault_contract::{AssetStrategySet as VaultAssetStrategySet, Strategy as VaultStrategy, ContractError as VaultContractError}; + +pub static MINIMUM_LIQUIDITY: i128 = 1000; + From 9bc1d949483f9bdf5a400bf4f81ba311fb08e8d6 Mon Sep 17 00:00:00 2001 From: coderipper Date: Thu, 14 Nov 2024 12:42:55 -0300 Subject: [PATCH 12/50] withdraw + invest + multiple users --- .../test_vault_one_strategy/test_withdraw.rs | 343 ++++++++++++++++++ 1 file changed, 343 insertions(+) diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_withdraw.rs b/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_withdraw.rs index ba966989..057524ae 100644 --- a/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_withdraw.rs +++ b/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_withdraw.rs @@ -380,6 +380,349 @@ fn test_withdraw_after_invest() { let strategy_balance = enviroment.strategy_contract.balance(&enviroment.vault_contract.address); assert_eq!(strategy_balance, charged_fee + MINIMUM_LIQUIDITY); + let vault_balance_after_withdraw = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance_after_withdraw, 0); +} + +#[test] +fn test_withdraw_multiple_users() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 2); + let user1 = &users[0]; + let user2 = &users[1]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user1, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user1, &user_starting_balance); + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user2, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user2, &user_starting_balance); + + let user1_balance = enviroment.token.balance(user1); + let user2_balance = enviroment.token.balance(user2); + assert_eq!(user1_balance, user_starting_balance); + assert_eq!(user2_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user1.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user1.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user1.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user1); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user2.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user2.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user2.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user2); + + let df_balance_user1 = enviroment.vault_contract.balance(&user1); + let df_balance_user2 = enviroment.vault_contract.balance(&user2); + assert_eq!(df_balance_user1, deposit_amount - MINIMUM_LIQUIDITY); + assert_eq!(df_balance_user2, deposit_amount); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user1.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "withdraw", + args: ( + df_balance_user1.clone(), + user1.clone() + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .withdraw(&df_balance_user1, &user1); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user2.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "withdraw", + args: ( + df_balance_user2.clone(), + user2.clone() + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .withdraw(&df_balance_user2, &user2); + + let df_balance_user1 = enviroment.vault_contract.balance(&user1); + let df_balance_user2 = enviroment.vault_contract.balance(&user2); + assert_eq!(df_balance_user1, 0); + assert_eq!(df_balance_user2, 0); + + let vault_balance_after_withdraw = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance_after_withdraw, MINIMUM_LIQUIDITY); + + let user1_balance_after_withdraw = enviroment.token.balance(user1); + let user2_balance_after_withdraw = enviroment.token.balance(user2); + assert_eq!(user1_balance_after_withdraw, user_starting_balance - MINIMUM_LIQUIDITY); + assert_eq!(user2_balance_after_withdraw, user_starting_balance); + + let total_supply = enviroment.vault_contract.total_supply(); + assert_eq!(total_supply, MINIMUM_LIQUIDITY); +} + +#[test] +fn test_withdraw_after_invest_multiple_users() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 10_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 2); + let user1 = &users[0]; + let user2 = &users[1]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user1, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user1, &user_starting_balance); + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user2, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user2, &user_starting_balance); + + let user1_balance = enviroment.token.balance(user1); + let user2_balance = enviroment.token.balance(user2); + assert_eq!(user1_balance, user_starting_balance); + assert_eq!(user2_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user1.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user1.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user1.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user1); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user2.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user2.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user2.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user2); + + let df_balance_user1 = enviroment.vault_contract.balance(&user1); + let df_balance_user2 = enviroment.vault_contract.balance(&user2); + assert_eq!(df_balance_user1, deposit_amount - MINIMUM_LIQUIDITY); + assert_eq!(df_balance_user2, deposit_amount); + + // Create investment strategies for the deposited tokens + let investments = svec![ + &setup.env, + Some(AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: svec![ + &setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount * 2, + }), + ], + }), + ]; + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &enviroment.manager.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "invest", + args: ( + Vec::from_array(&setup.env,[ + Some( + AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: + svec![&setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount * 2, + }) + ] + } + ) + ]), + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .invest(&investments); + + setup.env.ledger().set_timestamp(setup.env.ledger().timestamp() + ONE_YEAR_IN_SECONDS); + + let token_balance_after_invest = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(token_balance_after_invest, 0); + + let strategy_balance = enviroment.strategy_contract.balance(&enviroment.vault_contract.address); + assert_eq!(strategy_balance, deposit_amount * 2); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user1.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "withdraw", + args: ( + df_balance_user1.clone(), + user1.clone() + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .withdraw(&df_balance_user1, &user1); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user2.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "withdraw", + args: ( + df_balance_user2.clone(), + user2.clone() + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .withdraw(&df_balance_user2, &user2); + + let df_balance_user1 = enviroment.vault_contract.balance(&user1); + let df_balance_user2 = enviroment.vault_contract.balance(&user2); + assert_eq!(df_balance_user1, 0); + assert_eq!(df_balance_user2, 0); + + let token_balance_after_withdraw = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(token_balance_after_withdraw, 0); + + let charged_fee_user1 = (deposit_amount - MINIMUM_LIQUIDITY) * (DEFINDEX_FEE as i128 + VAULT_FEE as i128) / 10000; + let expected_amount_user1 = deposit_amount - MINIMUM_LIQUIDITY - charged_fee_user1; + + let charged_fee_user2 = deposit_amount * (DEFINDEX_FEE as i128 + VAULT_FEE as i128) / 10000; + let expected_amount_user2 = deposit_amount - charged_fee_user2; + + let user1_balance_after_withdraw = enviroment.token.balance(user1); + let user2_balance_after_withdraw = enviroment.token.balance(user2); + assert_eq!(user1_balance_after_withdraw, expected_amount_user1); + assert_eq!(user2_balance_after_withdraw, expected_amount_user2); + + let strategy_balance = enviroment.strategy_contract.balance(&enviroment.vault_contract.address); + assert_eq!(strategy_balance, charged_fee_user1 + charged_fee_user2 + MINIMUM_LIQUIDITY); + let vault_balance_after_withdraw = enviroment.token.balance(&enviroment.vault_contract.address); assert_eq!(vault_balance_after_withdraw, 0); } \ No newline at end of file From 0d1ab0b5b8cc2c4b2b45c039665898b1628941d2 Mon Sep 17 00:00:00 2001 From: coderipper Date: Thu, 14 Nov 2024 14:00:31 -0300 Subject: [PATCH 13/50] invest tests --- .../src/test/test_vault_one_strategy/mod.rs | 3 +- .../test_vault_one_strategy/test_deposit.rs | 395 ++++++++++------ .../test_vault_one_strategy/test_invest.rs | 426 ++++++++++++++++++ 3 files changed, 683 insertions(+), 141 deletions(-) create mode 100644 apps/contracts/integration-test/src/test/test_vault_one_strategy/test_invest.rs diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/mod.rs b/apps/contracts/integration-test/src/test/test_vault_one_strategy/mod.rs index 3f2289d7..46f7cb99 100644 --- a/apps/contracts/integration-test/src/test/test_vault_one_strategy/mod.rs +++ b/apps/contracts/integration-test/src/test/test_vault_one_strategy/mod.rs @@ -1,2 +1,3 @@ mod test_deposit; -mod test_withdraw; \ No newline at end of file +mod test_withdraw; +mod test_invest; \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_deposit.rs b/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_deposit.rs index 6769c13d..3ae650b0 100644 --- a/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_deposit.rs +++ b/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_deposit.rs @@ -1,4 +1,4 @@ -use crate::{setup::create_vault_one_asset_hodl_strategy, test::IntegrationTest, vault::MINIMUM_LIQUIDITY}; +use crate::{setup::create_vault_one_asset_hodl_strategy, test::IntegrationTest, vault::{VaultContractError, MINIMUM_LIQUIDITY}}; use soroban_sdk::{testutils::{MockAuth, MockAuthInvoke}, vec as svec, IntoVal, Vec}; #[test] @@ -64,142 +64,257 @@ fn test_deposit_success() { assert_eq!(total_supply, deposit_amount); } -// #[test] -// fn test_consecutive_deposits_and_partial_withdrawal() { -// let test = DeFindexFactoryTest::setup(); -// test.env.mock_all_auths(); - -// test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &50u32, &test.defindex_wasm_hash); - -// let asset_params = create_asset_params(&test); - -// let salt = BytesN::from_array(&test.env, &[0; 32]); - -// // Create a new DeFindex vault -// test.factory_contract.create_defindex_vault( -// &test.emergency_manager, -// &test.fee_receiver, -// &VAULT_FEE, -// &String::from_str(&test.env, "dfToken"), -// &String::from_str(&test.env, "DFT"), -// &test.manager, -// &asset_params, -// &salt -// ); - -// // Verify that the vault was created -// let deployed_defindexes = test.factory_contract.deployed_defindexes(); -// assert_eq!(deployed_defindexes.len(), 1); - -// // Get the address of the created vault and create a client for it -// let defindex_address = deployed_defindexes.get(0).unwrap(); -// let defindex_contract = defindex_vault_contract::Client::new(&test.env, &defindex_address); - -// // Define the amounts to be deposited -// let amount_token0_user1 = 1_000i128; -// let amount_token1_user1 = 12_000i128; -// let amount_token0_user2 = 500i128; -// let amount_token1_user2 = 6_000i128; - -// // Generate random users for the test -// let users = DeFindexFactoryTest::generate_random_users(&test.env, 2); - -// // Mint Token 0 and Token 1 to user 1 and verify the balance -// test.token0_admin_client.mint(&users[0], &amount_token0_user1); -// test.token1_admin_client.mint(&users[0], &amount_token1_user1); -// assert_eq!(test.token0.balance(&users[0]), amount_token0_user1); -// assert_eq!(test.token1.balance(&users[0]), amount_token1_user1); - -// // Mint Token 0 and Token 1 to user 2 and verify the balance -// test.token0_admin_client.mint(&users[1], &amount_token0_user2); -// test.token1_admin_client.mint(&users[1], &amount_token1_user2); -// assert_eq!(test.token0.balance(&users[1]), amount_token0_user2); -// assert_eq!(test.token1.balance(&users[1]), amount_token1_user2); - -// // Verify the initial balance of dfTokens for both users -// assert_eq!(defindex_contract.balance(&users[0]), 0i128); -// assert_eq!(defindex_contract.balance(&users[1]), 0i128); - -// // User 1 deposits Token 0 and Token 1 into the vault -// defindex_contract.deposit(&vec![&test.env, amount_token0_user1, amount_token1_user1], &vec![&test.env, 0, 0], &users[0]); -// assert_eq!(defindex_contract.balance(&users[0]), amount_token0_user1 + amount_token1_user1); - -// // User 1 should have deposited all their tokens into the vault -// assert_eq!(test.token0.balance(&users[0]), 0); -// assert_eq!(test.token1.balance(&users[0]), 0); -// assert_eq!(test.token0.balance(&defindex_contract.address), amount_token0_user1); -// assert_eq!(test.token1.balance(&defindex_contract.address), amount_token1_user1); - -// // Since this is the first deposit, no fees should be minted -// let total_supply = defindex_contract.total_supply(); -// assert_eq!(total_supply, amount_token0_user1 + amount_token1_user1); - -// let mut ledger_info = test.env.ledger().get(); -// ledger_info.timestamp += 31_536_000; -// test.env.ledger().set(ledger_info); - -// // User 2 deposits Token 0 and Token 1 into the vault -// // total_fees = (fee_rate as i128 * total_supply * time_elapsed) / ((SECONDS_PER_YEAR * MAX_BPS) - (fee_rate as i128 * time_elapsed)); -// let fee_rate = DEFINDEX_FEE + VAULT_FEE; -// let expected_minted_fee: i128 = (fee_rate as i128).checked_mul(total_supply).unwrap().checked_mul(31_536_000i128).unwrap().checked_div(31_536_000i128.checked_mul(MAX_BPS as i128).unwrap().checked_sub((fee_rate as i128).checked_mul(31_536_000i128).unwrap()).unwrap()).unwrap(); - -// defindex_contract.deposit(&vec![&test.env, amount_token0_user2, amount_token1_user2], &vec![&test.env, 0, 0], &users[1]); -// // tvl = 13000 = 13197 dfTokens -// // new_tvl = 13000 + 6500 = 19500 dfTokens - -// assert_eq!(defindex_contract.balance(&users[1]), 6598i128); -// // User 2 should have deposited all their tokens into the vault -// assert_eq!(test.token0.balance(&users[1]), 0); -// // TODO: There is an error with the deposit, since depositing only 500 of the token1 when the user is trying to deposit 6000 -// assert_eq!(test.token1.balance(&users[1]), 0); - -// assert_eq!(test.token0.balance(&defindex_contract.address), amount_token0_user1 + amount_token0_user2); -// assert_eq!(test.token1.balance(&defindex_contract.address), amount_token1_user1 + amount_token1_user2); - -// let total_supply = defindex_contract.total_supply(); -// assert_eq!(total_supply, (amount_token0_user1 + amount_token1_user1) + ((amount_token0_user1 + amount_token1_user1) * 150) / 10000); - -// // // Create investment strategies for the deposited tokens -// // let investments = vec![ -// // &test.env, -// // Investment { -// // amount: amount_token0_user1 + amount_token0_user2, -// // strategy: test.strategy_contract_token0.address.clone() -// // }, -// // Investment { -// // amount: amount_token1_user1 + amount_token1_user2, -// // strategy: test.strategy_contract_token1.address.clone() -// // }]; - -// // // Invest the tokens into the strategies -// // defindex_contract.invest(&investments); - -// // // Verify the vault's balance of Token 0 and Token 1 after investment -// // assert_eq!(test.token0.balance(&defindex_contract.address), 0i128); -// // assert_eq!(test.token1.balance(&defindex_contract.address), 0i128); - -// // // Verify the strategy's balance of Token 0 and Token 1 after investment -// // assert_eq!(test.token0.balance(&test.strategy_contract_token0.address), amount_token0_user1 + amount_token0_user2); -// // assert_eq!(test.token1.balance(&test.strategy_contract_token1.address), amount_token1_user1 + amount_token1_user2); - -// // // User 1 withdraws a part of their dfTokens -// // let partial_withdraw_amount = 5_000i128; -// // let withdraw_result = defindex_contract.withdraw(&partial_withdraw_amount, &users[0]); -// // assert_eq!(withdraw_result, vec![&test.env, 5000i128, 0i128]); - -// // // Verify the balance of dfTokens after partial withdrawal -// // assert_eq!(defindex_contract.balance(&users[0]), (amount_token0_user1 + amount_token1_user1) - partial_withdraw_amount); -// // assert_eq!(defindex_contract.balance(&users[1]), amount_token0_user2 + amount_token1_user2); - -// // // Verify the user's balance of Token 0 and Token 1 after partial withdrawal -// // assert_eq!(test.token0.balance(&users[0]), partial_withdraw_amount); -// // assert_eq!(test.token1.balance(&users[0]), 0i128); - -// // // Verify the vault's balance of Token 0 and Token 1 after partial withdrawal -// // assert_eq!(test.token0.balance(&defindex_contract.address), 0i128); -// // assert_eq!(test.token1.balance(&defindex_contract.address), 0i128); - -// // // Verify the strategy's balance of Token 0 and Token 1 after partial withdrawal -// // assert_eq!(test.token0.balance(&test.strategy_contract_token0.address), (amount_token0_user1 + amount_token0_user2) - partial_withdraw_amount); -// // assert_eq!(test.token1.balance(&test.strategy_contract_token1.address), amount_token1_user1 + amount_token1_user2); -// } \ No newline at end of file +#[test] +#[should_panic(expected = "HostError: Error(Contract, #10)")] +fn test_deposit_insufficient_balance() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 5_000_0_000_000i128; // Less than deposit amount + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); +} + +#[test] +fn test_deposit_multiple_users() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 2); + let user1 = &users[0]; + let user2 = &users[1]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user1, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user1, &user_starting_balance); + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user2, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user2, &user_starting_balance); + + let user1_balance = enviroment.token.balance(user1); + let user2_balance = enviroment.token.balance(user2); + assert_eq!(user1_balance, user_starting_balance); + assert_eq!(user2_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user1.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user1.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user1.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user1); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user2.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user2.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user2.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user2); + + let vault_balance = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance, deposit_amount * 2); + + let user1_balance_after_deposit = enviroment.token.balance(user1); + let user2_balance_after_deposit = enviroment.token.balance(user2); + assert_eq!(user1_balance_after_deposit, user_starting_balance - deposit_amount); + assert_eq!(user2_balance_after_deposit, user_starting_balance - deposit_amount); + + let df_balance_user1 = enviroment.vault_contract.balance(&user1); + let df_balance_user2 = enviroment.vault_contract.balance(&user2); + assert_eq!(df_balance_user1, deposit_amount - MINIMUM_LIQUIDITY); + assert_eq!(df_balance_user2, deposit_amount); + + let total_supply = enviroment.vault_contract.total_supply(); + assert_eq!(total_supply, deposit_amount * 2); +} + +#[test] +fn test_deposit_zero_amount() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 0i128; + let result = enviroment.vault_contract.mock_all_auths().try_deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + ); + + assert_eq!(result, Err(Ok(VaultContractError::InsufficientAmount))); +} + +#[test] +fn test_deposit_negative_amount() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = -10_000_0_000_000i128; + let result = enviroment.vault_contract.mock_all_auths().try_deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + ); + + assert_eq!(result, Err(Ok(VaultContractError::NegativeNotAllowed))); +} + +#[test] +fn test_deposit_insufficient_minimum_liquidity() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = MINIMUM_LIQUIDITY - 1; + let result = enviroment.vault_contract.mock_all_auths().try_deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + ); + + assert_eq!(result, Err(Ok(VaultContractError::InsufficientAmount))); +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_invest.rs b/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_invest.rs new file mode 100644 index 00000000..035faf50 --- /dev/null +++ b/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_invest.rs @@ -0,0 +1,426 @@ +use crate::{setup::create_vault_one_asset_hodl_strategy, test::{IntegrationTest, ONE_YEAR_IN_SECONDS}, vault::{defindex_vault_contract::{AssetInvestmentAllocation, StrategyInvestment}, VaultContractError, MINIMUM_LIQUIDITY}}; +use soroban_sdk::{testutils::{Ledger, MockAuth, MockAuthInvoke, Address as _}, vec as svec, Address, IntoVal, Vec}; + +#[test] +fn test_invest_success() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, deposit_amount - MINIMUM_LIQUIDITY); + + // Create investment strategies for the deposited tokens + let investments = svec![ + &setup.env, + Some(AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: svec![ + &setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount, + }), + ], + }), + ]; + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &enviroment.manager.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "invest", + args: ( + Vec::from_array(&setup.env,[ + Some( + AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: + svec![&setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount, + }) + ] + } + ) + ]), + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .invest(&investments); + + setup.env.ledger().set_timestamp(setup.env.ledger().timestamp() + ONE_YEAR_IN_SECONDS); + + let token_balance_after_invest = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(token_balance_after_invest, 0); + + let strategy_balance = enviroment.strategy_contract.balance(&enviroment.vault_contract.address); + assert_eq!(strategy_balance, deposit_amount); +} + +#[test] +fn test_invest_exceeding_investing_lenght() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, deposit_amount - MINIMUM_LIQUIDITY); + + let asset_address_2 = Address::generate(&setup.env); + let strategy_address_2 = Address::generate(&setup.env); + // Create investment strategies exceeding the allowed number + let investments = svec![ + &setup.env, + Some(AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: svec![ + &setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount, + }), + ], + }), + Some(AssetInvestmentAllocation { + asset: asset_address_2.clone(), + strategy_investments: svec![ + &setup.env, + Some(StrategyInvestment { + strategy: strategy_address_2.clone(), + amount: deposit_amount, + }), + ], + }), + ]; + + let result = enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &enviroment.manager.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "invest", + args: ( + Vec::from_array(&setup.env,[ + Some( + AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: + svec![&setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount, + }) + ] + } + ), + Some( + AssetInvestmentAllocation { + asset: asset_address_2.clone(), + strategy_investments: + svec![&setup.env, + Some(StrategyInvestment { + strategy: strategy_address_2.clone(), + amount: deposit_amount, + }) + ] + } + ) + ]), + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .try_invest(&investments); + + assert_eq!(result, Err(Ok(VaultContractError::WrongInvestmentLength))); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #10)")] +fn test_invest_insufficient_balance() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 5_000_0_000_000i128; // Less than deposit amount + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); +} + +#[test] +fn test_invest_multiple_users() { + let enviroment = create_vault_one_asset_hodl_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 2); + let user1 = &users[0]; + let user2 = &users[1]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user1, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user1, &user_starting_balance); + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user2, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user2, &user_starting_balance); + + let user1_balance = enviroment.token.balance(user1); + let user2_balance = enviroment.token.balance(user2); + assert_eq!(user1_balance, user_starting_balance); + assert_eq!(user2_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user1.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user1.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user1.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user1); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user2.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user2.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user2.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user2); + + let df_balance_user1 = enviroment.vault_contract.balance(&user1); + let df_balance_user2 = enviroment.vault_contract.balance(&user2); + assert_eq!(df_balance_user1, deposit_amount - MINIMUM_LIQUIDITY); + assert_eq!(df_balance_user2, deposit_amount); + + // Create investment strategies for the deposited tokens + let investments = svec![ + &setup.env, + Some(AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: svec![ + &setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount*2, + }), + ], + }), + ]; + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &enviroment.manager.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "invest", + args: ( + Vec::from_array(&setup.env,[ + Some( + AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: + svec![&setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount*2, + }) + ] + } + ) + ]), + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .invest(&investments); + + setup.env.ledger().set_timestamp(setup.env.ledger().timestamp() + ONE_YEAR_IN_SECONDS); + + let token_balance_after_invest = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(token_balance_after_invest, 0); + + let strategy_balance = enviroment.strategy_contract.balance(&enviroment.vault_contract.address); + assert_eq!(strategy_balance, deposit_amount * 2); +} \ No newline at end of file From 67101700e0ba3ad274037616876c77de48008127 Mon Sep 17 00:00:00 2001 From: coderipper Date: Thu, 14 Nov 2024 14:15:02 -0300 Subject: [PATCH 14/50] removed all_flow --- apps/contracts/factory/src/test.rs | 3 +- apps/contracts/factory/src/test/all_flow.rs | 359 -------------------- 2 files changed, 1 insertion(+), 361 deletions(-) delete mode 100644 apps/contracts/factory/src/test/all_flow.rs diff --git a/apps/contracts/factory/src/test.rs b/apps/contracts/factory/src/test.rs index 5eda131e..77d207c4 100644 --- a/apps/contracts/factory/src/test.rs +++ b/apps/contracts/factory/src/test.rs @@ -157,5 +157,4 @@ impl<'a> DeFindexFactoryTest<'a> { mod admin; mod initialize; -mod create_defindex; -mod all_flow; \ No newline at end of file +mod create_defindex; \ No newline at end of file diff --git a/apps/contracts/factory/src/test/all_flow.rs b/apps/contracts/factory/src/test/all_flow.rs deleted file mode 100644 index 3119621f..00000000 --- a/apps/contracts/factory/src/test/all_flow.rs +++ /dev/null @@ -1,359 +0,0 @@ -use soroban_sdk::{testutils::Ledger, vec, BytesN, String}; - -use crate::test::{ - create_asset_params, - defindex_vault_contract::{ - self, - AssetInvestmentAllocation, - StrategyInvestment, - }, DeFindexFactoryTest}; - -pub(crate) const DEFINDEX_FEE: u32 = 50u32; -pub(crate) const VAULT_FEE: u32 = 100u32; -pub(crate) const MAX_BPS: u32 = 10_000u32; - -#[test] -fn test_deposit_success() { - let test = DeFindexFactoryTest::setup(); - test.env.mock_all_auths(); - - test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &DEFINDEX_FEE, &test.defindex_wasm_hash); - - let asset_params = create_asset_params(&test); - - let salt = BytesN::from_array(&test.env, &[0; 32]); - - test.factory_contract.create_defindex_vault( - &test.emergency_manager, - &test.fee_receiver, - &VAULT_FEE, - &String::from_str(&test.env, "dfToken"), - &String::from_str(&test.env, "DFT"), - &test.manager, - &asset_params, - &salt - ); - - let deployed_defindexes = test.factory_contract.deployed_defindexes(); - assert_eq!(deployed_defindexes.len(), 1); - - let defindex_address = deployed_defindexes.get(0).unwrap(); - let defindex_contract = defindex_vault_contract::Client::new(&test.env, &defindex_address); - - let amount_token0 = 1_000i128; - let amount_token1 = 12_000i128; - - let users = DeFindexFactoryTest::generate_random_users(&test.env, 1); - - // Minting Token 0 to user - test.token0_admin_client.mint(&users[0], &amount_token0); - let user_balance = test.token0.balance(&users[0]); - assert_eq!(user_balance, amount_token0); - - // Minting Token 1 to user - test.token1_admin_client.mint(&users[0], &amount_token1); - let user_balance = test.token1.balance(&users[0]); - assert_eq!(user_balance, amount_token1); - - // Checking user balance of dfTokens - let df_balance = defindex_contract.balance(&users[0]); - assert_eq!(df_balance, 0i128); - - // Depositing Token 0 and Token 1 to defindex - defindex_contract.deposit(&vec![&test.env, amount_token0, amount_token1], &vec![&test.env, 0, 0], &users[0]); - - // Checking user balance of dfTokens - let df_balance = defindex_contract.balance(&users[0]); - assert_eq!(df_balance, amount_token0 + amount_token1 - 1000); // TODO: The amount of dfTokens minted is the sum of both asset deposited? - - - // defindex_contract.withdraw(&df_balance, &users[0]); - - // let df_balance = defindex_contract.user_balance(&users[0]); - // assert_eq!(df_balance, 0i128); - - // let user_balance = test.token0.balance(&users[0]); - // assert_eq!(user_balance, amount); - - // Since this is the first deposit, no fees should be minted - let total_supply = defindex_contract.total_supply(); - assert_eq!(total_supply, amount_token0 + amount_token1); -} - -#[test] -fn test_withdraw_success() { - let test = DeFindexFactoryTest::setup(); - test.env.mock_all_auths(); - - test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &DEFINDEX_FEE, &test.defindex_wasm_hash); - - let asset_params = create_asset_params(&test); - - let salt = BytesN::from_array(&test.env, &[0; 32]); - - // Create a new DeFindex vault - test.factory_contract.create_defindex_vault( - &test.emergency_manager, - &test.fee_receiver, - &VAULT_FEE, - &String::from_str(&test.env, "dfToken"), - &String::from_str(&test.env, "DFT"), - &test.manager, - &asset_params, - &salt - ); - - // Verify that the vault was created - let deployed_defindexes = test.factory_contract.deployed_defindexes(); - assert_eq!(deployed_defindexes.len(), 1); - - // Get the address of the created vault and create a client for it - let defindex_address = deployed_defindexes.get(0).unwrap(); - let defindex_contract = defindex_vault_contract::Client::new(&test.env, &defindex_address); - - // Define the amounts to be deposited - let amount_token0 = 1_000i128; - let amount_token1 = 12_000i128; - - // Generate random users for the test - let users = DeFindexFactoryTest::generate_random_users(&test.env, 1); - - // Mint Token 0 to the user and verify the balance - test.token0_admin_client.mint(&users[0], &amount_token0); - let user_balance = test.token0.balance(&users[0]); - assert_eq!(user_balance, amount_token0); - - // Mint Token 1 to the user and verify the balance - test.token1_admin_client.mint(&users[0], &amount_token1); - let user_balance = test.token1.balance(&users[0]); - assert_eq!(user_balance, amount_token1); - - // Verify the initial balance of dfTokens for the user - let df_balance = defindex_contract.balance(&users[0]); - assert_eq!(df_balance, 0i128); - - // Deposit Token 0 and Token 1 into the vault - defindex_contract.deposit(&vec![&test.env, amount_token0, amount_token1], &vec![&test.env, 0, 0], &users[0]); - - // Verify the balance of dfTokens after deposit - let df_balance = defindex_contract.balance(&users[0]); - assert_eq!(df_balance.clone(), amount_token0 + amount_token1 - 1000); // TODO: The amount of dfTokens minted is the sum of both asset deposited? - - // Verify the vault's balance of Token 0 and Token 1 after deposit - let vault_token0_balance = test.token0.balance(&defindex_contract.address); - assert_eq!(vault_token0_balance, amount_token0); - - let vault_token1_balance = test.token1.balance(&defindex_contract.address); - assert_eq!(vault_token1_balance, amount_token1); - - // Create investment strategies for the deposited tokens - let investments = vec![ - &test.env, - Some(AssetInvestmentAllocation { - asset: test.token0.address.clone(), - strategy_investments: vec![ - &test.env, - Some(StrategyInvestment { - strategy: test.strategy_contract_token0.address.clone(), - amount: amount_token0, - }), - ], - }), - Some(AssetInvestmentAllocation { - asset: test.token1.address.clone(), - strategy_investments: vec![ - &test.env, - Some(StrategyInvestment { - strategy: test.strategy_contract_token1.address.clone(), - amount: amount_token1, - }), - ], - }) - ]; - - // Invest the tokens into the strategies - defindex_contract.invest(&investments); - - // Verify the vault's balance of Token 0 and Token 1 after investment - let vault_token0_balance = test.token0.balance(&defindex_contract.address); - assert_eq!(vault_token0_balance, 0i128); - - let vault_token1_balance = test.token1.balance(&defindex_contract.address); - assert_eq!(vault_token1_balance, 0i128); - - // Verify the strategy's balance of Token 0 and Token 1 after investment - let strategy_token0_balance = test.token0.balance(&test.strategy_contract_token0.address); - assert_eq!(strategy_token0_balance, amount_token0); - - let strategy_token1_balance = test.token1.balance(&test.strategy_contract_token1.address); - assert_eq!(strategy_token1_balance, amount_token1); - - // Withdraw the dfTokens and verify the result - let withdraw_result = defindex_contract.withdraw(&df_balance, &users[0]); - assert_eq!(withdraw_result, vec![&test.env, 12000i128, 1000i128]); - - // Verify the balance of dfTokens after withdrawal - let df_balance = defindex_contract.balance(&users[0]); - assert_eq!(df_balance, 0i128); - - // Verify the user's balance of Token 0 and Token 1 after withdrawal - let user_balance = test.token0.balance(&users[0]); - assert_eq!(user_balance, amount_token0); - - let user_balance = test.token1.balance(&users[0]); - assert_eq!(user_balance, amount_token1); - - // Verify the vault's balance of Token 0 and Token 1 after withdrawal - let vault_token0_balance = test.token0.balance(&defindex_contract.address); - assert_eq!(vault_token0_balance, 0i128); - - let vault_token1_balance = test.token1.balance(&defindex_contract.address); - assert_eq!(vault_token1_balance, 0i128); - - // Verify the strategy's balance of Token 0 and Token 1 after withdrawal - let strategy_token0_balance = test.token0.balance(&test.strategy_contract_token0.address); - assert_eq!(strategy_token0_balance, 0i128); - - let strategy_token1_balance = test.token1.balance(&test.strategy_contract_token1.address); - assert_eq!(strategy_token1_balance, 0i128); -} - -#[test] -fn test_consecutive_deposits_and_partial_withdrawal() { - let test = DeFindexFactoryTest::setup(); - test.env.mock_all_auths(); - - test.factory_contract.initialize(&test.admin, &test.defindex_receiver, &50u32, &test.defindex_wasm_hash); - - let asset_params = create_asset_params(&test); - - let salt = BytesN::from_array(&test.env, &[0; 32]); - - // Create a new DeFindex vault - test.factory_contract.create_defindex_vault( - &test.emergency_manager, - &test.fee_receiver, - &VAULT_FEE, - &String::from_str(&test.env, "dfToken"), - &String::from_str(&test.env, "DFT"), - &test.manager, - &asset_params, - &salt - ); - - // Verify that the vault was created - let deployed_defindexes = test.factory_contract.deployed_defindexes(); - assert_eq!(deployed_defindexes.len(), 1); - - // Get the address of the created vault and create a client for it - let defindex_address = deployed_defindexes.get(0).unwrap(); - let defindex_contract = defindex_vault_contract::Client::new(&test.env, &defindex_address); - - // Define the amounts to be deposited - let amount_token0_user1 = 1_000i128; - let amount_token1_user1 = 12_000i128; - let amount_token0_user2 = 500i128; - let amount_token1_user2 = 6_000i128; - - // Generate random users for the test - let users = DeFindexFactoryTest::generate_random_users(&test.env, 2); - - // Mint Token 0 and Token 1 to user 1 and verify the balance - test.token0_admin_client.mint(&users[0], &amount_token0_user1); - test.token1_admin_client.mint(&users[0], &amount_token1_user1); - assert_eq!(test.token0.balance(&users[0]), amount_token0_user1); - assert_eq!(test.token1.balance(&users[0]), amount_token1_user1); - - // Mint Token 0 and Token 1 to user 2 and verify the balance - test.token0_admin_client.mint(&users[1], &amount_token0_user2); - test.token1_admin_client.mint(&users[1], &amount_token1_user2); - assert_eq!(test.token0.balance(&users[1]), amount_token0_user2); - assert_eq!(test.token1.balance(&users[1]), amount_token1_user2); - - // Verify the initial balance of dfTokens for both users - assert_eq!(defindex_contract.balance(&users[0]), 0i128); - assert_eq!(defindex_contract.balance(&users[1]), 0i128); - - // User 1 deposits Token 0 and Token 1 into the vault - defindex_contract.deposit(&vec![&test.env, amount_token0_user1, amount_token1_user1], &vec![&test.env, 0, 0], &users[0]); - assert_eq!(defindex_contract.balance(&users[0]), amount_token0_user1 + amount_token1_user1); - - // User 1 should have deposited all their tokens into the vault - assert_eq!(test.token0.balance(&users[0]), 0); - assert_eq!(test.token1.balance(&users[0]), 0); - assert_eq!(test.token0.balance(&defindex_contract.address), amount_token0_user1); - assert_eq!(test.token1.balance(&defindex_contract.address), amount_token1_user1); - - // Since this is the first deposit, no fees should be minted - let total_supply = defindex_contract.total_supply(); - assert_eq!(total_supply, amount_token0_user1 + amount_token1_user1); - - let mut ledger_info = test.env.ledger().get(); - ledger_info.timestamp += 31_536_000; - test.env.ledger().set(ledger_info); - - // User 2 deposits Token 0 and Token 1 into the vault - // total_fees = (fee_rate as i128 * total_supply * time_elapsed) / ((SECONDS_PER_YEAR * MAX_BPS) - (fee_rate as i128 * time_elapsed)); - let fee_rate = DEFINDEX_FEE + VAULT_FEE; - let expected_minted_fee: i128 = (fee_rate as i128).checked_mul(total_supply).unwrap().checked_mul(31_536_000i128).unwrap().checked_div(31_536_000i128.checked_mul(MAX_BPS as i128).unwrap().checked_sub((fee_rate as i128).checked_mul(31_536_000i128).unwrap()).unwrap()).unwrap(); - - defindex_contract.deposit(&vec![&test.env, amount_token0_user2, amount_token1_user2], &vec![&test.env, 0, 0], &users[1]); - // tvl = 13000 = 13197 dfTokens - // new_tvl = 13000 + 6500 = 19500 dfTokens - - assert_eq!(defindex_contract.balance(&users[1]), 6598i128); - // User 2 should have deposited all their tokens into the vault - assert_eq!(test.token0.balance(&users[1]), 0); - // TODO: There is an error with the deposit, since depositing only 500 of the token1 when the user is trying to deposit 6000 - assert_eq!(test.token1.balance(&users[1]), 0); - - assert_eq!(test.token0.balance(&defindex_contract.address), amount_token0_user1 + amount_token0_user2); - assert_eq!(test.token1.balance(&defindex_contract.address), amount_token1_user1 + amount_token1_user2); - - let total_supply = defindex_contract.total_supply(); - assert_eq!(total_supply, (amount_token0_user1 + amount_token1_user1) + ((amount_token0_user1 + amount_token1_user1) * 150) / 10000); - - // // Create investment strategies for the deposited tokens - // let investments = vec![ - // &test.env, - // Investment { - // amount: amount_token0_user1 + amount_token0_user2, - // strategy: test.strategy_contract_token0.address.clone() - // }, - // Investment { - // amount: amount_token1_user1 + amount_token1_user2, - // strategy: test.strategy_contract_token1.address.clone() - // }]; - - // // Invest the tokens into the strategies - // defindex_contract.invest(&investments); - - // // Verify the vault's balance of Token 0 and Token 1 after investment - // assert_eq!(test.token0.balance(&defindex_contract.address), 0i128); - // assert_eq!(test.token1.balance(&defindex_contract.address), 0i128); - - // // Verify the strategy's balance of Token 0 and Token 1 after investment - // assert_eq!(test.token0.balance(&test.strategy_contract_token0.address), amount_token0_user1 + amount_token0_user2); - // assert_eq!(test.token1.balance(&test.strategy_contract_token1.address), amount_token1_user1 + amount_token1_user2); - - // // User 1 withdraws a part of their dfTokens - // let partial_withdraw_amount = 5_000i128; - // let withdraw_result = defindex_contract.withdraw(&partial_withdraw_amount, &users[0]); - // assert_eq!(withdraw_result, vec![&test.env, 5000i128, 0i128]); - - // // Verify the balance of dfTokens after partial withdrawal - // assert_eq!(defindex_contract.balance(&users[0]), (amount_token0_user1 + amount_token1_user1) - partial_withdraw_amount); - // assert_eq!(defindex_contract.balance(&users[1]), amount_token0_user2 + amount_token1_user2); - - // // Verify the user's balance of Token 0 and Token 1 after partial withdrawal - // assert_eq!(test.token0.balance(&users[0]), partial_withdraw_amount); - // assert_eq!(test.token1.balance(&users[0]), 0i128); - - // // Verify the vault's balance of Token 0 and Token 1 after partial withdrawal - // assert_eq!(test.token0.balance(&defindex_contract.address), 0i128); - // assert_eq!(test.token1.balance(&defindex_contract.address), 0i128); - - // // Verify the strategy's balance of Token 0 and Token 1 after partial withdrawal - // assert_eq!(test.token0.balance(&test.strategy_contract_token0.address), (amount_token0_user1 + amount_token0_user2) - partial_withdraw_amount); - // assert_eq!(test.token1.balance(&test.strategy_contract_token1.address), amount_token1_user1 + amount_token1_user2); -} \ No newline at end of file From 8eef088d0228fb0eead9024996728ab479fbe431 Mon Sep 17 00:00:00 2001 From: coderipper Date: Thu, 14 Nov 2024 15:12:07 -0300 Subject: [PATCH 15/50] created tests for vault one asset fixed strategy --- .../integration-test/src/fixed_strategy.rs | 18 + apps/contracts/integration-test/src/lib.rs | 1 + apps/contracts/integration-test/src/setup.rs | 139 +++++++- apps/contracts/integration-test/src/test.rs | 3 +- .../test/test_vault_one_fixed_strategy/mod.rs | 1 + .../test_deposit.rs | 320 ++++++++++++++++++ .../test_withdraw.rs | 0 .../mod.rs | 0 .../test_deposit.rs | 0 .../test_invest.rs | 0 .../test_withdraw.rs | 0 11 files changed, 476 insertions(+), 6 deletions(-) create mode 100644 apps/contracts/integration-test/src/fixed_strategy.rs create mode 100644 apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/mod.rs create mode 100644 apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_deposit.rs create mode 100644 apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_withdraw.rs rename apps/contracts/integration-test/src/test/{test_vault_one_strategy => test_vault_one_hodl_strategy}/mod.rs (100%) rename apps/contracts/integration-test/src/test/{test_vault_one_strategy => test_vault_one_hodl_strategy}/test_deposit.rs (100%) rename apps/contracts/integration-test/src/test/{test_vault_one_strategy => test_vault_one_hodl_strategy}/test_invest.rs (100%) rename apps/contracts/integration-test/src/test/{test_vault_one_strategy => test_vault_one_hodl_strategy}/test_withdraw.rs (100%) diff --git a/apps/contracts/integration-test/src/fixed_strategy.rs b/apps/contracts/integration-test/src/fixed_strategy.rs new file mode 100644 index 00000000..1b920aa2 --- /dev/null +++ b/apps/contracts/integration-test/src/fixed_strategy.rs @@ -0,0 +1,18 @@ +// DeFindex Hodl Strategy Contract +mod fixed_strategy { + soroban_sdk::contractimport!(file = "../target/wasm32-unknown-unknown/release/fixed_apr_strategy.optimized.wasm"); + pub type FixedStrategyClient<'a> = Client<'a>; +} + +pub use fixed_strategy::FixedStrategyClient; +use soroban_sdk::{Address, Env, Val, Vec}; + +pub fn create_fixed_strategy_contract<'a>(e: &Env, asset: &Address, init_args: &Vec) -> FixedStrategyClient<'a> { + let address = &e.register_contract_wasm(None, fixed_strategy::WASM); + let strategy = FixedStrategyClient::new(e, address); + + strategy.mock_all_auths().initialize(asset, init_args); + strategy +} + + diff --git a/apps/contracts/integration-test/src/lib.rs b/apps/contracts/integration-test/src/lib.rs index 3012abff..5eaa850f 100644 --- a/apps/contracts/integration-test/src/lib.rs +++ b/apps/contracts/integration-test/src/lib.rs @@ -2,6 +2,7 @@ pub mod test; pub mod token; pub mod hodl_strategy; +pub mod fixed_strategy; pub mod vault; pub mod factory; pub mod setup; \ 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 3f44050e..e815b96a 100644 --- a/apps/contracts/integration-test/src/setup.rs +++ b/apps/contracts/integration-test/src/setup.rs @@ -1,14 +1,15 @@ use soroban_sdk::token::{StellarAssetClient, TokenClient}; -use soroban_sdk::BytesN; -use soroban_sdk::{testutils::Address as _, vec as sorobanvec, Address, String}; +use soroban_sdk::{BytesN, Val, IntoVal}; +use soroban_sdk::{testutils::Address as _, vec as sorobanvec, Address, String, Vec}; +use crate::fixed_strategy::{create_fixed_strategy_contract, FixedStrategyClient}; use crate::hodl_strategy::{create_hodl_strategy_contract, HodlStrategyClient}; use crate::test::IntegrationTest; use crate::token::create_token; use crate::factory::{AssetStrategySet, Strategy}; use crate::vault::defindex_vault_contract::VaultContractClient; -pub struct VaultOneAseetHodl<'a> { +pub struct VaultOneAseetHodlStrategy<'a> { pub setup: IntegrationTest<'a>, pub token: TokenClient<'a>, pub token_admin: Address, @@ -23,7 +24,7 @@ pub struct VaultOneAseetHodl<'a> { pub static VAULT_FEE: u32 = 100; -pub fn create_vault_one_asset_hodl_strategy<'a>() -> VaultOneAseetHodl<'a> { +pub fn create_vault_one_asset_hodl_strategy<'a>() -> VaultOneAseetHodlStrategy<'a> { let setup = IntegrationTest::setup(); let token_admin = Address::generate(&setup.env); @@ -68,7 +69,89 @@ pub fn create_vault_one_asset_hodl_strategy<'a>() -> VaultOneAseetHodl<'a> { let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); - VaultOneAseetHodl { + VaultOneAseetHodlStrategy { + setup, + token, + token_admin, + token_admin_client, + strategy_contract, + vault_contract, + manager, + emergency_manager, + fee_receiver, + vault_fee, + } +} + +pub struct VaultOneAseetFixedStrategy<'a> { + pub setup: IntegrationTest<'a>, + pub token: TokenClient<'a>, + pub token_admin: Address, + pub token_admin_client: StellarAssetClient<'a>, + pub strategy_contract: FixedStrategyClient<'a>, + pub vault_contract: VaultContractClient<'a>, + pub manager: Address, + pub emergency_manager: Address, + pub fee_receiver: Address, + pub vault_fee: u32, +} + +pub fn create_vault_one_asset_fixed_strategy<'a>() -> VaultOneAseetFixedStrategy<'a> { + let setup = IntegrationTest::setup(); + + let token_admin = Address::generate(&setup.env); + let (token, token_admin_client) = create_token(&setup.env, &token_admin); + + let strategy_admin = Address::generate(&setup.env); + let starting_amount = 100_000_000_000_0_000_000i128; + token_admin_client.mock_all_auths().mint(&strategy_admin, &starting_amount); + + let init_fn_args: Vec = sorobanvec![&setup.env, + 1000u32.into_val(&setup.env), + strategy_admin.into_val(&setup.env), + starting_amount.into_val(&setup.env), + ]; + + let strategy_contract = create_fixed_strategy_contract(&setup.env, &token.address, &init_fn_args); + + let emergency_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, "FixedVault"); + let vault_symbol = String::from_str(&setup.env, "FVLT"); + let manager = Address::generate(&setup.env); + + let assets = sorobanvec![ + &setup.env, + AssetStrategySet { + address: token.address.clone(), + strategies: sorobanvec![ + &setup.env, + Strategy { + address: strategy_contract.address.clone(), + name: String::from_str(&setup.env, "Fixed Strategy"), + paused: false, + } + ], + } + ]; + + let salt = BytesN::from_array(&setup.env, &[0; 32]); + + let vault_contract_address = setup.factory_contract.create_defindex_vault( + &emergency_manager, + &fee_receiver, + &vault_fee, + &vault_name, + &vault_symbol, + &manager, + &assets, + &salt + ); + + let vault_contract = VaultContractClient::new(&setup.env, &vault_contract_address); + + VaultOneAseetFixedStrategy { setup, token, token_admin, @@ -131,4 +214,50 @@ mod tests { assert_eq!(vault_symbol, String::from_str(&setup.env, "HVLT")); } + #[test] + fn test_create_vault_one_asset_fixed_strategy() { + let enviroment = create_vault_one_asset_fixed_strategy(); + let setup = enviroment.setup; + assert_eq!(setup.factory_contract.deployed_defindexes().len(), 1); + + let strategy_token = enviroment.strategy_contract.asset(); + assert_eq!(strategy_token, enviroment.token.address); + + let assets = sorobanvec![ + &setup.env, + VaultAssetStrategySet { + address: enviroment.token.address.clone(), + strategies: sorobanvec![ + &setup.env, + VaultStrategy { + address: enviroment.strategy_contract.address.clone(), + name: String::from_str(&setup.env, "Fixed Strategy"), + paused: false, + } + ], + } + ]; + + let vault_assets = enviroment.vault_contract.get_assets(); + assert_eq!(vault_assets, assets); + + let strategy_contract_balance = enviroment.token.balance(&enviroment.strategy_contract.address); + assert_eq!(strategy_contract_balance, 100_000_000_000_0_000_000i128); + + let vault_emergency_manager = enviroment.vault_contract.get_emergency_manager(); + assert_eq!(vault_emergency_manager, enviroment.emergency_manager); + + let vault_fee_receiver = enviroment.vault_contract.get_fee_receiver(); + assert_eq!(vault_fee_receiver, enviroment.fee_receiver); + + let vault_manager = enviroment.vault_contract.get_manager(); + assert_eq!(vault_manager, enviroment.manager); + + let vault_name = enviroment.vault_contract.name(); + assert_eq!(vault_name, String::from_str(&setup.env, "FixedVault")); + + let vault_symbol = enviroment.vault_contract.symbol(); + assert_eq!(vault_symbol, String::from_str(&setup.env, "FVLT")); + } + } \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test.rs b/apps/contracts/integration-test/src/test.rs index 433c7de5..e3d16982 100644 --- a/apps/contracts/integration-test/src/test.rs +++ b/apps/contracts/integration-test/src/test.rs @@ -54,4 +54,5 @@ impl<'a> IntegrationTest<'a> { } #[cfg(test)] -mod test_vault_one_strategy; \ No newline at end of file +mod test_vault_one_hodl_strategy; +mod test_vault_one_fixed_strategy; \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/mod.rs b/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/mod.rs new file mode 100644 index 00000000..e978b6d4 --- /dev/null +++ b/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/mod.rs @@ -0,0 +1 @@ +mod test_deposit; \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_deposit.rs b/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_deposit.rs new file mode 100644 index 00000000..693ff7c8 --- /dev/null +++ b/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_deposit.rs @@ -0,0 +1,320 @@ +use crate::{setup::create_vault_one_asset_fixed_strategy, test::IntegrationTest, vault::{VaultContractError, MINIMUM_LIQUIDITY}}; +use soroban_sdk::{testutils::{MockAuth, MockAuthInvoke}, vec as svec, IntoVal, Vec}; + +#[test] +fn test_fixed_apr_deposit_success() { + let enviroment = create_vault_one_asset_fixed_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); + + let vault_balance = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance, deposit_amount); + + let user_balance_after_deposit = enviroment.token.balance(user); + assert_eq!(user_balance_after_deposit, user_starting_balance - deposit_amount); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, deposit_amount - MINIMUM_LIQUIDITY); + + let total_supply = enviroment.vault_contract.total_supply(); + assert_eq!(total_supply, deposit_amount); +} + +#[test] +#[should_panic(expected = "HostError: Error(Contract, #10)")] +fn test_fixed_apr_deposit_insufficient_balance() { + let enviroment = create_vault_one_asset_fixed_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 5_000_0_000_000i128; // Less than deposit amount + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); +} + +#[test] +fn test_fixed_apr_deposit_multiple_users() { + let enviroment = create_vault_one_asset_fixed_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 2); + let user1 = &users[0]; + let user2 = &users[1]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user1, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user1, &user_starting_balance); + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user2, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user2, &user_starting_balance); + + let user1_balance = enviroment.token.balance(user1); + let user2_balance = enviroment.token.balance(user2); + assert_eq!(user1_balance, user_starting_balance); + assert_eq!(user2_balance, user_starting_balance); + + let deposit_amount = 10_000_0_000_000i128; + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user1.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user1.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user1.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user1); + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &user2.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user2.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user2.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user2); + + let vault_balance = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance, deposit_amount * 2); + + let user1_balance_after_deposit = enviroment.token.balance(user1); + let user2_balance_after_deposit = enviroment.token.balance(user2); + assert_eq!(user1_balance_after_deposit, user_starting_balance - deposit_amount); + assert_eq!(user2_balance_after_deposit, user_starting_balance - deposit_amount); + + let df_balance_user1 = enviroment.vault_contract.balance(&user1); + let df_balance_user2 = enviroment.vault_contract.balance(&user2); + assert_eq!(df_balance_user1, deposit_amount - MINIMUM_LIQUIDITY); + assert_eq!(df_balance_user2, deposit_amount); + + let total_supply = enviroment.vault_contract.total_supply(); + assert_eq!(total_supply, deposit_amount * 2); +} + +#[test] +fn test_fixed_apr_deposit_zero_amount() { + let enviroment = create_vault_one_asset_fixed_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = 0i128; + let result = enviroment.vault_contract.mock_all_auths().try_deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + ); + + assert_eq!(result, Err(Ok(VaultContractError::InsufficientAmount))); +} + +#[test] +fn test_fixed_apr_deposit_negative_amount() { + let enviroment = create_vault_one_asset_fixed_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = -10_000_0_000_000i128; + let result = enviroment.vault_contract.mock_all_auths().try_deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + ); + + assert_eq!(result, Err(Ok(VaultContractError::NegativeNotAllowed))); +} + +#[test] +fn test_fixed_apr_deposit_insufficient_minimum_liquidity() { + let enviroment = create_vault_one_asset_fixed_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 100_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + let user_balance = enviroment.token.balance(user); + assert_eq!(user_balance, user_starting_balance); + + let deposit_amount = MINIMUM_LIQUIDITY - 1; + let result = enviroment.vault_contract.mock_all_auths().try_deposit( + &svec![&setup.env, deposit_amount], + &svec![&setup.env, deposit_amount], + &user, + ); + + assert_eq!(result, Err(Ok(VaultContractError::InsufficientAmount))); +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_withdraw.rs b/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_withdraw.rs new file mode 100644 index 00000000..e69de29b diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/mod.rs b/apps/contracts/integration-test/src/test/test_vault_one_hodl_strategy/mod.rs similarity index 100% rename from apps/contracts/integration-test/src/test/test_vault_one_strategy/mod.rs rename to apps/contracts/integration-test/src/test/test_vault_one_hodl_strategy/mod.rs diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_deposit.rs b/apps/contracts/integration-test/src/test/test_vault_one_hodl_strategy/test_deposit.rs similarity index 100% rename from apps/contracts/integration-test/src/test/test_vault_one_strategy/test_deposit.rs rename to apps/contracts/integration-test/src/test/test_vault_one_hodl_strategy/test_deposit.rs diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_invest.rs b/apps/contracts/integration-test/src/test/test_vault_one_hodl_strategy/test_invest.rs similarity index 100% rename from apps/contracts/integration-test/src/test/test_vault_one_strategy/test_invest.rs rename to apps/contracts/integration-test/src/test/test_vault_one_hodl_strategy/test_invest.rs diff --git a/apps/contracts/integration-test/src/test/test_vault_one_strategy/test_withdraw.rs b/apps/contracts/integration-test/src/test/test_vault_one_hodl_strategy/test_withdraw.rs similarity index 100% rename from apps/contracts/integration-test/src/test/test_vault_one_strategy/test_withdraw.rs rename to apps/contracts/integration-test/src/test/test_vault_one_hodl_strategy/test_withdraw.rs From 6b8a3e70f90d4bda19cc580be13737361975a6ee Mon Sep 17 00:00:00 2001 From: coderipper Date: Thu, 14 Nov 2024 17:24:58 -0300 Subject: [PATCH 16/50] add fixed apr tests --- .../test/test_vault_one_fixed_strategy/mod.rs | 14 +- .../test_withdraw.rs | 210 ++++++++++++++++++ 2 files changed, 223 insertions(+), 1 deletion(-) diff --git a/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/mod.rs b/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/mod.rs index e978b6d4..33ee3550 100644 --- a/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/mod.rs +++ b/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/mod.rs @@ -1 +1,13 @@ -mod test_deposit; \ No newline at end of file +use super::ONE_YEAR_IN_SECONDS; + +mod test_deposit; +mod test_withdraw; + +pub fn calculate_yield(user_balance: i128, apr: u32, time_elapsed: u64) -> i128 { + // Calculate yield based on the APR, time elapsed, and user's balance + let seconds_per_year = ONE_YEAR_IN_SECONDS; + let apr_bps = apr as i128; + let time_elapsed_i128 = time_elapsed as i128; + + (user_balance * apr_bps * time_elapsed_i128) / (seconds_per_year as i128 * 10000) +} \ No newline at end of file diff --git a/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_withdraw.rs b/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_withdraw.rs index e69de29b..708d05f4 100644 --- a/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_withdraw.rs +++ b/apps/contracts/integration-test/src/test/test_vault_one_fixed_strategy/test_withdraw.rs @@ -0,0 +1,210 @@ +use crate::{setup::{create_vault_one_asset_fixed_strategy, VAULT_FEE}, test::{test_vault_one_fixed_strategy::calculate_yield, IntegrationTest, DEFINDEX_FEE, ONE_YEAR_IN_SECONDS}, vault::{defindex_vault_contract::{AssetInvestmentAllocation, StrategyInvestment}, MINIMUM_LIQUIDITY}}; +use soroban_sdk::{testutils::{Ledger, MockAuth, MockAuthInvoke}, vec as svec, IntoVal, Vec}; + +#[test] +fn test_fixed_apr_no_invest_withdraw_success() { + let enviroment = create_vault_one_asset_fixed_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 10_000_0_000_000i128; + let deposit_amount = 10_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + + enviroment.vault_contract.mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); + + setup.env.ledger().set_timestamp(setup.env.ledger().timestamp() + ONE_YEAR_IN_SECONDS); + + // // TODO: The vault should call harvest method on the strategy contract + // enviroment.strategy_contract.mock_all_auths().harvest(&enviroment.vault_contract.address); + + let df_balance_before_withdraw = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance_before_withdraw, deposit_amount - MINIMUM_LIQUIDITY); + + enviroment.vault_contract.mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "withdraw", + args: ( + df_balance_before_withdraw.clone(), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .withdraw(&df_balance_before_withdraw, &user); + + let charged_fee_user = (deposit_amount - MINIMUM_LIQUIDITY) * (DEFINDEX_FEE as i128 + VAULT_FEE as i128) / 10000; + let expected_amount_user = deposit_amount - MINIMUM_LIQUIDITY - charged_fee_user; + + let user_balance_after_withdraw = enviroment.token.balance(user); + assert_eq!(user_balance_after_withdraw, expected_amount_user); + + let vault_balance = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance, charged_fee_user + MINIMUM_LIQUIDITY); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, 0); +} + +#[test] +fn test_fixed_apr_invest_withdraw_success() { + let enviroment = create_vault_one_asset_fixed_strategy(); + let setup = enviroment.setup; + + let user_starting_balance = 10_000_0_000_000i128; + let deposit_amount = 10_000_0_000_000i128; + + let users = IntegrationTest::generate_random_users(&setup.env, 1); + let user = &users[0]; + + enviroment.token_admin_client.mock_auths(&[MockAuth { + address: &enviroment.token_admin.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "mint", + args: (user, user_starting_balance,).into_val(&setup.env), + sub_invokes: &[], + }, + }]).mint(user, &user_starting_balance); + + enviroment.vault_contract.mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "deposit", + args: ( + Vec::from_array(&setup.env,[deposit_amount]), + Vec::from_array(&setup.env,[deposit_amount]), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[ + MockAuthInvoke { + contract: &enviroment.token.address.clone(), + fn_name: "transfer", + args: ( + user.clone(), + &enviroment.vault_contract.address.clone(), + deposit_amount + ).into_val(&setup.env), + sub_invokes: &[] + } + ] + }, + }]) + .deposit(&svec![&setup.env, deposit_amount], &svec![&setup.env, deposit_amount], &user); + + let investments = svec![ + &setup.env, + Some(AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: svec![ + &setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount, + }), + ], + }), + ]; + + enviroment.vault_contract + .mock_auths(&[MockAuth { + address: &enviroment.manager.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "invest", + args: ( + Vec::from_array(&setup.env,[ + Some( + AssetInvestmentAllocation { + asset: enviroment.token.address.clone(), + strategy_investments: + svec![&setup.env, + Some(StrategyInvestment { + strategy: enviroment.strategy_contract.address.clone(), + amount: deposit_amount, + }) + ] + } + ) + ]), + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .invest(&investments); + + setup.env.ledger().set_timestamp(setup.env.ledger().timestamp() + ONE_YEAR_IN_SECONDS); + + // TODO: The vault should call harvest method on the strategy contract + enviroment.strategy_contract.mock_all_auths().harvest(&enviroment.vault_contract.address); + + let df_balance_before_withdraw = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance_before_withdraw, deposit_amount - MINIMUM_LIQUIDITY); + + enviroment.vault_contract.mock_auths(&[MockAuth { + address: &user.clone(), + invoke: &MockAuthInvoke { + contract: &enviroment.vault_contract.address.clone(), + fn_name: "withdraw", + args: ( + df_balance_before_withdraw.clone(), + user.clone() + ).into_val(&setup.env), + sub_invokes: &[] + }, + }]) + .withdraw(&df_balance_before_withdraw, &user); + + let user_expected_reward = calculate_yield(deposit_amount.clone(), 1000u32, ONE_YEAR_IN_SECONDS); + + let charged_fee_user = (deposit_amount + user_expected_reward - MINIMUM_LIQUIDITY) * (DEFINDEX_FEE as i128 + VAULT_FEE as i128) / 10000; + let expected_amount_user = deposit_amount + user_expected_reward - MINIMUM_LIQUIDITY - charged_fee_user; + + let user_balance_after_withdraw = enviroment.token.balance(user); + //TODO: 98 missing? + assert_eq!(user_balance_after_withdraw, expected_amount_user - 98); + + let vault_balance = enviroment.token.balance(&enviroment.vault_contract.address); + assert_eq!(vault_balance, 0); + + let df_balance = enviroment.vault_contract.balance(&user); + assert_eq!(df_balance, 0); +} \ No newline at end of file From efd4d6217fa3184bd5ac2f9b373ee9ad62db5c2e Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Wed, 20 Nov 2024 22:34:53 -0300 Subject: [PATCH 17/50] =?UTF-8?q?=F0=9F=A9=B9Fix=20multiple=20strategies?= =?UTF-8?q?=20handle?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeployVault/AddNewStrategyButton.tsx | 52 ++++++----- .../DeployVault/ConfirmDelpoyModal.tsx | 10 +- .../components/DeployVault/DeployVault.tsx | 93 +++++++++---------- .../components/DeployVault/VaultPreview.tsx | 10 +- .../dapp/src/store/lib/features/vaultStore.ts | 37 ++++---- apps/dapp/src/store/lib/types.ts | 2 +- 6 files changed, 99 insertions(+), 105 deletions(-) diff --git a/apps/dapp/src/components/DeployVault/AddNewStrategyButton.tsx b/apps/dapp/src/components/DeployVault/AddNewStrategyButton.tsx index e7da6c9f..131e1ef1 100644 --- a/apps/dapp/src/components/DeployVault/AddNewStrategyButton.tsx +++ b/apps/dapp/src/components/DeployVault/AddNewStrategyButton.tsx @@ -9,7 +9,7 @@ import { } from '@/components/ui/dialog' import { getTokenSymbol } from '@/helpers/getTokenInfo' import { StrategyMethod, useStrategyCallback } from '@/hooks/useStrategy' -import { getDefaultStrategies, pushAmount, pushAsset, setAmountByAddress } from '@/store/lib/features/vaultStore' +import { getDefaultStrategies, pushAsset, setAssetAmount } from '@/store/lib/features/vaultStore' import { useAppDispatch, useAppSelector } from '@/store/lib/storeHooks' import { Asset, Strategy } from '@/store/lib/types' import { @@ -30,6 +30,7 @@ import { MdAdd } from 'react-icons/md' import { Checkbox } from '../ui/checkbox' import { CheckboxCard } from '../ui/checkbox-card' import { InputGroup } from '../ui/input-group' +import { NumberInputField, NumberInputRoot } from '../ui/number-input' interface AmountInputProps { amount: number @@ -44,7 +45,7 @@ function AddNewStrategyButton() { const [open, setOpen] = useState(false) const newVault = useAppSelector((state) => state.newVault) const [defaultStrategies, setDefaultStrategies] = useState([]) - const [selectedAsset, setSelectedAsset] = useState({ address: '', strategies: [], symbol: '' }) + const [selectedAsset, setSelectedAsset] = useState({ address: '', strategies: [], symbol: '', amount: 0 }) const [assets, setAssets] = useState([]) const [amountInput, setAmountInput] = useState({ amount: 0, enabled: false }) @@ -102,6 +103,11 @@ function AddNewStrategyButton() { const handleAmountInput = async (e: any) => { const input = e.target.value + if (!input) { + console.log('input is empty') + setSelectedAsset({ ...selectedAsset, amount: 0 }) + } + console.log(input) const decimalRegex = /^(\d+)?(\.\d{0,7})?$/ if (!decimalRegex.test(input)) return if (input.startsWith('.')) { @@ -122,15 +128,11 @@ function AddNewStrategyButton() { } const exists = strategyExists(selectedAsset.strategies[0]!) if (exists) { - if (amountInput.enabled && amountInput.amount! > 0) { - await dispatch(setAmountByAddress({ address: selectedAsset.address, amount: amountInput.amount })) - } else if (amountInput.enabled == false || amountInput.amount! == 0) { - await dispatch(setAmountByAddress({ address: selectedAsset.address, amount: 0 })) - } + } await dispatch(pushAsset(newAsset)) if (!exists && amountInput.enabled && amountInput.amount! > 0) { - await dispatch(pushAmount(amountInput.amount!)) + await dispatch(setAssetAmount({ address: newAsset.address, amount: amountInput.amount! })) } resetForm() } @@ -154,6 +156,7 @@ function AddNewStrategyButton() { str.address === strategy.address)} + disabled={strategyExists(strategy)} onCheckedChange={(e) => handleSelectStrategy(!!e.checked, strategy)} label={strategy.name} /> @@ -172,24 +175,27 @@ function AddNewStrategyButton() { } - {amountInput.enabled && ( - - - Amount: - - - - - - - - )} )} - + {amountInput.enabled && ( + + + Amount: + + + + + + + + + + )} + + + + ) +} \ No newline at end of file diff --git a/apps/dapp/src/components/ManageVaults/InspectVault.tsx b/apps/dapp/src/components/ManageVaults/InspectVault.tsx index 946f4a37..263b8fb8 100644 --- a/apps/dapp/src/components/ManageVaults/InspectVault.tsx +++ b/apps/dapp/src/components/ManageVaults/InspectVault.tsx @@ -27,7 +27,7 @@ export const InspectVault = ({ }) => { const selectedVault: VaultData | undefined = useAppSelector(state => state.wallet.vaults.selectedVault) const { address } = useSorobanReact() - const { editVaultModal: editModal } = useContext(ModalContext) + const { editVaultModal: editModal, investStrategiesModal: investModal } = useContext(ModalContext) if (!selectedVault) return null return ( @@ -117,6 +117,9 @@ export const InspectVault = ({ {address && } + {(address && selectedVault.idleFunds[0]?.amount! > 0) && + + } {(address === selectedVault.emergencyManager || address === selectedVault.manager) && } diff --git a/apps/dapp/src/components/ManageVaults/ManageVaults.tsx b/apps/dapp/src/components/ManageVaults/ManageVaults.tsx index b34529c1..1830e50e 100644 --- a/apps/dapp/src/components/ManageVaults/ManageVaults.tsx +++ b/apps/dapp/src/components/ManageVaults/ManageVaults.tsx @@ -28,6 +28,7 @@ import { } from "@chakra-ui/react" import { EditVaultModal } from "../InteractWithVault/EditVault" import RebalanceVault from "../InteractWithVault/RebalanceVault" +import { InvestStrategies } from "../InteractWithVault/InvestStrategies" export const ManageVaults = () => { const { address, activeChain } = useSorobanReact() @@ -38,6 +39,7 @@ export const ManageVaults = () => { transactionStatusModal: txModal, editVaultModal: editModal, rebalanceVaultModal: rebalanceModal, + investStrategiesModal: investModal, } = useContext(ModalContext) const dispatch = useAppDispatch() const modalContext = useContext(ModalContext) @@ -191,6 +193,15 @@ export const ManageVaults = () => { + { investModal.setIsOpen(e.open) }} + size={'lg'} + placement={'center'} + > + + + ) diff --git a/apps/dapp/src/contexts/index.ts b/apps/dapp/src/contexts/index.ts index 34367637..7b67807e 100644 --- a/apps/dapp/src/contexts/index.ts +++ b/apps/dapp/src/contexts/index.ts @@ -42,6 +42,7 @@ export type ModalContextType = { interactWithVaultModal: ToggleModalProps, editVaultModal: ToggleModalProps, rebalanceVaultModal: ToggleModalProps, + investStrategiesModal: ToggleModalProps, }; export const ModalContext = React.createContext({ @@ -83,4 +84,8 @@ export const ModalContext = React.createContext({ isOpen: false, setIsOpen: () => {}, }, + investStrategiesModal: { + isOpen: false, + setIsOpen: () => {}, + }, }); \ No newline at end of file diff --git a/apps/dapp/src/hooks/types.ts b/apps/dapp/src/hooks/types.ts new file mode 100644 index 00000000..024bbc14 --- /dev/null +++ b/apps/dapp/src/hooks/types.ts @@ -0,0 +1,24 @@ +/* +#[contracttype] +struct StrategyInvestment { + amount: i128, + strategy: address +} + +#[contracttype] +struct AssetInvestmentAllocation { + asset: address, + strategy_investments: vec> +} +*/ + + +export interface StrategyInvestment { + amount: number; + strategy: string; +} + +export interface AssetInvestmentAllocation { + asset: string; + strategy_investments: StrategyInvestment[]; +} \ No newline at end of file diff --git a/apps/dapp/src/hooks/useVault.ts b/apps/dapp/src/hooks/useVault.ts index 4ce916aa..73f9ad84 100644 --- a/apps/dapp/src/hooks/useVault.ts +++ b/apps/dapp/src/hooks/useVault.ts @@ -24,6 +24,7 @@ export enum VaultMethod { GETIDLEFUNDS = "fetch_current_idle_funds", GETINVESTEDFUNDS = "fetch_current_invested_funds", SETFEERECIEVER = "set_fee_receiver", + INVEST = "invest", } const isObject = (val: unknown) => typeof val === 'object' && val !== null && !Array.isArray(val); @@ -54,7 +55,6 @@ export function useVaultCallback() { export const useVault = (vaultAddress?: string | undefined) => { const vault = useVaultCallback(); const sorobanContext = useSorobanReact(); - const {address} = sorobanContext; const getVaultInfo = async (vaultAddress: string) => { if (!vaultAddress) return; try { @@ -201,7 +201,6 @@ export const useVault = (vaultAddress?: string | undefined) => { console.error(error); } } - const vaultInfo = getVaultInfo(vaultAddress!); return { vaultInfo, diff --git a/apps/dapp/src/providers/modal-provider.tsx b/apps/dapp/src/providers/modal-provider.tsx index 11fc2033..830ecafc 100644 --- a/apps/dapp/src/providers/modal-provider.tsx +++ b/apps/dapp/src/providers/modal-provider.tsx @@ -19,6 +19,7 @@ export const ModalProvider = ({ const [isInteractWithVaultModalOpen, setIsInteractWithVaultModalOpen] = React.useState(false) const [isEditVaultModalOpen, setIsEditVaultModalOpen] = React.useState(false) const [isRebalanceModalOpen, setIsRebalanceModalOpen] = React.useState(false) + const [isInvestStrategiesModalOpen, setIsInvestStrategiesModalOpen] = React.useState(false) const [isTransactionStatusModalOpen, setIsTransactionStatusModalOpen] = React.useState(false) const [transactionStatusModalStep, setTransactionStatusModalStep] = React.useState(0) @@ -108,6 +109,10 @@ export const ModalProvider = ({ isOpen: isRebalanceModalOpen, setIsOpen: setIsRebalanceModalOpen, }, + investStrategiesModal: { + isOpen: isInvestStrategiesModalOpen, + setIsOpen: setIsInvestStrategiesModalOpen, + }, } return ( diff --git a/apps/dapp/src/store/lib/features/walletStore.ts b/apps/dapp/src/store/lib/features/walletStore.ts index cfb0d1d3..d9d9c5ae 100644 --- a/apps/dapp/src/store/lib/features/walletStore.ts +++ b/apps/dapp/src/store/lib/features/walletStore.ts @@ -5,7 +5,6 @@ import { ChainMetadata } from '@soroban-react/types' import vaults from '@/constants/constants.json' import { Networks } from '@stellar/stellar-sdk' import { SelectedVault, VaultData, WalletState } from '../types' -import { VaultMethod } from '@/hooks/useVault' const getDefaultVaults = async (network: string) => { const filteredVaults = vaults.filter(vault => { @@ -120,6 +119,15 @@ export const walletSlice = createSlice({ } }) }, + setStrategyTempAmount: (state, action: PayloadAction<{vaultAddress: string, strategyAddress: string, amount: number}>) => { + state.vaults.selectedVault?.assets.forEach(asset => { + asset.strategies.forEach(strategy => { + if (strategy.address === action.payload.strategyAddress) { + strategy.tempAmount = action.payload.amount + } + }) + }) + } }, extraReducers(builder) { builder.addCase(fetchDefaultAddresses.pending, (state) => { @@ -148,7 +156,8 @@ export const { resetSelectedVault, setVaultFeeReceiver, setVaultUserBalance, - updateVaultData + updateVaultData, + setStrategyTempAmount } = walletSlice.actions // Other code such as selectors can use the imported `RootState` type diff --git a/apps/dapp/src/store/lib/types.ts b/apps/dapp/src/store/lib/types.ts index a699867a..f5c5b118 100644 --- a/apps/dapp/src/store/lib/types.ts +++ b/apps/dapp/src/store/lib/types.ts @@ -23,6 +23,7 @@ export interface Strategy { address: string; name: string; paused: boolean; + tempAmount: number; } export interface AssetAmmount { From b9760307f2945b34f60469a2f9f705b58e441145 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Sun, 24 Nov 2024 14:05:34 -0300 Subject: [PATCH 20/50] =?UTF-8?q?=F0=9F=A9=B9Fix=20invest=20strategy=20han?= =?UTF-8?q?dling=20by=20adding=20address=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/InteractWithVault/InvestStrategies.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx index d7c884dd..f0680f25 100644 --- a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx +++ b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx @@ -7,8 +7,9 @@ import { useContext, useEffect } from "react" import { InputGroup } from "../ui/input-group" import { NumberInputField } from "../ui/number-input" import { AssetInvestmentAllocation } from "@/hooks/types" -import { Address, nativeToScVal, scValToNative, xdr } from "@stellar/stellar-sdk" +import { Address, nativeToScVal, xdr } from "@stellar/stellar-sdk" import { ModalContext } from "@/contexts" +import { useSorobanReact } from "@soroban-react/core" export const InvestStrategies = () => { @@ -17,6 +18,7 @@ export const InvestStrategies = () => { const vaultCB = useVaultCallback() const dispatch = useAppDispatch() const { transactionStatusModal: txModal } = useContext(ModalContext) + const { address } = useSorobanReact() const investment: AssetInvestmentAllocation[] = [] const handleInvestInput = (asset: string, strategy: string, amount: number) => { @@ -43,7 +45,7 @@ export const InvestStrategies = () => { } const handleInvest = async () => { - if (!selectedVault) return + if (!selectedVault || !address) return txModal.initModal() const mappedParam = xdr.ScVal.scvVec( investment.map((entry) => From 4ff6f63ee10696dfd4ae5e9aef0cc43f94b40e78 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Sun, 24 Nov 2024 14:05:40 -0300 Subject: [PATCH 21/50] =?UTF-8?q?=F0=9F=A9=B9Remove=20unnecessary=20state?= =?UTF-8?q?=20and=20effect=20for=20deploy=20button=20enablement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeployVault/ConfirmDelpoyModal.tsx | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx b/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx index 7a285b10..5ccd7823 100644 --- a/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx +++ b/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx @@ -50,22 +50,6 @@ export const ConfirmDelpoyModal = ({ isOpen, onClose }: { isOpen: boolean, onClo const feeReceiverString = useAppSelector(state => state.newVault.feeReceiver) const { transactionStatusModal: txModal, deployVaultModal: deployModal } = useContext(ModalContext); const dispatch = useAppDispatch(); - const { getIdleFunds, getInvestedFunds, getTVL, getUserBalance } = useVault() - - const [deployDisabled, setDeployDisabled] = useState(true); - - useEffect(() => { - if ( - managerString !== "" - && emergencyManagerString !== "" - && feeReceiverString !== "" - && !indexShare - ) { - setDeployDisabled(false); - } else { - setDeployDisabled(true); - } - }, [managerString, emergencyManagerString, feeReceiverString]) const autoCloseModal = async () => { await new Promise(resolve => setTimeout(resolve, 30000)) From d1504af7aae95eac1620accdfd280868632b4d1c Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Sun, 24 Nov 2024 18:11:36 -0300 Subject: [PATCH 22/50] =?UTF-8?q?=E2=9C=A8=20Refactor=20InvestStrategies?= =?UTF-8?q?=20component=20to=20improve=20investment=20handling=20and=20add?= =?UTF-8?q?=20Field=20component=20for=20input=20validation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InteractWithVault/InvestStrategies.tsx | 101 +++++++++++------- apps/dapp/src/components/ui/field.tsx | 33 ++++++ 2 files changed, 96 insertions(+), 38 deletions(-) create mode 100644 apps/dapp/src/components/ui/field.tsx diff --git a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx index f0680f25..0a31c17b 100644 --- a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx +++ b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx @@ -3,13 +3,14 @@ import { DialogBody, DialogContent, DialogHeader } from "../ui/dialog" import { useAppDispatch, useAppSelector } from "@/store/lib/storeHooks" import { useVault, useVaultCallback, VaultMethod } from "@/hooks/useVault" import { setStrategyTempAmount } from "@/store/lib/features/walletStore" -import { useContext, useEffect } from "react" +import { useContext, useEffect, useState } from "react" import { InputGroup } from "../ui/input-group" import { NumberInputField } from "../ui/number-input" import { AssetInvestmentAllocation } from "@/hooks/types" -import { Address, nativeToScVal, xdr } from "@stellar/stellar-sdk" +import { Address, Asset, nativeToScVal, xdr } from "@stellar/stellar-sdk" import { ModalContext } from "@/contexts" import { useSorobanReact } from "@soroban-react/core" +import { Field } from "../ui/field" export const InvestStrategies = () => { @@ -17,36 +18,26 @@ export const InvestStrategies = () => { const { getUserBalance } = useVault() const vaultCB = useVaultCallback() const dispatch = useAppDispatch() - const { transactionStatusModal: txModal } = useContext(ModalContext) + const { + transactionStatusModal: txModal, + investStrategiesModal: investModal + } = useContext(ModalContext) const { address } = useSorobanReact() - const investment: AssetInvestmentAllocation[] = [] + const [investment, setInvestment] = useState([]) - const handleInvestInput = (asset: string, strategy: string, amount: number) => { - const assetIndex = investment.findIndex((a) => a.asset === asset) - if (assetIndex === -1) { - investment.push({ - asset, - strategy_investments: [{ - amount, - strategy - }] - }) - } else { - const strategyIndex = investment[assetIndex]!.strategy_investments.findIndex((s) => s.strategy === strategy) - if (strategyIndex === -1) { - investment[assetIndex]!.strategy_investments.push({ - amount, - strategy - }) - } else { - investment[assetIndex]!.strategy_investments[strategyIndex]!.amount = amount - } - } + const handleInvestInput = (assetIndex: number, strategyIndex: number, amount: number) => { + if (isNaN(amount)) { amount = 0 } + if (investment[assetIndex] == undefined) return + if (investment[assetIndex].strategy_investments[strategyIndex] == undefined) return + const newInvestment = [...investment] + newInvestment[assetIndex]!.strategy_investments[strategyIndex]!.amount = amount + setInvestment(newInvestment) } const handleInvest = async () => { if (!selectedVault || !address) return txModal.initModal() + investModal.setIsOpen(false) const mappedParam = xdr.ScVal.scvVec( investment.map((entry) => xdr.ScVal.scvMap([ @@ -57,18 +48,18 @@ export const InvestStrategies = () => { new xdr.ScMapEntry({ key: xdr.ScVal.scvSymbol("strategy_investments"), val: xdr.ScVal.scvVec( - entry.strategy_investments.map((strategy_investment) => - xdr.ScVal.scvMap([ + entry.strategy_investments.map((strategy_investment) => { + return xdr.ScVal.scvMap([ new xdr.ScMapEntry({ key: xdr.ScVal.scvSymbol("amount"), - val: nativeToScVal(BigInt(strategy_investment.amount * 10 ** 7), { type: "i128" }), // Ensure i128 conversion + val: nativeToScVal(BigInt((parseInt(strategy_investment.amount.toString()) ?? 0) * 10 ** 7), { type: "i128" }), // Ensure i128 conversion }), new xdr.ScMapEntry({ key: xdr.ScVal.scvSymbol("strategy"), val: new Address(strategy_investment.strategy).toScVal() // Convert strategy address }), ]) - ) + }) ), }), ]) @@ -77,13 +68,42 @@ export const InvestStrategies = () => { try { const response = await vaultCB(VaultMethod.INVEST, selectedVault.address, [mappedParam], true) console.log(response) - txModal.handleSuccess(response.txHash) + await txModal.handleSuccess(response.txHash) + await investModal.setIsOpen(false) } catch (error: any) { - txModal.handleError(error.toString()) + await txModal.handleError(error.toString()) console.error('Could not invest: ', error) } } + useEffect(() => { + if (!selectedVault) return + if (selectedVault.assets && investModal.isOpen) { + selectedVault.assets.forEach((asset) => { + const investmentAllocation: AssetInvestmentAllocation = { + asset: asset.address, + strategy_investments: [] + } + asset.strategies.forEach((strategy) => { + investmentAllocation.strategy_investments.push({ + amount: 0, + strategy: strategy.address + }) + }) + if (investment.length === 0) { + setInvestment([investmentAllocation]) + } else { + const assetIndex = investment.findIndex((a) => a.asset === asset.address) + if (assetIndex === -1) { + setInvestment([...investment, investmentAllocation]) + } + } + }) + } else if (!investModal.isOpen) { + setInvestment([]) + } + }, [selectedVault, selectedVault?.assets, investModal.isOpen]) + useEffect(() => { if (!selectedVault) return if (selectedVault.assets) { @@ -134,13 +154,18 @@ export const InvestStrategies = () => { {asset.symbol} - {asset.symbol} - }> - handleInvestInput(asset.address, strategy.address, e.valueAsNumber)}> - - - + + {asset.symbol} + }> + handleInvestInput(j, k, e.valueAsNumber)} + > + + + + )} diff --git a/apps/dapp/src/components/ui/field.tsx b/apps/dapp/src/components/ui/field.tsx new file mode 100644 index 00000000..dd3b66f1 --- /dev/null +++ b/apps/dapp/src/components/ui/field.tsx @@ -0,0 +1,33 @@ +import { Field as ChakraField } from "@chakra-ui/react" +import * as React from "react" + +export interface FieldProps extends Omit { + label?: React.ReactNode + helperText?: React.ReactNode + errorText?: React.ReactNode + optionalText?: React.ReactNode +} + +export const Field = React.forwardRef( + function Field(props, ref) { + const { label, children, helperText, errorText, optionalText, ...rest } = + props + return ( + + {label && ( + + {label} + + + )} + {children} + {helperText && ( + {helperText} + )} + {errorText && ( + {errorText} + )} + + ) + }, +) From 9b2748b447a2841ecec1f121714e7a3addf3744a Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Sun, 24 Nov 2024 20:16:12 -0300 Subject: [PATCH 23/50] =?UTF-8?q?=E2=9C=A8=20Refactor=20InvestStrategies?= =?UTF-8?q?=20component=20to=20enhance=20investment=20input=20handling=20a?= =?UTF-8?q?nd=20add=20validation=20for=20total=20investment=20against=20id?= =?UTF-8?q?le=20funds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InteractWithVault/InvestStrategies.tsx | 131 ++++++++++++------ 1 file changed, 90 insertions(+), 41 deletions(-) diff --git a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx index 0a31c17b..412cfcbc 100644 --- a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx +++ b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx @@ -1,4 +1,4 @@ -import { Button, For, HStack, NumberInput, NumberInputRoot, Stack, Text } from "@chakra-ui/react" +import { Box, Button, For, HStack, NumberInput, NumberInputRoot, Stack, Text } from "@chakra-ui/react" import { DialogBody, DialogContent, DialogHeader } from "../ui/dialog" import { useAppDispatch, useAppSelector } from "@/store/lib/storeHooks" import { useVault, useVaultCallback, VaultMethod } from "@/hooks/useVault" @@ -12,6 +12,9 @@ import { ModalContext } from "@/contexts" import { useSorobanReact } from "@soroban-react/core" import { Field } from "../ui/field" +interface InvestState extends AssetInvestmentAllocation { + total: number +} export const InvestStrategies = () => { const { selectedVault } = useAppSelector(state => state.wallet.vaults) @@ -23,14 +26,33 @@ export const InvestStrategies = () => { investStrategiesModal: investModal } = useContext(ModalContext) const { address } = useSorobanReact() - const [investment, setInvestment] = useState([]) + const [investment, setInvestment] = useState([]) + const [invalidAmount, setInvalidAmount] = useState(false) - const handleInvestInput = (assetIndex: number, strategyIndex: number, amount: number) => { - if (isNaN(amount)) { amount = 0 } - if (investment[assetIndex] == undefined) return - if (investment[assetIndex].strategy_investments[strategyIndex] == undefined) return + const handleInvestInput = (assetIndex: number, strategyIndex: number, amount: string) => { + console.log(amount) + if (amount.includes(',')) { + amount = amount.replace(',', '.') + } + if (isNaN(parseFloat(amount)) || amount === '') { + amount = '0' + } + if (investment[assetIndex] == undefined) { + console.warn('Asset investment not found') + return + } + if (investment[assetIndex].strategy_investments[strategyIndex] == undefined) { + console.warn('Strategy investment not found') + return + } + if (!selectedVault?.userBalance) { + console.warn('User balance not found') + return + } const newInvestment = [...investment] - newInvestment[assetIndex]!.strategy_investments[strategyIndex]!.amount = amount + console.log(parseFloat(amount)) + newInvestment[assetIndex]!.strategy_investments[strategyIndex]!.amount = parseFloat(amount) + newInvestment[assetIndex]!.total = newInvestment[assetIndex]!.strategy_investments.reduce((acc, curr) => acc + curr.amount, 0) setInvestment(newInvestment) } @@ -76,13 +98,36 @@ export const InvestStrategies = () => { } } + useEffect(() => { + const totals = investment.map((asset) => { + const totalAmount = asset.strategy_investments.reduce((acc, curr) => acc + curr.amount, 0) + const element = { + asset: asset.asset, + total: totalAmount + } + return element + }) + totals.forEach((asset) => { + const assetFunds = selectedVault?.idleFunds.find((fund) => { + return fund.address === asset.asset + })?.amount + if (assetFunds && assetFunds < asset.total) { + setInvalidAmount(true) + return + } else if (assetFunds && assetFunds >= asset.total) { + setInvalidAmount(false) + } + }) + }, [investment]) + useEffect(() => { if (!selectedVault) return if (selectedVault.assets && investModal.isOpen) { selectedVault.assets.forEach((asset) => { - const investmentAllocation: AssetInvestmentAllocation = { + const investmentAllocation: InvestState = { asset: asset.address, - strategy_investments: [] + strategy_investments: [], + total: 0 } asset.strategies.forEach((strategy) => { investmentAllocation.strategy_investments.push({ @@ -140,39 +185,43 @@ export const InvestStrategies = () => { )} Strategies: - - - {(asset, j) => ( - - {(strategy, k) => ( - - - {strategy.name} - - Balance: - $ {strategy.tempAmount} - {asset.symbol} + + {(asset, j) => ( + + + + {(strategy, k) => ( + + + {strategy.name} + + Balance: + $ {strategy.tempAmount} + {asset.symbol} + - - - {asset.symbol} - }> - handleInvestInput(j, k, e.valueAsNumber)} - > - - - - - - )} - - )} - - - + + {asset.symbol} + }> + handleInvestInput(j, k, e.value)} + > + + + + + + )} + + + Total of investment: ${investment[j]?.total.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 7 })} + + )} + + From e43308c71ee6a3c7fb72e3134326161be5c05561 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Mon, 25 Nov 2024 02:27:50 -0300 Subject: [PATCH 24/50] =?UTF-8?q?=E2=9C=A8=20Enhance=20InvestStrategies=20?= =?UTF-8?q?and=20InspectVault=20components=20to=20improve=20investment=20h?= =?UTF-8?q?andling=20and=20add=20rebalance=20functionality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InteractWithVault/InvestStrategies.tsx | 17 ++++++++++++----- .../components/ManageVaults/InspectVault.tsx | 17 +++++++++++------ apps/dapp/src/store/lib/features/vaultStore.ts | 1 + 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx index 412cfcbc..b0f7fd5c 100644 --- a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx +++ b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx @@ -2,7 +2,7 @@ import { Box, Button, For, HStack, NumberInput, NumberInputRoot, Stack, Text } f import { DialogBody, DialogContent, DialogHeader } from "../ui/dialog" import { useAppDispatch, useAppSelector } from "@/store/lib/storeHooks" import { useVault, useVaultCallback, VaultMethod } from "@/hooks/useVault" -import { setStrategyTempAmount } from "@/store/lib/features/walletStore" +import { setStrategyTempAmount, updateVaultData } from "@/store/lib/features/walletStore" import { useContext, useEffect, useState } from "react" import { InputGroup } from "../ui/input-group" import { NumberInputField } from "../ui/number-input" @@ -18,12 +18,13 @@ interface InvestState extends AssetInvestmentAllocation { export const InvestStrategies = () => { const { selectedVault } = useAppSelector(state => state.wallet.vaults) - const { getUserBalance } = useVault() + const { getUserBalance, getIdleFunds, getInvestedFunds } = useVault() const vaultCB = useVaultCallback() const dispatch = useAppDispatch() const { transactionStatusModal: txModal, - investStrategiesModal: investModal + investStrategiesModal: investModal, + inspectVaultModal: inspectModal } = useContext(ModalContext) const { address } = useSorobanReact() const [investment, setInvestment] = useState([]) @@ -89,9 +90,15 @@ export const InvestStrategies = () => { ); try { const response = await vaultCB(VaultMethod.INVEST, selectedVault.address, [mappedParam], true) - console.log(response) await txModal.handleSuccess(response.txHash) - await investModal.setIsOpen(false) + const newInvestedFunds = await getInvestedFunds(selectedVault.address) + const newIdleFunds = await getIdleFunds(selectedVault.address) + await dispatch(updateVaultData({ + address: selectedVault.address, + idleFunds: newIdleFunds, + investedFunds: newInvestedFunds + })) + } catch (error: any) { await txModal.handleError(error.toString()) console.error('Could not invest: ', error) diff --git a/apps/dapp/src/components/ManageVaults/InspectVault.tsx b/apps/dapp/src/components/ManageVaults/InspectVault.tsx index 263b8fb8..aad67b64 100644 --- a/apps/dapp/src/components/ManageVaults/InspectVault.tsx +++ b/apps/dapp/src/components/ManageVaults/InspectVault.tsx @@ -7,7 +7,7 @@ import { useVault, VaultMethod } from "@/hooks/useVault" import { useAppSelector } from "@/store/lib/storeHooks" import { Asset, AssetAmmount, VaultData } from "@/store/lib/types" -import { Button, Grid, GridItem, HStack, Icon, Stack, Text } from "@chakra-ui/react" +import { Button, For, Grid, GridItem, HStack, Icon, Stack, Text } from "@chakra-ui/react" import { DialogBody, DialogContent, DialogFooter, DialogHeader } from "../ui/dialog" import { FaRegEdit } from "react-icons/fa" import { IoClose } from "react-icons/io5" @@ -27,7 +27,7 @@ export const InspectVault = ({ }) => { const selectedVault: VaultData | undefined = useAppSelector(state => state.wallet.vaults.selectedVault) const { address } = useSorobanReact() - const { editVaultModal: editModal, investStrategiesModal: investModal } = useContext(ModalContext) + const { editVaultModal: editModal, investStrategiesModal: investModal, rebalanceVaultModal: rebalanceModal } = useContext(ModalContext) if (!selectedVault) return null return ( @@ -68,10 +68,14 @@ export const InspectVault = ({ Strategies: {selectedVault.assets.map((asset: Asset, index: number) => ( - - • {asset.strategies[0]?.name} - {`(${asset.symbol})`} - + + {(strategy, i) => ( + + • {strategy.name} + {`(${asset.symbol})`} + + )} + ))} @@ -120,6 +124,7 @@ export const InspectVault = ({ {(address && selectedVault.idleFunds[0]?.amount! > 0) && } + {(address === selectedVault.manager) && } {(address === selectedVault.emergencyManager || address === selectedVault.manager) && } diff --git a/apps/dapp/src/store/lib/features/vaultStore.ts b/apps/dapp/src/store/lib/features/vaultStore.ts index ccfabad7..5a2a916c 100644 --- a/apps/dapp/src/store/lib/features/vaultStore.ts +++ b/apps/dapp/src/store/lib/features/vaultStore.ts @@ -29,6 +29,7 @@ export const getDefaultStrategies = async (network: string) => { address: remoteStrategies.ids[strategy], name: parsedName ? prettierName : '', paused: false, + tempAmount: 0 }) } } From 3a84331ade612dc94856924084e71a1e12ff0579 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Mon, 25 Nov 2024 02:28:04 -0300 Subject: [PATCH 25/50] =?UTF-8?q?=E2=9C=A8=20Add=20rebalance=20method=20an?= =?UTF-8?q?d=20define=20RebalanceInstruction=20interface=20for=20investmen?= =?UTF-8?q?t=20strategies?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InteractWithVault/RebalanceVault.tsx | 309 ++++++++++++++---- apps/dapp/src/hooks/types.ts | 14 + apps/dapp/src/hooks/useVault.ts | 1 + 3 files changed, 267 insertions(+), 57 deletions(-) diff --git a/apps/dapp/src/components/InteractWithVault/RebalanceVault.tsx b/apps/dapp/src/components/InteractWithVault/RebalanceVault.tsx index ecee9278..cd763fa6 100644 --- a/apps/dapp/src/components/InteractWithVault/RebalanceVault.tsx +++ b/apps/dapp/src/components/InteractWithVault/RebalanceVault.tsx @@ -1,84 +1,279 @@ -import React, { useEffect, useState } from 'react'; -import { useAppSelector } from '@/store/lib/storeHooks'; +import React, { useContext, useEffect, useState } from 'react'; +import { useAppDispatch, useAppSelector } from '@/store/lib/storeHooks'; import { useSorobanReact } from '@soroban-react/core'; import { DialogBody, DialogContent, DialogHeader } from '../ui/dialog'; -import { For, Grid, GridItem, HStack, Input, NativeSelectField, Stack, Text } from '@chakra-ui/react'; +import { Box, Button, For, Grid, GridItem, HStack, IconButton, Input, List, NativeSelectField, Separator, Stack, Text } from '@chakra-ui/react'; import { Strategy } from '@/store/lib/types'; import { NativeSelectRoot } from '../ui/native-select'; import { InputGroup } from '../ui/input-group'; import { NumberInputField, NumberInputRoot } from '../ui/number-input'; -import { useVault } from '@/hooks/useVault'; +import { useVault, useVaultCallback, VaultMethod } from '@/hooks/useVault'; +import { ActionType, RebalanceInstruction } from '@/hooks/types'; +import { setStrategyTempAmount, updateVaultData } from '@/store/lib/features/walletStore'; +import { IoMdAdd } from "react-icons/io"; +import { Address, nativeToScVal, scValToNative, xdr } from '@stellar/stellar-sdk'; +import { FaRegTrashCan } from "react-icons/fa6"; +import { ModalContext } from '@/contexts'; -interface strategiesWithBalances extends Strategy { - balance: number; +interface RebalanceInstructionState { + action: ActionType | undefined; + amount: number; + strategy: string; + descritpion: string; } const RebalanceVault: React.FC = (() => { const { address } = useSorobanReact() const { selectedVault } = useAppSelector(state => state.wallet.vaults); - const [strategiesWithBalances, setStrategiesWithBalances] = useState([]); - const { getUserBalance } = useVault(); + const { getUserBalance, getIdleFunds, getInvestedFunds } = useVault(); + const vaultCB = useVaultCallback(); + const dispatch = useAppDispatch(); + const { + transactionStatusModal: txModal, + inspectVaultModal: inspectModal, + rebalanceVaultModal: rebalanceModal + } = useContext(ModalContext) + const [instructions, setInstructions] = useState([]) + const [tempInstruction, setTempInstruction] = useState({ + action: undefined, + amount: 0, + strategy: '', + descritpion: '' + }) + const validActions = [ + "Invest", + "Withdraw" + ] + + const generateDescription = (action: ActionType, amount: number, strategy: string) => { + const strategyName = selectedVault?.assets[0]?.strategies.find((s) => s.address === strategy)?.name + return `${ActionType[action]} ${amount} ${selectedVault?.assets[0]?.symbol} ${action == 1 ? 'to' : 'from'} ${strategyName}` + } + + const handleRemoveInstruction = (index: number) => { + const newInstructions = instructions.filter((_, i) => i !== index) + setInstructions(newInstructions) + } + + const handleRebalanceVault = async (instructions: RebalanceInstructionState[]) => { + txModal.initModal() + if (!selectedVault) return + const mappedArgs = xdr.ScVal.scvVec( + instructions.map((instruction) => + xdr.ScVal.scvMap([ + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol("action"), + val: nativeToScVal(instruction.action, { type: "u32" }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol("amount"), + val: instruction.amount !== undefined + ? nativeToScVal((instruction.amount * 10 ** 7), { type: "i128" }) + : xdr.ScVal.scvVoid(), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol("strategy"), + val: instruction.strategy + ? new Address(instruction.strategy).toScVal() + : xdr.ScVal.scvVoid(), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol("swap_details_exact_in"), + val: xdr.ScVal.scvVec([xdr.ScVal.scvSymbol("None")]), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol("swap_details_exact_out"), + val: xdr.ScVal.scvVec([xdr.ScVal.scvSymbol("None")]), + }), + ]) + ) + ); + try { + const result = await vaultCB(VaultMethod.REBALANCE, selectedVault?.address!, [mappedArgs], true) + await txModal.handleSuccess(result.txHash) + const newInvestedFunds = await getInvestedFunds(selectedVault.address) + const newIdleFunds = await getIdleFunds(selectedVault.address) + await dispatch(updateVaultData({ + address: selectedVault.address, + idleFunds: newIdleFunds, + investedFunds: newInvestedFunds + })) + await setTimeout(() => { + rebalanceModal.setIsOpen(false) + inspectModal.setIsOpen(false) + }, 4500) + } catch (e: any) { + console.error(e) + await txModal.handleError(e) + } + } useEffect(() => { - const tempStrategies: strategiesWithBalances[] = []; - selectedVault?.assets[0]?.strategies.forEach(async (strategy) => { - const balance = await getUserBalance(selectedVault.address, strategy.address); - console.log(balance) - tempStrategies.push({ ...strategy, balance: balance ?? 0 }); - }); - setStrategiesWithBalances(tempStrategies); + if (!selectedVault) return + if (selectedVault.assets) { + selectedVault.assets.forEach(async (asset) => { + asset.strategies.forEach(async (strategy) => { + const tempAmount = await getUserBalance(selectedVault.address, strategy.address) + console.log(tempAmount) + dispatch(setStrategyTempAmount({ + vaultAddress: selectedVault?.address!, + strategyAddress: strategy.address, + amount: tempAmount ?? 0 + })) + }) + }) + } }, [selectedVault]); - enum Action { - INVEST = 'invest', - WITHDRAW = 'withdraw', - } + useEffect(() => { + setTempInstruction({ + ...tempInstruction, + descritpion: generateDescription(tempInstruction.action!, tempInstruction.amount, tempInstruction.strategy) + }) + }, [tempInstruction.action, tempInstruction.amount, tempInstruction.strategy]) + + useEffect(() => { + if (!rebalanceModal.isOpen) { + setInstructions([]) + } + }, [rebalanceModal.isOpen]) + return ( Rebalance - - Strategies: - - - {(strategy, index) => ( - - - {strategy.name} - Balance: ${strategy.balance} + + + + {(asset, i) => ( + + {asset.symbol} strategies: + + {(strategy, j) => ( + + {strategy.name} ${strategy.tempAmount} + {selectedVault?.assets[0]?.symbol} + + )} + + + )} + + + + + + Idle funds: + + {selectedVault?.idleFunds.map((idleFund, i) => ( + + ${idleFund.amount} {selectedVault.assets[i]?.symbol} - - - - - {Object.values(Action).map((action) => ( - - ))} - - - - - - {selectedVault?.assets.find(asset => asset.strategies.includes(strategy))?.symbol} - - }> - - - - - - - - )} - - + ))} + + + + + Invested funds: + + {selectedVault?.investedFunds.map((investedFund, i) => ( + + ${investedFund.amount} {selectedVault.assets[i]?.symbol} + + ))} + + + + + + + + setTempInstruction({ ...tempInstruction, action: ActionType[e.currentTarget.value as keyof typeof ActionType] })}> + + + {(action, index) => ( + + )} + + + + + + + setTempInstruction({ ...tempInstruction, strategy: e.currentTarget.value })}> + + + {(strategy, index) => ( + + )} + + + + + + + {selectedVault?.assets[0]?.symbol} + + } + > + setTempInstruction({ ...tempInstruction, amount: Number(e.value) })} + > + + + + + + { + setInstructions([...instructions, tempInstruction as RebalanceInstructionState]) + } + } + > + + + + + + {instructions.length > 0 && + + {(instruction, index) => ( + + + + {instruction.descritpion} + + handleRemoveInstruction(index)} + colorPalette={'red'} + > + + + + + )} + + } + diff --git a/apps/dapp/src/hooks/types.ts b/apps/dapp/src/hooks/types.ts index 024bbc14..d152dadb 100644 --- a/apps/dapp/src/hooks/types.ts +++ b/apps/dapp/src/hooks/types.ts @@ -21,4 +21,18 @@ export interface StrategyInvestment { export interface AssetInvestmentAllocation { asset: string; strategy_investments: StrategyInvestment[]; +} +export interface RebalanceInstruction { + action: ActionType; + amount: number; + strategy: string; + swapDetailsExactIn?: any//SwapDetails; + swapDetailsExactOut?: any//SwapDetails; +} +export enum ActionType { + Withdraw = 0, + Invest = 1, + SwapExactIn = 2, + SwapExactOut = 3, + Zapper = 4, } \ No newline at end of file diff --git a/apps/dapp/src/hooks/useVault.ts b/apps/dapp/src/hooks/useVault.ts index 73f9ad84..eea14e2e 100644 --- a/apps/dapp/src/hooks/useVault.ts +++ b/apps/dapp/src/hooks/useVault.ts @@ -25,6 +25,7 @@ export enum VaultMethod { GETINVESTEDFUNDS = "fetch_current_invested_funds", SETFEERECIEVER = "set_fee_receiver", INVEST = "invest", + REBALANCE= "rebalance", } const isObject = (val: unknown) => typeof val === 'object' && val !== null && !Array.isArray(val); From 7de14a8652ea4f2107254590d02180603814b538 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Mon, 25 Nov 2024 02:35:35 -0300 Subject: [PATCH 26/50] =?UTF-8?q?=F0=9F=A9=B9Fix=20decimals=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InteractWithVault/InvestStrategies.tsx | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx index b0f7fd5c..e0623371 100644 --- a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx +++ b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx @@ -30,14 +30,8 @@ export const InvestStrategies = () => { const [investment, setInvestment] = useState([]) const [invalidAmount, setInvalidAmount] = useState(false) - const handleInvestInput = (assetIndex: number, strategyIndex: number, amount: string) => { + const handleInvestInput = (assetIndex: number, strategyIndex: number, amount: number) => { console.log(amount) - if (amount.includes(',')) { - amount = amount.replace(',', '.') - } - if (isNaN(parseFloat(amount)) || amount === '') { - amount = '0' - } if (investment[assetIndex] == undefined) { console.warn('Asset investment not found') return @@ -51,8 +45,7 @@ export const InvestStrategies = () => { return } const newInvestment = [...investment] - console.log(parseFloat(amount)) - newInvestment[assetIndex]!.strategy_investments[strategyIndex]!.amount = parseFloat(amount) + newInvestment[assetIndex]!.strategy_investments[strategyIndex]!.amount = amount newInvestment[assetIndex]!.total = newInvestment[assetIndex]!.strategy_investments.reduce((acc, curr) => acc + curr.amount, 0) setInvestment(newInvestment) } @@ -75,7 +68,7 @@ export const InvestStrategies = () => { return xdr.ScVal.scvMap([ new xdr.ScMapEntry({ key: xdr.ScVal.scvSymbol("amount"), - val: nativeToScVal(BigInt((parseInt(strategy_investment.amount.toString()) ?? 0) * 10 ** 7), { type: "i128" }), // Ensure i128 conversion + val: nativeToScVal(BigInt((strategy_investment.amount ?? 0) * 10 ** 7), { type: "i128" }), // Ensure i128 conversion }), new xdr.ScMapEntry({ key: xdr.ScVal.scvSymbol("strategy"), @@ -213,8 +206,8 @@ export const InvestStrategies = () => { }> handleInvestInput(j, k, e.value)} + defaultValue="0" + onValueChange={(e) => handleInvestInput(j, k, Number(e.value))} > From c1458f25d181eda1b582d84431063c2639862c3e Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Mon, 25 Nov 2024 02:48:12 -0300 Subject: [PATCH 27/50] =?UTF-8?q?=E2=9C=A8=20Refactor=20input=20handling?= =?UTF-8?q?=20in=20DeployVault=20components=20to=20improve=20amount=20vali?= =?UTF-8?q?dation=20and=20integrate=20NumberInput=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeployVault/AddNewStrategyButton.tsx | 15 +++++---------- .../components/DeployVault/ConfirmDelpoyModal.tsx | 4 +++- .../src/components/DeployVault/VaultPreview.tsx | 14 ++++++-------- .../InteractWithVault/RebalanceVault.tsx | 1 - 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/apps/dapp/src/components/DeployVault/AddNewStrategyButton.tsx b/apps/dapp/src/components/DeployVault/AddNewStrategyButton.tsx index 131e1ef1..1db7596f 100644 --- a/apps/dapp/src/components/DeployVault/AddNewStrategyButton.tsx +++ b/apps/dapp/src/components/DeployVault/AddNewStrategyButton.tsx @@ -102,19 +102,14 @@ function AddNewStrategyButton() { } const handleAmountInput = async (e: any) => { - const input = e.target.value - if (!input) { + if (!e) { console.log('input is empty') setSelectedAsset({ ...selectedAsset, amount: 0 }) } - console.log(input) + console.log(e) const decimalRegex = /^(\d+)?(\.\d{0,7})?$/ - if (!decimalRegex.test(input)) return - if (input.startsWith('.')) { - setAmountInput({ amount: 0 + input, enabled: true }); - return - } - setAmountInput({ amount: input, enabled: true }); + if (!decimalRegex.test(e)) return + setAmountInput({ amount: e, enabled: true }); } const strategyExists = (strategy: Strategy) => { const exists = newVault.assets.some((asset) => asset.strategies.some((str) => str.address === strategy.address)) @@ -188,7 +183,7 @@ function AddNewStrategyButton() { endElement={`${selectedAsset.symbol}`} > handleAmountInput(Number(e.value))} > diff --git a/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx b/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx index 5ccd7823..57181246 100644 --- a/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx +++ b/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx @@ -41,6 +41,7 @@ export const ConfirmDelpoyModal = ({ isOpen, onClose }: { isOpen: boolean, onClo const sorobanContext = useSorobanReact(); const { activeChain, address } = sorobanContext; const factory = useFactoryCallback(); + const { getInvestedFunds } = useVault(); const newVault: NewVaultState = useAppSelector(state => state.newVault); const indexName = useAppSelector(state => state.newVault.name) const indexSymbol = useAppSelector(state => state.newVault.symbol) @@ -242,6 +243,7 @@ export const ConfirmDelpoyModal = ({ isOpen, onClose }: { isOpen: boolean, onClo amount: newVault.assets[index]?.amount || 0 } }) + const investedFunds = await getInvestedFunds(parsedResult); const tempVault: VaultData = { ...newVault, address: parsedResult, @@ -251,7 +253,7 @@ export const ConfirmDelpoyModal = ({ isOpen, onClose }: { isOpen: boolean, onClo TVL: 0, totalSupply: 0, idleFunds: idleFunds, - investedFunds: [{ address: '', amount: 0 }], + investedFunds: investedFunds ?? [], } await txModal.handleSuccess(result.txHash); dispatch(pushVault(tempVault)); diff --git a/apps/dapp/src/components/DeployVault/VaultPreview.tsx b/apps/dapp/src/components/DeployVault/VaultPreview.tsx index e1945d56..1a876538 100644 --- a/apps/dapp/src/components/DeployVault/VaultPreview.tsx +++ b/apps/dapp/src/components/DeployVault/VaultPreview.tsx @@ -27,6 +27,7 @@ import { import { IoIosArrowDown, IoIosArrowUp } from 'react-icons/io' import { Asset } from '@/store/lib/types' import { InfoTip } from '../ui/toggle-tip' +import { NumberInputField, NumberInputRoot } from '../ui/number-input' export enum AccordionItems { @@ -260,10 +261,6 @@ export const VaultPreview: React.FC = ({ data, accordionValue if (input < 0 || input > 100) return const decimalRegex = /^(\d+)?(\.\d{0,2})?$/ if (!decimalRegex.test(input)) return - if (input.startsWith('.')) { - setFormControl({ ...formControl, vaultShare: 0 + input }); - return - } setFormControl({ ...formControl, vaultShare: input @@ -374,11 +371,12 @@ export const VaultPreview: React.FC = ({ data, accordionValue } /> - { handleVaultShareChange(e.target.value) }} + { handleVaultShareChange(Number(e.value)) }} required - /> + > + + This field is required. diff --git a/apps/dapp/src/components/InteractWithVault/RebalanceVault.tsx b/apps/dapp/src/components/InteractWithVault/RebalanceVault.tsx index cd763fa6..04b62332 100644 --- a/apps/dapp/src/components/InteractWithVault/RebalanceVault.tsx +++ b/apps/dapp/src/components/InteractWithVault/RebalanceVault.tsx @@ -114,7 +114,6 @@ const RebalanceVault: React.FC = (() => { selectedVault.assets.forEach(async (asset) => { asset.strategies.forEach(async (strategy) => { const tempAmount = await getUserBalance(selectedVault.address, strategy.address) - console.log(tempAmount) dispatch(setStrategyTempAmount({ vaultAddress: selectedVault?.address!, strategyAddress: strategy.address, From a2113e91b1367366385ea12b8a1772640be6ca7b Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Mon, 25 Nov 2024 09:45:26 -0300 Subject: [PATCH 28/50] =?UTF-8?q?=F0=9F=A9=B9=20Fix=20build?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx index e0623371..8370a224 100644 --- a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx +++ b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx @@ -36,7 +36,7 @@ export const InvestStrategies = () => { console.warn('Asset investment not found') return } - if (investment[assetIndex].strategy_investments[strategyIndex] == undefined) { + if (investment[assetIndex]!.strategy_investments[strategyIndex] !== undefined) { console.warn('Strategy investment not found') return } From f40dfdc6b634dab4be5c57ebfe1d182752a750e7 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Mon, 25 Nov 2024 16:27:00 -0300 Subject: [PATCH 29/50] =?UTF-8?q?=F0=9F=A9=B9=20Remove=20unnecessary=20con?= =?UTF-8?q?sole=20warnings=20in=20handleInvestInput=20and=20clean=20up=20d?= =?UTF-8?q?efault=20value=20in=20NumberInput?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../InteractWithVault/InvestStrategies.tsx | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx index 8370a224..1c8488e4 100644 --- a/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx +++ b/apps/dapp/src/components/InteractWithVault/InvestStrategies.tsx @@ -31,19 +31,6 @@ export const InvestStrategies = () => { const [invalidAmount, setInvalidAmount] = useState(false) const handleInvestInput = (assetIndex: number, strategyIndex: number, amount: number) => { - console.log(amount) - if (investment[assetIndex] == undefined) { - console.warn('Asset investment not found') - return - } - if (investment[assetIndex]!.strategy_investments[strategyIndex] !== undefined) { - console.warn('Strategy investment not found') - return - } - if (!selectedVault?.userBalance) { - console.warn('User balance not found') - return - } const newInvestment = [...investment] newInvestment[assetIndex]!.strategy_investments[strategyIndex]!.amount = amount newInvestment[assetIndex]!.total = newInvestment[assetIndex]!.strategy_investments.reduce((acc, curr) => acc + curr.amount, 0) @@ -206,7 +193,6 @@ export const InvestStrategies = () => { }> handleInvestInput(j, k, Number(e.value))} > From c419d56dc5c0c3a826aaa37c15e241e0a6a0e8ea Mon Sep 17 00:00:00 2001 From: coderipper Date: Mon, 25 Nov 2024 18:57:59 -0300 Subject: [PATCH 30/50] deposit and invest working and tested --- apps/contracts/vault/src/interface.rs | 20 ++ apps/contracts/vault/src/lib.rs | 158 ++++++++- apps/contracts/vault/src/test.rs | 1 + .../vault/src/test/deposit_and_invest.rs | 320 ++++++++++++++++++ 4 files changed, 496 insertions(+), 3 deletions(-) create mode 100644 apps/contracts/vault/src/test/deposit_and_invest.rs diff --git a/apps/contracts/vault/src/interface.rs b/apps/contracts/vault/src/interface.rs index 82d684a2..7449c7e7 100644 --- a/apps/contracts/vault/src/interface.rs +++ b/apps/contracts/vault/src/interface.rs @@ -80,6 +80,26 @@ pub trait VaultTrait { from: Address, ) -> Result<(Vec, i128), ContractError>; + /// Handles user deposits into the DeFindex Vault and invest them immediately. + /// + /// + /// # Parameters + /// * `e` - The current environment reference (`Env`), for access to the contract state and utilities. + /// * `amounts_desired` - A vector specifying the user's intended deposit amounts for each asset. + /// * `amounts_min` - A vector of minimum deposit amounts required for the transaction to proceed. + /// * `from` - The address of the user making the deposit. + /// + /// # Returns + /// * `Result<(Vec, i128), ContractError>` - Returns the actual deposited `amounts` and `shares_to_mint` if successful, + /// otherwise a `ContractError`. + /// + fn deposit_and_invest( + e: Env, + amounts_desired: Vec, + amounts_min: Vec, + from: Address, + ) -> Result<(Vec, i128), ContractError>; + /// Withdraws assets from the DeFindex Vault by burning dfTokens. /// /// This function calculates the amount of assets to withdraw based on the number of dfTokens being burned, diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index 7204973f..3f2fb0b6 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -25,12 +25,11 @@ mod utils; use access::{AccessControl, AccessControlTrait, RolesDataKey}; use aggregator::{internal_swap_exact_tokens_for_tokens, internal_swap_tokens_for_exact_tokens}; use fee::{collect_fees, fetch_defindex_fee}; -use funds::{fetch_current_idle_funds, fetch_current_invested_funds, fetch_total_managed_funds}; //, fetch_idle_funds_for_asset}; +use funds::{fetch_current_idle_funds, fetch_current_invested_funds, fetch_invested_funds_for_asset, fetch_invested_funds_for_strategy, fetch_total_managed_funds}; //, fetch_idle_funds_for_asset}; use interface::{AdminInterfaceTrait, VaultManagementTrait, VaultTrait}; use investment::{check_and_execute_investments}; use models::{ - ActionType, AssetStrategySet, Instruction, AssetInvestmentAllocation, OptionalSwapDetailsExactIn, - OptionalSwapDetailsExactOut, + ActionType, AssetInvestmentAllocation, AssetStrategySet, Instruction, OptionalSwapDetailsExactIn, OptionalSwapDetailsExactOut, StrategyInvestment }; use storage::{ extend_instance_ttl, get_assets, get_vault_fee, set_asset, set_defindex_protocol_fee_receiver, set_factory, set_total_assets, set_vault_fee @@ -289,6 +288,159 @@ impl VaultTrait for DeFindexVault { Ok((amounts, shares_to_mint)) } + /// Handles user deposits into the DeFindex Vault and invest them immediately. + /// + /// + /// # Parameters + /// * `e` - The current environment reference (`Env`), for access to the contract state and utilities. + /// * `amounts_desired` - A vector specifying the user's intended deposit amounts for each asset. + /// * `amounts_min` - A vector of minimum deposit amounts required for the transaction to proceed. + /// * `from` - The address of the user making the deposit. + /// + /// # Returns + /// * `Result<(Vec, i128), ContractError>` - Returns the actual deposited `amounts` and `shares_to_mint` if successful, + /// otherwise a `ContractError`. + /// + fn deposit_and_invest( + e: Env, + amounts_desired: Vec, + amounts_min: Vec, + from: Address, + ) -> Result<(Vec, i128), ContractError> { + extend_instance_ttl(&e); + check_initialized(&e)?; + from.require_auth(); + + // Collect Fees + // If this was not done before, last_fee_assesment will set to be current timestamp and this will return without action + collect_fees(&e)?; + + // get assets + let assets = get_assets(&e); + let assets_length = assets.len(); + + // assets lenght should be equal to amounts_desired and amounts_min length + if assets_length != amounts_desired.len() || assets_length != amounts_min.len() { + panic_with_error!(&e, ContractError::WrongAmountsLength); + } + + // for every amount desired, check non negative + for amount in amounts_desired.iter() { + check_nonnegative_amount(amount)?; + } + // for amount min is not necesary to check if it is negative + + let total_supply = VaultToken::total_supply(e.clone()); + let (amounts, shares_to_mint) = if assets_length == 1 { + let shares = if total_supply == 0 { + // If we have only one asset, and this is the first deposit, we will mint a share proportional to the amount desired + // TODO In this case we might also want to mint a MINIMUM LIQUIDITY to be locked forever in the contract + // this might be for security and practical reasons as well + // shares will be equal to the amount desired to deposit, just for simplicity + amounts_desired.get(0).unwrap() // here we have already check same lenght + } else { + // If we have only one asset, but we already have some shares minted + // we will mint a share proportional to the total managed funds + // read whitepaper! + let total_managed_funds = fetch_total_managed_funds(&e); + // if checked mul gives error, return ArithmeticError + VaultToken::total_supply(e.clone()).checked_mul(amounts_desired.get(0) + .unwrap()).unwrap_or_else(|| panic_with_error!(&e, ContractError::ArithmeticError)) + .checked_div(total_managed_funds.get(assets.get(0).unwrap().address.clone()) + .unwrap()).unwrap_or_else(|| panic_with_error!(&e, ContractError::ArithmeticError)) + }; + // TODO check that min amount is ok + (amounts_desired, shares) + } else { + if total_supply == 0 { + // for ths first supply, we will consider the amounts desired, and the shares to mint will just be the sum + // of the amounts desired + (amounts_desired.clone(), amounts_desired.iter().sum()) + } + else { + // If Total Assets > 1 + calculate_deposit_amounts_and_shares_to_mint( + &e, + &assets, + &amounts_desired, + &amounts_min, + )? + } + }; + + // for every asset + for (i, amount) in amounts.iter().enumerate() { + // if amount is less than minimum, return error InsufficientAmount + if amount < amounts_min.get(i as u32).unwrap() { + panic_with_error!(&e, ContractError::InsufficientAmount); + } + // its possible that some amounts are 0. + if amount > 0 { + let asset = assets.get(i as u32).unwrap(); + let asset_client = TokenClient::new(&e, &asset.address); + // send the current amount to this contract. This will be held as idle funds. + asset_client.transfer(&from, &e.current_contract_address(), &amount); + } + } + + // Now we mint the corresponding dfToken shares to the user + // If total_sypply==0, mint minimum liquidity to be locked forever in the contract. So we will never come again to total_supply==0 + if total_supply == 0 { + if shares_to_mint < MINIMUM_LIQUIDITY { + panic_with_error!(&e, ContractError::InsufficientAmount); + } + internal_mint(e.clone(), e.current_contract_address(), MINIMUM_LIQUIDITY); + internal_mint(e.clone(), from.clone(), shares_to_mint.checked_sub(MINIMUM_LIQUIDITY).unwrap()); + } + else { + internal_mint(e.clone(), from.clone(), shares_to_mint); + } + + events::emit_deposit_event(&e, from, amounts.clone(), shares_to_mint.clone()); + + let mut asset_investments = Vec::new(&e); + + for (i, amount) in amounts.iter().enumerate() { + let asset = assets.get(i as u32).unwrap(); + let asset_invested_funds = fetch_invested_funds_for_asset(&e, &asset); + + let mut strategy_investments = Vec::new(&e); + + let mut remaining_amount = amount.clone(); + + for (j, strategy) in asset.strategies.iter().enumerate() { + let strategy_invested_funds = fetch_invested_funds_for_strategy(&e, &strategy.address); + + let mut invest_amount = if asset_invested_funds > 0 { + (amount * strategy_invested_funds) / asset_invested_funds + } else { + 0 + }; + + if j == asset.strategies.len() as usize - 1 { + invest_amount = remaining_amount; + } + + remaining_amount -= invest_amount; + + strategy_investments.push_back(Some(StrategyInvestment { + strategy: strategy.address.clone(), + amount: invest_amount, + })); + } + + // Add the asset investment allocation to the main vector + asset_investments.push_back(Some(AssetInvestmentAllocation { + asset: asset.address.clone(), + strategy_investments, + })); + } + + // Execute the investments using the calculated allocations + check_and_execute_investments(e, assets, asset_investments)?; + Ok((amounts, shares_to_mint)) + } + /// Withdraws assets from the DeFindex Vault by burning Vault Share tokens. /// /// This function calculates the amount of assets to withdraw based on the number of Vault Share tokens being burned, diff --git a/apps/contracts/vault/src/test.rs b/apps/contracts/vault/src/test.rs index f696f7ee..de9bb0d3 100755 --- a/apps/contracts/vault/src/test.rs +++ b/apps/contracts/vault/src/test.rs @@ -174,3 +174,4 @@ mod invest; mod withdraw; mod emergency_withdraw; mod rebalance; +mod deposit_and_invest; diff --git a/apps/contracts/vault/src/test/deposit_and_invest.rs b/apps/contracts/vault/src/test/deposit_and_invest.rs new file mode 100644 index 00000000..8237b69d --- /dev/null +++ b/apps/contracts/vault/src/test/deposit_and_invest.rs @@ -0,0 +1,320 @@ +use soroban_sdk::{vec as sorobanvec, String, Vec, Map}; + +use crate::test::defindex_vault::{AssetStrategySet}; +use crate::test::{ + create_strategy_params_token0, create_strategy_params_token1, DeFindexVaultTest, +}; + +// test deposit one asset success +#[test] +fn deposit_and_invest_one_asset_success() { + let test = DeFindexVaultTest::setup(); + test.env.mock_all_auths(); + let strategy_params_token0 = create_strategy_params_token0(&test); + + // initialize with 1 assets + let assets: Vec = sorobanvec![ + &test.env, + AssetStrategySet { + address: test.token0.address.clone(), + strategies: strategy_params_token0.clone() + } + ]; + + test.defindex_contract.initialize( + &assets, + &test.manager, + &test.emergency_manager, + &test.vault_fee_receiver, + &2000u32, + &test.defindex_protocol_receiver, + &test.defindex_factory, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + ); + let amount = 123456789i128; + + let users = DeFindexVaultTest::generate_random_users(&test.env, 1); + + // Balances before deposit + test.token0_admin_client.mint(&users[0], &amount); + let user_balance = test.token0.balance(&users[0]); + assert_eq!(user_balance, amount); + + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, 0i128); + + // deposit + test.defindex_contract.deposit_and_invest( + &sorobanvec![&test.env, amount], + &sorobanvec![&test.env, amount], + &users[0], + ); + + // check balances after deposit + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, amount - 1000); + + let user_balance = test.token0.balance(&users[0]); + assert_eq!(user_balance, 0i128); + + //map shuould be map + let mut expected_invested_map = Map::new(&test.env); + expected_invested_map.set(test.token0.address.clone(), amount); + + let mut expected_idle_map = Map::new(&test.env); + expected_idle_map.set(test.token0.address.clone(), 0); + + // check that all the assets are invested + let vault_balance = test.token0.balance(&test.defindex_contract.address); + assert_eq!(vault_balance, 0); + + // check that fetch_total_managed_funds returns correct amount + let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); + assert_eq!(total_managed_funds, expected_invested_map); + + // check current idle funds, + let current_idle_funds = test.defindex_contract.fetch_current_idle_funds(); + assert_eq!(current_idle_funds, expected_idle_map); + + // check that current invested funds is now 0, funds still in idle funds + let current_invested_funds = test.defindex_contract.fetch_current_invested_funds(); + assert_eq!(current_invested_funds, expected_invested_map); + + // Now user deposits for the second time + let amount2 = 987654321i128; + test.token0_admin_client.mint(&users[0], &amount2); + let user_balance = test.token0.balance(&users[0]); + assert_eq!(user_balance, amount2); + + // deposit + test.defindex_contract.deposit_and_invest( + &sorobanvec![&test.env, amount2], + &sorobanvec![&test.env, amount2], + &users[0], + ); + + //map shuould be map + let mut expected_invested_map = Map::new(&test.env); + expected_invested_map.set(test.token0.address.clone(), amount + amount2); + + let mut expected_idle_map = Map::new(&test.env); + expected_idle_map.set(test.token0.address.clone(), 0); + + // check balances after deposit + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, amount + amount2 - 1000); + + let user_balance = test.token0.balance(&users[0]); + assert_eq!(user_balance, 0i128); + + // check that the assets are not in the vault + let vault_balance = test.token0.balance(&test.defindex_contract.address); + assert_eq!(vault_balance, 0); + + // check that fetch_total_managed_funds returns correct amount + let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); + assert_eq!(total_managed_funds, expected_invested_map); + + // check current idle funds + let current_idle_funds = test.defindex_contract.fetch_current_idle_funds(); + assert_eq!(current_idle_funds, expected_idle_map); + + // check that current invested funds is now 0, funds still in idle funds + let current_invested_funds = test.defindex_contract.fetch_current_invested_funds(); + assert_eq!(current_invested_funds, expected_invested_map); +} + +// test deposit of several asset, considering different proportion of assets +#[test] +fn deposit_and_invest_several_assets_success() { + let test = DeFindexVaultTest::setup(); + test.env.mock_all_auths(); + let strategy_params_token0 = create_strategy_params_token0(&test); + let strategy_params_token1 = create_strategy_params_token1(&test); + + // initialize with 2 assets + let assets: Vec = sorobanvec![ + &test.env, + AssetStrategySet { + address: test.token0.address.clone(), + strategies: strategy_params_token0.clone() + }, + AssetStrategySet { + address: test.token1.address.clone(), + strategies: strategy_params_token1.clone() + } + ]; + + test.defindex_contract.initialize( + &assets, + &test.manager, + &test.emergency_manager, + &test.vault_fee_receiver, + &2000u32, + &test.defindex_protocol_receiver, + &test.defindex_factory, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + ); + let amount0 = 123456789i128; + let amount1 = 987654321i128; + + let users = DeFindexVaultTest::generate_random_users(&test.env, 2); + + // Balances before deposit + test.token0_admin_client.mint(&users[0], &amount0); + test.token1_admin_client.mint(&users[0], &amount1); + let user_balance0 = test.token0.balance(&users[0]); + assert_eq!(user_balance0, amount0); + let user_balance1 = test.token1.balance(&users[0]); + assert_eq!(user_balance1, amount1); + + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, 0i128); + + // deposit + let deposit_result=test.defindex_contract.deposit_and_invest( + &sorobanvec![&test.env, amount0, amount1], + &sorobanvec![&test.env, amount0, amount1], + &users[0], + ); + + // check deposit result + assert_eq!(deposit_result, (sorobanvec![&test.env, amount0, amount1], amount0 + amount1)); + + // check balances after deposit + let df_balance = test.defindex_contract.balance(&users[0]); + // For first deposit, a minimum amount LIQUIDITY OF 1000 is being locked in the contract + assert_eq!(df_balance, amount0 + amount1 - 1000); + + // check that the vault holds 1000 shares + let vault_df_shares = test.defindex_contract.balance(&test.defindex_contract.address); + assert_eq!(vault_df_shares, 1000i128); + + let user_balance0 = test.token0.balance(&users[0]); + assert_eq!(user_balance0,0i128); + let user_balance1 = test.token1.balance(&users[0]); + assert_eq!(user_balance1,0i128); + + // check vault balance of asset 0 + let vault_balance0 = test.token0.balance(&test.defindex_contract.address); + assert_eq!(vault_balance0, 0); + // check vault balance of asset 1 + let vault_balance1 = test.token1.balance(&test.defindex_contract.address); + assert_eq!(vault_balance1, 0); + + //map shuould be map + let mut expected_invested_map = Map::new(&test.env); + expected_invested_map.set(test.token0.address.clone(), amount0); + expected_invested_map.set(test.token1.address.clone(), amount1); + + let mut expected_idle_map = Map::new(&test.env); + expected_idle_map.set(test.token0.address.clone(), 0); + expected_idle_map.set(test.token1.address.clone(), 0); + + // check that fetch_total_managed_funds returns correct amount + let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); + assert_eq!(total_managed_funds, expected_invested_map); + + // check current idle funds + let current_idle_funds = test.defindex_contract.fetch_current_idle_funds(); + assert_eq!(current_idle_funds, expected_idle_map); + + // check that current invested funds is now correct, funds should be invested + let current_invested_funds = test.defindex_contract.fetch_current_invested_funds(); + assert_eq!(current_invested_funds, expected_invested_map); + + // new user wants to do a deposit with more assets 0 than the proportion, but with minium amount 0 + // multiply amount0 by 2 + let amount0_new = amount0*2 +100 ; + let amount1_new = amount1*2; + + // mint this to user 1 + test.token0_admin_client.mint(&users[1], &amount0_new); + test.token1_admin_client.mint(&users[1], &amount1_new); + + // check user balances + let user_balance0 = test.token0.balance(&users[1]); + assert_eq!(user_balance0, amount0_new); + let user_balance1 = test.token1.balance(&users[1]); + assert_eq!(user_balance1, amount1_new); + + + // user 1 deposits + let deposit_result=test.defindex_contract.deposit_and_invest( + &sorobanvec![&test.env, amount0_new, amount1_new], + &sorobanvec![&test.env, 0i128, 0i128], + &users[1], + ); + + // check deposit result. Ok((amounts, shares_to_mint)) + // Vec, i128 + + assert_eq!(deposit_result, (sorobanvec![&test.env, amount0*2, amount1*2], amount0*2 + amount1*2)); + + + // check balances after deposit + let df_balance = test.defindex_contract.balance(&users[1]); + assert_eq!(df_balance, 2*(amount0 + amount1)); + + let user_balance0 = test.token0.balance(&users[1]); + assert_eq!(user_balance0, amount0_new - 2*amount0); + + let user_balance1 = test.token1.balance(&users[1]); + assert_eq!(user_balance1, amount1_new - 2*amount1); + + // check vault balance of asset 0 + let vault_balance0 = test.token0.balance(&test.defindex_contract.address); + assert_eq!(vault_balance0, 0); + // check vault balance of asset 1 + let vault_balance1 = test.token1.balance(&test.defindex_contract.address); + assert_eq!(vault_balance1, 0); + + //map shuould be map + let mut expected_invested_map = Map::new(&test.env); + expected_invested_map.set(test.token0.address.clone(), 3*amount0); + expected_invested_map.set(test.token1.address.clone(), 3*amount1); + + let mut expected_idle_map = Map::new(&test.env); + expected_idle_map.set(test.token0.address.clone(), 0); + expected_idle_map.set(test.token1.address.clone(), 0); + + // check that fetch_total_managed_funds returns correct amount + let total_managed_funds = test.defindex_contract.fetch_total_managed_funds(); + assert_eq!(total_managed_funds, expected_invested_map); + + // check current idle funds + let current_idle_funds = test.defindex_contract.fetch_current_idle_funds(); + assert_eq!(current_idle_funds, expected_idle_map); + + // check that current invested funds is now 0, funds still in idle funds + let current_invested_funds = test.defindex_contract.fetch_current_invested_funds(); + assert_eq!(current_invested_funds, expected_invested_map); + + // we will repeat one more time, now enforcing the first asset + let amount0_new = amount0*2; + let amount1_new = amount1*2+100; + + // mint this to user 1 + test.token0_admin_client.mint(&users[1], &amount0_new); + test.token1_admin_client.mint(&users[1], &amount1_new); + + // check user balances + let user_balance0 = test.token0.balance(&users[1]); + assert_eq!(user_balance0, 100 + amount0_new); // we still have 100 from before + let user_balance1 = test.token1.balance(&users[1]); + assert_eq!(user_balance1, amount1_new); + + // user 1 deposits + let deposit_result=test.defindex_contract.deposit_and_invest( + &sorobanvec![&test.env, amount0_new, amount1_new], + &sorobanvec![&test.env, 0i128, 0i128], + &users[1], + ); + + // check deposit result. Ok((amounts, shares_to_mint)) + // Vec, i128 + assert_eq!(deposit_result, (sorobanvec![&test.env, amount0*2, amount1*2], amount0*2 + amount1*2)); + +} \ No newline at end of file From a7825dd30e7994fb42a5f5c4952fa6d8ad09683c Mon Sep 17 00:00:00 2001 From: coderipper Date: Mon, 25 Nov 2024 19:17:43 -0300 Subject: [PATCH 31/50] modular deposit approach --- apps/contracts/vault/src/deposit.rs | 140 +++++++++++++++++++ apps/contracts/vault/src/lib.rs | 208 +--------------------------- 2 files changed, 147 insertions(+), 201 deletions(-) create mode 100644 apps/contracts/vault/src/deposit.rs diff --git a/apps/contracts/vault/src/deposit.rs b/apps/contracts/vault/src/deposit.rs new file mode 100644 index 00000000..173cb8d4 --- /dev/null +++ b/apps/contracts/vault/src/deposit.rs @@ -0,0 +1,140 @@ +use soroban_sdk::{panic_with_error, token::TokenClient, Address, Env, Vec}; + +use crate::{funds::{fetch_invested_funds_for_asset, fetch_invested_funds_for_strategy, fetch_total_managed_funds}, investment::check_and_execute_investments, models::{AssetInvestmentAllocation, AssetStrategySet, StrategyInvestment}, storage::get_assets, token::{internal_mint, VaultToken}, utils::{calculate_deposit_amounts_and_shares_to_mint, check_nonnegative_amount}, ContractError, MINIMUM_LIQUIDITY}; + +/// Common logic for processing deposits. +pub fn process_deposit( + e: &Env, + amounts_desired: &Vec, + amounts_min: &Vec, + from: &Address, +) -> Result<(Vec, i128), ContractError> { + let assets = get_assets(&e); + let assets_length = assets.len(); + + // Validate inputs + if assets_length != amounts_desired.len() || assets_length != amounts_min.len() { + panic_with_error!(&e, ContractError::WrongAmountsLength); + } + + for amount in amounts_desired.iter() { + check_nonnegative_amount(amount)?; + } + + let total_supply = VaultToken::total_supply(e.clone()); + let (amounts, shares_to_mint) = if assets_length == 1 { + calculate_single_asset_shares(e, amounts_desired, total_supply)? + } else { + if total_supply == 0 { + (amounts_desired.clone(), amounts_desired.iter().sum()) + } else { + calculate_deposit_amounts_and_shares_to_mint(&e, &assets, amounts_desired, amounts_min)? + } + }; + + // Transfer assets + for (i, amount) in amounts.iter().enumerate() { + if amount < amounts_min.get(i as u32).unwrap() { + panic_with_error!(&e, ContractError::InsufficientAmount); + } + if amount > 0 { + let asset = assets.get(i as u32).unwrap(); + let asset_client = TokenClient::new(&e, &asset.address); + asset_client.transfer(&from, &e.current_contract_address(), &amount); + } + } + + // Mint shares + mint_shares(e, total_supply, shares_to_mint, from.clone())?; + + Ok((amounts, shares_to_mint)) +} + +/// Calculate shares for single-asset deposits. +fn calculate_single_asset_shares( + e: &Env, + amounts_desired: &Vec, + total_supply: i128, +) -> Result<(Vec, i128), ContractError> { + let shares = if total_supply == 0 { + amounts_desired.get(0).unwrap() + } else { + let total_managed_funds = fetch_total_managed_funds(&e); + VaultToken::total_supply(e.clone()) + .checked_mul(amounts_desired.get(0).unwrap()) + .unwrap_or_else(|| panic_with_error!(&e, ContractError::ArithmeticError)) + .checked_div(total_managed_funds.get(get_assets(&e).get(0).unwrap().address.clone()) + .unwrap()) + .unwrap_or_else(|| panic_with_error!(&e, ContractError::ArithmeticError)) + }; + Ok((amounts_desired.clone(), shares)) +} + +/// Mint vault shares. +fn mint_shares( + e: &Env, + total_supply: i128, + shares_to_mint: i128, + from: Address, +) -> Result<(), ContractError> { + if total_supply == 0 { + if shares_to_mint < MINIMUM_LIQUIDITY { + panic_with_error!(&e, ContractError::InsufficientAmount); + } + internal_mint(e.clone(), e.current_contract_address(), MINIMUM_LIQUIDITY); + internal_mint( + e.clone(), + from.clone(), + shares_to_mint.checked_sub(MINIMUM_LIQUIDITY).unwrap(), + ); + } else { + internal_mint(e.clone(), from, shares_to_mint); + } + Ok(()) +} + +/// Generate investment allocations and execute them. +pub fn generate_and_execute_investments( + e: &Env, + amounts: &Vec, + assets: Vec, +) -> Result<(), ContractError> { + let mut asset_investments = Vec::new(&e); + + for (i, amount) in amounts.iter().enumerate() { + let asset = assets.get(i as u32).unwrap(); + let asset_invested_funds = fetch_invested_funds_for_asset(&e, &asset); + + let mut strategy_investments = Vec::new(&e); + let mut remaining_amount = amount; + + for (j, strategy) in asset.strategies.iter().enumerate() { + let strategy_invested_funds = fetch_invested_funds_for_strategy(&e, &strategy.address); + + let mut invest_amount = if asset_invested_funds > 0 { + (amount * strategy_invested_funds) / asset_invested_funds + } else { + 0 + }; + + if j == asset.strategies.len() as usize - 1 { + invest_amount = remaining_amount; + } + + remaining_amount -= invest_amount; + + strategy_investments.push_back(Some(StrategyInvestment { + strategy: strategy.address.clone(), + amount: invest_amount, + })); + } + + asset_investments.push_back(Some(AssetInvestmentAllocation { + asset: asset.address.clone(), + strategy_investments, + })); + } + + check_and_execute_investments(e.clone(), assets, asset_investments)?; + Ok(()) +} \ No newline at end of file diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index 3f2fb0b6..72a13f86 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +use deposit::{generate_and_execute_investments, process_deposit}; use soroban_sdk::{ contract, contractimpl, panic_with_error, token::{TokenClient, TokenInterface}, @@ -9,6 +10,7 @@ use soroban_token_sdk::metadata::TokenMetadata; mod access; mod aggregator; mod constants; +mod deposit; mod error; mod events; mod fee; @@ -202,86 +204,7 @@ impl VaultTrait for DeFindexVault { // If this was not done before, last_fee_assesment will set to be current timestamp and this will return without action collect_fees(&e)?; - // get assets - let assets = get_assets(&e); - let assets_length = assets.len(); - - // assets lenght should be equal to amounts_desired and amounts_min length - if assets_length != amounts_desired.len() || assets_length != amounts_min.len() { - panic_with_error!(&e, ContractError::WrongAmountsLength); - } - - // for every amount desired, check non negative - for amount in amounts_desired.iter() { - check_nonnegative_amount(amount)?; - } - // for amount min is not necesary to check if it is negative - - let total_supply = VaultToken::total_supply(e.clone()); - let (amounts, shares_to_mint) = if assets_length == 1 { - let shares = if total_supply == 0 { - // If we have only one asset, and this is the first deposit, we will mint a share proportional to the amount desired - // TODO In this case we might also want to mint a MINIMUM LIQUIDITY to be locked forever in the contract - // this might be for security and practical reasons as well - // shares will be equal to the amount desired to deposit, just for simplicity - amounts_desired.get(0).unwrap() // here we have already check same lenght - } else { - // If we have only one asset, but we already have some shares minted - // we will mint a share proportional to the total managed funds - // read whitepaper! - let total_managed_funds = fetch_total_managed_funds(&e); - // if checked mul gives error, return ArithmeticError - VaultToken::total_supply(e.clone()).checked_mul(amounts_desired.get(0) - .unwrap()).unwrap_or_else(|| panic_with_error!(&e, ContractError::ArithmeticError)) - .checked_div(total_managed_funds.get(assets.get(0).unwrap().address.clone()) - .unwrap()).unwrap_or_else(|| panic_with_error!(&e, ContractError::ArithmeticError)) - }; - // TODO check that min amount is ok - (amounts_desired, shares) - } else { - if total_supply == 0 { - // for ths first supply, we will consider the amounts desired, and the shares to mint will just be the sum - // of the amounts desired - (amounts_desired.clone(), amounts_desired.iter().sum()) - } - else { - // If Total Assets > 1 - calculate_deposit_amounts_and_shares_to_mint( - &e, - &assets, - &amounts_desired, - &amounts_min, - )? - } - }; - - // for every asset - for (i, amount) in amounts.iter().enumerate() { - // if amount is less than minimum, return error InsufficientAmount - if amount < amounts_min.get(i as u32).unwrap() { - panic_with_error!(&e, ContractError::InsufficientAmount); - } - // its possible that some amounts are 0. - if amount > 0 { - let asset = assets.get(i as u32).unwrap(); - let asset_client = TokenClient::new(&e, &asset.address); - // send the current amount to this contract. This will be held as idle funds. - asset_client.transfer(&from, &e.current_contract_address(), &amount); - } - } - - // Now we mint the corresponding dfToken shares to the user - // If total_sypply==0, mint minimum liquidity to be locked forever in the contract. So we will never come again to total_supply==0 - if total_supply == 0 { - if shares_to_mint < MINIMUM_LIQUIDITY { - panic_with_error!(&e, ContractError::InsufficientAmount); - } - internal_mint(e.clone(), e.current_contract_address(), MINIMUM_LIQUIDITY); - internal_mint(e.clone(), from.clone(), shares_to_mint.checked_sub(MINIMUM_LIQUIDITY).unwrap()); - } - else { - internal_mint(e.clone(), from.clone(), shares_to_mint); - } + let (amounts, shares_to_mint) = process_deposit(&e, &amounts_desired, &amounts_min, &from)?; events::emit_deposit_event(&e, from, amounts.clone(), shares_to_mint.clone()); @@ -312,132 +235,15 @@ impl VaultTrait for DeFindexVault { from.require_auth(); // Collect Fees - // If this was not done before, last_fee_assesment will set to be current timestamp and this will return without action collect_fees(&e)?; - // get assets - let assets = get_assets(&e); - let assets_length = assets.len(); - - // assets lenght should be equal to amounts_desired and amounts_min length - if assets_length != amounts_desired.len() || assets_length != amounts_min.len() { - panic_with_error!(&e, ContractError::WrongAmountsLength); - } - - // for every amount desired, check non negative - for amount in amounts_desired.iter() { - check_nonnegative_amount(amount)?; - } - // for amount min is not necesary to check if it is negative - - let total_supply = VaultToken::total_supply(e.clone()); - let (amounts, shares_to_mint) = if assets_length == 1 { - let shares = if total_supply == 0 { - // If we have only one asset, and this is the first deposit, we will mint a share proportional to the amount desired - // TODO In this case we might also want to mint a MINIMUM LIQUIDITY to be locked forever in the contract - // this might be for security and practical reasons as well - // shares will be equal to the amount desired to deposit, just for simplicity - amounts_desired.get(0).unwrap() // here we have already check same lenght - } else { - // If we have only one asset, but we already have some shares minted - // we will mint a share proportional to the total managed funds - // read whitepaper! - let total_managed_funds = fetch_total_managed_funds(&e); - // if checked mul gives error, return ArithmeticError - VaultToken::total_supply(e.clone()).checked_mul(amounts_desired.get(0) - .unwrap()).unwrap_or_else(|| panic_with_error!(&e, ContractError::ArithmeticError)) - .checked_div(total_managed_funds.get(assets.get(0).unwrap().address.clone()) - .unwrap()).unwrap_or_else(|| panic_with_error!(&e, ContractError::ArithmeticError)) - }; - // TODO check that min amount is ok - (amounts_desired, shares) - } else { - if total_supply == 0 { - // for ths first supply, we will consider the amounts desired, and the shares to mint will just be the sum - // of the amounts desired - (amounts_desired.clone(), amounts_desired.iter().sum()) - } - else { - // If Total Assets > 1 - calculate_deposit_amounts_and_shares_to_mint( - &e, - &assets, - &amounts_desired, - &amounts_min, - )? - } - }; - - // for every asset - for (i, amount) in amounts.iter().enumerate() { - // if amount is less than minimum, return error InsufficientAmount - if amount < amounts_min.get(i as u32).unwrap() { - panic_with_error!(&e, ContractError::InsufficientAmount); - } - // its possible that some amounts are 0. - if amount > 0 { - let asset = assets.get(i as u32).unwrap(); - let asset_client = TokenClient::new(&e, &asset.address); - // send the current amount to this contract. This will be held as idle funds. - asset_client.transfer(&from, &e.current_contract_address(), &amount); - } - } - - // Now we mint the corresponding dfToken shares to the user - // If total_sypply==0, mint minimum liquidity to be locked forever in the contract. So we will never come again to total_supply==0 - if total_supply == 0 { - if shares_to_mint < MINIMUM_LIQUIDITY { - panic_with_error!(&e, ContractError::InsufficientAmount); - } - internal_mint(e.clone(), e.current_contract_address(), MINIMUM_LIQUIDITY); - internal_mint(e.clone(), from.clone(), shares_to_mint.checked_sub(MINIMUM_LIQUIDITY).unwrap()); - } - else { - internal_mint(e.clone(), from.clone(), shares_to_mint); - } - + let (amounts, shares_to_mint) = process_deposit(&e, &amounts_desired, &amounts_min, &from)?; events::emit_deposit_event(&e, from, amounts.clone(), shares_to_mint.clone()); - let mut asset_investments = Vec::new(&e); - - for (i, amount) in amounts.iter().enumerate() { - let asset = assets.get(i as u32).unwrap(); - let asset_invested_funds = fetch_invested_funds_for_asset(&e, &asset); - - let mut strategy_investments = Vec::new(&e); - - let mut remaining_amount = amount.clone(); - - for (j, strategy) in asset.strategies.iter().enumerate() { - let strategy_invested_funds = fetch_invested_funds_for_strategy(&e, &strategy.address); - - let mut invest_amount = if asset_invested_funds > 0 { - (amount * strategy_invested_funds) / asset_invested_funds - } else { - 0 - }; - - if j == asset.strategies.len() as usize - 1 { - invest_amount = remaining_amount; - } - - remaining_amount -= invest_amount; - - strategy_investments.push_back(Some(StrategyInvestment { - strategy: strategy.address.clone(), - amount: invest_amount, - })); - } - - // Add the asset investment allocation to the main vector - asset_investments.push_back(Some(AssetInvestmentAllocation { - asset: asset.address.clone(), - strategy_investments, - })); - } + let assets = get_assets(&e); + // Generate investment allocations and execute them + generate_and_execute_investments(&e, &amounts, assets)?; - // Execute the investments using the calculated allocations - check_and_execute_investments(e, assets, asset_investments)?; Ok((amounts, shares_to_mint)) } From 996b5de870f5a8d8e928800a6a70c7a4e17f383a Mon Sep 17 00:00:00 2001 From: coderipper Date: Tue, 26 Nov 2024 10:10:42 -0300 Subject: [PATCH 32/50] invest as deposit param boolean --- apps/contracts/vault/src/deposit.rs | 6 +-- apps/contracts/vault/src/interface.rs | 21 +-------- apps/contracts/vault/src/lib.rs | 43 +++---------------- apps/contracts/vault/src/test/deposit.rs | 15 +++++++ .../vault/src/test/deposit_and_invest.rs | 15 ++++--- .../vault/src/test/emergency_withdraw.rs | 1 + apps/contracts/vault/src/test/invest.rs | 3 ++ apps/contracts/vault/src/test/rebalance.rs | 1 + apps/contracts/vault/src/test/withdraw.rs | 2 + 9 files changed, 43 insertions(+), 64 deletions(-) diff --git a/apps/contracts/vault/src/deposit.rs b/apps/contracts/vault/src/deposit.rs index 173cb8d4..c98a13cd 100644 --- a/apps/contracts/vault/src/deposit.rs +++ b/apps/contracts/vault/src/deposit.rs @@ -5,11 +5,11 @@ use crate::{funds::{fetch_invested_funds_for_asset, fetch_invested_funds_for_str /// Common logic for processing deposits. pub fn process_deposit( e: &Env, + assets: &Vec, amounts_desired: &Vec, amounts_min: &Vec, from: &Address, ) -> Result<(Vec, i128), ContractError> { - let assets = get_assets(&e); let assets_length = assets.len(); // Validate inputs @@ -97,7 +97,7 @@ fn mint_shares( pub fn generate_and_execute_investments( e: &Env, amounts: &Vec, - assets: Vec, + assets: &Vec, ) -> Result<(), ContractError> { let mut asset_investments = Vec::new(&e); @@ -135,6 +135,6 @@ pub fn generate_and_execute_investments( })); } - check_and_execute_investments(e.clone(), assets, asset_investments)?; + check_and_execute_investments(e.clone(), assets.clone(), asset_investments)?; Ok(()) } \ No newline at end of file diff --git a/apps/contracts/vault/src/interface.rs b/apps/contracts/vault/src/interface.rs index 7449c7e7..eb5605e6 100644 --- a/apps/contracts/vault/src/interface.rs +++ b/apps/contracts/vault/src/interface.rs @@ -78,26 +78,7 @@ pub trait VaultTrait { amounts_desired: Vec, amounts_min: Vec, from: Address, - ) -> Result<(Vec, i128), ContractError>; - - /// Handles user deposits into the DeFindex Vault and invest them immediately. - /// - /// - /// # Parameters - /// * `e` - The current environment reference (`Env`), for access to the contract state and utilities. - /// * `amounts_desired` - A vector specifying the user's intended deposit amounts for each asset. - /// * `amounts_min` - A vector of minimum deposit amounts required for the transaction to proceed. - /// * `from` - The address of the user making the deposit. - /// - /// # Returns - /// * `Result<(Vec, i128), ContractError>` - Returns the actual deposited `amounts` and `shares_to_mint` if successful, - /// otherwise a `ContractError`. - /// - fn deposit_and_invest( - e: Env, - amounts_desired: Vec, - amounts_min: Vec, - from: Address, + invest: bool, ) -> Result<(Vec, i128), ContractError>; /// Withdraws assets from the DeFindex Vault by burning dfTokens. diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index 72a13f86..d2c2fe6e 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -195,6 +195,7 @@ impl VaultTrait for DeFindexVault { amounts_desired: Vec, amounts_min: Vec, from: Address, + invest: bool, ) -> Result<(Vec, i128), ContractError> { extend_instance_ttl(&e); check_initialized(&e)?; @@ -204,45 +205,15 @@ impl VaultTrait for DeFindexVault { // If this was not done before, last_fee_assesment will set to be current timestamp and this will return without action collect_fees(&e)?; - let (amounts, shares_to_mint) = process_deposit(&e, &amounts_desired, &amounts_min, &from)?; - - events::emit_deposit_event(&e, from, amounts.clone(), shares_to_mint.clone()); - - Ok((amounts, shares_to_mint)) - } - - /// Handles user deposits into the DeFindex Vault and invest them immediately. - /// - /// - /// # Parameters - /// * `e` - The current environment reference (`Env`), for access to the contract state and utilities. - /// * `amounts_desired` - A vector specifying the user's intended deposit amounts for each asset. - /// * `amounts_min` - A vector of minimum deposit amounts required for the transaction to proceed. - /// * `from` - The address of the user making the deposit. - /// - /// # Returns - /// * `Result<(Vec, i128), ContractError>` - Returns the actual deposited `amounts` and `shares_to_mint` if successful, - /// otherwise a `ContractError`. - /// - fn deposit_and_invest( - e: Env, - amounts_desired: Vec, - amounts_min: Vec, - from: Address, - ) -> Result<(Vec, i128), ContractError> { - extend_instance_ttl(&e); - check_initialized(&e)?; - from.require_auth(); - - // Collect Fees - collect_fees(&e)?; + let assets = get_assets(&e); - let (amounts, shares_to_mint) = process_deposit(&e, &amounts_desired, &amounts_min, &from)?; + let (amounts, shares_to_mint) = process_deposit(&e, &assets, &amounts_desired, &amounts_min, &from)?; events::emit_deposit_event(&e, from, amounts.clone(), shares_to_mint.clone()); - let assets = get_assets(&e); - // Generate investment allocations and execute them - generate_and_execute_investments(&e, &amounts, assets)?; + if invest { + // Generate investment allocations and execute them + generate_and_execute_investments(&e, &amounts, &assets)?; + } Ok((amounts, shares_to_mint)) } diff --git a/apps/contracts/vault/src/test/deposit.rs b/apps/contracts/vault/src/test/deposit.rs index 5b9ffd6f..08fbe5fe 100644 --- a/apps/contracts/vault/src/test/deposit.rs +++ b/apps/contracts/vault/src/test/deposit.rs @@ -15,6 +15,7 @@ fn test_deposit_not_yet_initialized() { &sorobanvec![&test.env, 100i128], &sorobanvec![&test.env, 100i128], &users[0], + &false, ); assert_eq!(result, Err(Ok(ContractError::NotInitialized))); @@ -60,6 +61,7 @@ fn deposit_amounts_desired_less_length() { &sorobanvec![&test.env, amount], // wrong amount desired &sorobanvec![&test.env, amount, amount], &users[0], + &false, ); assert_eq!(response, Err(Ok(ContractError::WrongAmountsLength))); @@ -101,6 +103,7 @@ fn deposit_amounts_desired_more_length() { &sorobanvec![&test.env, amount, amount], // wrong amount desired &sorobanvec![&test.env, amount], &users[0], + &false, ); assert_eq!(response, Err(Ok(ContractError::WrongAmountsLength))); @@ -146,6 +149,7 @@ fn deposit_amounts_min_less_length() { &sorobanvec![&test.env, amount, amount], &sorobanvec![&test.env, amount], // wrong amount min &users[0], + &false, ); assert_eq!(response, Err(Ok(ContractError::WrongAmountsLength))); @@ -192,6 +196,7 @@ fn deposit_amounts_min_more_length() { &sorobanvec![&test.env, amount, amount], &sorobanvec![&test.env, amount, amount, amount], // wrong amount min &users[0], + &false, ); assert_eq!(response, Err(Ok(ContractError::WrongAmountsLength))); @@ -237,6 +242,7 @@ fn deposit_amounts_desired_negative() { &sorobanvec![&test.env, -amount, amount], &sorobanvec![&test.env, amount, amount], &users[0], + &false, ); assert_eq!(response, Err(Ok(ContractError::NegativeNotAllowed))); @@ -286,6 +292,7 @@ fn deposit_one_asset_success() { &sorobanvec![&test.env, amount], &sorobanvec![&test.env, amount], &users[0], + &false, ); // check balances after deposit @@ -330,6 +337,7 @@ fn deposit_one_asset_success() { &sorobanvec![&test.env, amount2], &sorobanvec![&test.env, amount2], &users[0], + &false, ); //map shuould be map @@ -412,6 +420,7 @@ fn deposit_one_asset_min_more_than_desired() { &sorobanvec![&test.env, amount], &sorobanvec![&test.env, amount + 1], &users[0], + &false, ); // this should fail assert_eq!(result, Err(Ok(ContractError::InsufficientAmount))); @@ -471,6 +480,7 @@ fn deposit_several_assets_success() { &sorobanvec![&test.env, amount0, amount1], &sorobanvec![&test.env, amount0, amount1], &users[0], + &false, ); // check deposit result @@ -541,6 +551,7 @@ fn deposit_several_assets_success() { &sorobanvec![&test.env, amount0_new, amount1_new], &sorobanvec![&test.env, 0i128, 0i128], &users[1], + &false, ); // check deposit result. Ok((amounts, shares_to_mint)) @@ -607,6 +618,7 @@ fn deposit_several_assets_success() { &sorobanvec![&test.env, amount0_new, amount1_new], &sorobanvec![&test.env, 0i128, 0i128], &users[1], + &false, ); // check deposit result. Ok((amounts, shares_to_mint)) @@ -668,6 +680,7 @@ fn deposit_several_assets_min_greater_than_optimal() { &sorobanvec![&test.env, amount0, amount1], &sorobanvec![&test.env, amount0 + 1, amount1], &users[0], + &false, ); // this should fail @@ -678,6 +691,7 @@ fn deposit_several_assets_min_greater_than_optimal() { &sorobanvec![&test.env, amount0, amount1], &sorobanvec![&test.env, amount0, amount1], &users[0], + &false, ); // check deposit result @@ -697,6 +711,7 @@ fn deposit_several_assets_min_greater_than_optimal() { &sorobanvec![&test.env, amount0_new, amount1_new], &sorobanvec![&test.env, amount0*2+1, amount1*2], &users[0], + &false, ); // this should fail diff --git a/apps/contracts/vault/src/test/deposit_and_invest.rs b/apps/contracts/vault/src/test/deposit_and_invest.rs index 8237b69d..a85ff29b 100644 --- a/apps/contracts/vault/src/test/deposit_and_invest.rs +++ b/apps/contracts/vault/src/test/deposit_and_invest.rs @@ -45,10 +45,11 @@ fn deposit_and_invest_one_asset_success() { assert_eq!(df_balance, 0i128); // deposit - test.defindex_contract.deposit_and_invest( + test.defindex_contract.deposit( &sorobanvec![&test.env, amount], &sorobanvec![&test.env, amount], &users[0], + &true, ); // check balances after deposit @@ -88,10 +89,11 @@ fn deposit_and_invest_one_asset_success() { assert_eq!(user_balance, amount2); // deposit - test.defindex_contract.deposit_and_invest( + test.defindex_contract.deposit( &sorobanvec![&test.env, amount2], &sorobanvec![&test.env, amount2], &users[0], + &true, ); //map shuould be map @@ -174,10 +176,11 @@ fn deposit_and_invest_several_assets_success() { assert_eq!(df_balance, 0i128); // deposit - let deposit_result=test.defindex_contract.deposit_and_invest( + let deposit_result=test.defindex_contract.deposit( &sorobanvec![&test.env, amount0, amount1], &sorobanvec![&test.env, amount0, amount1], &users[0], + &true, ); // check deposit result @@ -242,10 +245,11 @@ fn deposit_and_invest_several_assets_success() { // user 1 deposits - let deposit_result=test.defindex_contract.deposit_and_invest( + let deposit_result=test.defindex_contract.deposit( &sorobanvec![&test.env, amount0_new, amount1_new], &sorobanvec![&test.env, 0i128, 0i128], &users[1], + &true, ); // check deposit result. Ok((amounts, shares_to_mint)) @@ -307,10 +311,11 @@ fn deposit_and_invest_several_assets_success() { assert_eq!(user_balance1, amount1_new); // user 1 deposits - let deposit_result=test.defindex_contract.deposit_and_invest( + let deposit_result=test.defindex_contract.deposit( &sorobanvec![&test.env, amount0_new, amount1_new], &sorobanvec![&test.env, 0i128, 0i128], &users[1], + &true, ); // check deposit result. Ok((amounts, shares_to_mint)) diff --git a/apps/contracts/vault/src/test/emergency_withdraw.rs b/apps/contracts/vault/src/test/emergency_withdraw.rs index 1c014738..cf74ec8b 100644 --- a/apps/contracts/vault/src/test/emergency_withdraw.rs +++ b/apps/contracts/vault/src/test/emergency_withdraw.rs @@ -49,6 +49,7 @@ fn test_emergency_withdraw_success() { &sorobanvec![&test.env, amount], &sorobanvec![&test.env, amount], &users[0], + &false ); let df_balance = test.defindex_contract.balance(&users[0]); diff --git a/apps/contracts/vault/src/test/invest.rs b/apps/contracts/vault/src/test/invest.rs index 338c2c2a..a5b06171 100644 --- a/apps/contracts/vault/src/test/invest.rs +++ b/apps/contracts/vault/src/test/invest.rs @@ -446,6 +446,7 @@ fn test_invest_in_strategy() { &sorobanvec![&test.env, amount_0, amount_1], // asset 0 &sorobanvec![&test.env, amount_0, amount_1], // asset 1 &users[0], + &false, ); @@ -609,6 +610,7 @@ fn test_invest_more_than_idle_funds() { &sorobanvec![&test.env, amount_0, amount_1], // asset 0 &sorobanvec![&test.env, amount_0, amount_1], // asset 1 &users[0], + &false, ); // check vault balances @@ -753,6 +755,7 @@ fn test_invest_without_mock_all_auths() { &sorobanvec![&test.env, amount_0, amount_1], // asset 0 &sorobanvec![&test.env, amount_0, amount_1], // asset 1 &users[0], + &false, ); // TODO check that the blockchain saw this authorizations diff --git a/apps/contracts/vault/src/test/rebalance.rs b/apps/contracts/vault/src/test/rebalance.rs index dffb1e7a..c0aec907 100644 --- a/apps/contracts/vault/src/test/rebalance.rs +++ b/apps/contracts/vault/src/test/rebalance.rs @@ -53,6 +53,7 @@ fn rebalance() { &sorobanvec![&test.env, amount], &sorobanvec![&test.env, amount], &users[0], + &false ); let df_balance = test.defindex_contract.balance(&users[0]); diff --git a/apps/contracts/vault/src/test/withdraw.rs b/apps/contracts/vault/src/test/withdraw.rs index b5fe4ef8..38edb80f 100644 --- a/apps/contracts/vault/src/test/withdraw.rs +++ b/apps/contracts/vault/src/test/withdraw.rs @@ -54,6 +54,7 @@ fn test_withdraw_from_idle_success() { &sorobanvec![&test.env, amount_to_deposit], &sorobanvec![&test.env, amount_to_deposit], &users[0], + &false ); // Check Balances after deposit @@ -165,6 +166,7 @@ fn test_withdraw_from_strategy_success() { &sorobanvec![&test.env, amount], &sorobanvec![&test.env, amount], &users[0], + &false ); let df_balance = test.defindex_contract.balance(&users[0]); From 06f9223ddf62d22579d8ea91709922f313d70a09 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:11:50 -0300 Subject: [PATCH 33/50] =?UTF-8?q?=E2=9C=A8Test=20blend=20vault?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/contracts/package.json | 1 + apps/contracts/src/tests/blend/test.ts | 232 +++++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 apps/contracts/src/tests/blend/test.ts diff --git a/apps/contracts/package.json b/apps/contracts/package.json index 4ef8a7bc..7f349d64 100644 --- a/apps/contracts/package.json +++ b/apps/contracts/package.json @@ -11,6 +11,7 @@ "publish-addresses": "tsc && node dist/publish_addresses.js", "test": "tsc && node dist/test.js", "test-vault": "tsc && node dist/tests/testOnlyVault.js", + "test-blend": "tsc && node dist/tests/blend/test.js", "test-dev": "tsc && node dist/tests/dev.js", "test-two-strat-vault": "tsc && node dist/tests/testTwoStrategiesVault.js" }, diff --git a/apps/contracts/src/tests/blend/test.ts b/apps/contracts/src/tests/blend/test.ts new file mode 100644 index 00000000..d9a36d77 --- /dev/null +++ b/apps/contracts/src/tests/blend/test.ts @@ -0,0 +1,232 @@ +import { Address, Keypair, nativeToScVal, scValToNative, xdr } from "@stellar/stellar-sdk"; +import { airdropAccount, invokeCustomContract } from "../../utils/contract.js"; +import { getDfTokenBalance } from "../vault.js"; +import { randomBytes } from "crypto"; +import { TxResponse } from '@soroban-react/contracts'; +const blendStrategyAddress = "CCNFSOPH4XFQ5TNWGTJB4ZVKUUARSNQ67SETXVIQLUIW3B3F7KHA3NKJ" +const factoryAddress = "CB6RQM6ECU775ZC26NMZ6RJNKQLKQGLIJWWN2VZO6AGSE4V4DBQDL23O" +const XLMAddress = "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC" +const BlendUSDCAddress = "" +const network = process.argv[2]; +const purple = '\x1b[35m%s\x1b[0m'; +const green = '\x1b[32m%s\x1b[0m'; + + +const newVault = { + address: '', + emergencyManager: 'GCH6YKNJ3KPESGSAIGBNHRNCIYXXXSRVU7OC552RDGQFHZ4SYRI26DQE', + feeReceiver: 'GCH6YKNJ3KPESGSAIGBNHRNCIYXXXSRVU7OC552RDGQFHZ4SYRI26DQE', + manager: 'GCH6YKNJ3KPESGSAIGBNHRNCIYXXXSRVU7OC552RDGQFHZ4SYRI26DQE', + name: 'Test', + symbol: 'Test1', + vaultShare: 10, + assets: [ + { + address: 'CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC', + strategies: [ + { + address: 'CCNFSOPH4XFQ5TNWGTJB4ZVKUUARSNQ67SETXVIQLUIW3B3F7KHA3NKJ', + name: 'Blend', + paused: false + } + ], + symbol: 'XLM', + amount: 1000 + } + ], + TVL: 0 +} +export async function createVault(user?: Keypair) { + // Create and fund a new user account if not provided + console.log(purple, '--------------------------------------------------------------------') + console.log(purple, '----------------------- Creating new account -----------------------') + console.log(purple, '--------------------------------------------------------------------') + const newUser = Keypair.random(); + console.log('🚀 ~ depositToVault ~ newUser.publicKey():', newUser.publicKey()); + console.log('🚀 ~ depositToVault ~ newUser.secret():', newUser.secret()); + + console.log(green, '----------------------- New account created -------------------------') + console.log(green, 'Public key: ',newUser.publicKey()) + console.log(green, '---------------------------------------------------------------------') + + if (network !== "mainnet") { + console.log(purple, '-------------------------------------------------------------------') + console.log(purple, '----------------------- Funding new account -----------------------') + console.log(purple, '-------------------------------------------------------------------') + await airdropAccount(newUser); + } + console.log("New user publicKey:", newUser.publicKey()); + + + const indexName = "test"; + const indexSymbol = "TEST"; + const indexShare = 10; + const managerString = newUser.publicKey(); + const vaultName = nativeToScVal(indexName, { type: "string" }) + const vaultSymbol = nativeToScVal(indexSymbol, { type: "string" }) + const vaultShare = nativeToScVal(indexShare, { type: "u32" }) + const emergencyManager = new Address(managerString) + const feeReceiver = new Address(managerString) + const manager = new Address(managerString) + const salt = randomBytes(32) + + const strategyParamsScVal = xdr.ScVal.scvMap([ + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('address'), + val: new Address(blendStrategyAddress).toScVal(), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('name'), + val: nativeToScVal('Blend', { type: "string" }), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('paused'), + val: nativeToScVal(false, { type: "bool" }), + }), + ]); + const strategyParamsScValVec = xdr.ScVal.scvVec([strategyParamsScVal]); + const assetsParams = xdr.ScVal.scvMap([ + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('address'), + val: new Address(newVault.assets[0].address).toScVal(), + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol('strategies'), + val: strategyParamsScValVec, + }), + ]); + const assetParamsScValVec = xdr.ScVal.scvVec([assetsParams]); + const createDefindexParams: xdr.ScVal[] = [ + emergencyManager.toScVal(), + feeReceiver.toScVal(), + vaultShare, + vaultName, + vaultSymbol, + manager.toScVal(), + assetParamsScValVec, + nativeToScVal(salt), + ]; + let result: any; + let blendVaultAddress: string; + try { + console.log(purple, '--------------------------------------------------------------') + console.log(purple, '----------------------- Creating vault -----------------------') + console.log(purple, '--------------------------------------------------------------') + result = await invokeCustomContract( + factoryAddress, + 'create_defindex_vault', + createDefindexParams, + newUser, + false + ); + blendVaultAddress = scValToNative(result.returnValue); + console.log(green, '----------------------- Vault created -------------------------') + console.log(green, 'result', blendVaultAddress) + console.log(green, '---------------------------------------------------------------') + + + // Deposit assets to the vault + + console.log(purple, '---------------------------------------------------------------------------') + console.log(purple, '----------------------- Depositing XLM to the vault -----------------------') + console.log(purple, '---------------------------------------------------------------------------') + const depositParams: xdr.ScVal[] = [ + xdr.ScVal.scvVec([nativeToScVal(987654321, { type: "i128" })]), + xdr.ScVal.scvVec([nativeToScVal(Math.ceil(987654321 * 0.9), { type: "i128" })]), + new Address(newUser.publicKey()).toScVal(), + ] + const depositResult = await invokeCustomContract( + blendVaultAddress, + 'deposit', + depositParams, + newUser, + false + ); + const depositResultValue = scValToNative(depositResult.returnValue); + + console.log(green, '------------ XLM deposited to the vault ------------') + console.log(green, 'depositResult', depositResultValue) + console.log(green, '----------------------------------------------------') + + // Withdraw assets from the vault + + console.log(purple, '------------------------------------------------------------------------------') + console.log(purple, '----------------------- Withdrawing XLM from the vault -----------------------') + console.log(purple, '------------------------------------------------------------------------------') + const withdrawAmount = Math.ceil(100); + const withdrawParams: xdr.ScVal[] = [ + nativeToScVal(withdrawAmount, { type: "i128" }), + new Address(newUser.publicKey()).toScVal(), + ] + const withdrawResult = await invokeCustomContract( + blendVaultAddress, + 'withdraw', + withdrawParams, + newUser, + false + ); + const withdrawResultValue = scValToNative(withdrawResult.returnValue); + console.log(green, '---------------- XLM withdrawn from the vault ----------------') + console.log(green, 'Withdrawed: ', withdrawResultValue, ' from the vault') + console.log(green, '--------------------------------------------------------------') + + // Invest in strategy + + console.log(purple, '---------------------------------------------------------------------------') + console.log(purple, '-------------------------- Investing in strategy --------------------------') + console.log(purple, '---------------------------------------------------------------------------') + + const investment: any = [{ + "asset": "CDLZFC3SYJYDZT7K67VZ75HPJVIEUVNIXF47ZG2FB2RMQQVU2HHGCYSC", + "strategy_investments": [ + { + "amount": 24, + "strategy": "CCWUMJGE6LKWRDJ2IYEJBLCWJSMSUC3QCYZNI2MHTOEYPZRWZN56MIVA" + } + ], + }] + + const investmentParams = investment.map((entry:any) => + xdr.ScVal.scvMap([ + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol("asset"), + val: new Address(entry.asset).toScVal()// Convert asset address to ScVal + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol("strategy_investments"), + val: xdr.ScVal.scvVec( + entry.strategy_investments.map((strategy_investment: any) => { + return xdr.ScVal.scvMap([ + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol("amount"), + val: nativeToScVal(BigInt((strategy_investment.amount ?? 0) * 10 ** 7), { type: "i128" }), // Ensure i128 conversion + }), + new xdr.ScMapEntry({ + key: xdr.ScVal.scvSymbol("strategy"), + val: new Address(strategy_investment.strategy).toScVal() // Convert strategy address + }), + ]) + }) + ), + }), + ]) + ) + const investmentParamsScValVec = xdr.ScVal.scvVec(investmentParams); + + const investResult = await invokeCustomContract( + blendVaultAddress, + 'invest', + [investmentParamsScValVec], + newUser, + false + ); + const investResultValue = scValToNative(investResult.returnValue); + console.log(green, '---------------------- Invested in strategy ----------------------') + console.log(green, 'Invested: ', investResultValue, ' in the strategy') + console.log(green, '------------------------------------------------------------------') + + }catch(e){ + console.log('error', e) + } +} +await createVault(); \ No newline at end of file From 017530c10b267383f7476a2ef6d5d9a22255ae07 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Tue, 26 Nov 2024 10:12:37 -0300 Subject: [PATCH 34/50] =?UTF-8?q?=E2=9C=A8Add=20tests=20to=20deposit=20tes?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/contracts/vault/src/test/deposit.rs | 237 +++++++++++++++++++++++ 1 file changed, 237 insertions(+) diff --git a/apps/contracts/vault/src/test/deposit.rs b/apps/contracts/vault/src/test/deposit.rs index 5b9ffd6f..815a738e 100644 --- a/apps/contracts/vault/src/test/deposit.rs +++ b/apps/contracts/vault/src/test/deposit.rs @@ -705,3 +705,240 @@ fn deposit_several_assets_min_greater_than_optimal() { } +//test deposit amounts_min greater than amounts_desired +#[test] +fn deposit_amounts_min_greater_than_amounts_desired(){ + let test = DeFindexVaultTest::setup(); + test.env.mock_all_auths(); + let strategy_params_token0 = create_strategy_params_token0(&test); + let strategy_params_token1 = create_strategy_params_token1(&test); + + // initialize with 2 assets + let assets: Vec = sorobanvec![ + &test.env, + AssetStrategySet { + address: test.token0.address.clone(), + strategies: strategy_params_token0.clone() + }, + AssetStrategySet { + address: test.token1.address.clone(), + strategies: strategy_params_token1.clone() + } + ]; + + test.defindex_contract.initialize( + &assets, + &test.manager, + &test.emergency_manager, + &test.vault_fee_receiver, + &2000u32, + &test.defindex_protocol_receiver, + &test.defindex_factory, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + ); + let amount0 = 123456789i128; + let amount1 = 987654321i128; + + let users = DeFindexVaultTest::generate_random_users(&test.env, 2); + + // Balances before deposit + test.token0_admin_client.mint(&users[0], &amount0); + test.token1_admin_client.mint(&users[0], &amount1); + let user_balance0 = test.token0.balance(&users[0]); + assert_eq!(user_balance0, amount0); + let user_balance1 = test.token1.balance(&users[0]); + assert_eq!(user_balance1, amount1); + + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, 0i128); + + // deposit + let deposit_result=test.defindex_contract.try_deposit( + &sorobanvec![&test.env, amount0, amount1], + &sorobanvec![&test.env, amount0 + 1, amount1 + 1], + &users[0], + ); + + // this should fail + assert_eq!(deposit_result, Err(Ok(ContractError::InsufficientAmount))); +} + +//Test token transfer from user to vault on deposit +#[test] +fn deposit_transfers_tokens_from_user_to_vault(){ + let test = DeFindexVaultTest::setup(); + test.env.mock_all_auths(); + let strategy_params_token0 = create_strategy_params_token0(&test); + let strategy_params_token1 = create_strategy_params_token1(&test); + + // initialize with 2 assets + let assets: Vec = sorobanvec![ + &test.env, + AssetStrategySet { + address: test.token0.address.clone(), + strategies: strategy_params_token0.clone() + }, + AssetStrategySet { + address: test.token1.address.clone(), + strategies: strategy_params_token1.clone() + } + ]; + + test.defindex_contract.initialize( + &assets, + &test.manager, + &test.emergency_manager, + &test.vault_fee_receiver, + &2000u32, + &test.defindex_protocol_receiver, + &test.defindex_factory, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + ); + let amount0 = 123456789i128; + let amount1 = 987654321i128; + + let users = DeFindexVaultTest::generate_random_users(&test.env, 2); + + // Balances before deposit + test.token0_admin_client.mint(&users[0], &amount0); + test.token1_admin_client.mint(&users[0], &amount1); + let user_balance0 = test.token0.balance(&users[0]); + assert_eq!(user_balance0, amount0); + let user_balance1 = test.token1.balance(&users[0]); + assert_eq!(user_balance1, amount1); + + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, 0i128); + + // deposit + test.defindex_contract.deposit( + &sorobanvec![&test.env, amount0, amount1], + &sorobanvec![&test.env, amount0, amount1], + &users[0], + ); + + // check balances after deposit + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, amount0 + amount1 - 1000); + + let user_balance0 = test.token0.balance(&users[0]); + assert_eq!(user_balance0, 0i128); +} + +#[test] +fn test_deposit_arithmetic_error() { + let test = DeFindexVaultTest::setup(); + test.env.mock_all_auths(); + let strategy_params_token0 = create_strategy_params_token0(&test); + + // Initialize with 1 asset + let assets: Vec = sorobanvec![ + &test.env, + AssetStrategySet { + address: test.token0.address.clone(), + strategies: strategy_params_token0.clone() + } + ]; + + test.defindex_contract.initialize( + &assets, + &test.manager, + &test.emergency_manager, + &test.vault_fee_receiver, + &2000u32, + &test.defindex_protocol_receiver, + &test.defindex_factory, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + ); + + // Mock the environment to provoke a division by zero + let mut mock_map = Map::new(&test.env); + mock_map.set(test.token0.address.clone(), 0i128); // Total funds for token0 is 0 + + let amount = 123456789i128; + + let users = DeFindexVaultTest::generate_random_users(&test.env, 1); + + // Mint tokens to user + test.token0_admin_client.mint(&users[0], &amount); + + let large_amount = i128::MAX / 2; + test.token0_admin_client.mint(&users[0], &large_amount); + + //first deposit to overflow the balance + test.defindex_contract.deposit( + &sorobanvec![&test.env, large_amount], + &sorobanvec![&test.env, large_amount], + &users[0], + ); + + // Try to deposit a large amount + let result = test.defindex_contract.try_deposit( + &sorobanvec![&test.env, large_amount], + &sorobanvec![&test.env, large_amount], + &users[0], + ); + + // Verify that the returned error is ContractError::ArithmeticError + assert_eq!(result, Err(Ok(ContractError::ArithmeticError))); +} + +//all amounts are cero +#[test] +fn deposit_amounts_desired_zero() { + let test = DeFindexVaultTest::setup(); + test.env.mock_all_auths(); + let strategy_params_token0 = create_strategy_params_token0(&test); + + // Initialize with 1 asset + let assets: Vec = sorobanvec![ + &test.env, + AssetStrategySet { + address: test.token0.address.clone(), + strategies: strategy_params_token0.clone() + } + ]; + + test.defindex_contract.initialize( + &assets, + &test.manager, + &test.emergency_manager, + &test.vault_fee_receiver, + &2000u32, + &test.defindex_protocol_receiver, + &test.defindex_factory, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + ); + + let amount = 123456789i128; + + let users = DeFindexVaultTest::generate_random_users(&test.env, 1); + + // Mint tokens to user + test.token0_admin_client.mint(&users[0], &amount); + + // Balances before deposit + let user_balance_before = test.token0.balance(&users[0]); + assert_eq!(user_balance_before, amount); + + let vault_balance_before = test.token0.balance(&test.defindex_contract.address); + assert_eq!(vault_balance_before, 0i128); + + let df_balance_before = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance_before, 0i128); + + // Attempt to deposit with amounts_desired all set to 0 + let deposit_result = test.defindex_contract.try_deposit( + &sorobanvec![&test.env, 0i128], + &sorobanvec![&test.env, 0i128], + &users[0], + ); + + // Verify that the returned error is ContractError::InsufficientAmount + assert_eq!(deposit_result, Err(Ok(ContractError::InsufficientAmount))); +} + From 964b5b34039dcafb36b33a7a7b4e546f27da54f2 Mon Sep 17 00:00:00 2001 From: coderipper Date: Tue, 26 Nov 2024 10:27:48 -0300 Subject: [PATCH 35/50] fixed test --- apps/contracts/vault/src/lib.rs | 10 +++++----- apps/contracts/vault/src/test/invest.rs | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/contracts/vault/src/lib.rs b/apps/contracts/vault/src/lib.rs index d2c2fe6e..dfc32584 100755 --- a/apps/contracts/vault/src/lib.rs +++ b/apps/contracts/vault/src/lib.rs @@ -27,11 +27,11 @@ mod utils; use access::{AccessControl, AccessControlTrait, RolesDataKey}; use aggregator::{internal_swap_exact_tokens_for_tokens, internal_swap_tokens_for_exact_tokens}; use fee::{collect_fees, fetch_defindex_fee}; -use funds::{fetch_current_idle_funds, fetch_current_invested_funds, fetch_invested_funds_for_asset, fetch_invested_funds_for_strategy, fetch_total_managed_funds}; //, fetch_idle_funds_for_asset}; +use funds::{fetch_current_idle_funds, fetch_current_invested_funds, fetch_total_managed_funds}; //, fetch_idle_funds_for_asset}; use interface::{AdminInterfaceTrait, VaultManagementTrait, VaultTrait}; -use investment::{check_and_execute_investments}; +use investment::check_and_execute_investments; use models::{ - ActionType, AssetInvestmentAllocation, AssetStrategySet, Instruction, OptionalSwapDetailsExactIn, OptionalSwapDetailsExactOut, StrategyInvestment + ActionType, AssetInvestmentAllocation, AssetStrategySet, Instruction, OptionalSwapDetailsExactIn, OptionalSwapDetailsExactOut }; use storage::{ extend_instance_ttl, get_assets, get_vault_fee, set_asset, set_defindex_protocol_fee_receiver, set_factory, set_total_assets, set_vault_fee @@ -41,9 +41,9 @@ use strategies::{ get_strategy_struct, invest_in_strategy, pause_strategy, unpause_strategy, withdraw_from_strategy, }; -use token::{internal_burn, internal_mint, write_metadata, VaultToken}; +use token::{internal_burn, write_metadata, VaultToken}; use utils::{ - calculate_asset_amounts_for_dftokens, calculate_deposit_amounts_and_shares_to_mint, + calculate_asset_amounts_for_dftokens, calculate_withdrawal_amounts, check_initialized, check_nonnegative_amount, }; diff --git a/apps/contracts/vault/src/test/invest.rs b/apps/contracts/vault/src/test/invest.rs index a5b06171..b6f7d93f 100644 --- a/apps/contracts/vault/src/test/invest.rs +++ b/apps/contracts/vault/src/test/invest.rs @@ -726,7 +726,8 @@ fn test_invest_without_mock_all_auths() { args: ( Vec::from_array(&test.env,[amount_0, amount_1]), Vec::from_array(&test.env,[amount_0, amount_1]), - users[0].clone() + users[0].clone(), + false ).into_val(&test.env), // mock toke 0 and token 1 subtransfer sub_invokes: &[ From f20887b934a34ec5899625f2fc86825fe32ed3a2 Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Tue, 26 Nov 2024 16:55:17 -0300 Subject: [PATCH 36/50] =?UTF-8?q?=E2=9C=A8Add=20test=20for=20deposit=20wit?= =?UTF-8?q?h=20insufficient=20funds=20and=20error=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/contracts/vault/src/test/deposit.rs | 68 +++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/apps/contracts/vault/src/test/deposit.rs b/apps/contracts/vault/src/test/deposit.rs index 815a738e..b82d872d 100644 --- a/apps/contracts/vault/src/test/deposit.rs +++ b/apps/contracts/vault/src/test/deposit.rs @@ -1,4 +1,4 @@ -use soroban_sdk::{vec as sorobanvec, String, Vec, Map}; +use soroban_sdk::{vec as sorobanvec, InvokeError, Map, String, Vec}; use crate::test::defindex_vault::{AssetStrategySet, ContractError}; use crate::test::{ @@ -942,3 +942,69 @@ fn deposit_amounts_desired_zero() { assert_eq!(deposit_result, Err(Ok(ContractError::InsufficientAmount))); } + + // Deposit with insufficient funds and check for specific error message +#[test] +fn deposit_insufficient_funds_with_error_message() { + let test = DeFindexVaultTest::setup(); + test.env.mock_all_auths(); + let strategy_params_token0 = create_strategy_params_token0(&test); + let strategy_params_token1 = create_strategy_params_token1(&test); + + // Initialize with 2 assets + let assets: Vec = sorobanvec![ + &test.env, + AssetStrategySet { + address: test.token0.address.clone(), + strategies: strategy_params_token0.clone() + }, + AssetStrategySet { + address: test.token1.address.clone(), + strategies: strategy_params_token1.clone() + } + ]; + + test.defindex_contract.initialize( + &assets, + &test.manager, + &test.emergency_manager, + &test.vault_fee_receiver, + &2000u32, + &test.defindex_protocol_receiver, + &test.defindex_factory, + &String::from_str(&test.env, "dfToken"), + &String::from_str(&test.env, "DFT"), + ); + + let amount0 = 123456789i128; + let amount1 = 987654321i128; + + let users = DeFindexVaultTest::generate_random_users(&test.env, 1); + + // Mint tokens to user + test.token0_admin_client.mint(&users[0], &amount0); + test.token1_admin_client.mint(&users[0], &amount1); + + // Balances before deposit + let user_balance0 = test.token0.balance(&users[0]); + assert_eq!(user_balance0, amount0); + let user_balance1 = test.token1.balance(&users[0]); + assert_eq!(user_balance1, amount1); + + let df_balance = test.defindex_contract.balance(&users[0]); + assert_eq!(df_balance, 0i128); + + // Attempt to deposit more than available balance + let deposit_result = test.defindex_contract.try_deposit( + &sorobanvec![&test.env, amount0 + 1, amount1 + 1], + &sorobanvec![&test.env, amount0 + 1, amount1 + 1], + &users[0], + ); + + if deposit_result == Err(Err(InvokeError::Contract(10))) { + return; + } else { + panic!("Expected error not returned"); + } + +} \ No newline at end of file From 8af05b6ac1602faa65a784314b57622b853ceffc Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Wed, 27 Nov 2024 10:03:06 -0300 Subject: [PATCH 37/50] =?UTF-8?q?=E2=9C=A8=20Add=20defindex=20&=20vault=20?= =?UTF-8?q?fees=20on=20inspect=20modal?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../DeployVault/ConfirmDelpoyModal.tsx | 4 +++- .../components/ManageVaults/InspectVault.tsx | 11 ++++++++++- .../components/ManageVaults/ManageVaults.tsx | 10 +++++++++- apps/dapp/src/hooks/useVault.ts | 19 ++++++++++++++++--- apps/dapp/src/store/lib/types.ts | 3 ++- 5 files changed, 40 insertions(+), 7 deletions(-) diff --git a/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx b/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx index 7a285b10..b136c425 100644 --- a/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx +++ b/apps/dapp/src/components/DeployVault/ConfirmDelpoyModal.tsx @@ -50,7 +50,7 @@ export const ConfirmDelpoyModal = ({ isOpen, onClose }: { isOpen: boolean, onClo const feeReceiverString = useAppSelector(state => state.newVault.feeReceiver) const { transactionStatusModal: txModal, deployVaultModal: deployModal } = useContext(ModalContext); const dispatch = useAppDispatch(); - const { getIdleFunds, getInvestedFunds, getTVL, getUserBalance } = useVault() + const { getFees } = useVault() const [deployDisabled, setDeployDisabled] = useState(true); @@ -258,6 +258,7 @@ export const ConfirmDelpoyModal = ({ isOpen, onClose }: { isOpen: boolean, onClo amount: newVault.assets[index]?.amount || 0 } }) + const fees = await getFees(parsedResult) const tempVault: VaultData = { ...newVault, address: parsedResult, @@ -268,6 +269,7 @@ export const ConfirmDelpoyModal = ({ isOpen, onClose }: { isOpen: boolean, onClo totalSupply: 0, idleFunds: idleFunds, investedFunds: [{ address: '', amount: 0 }], + fees: fees, } await txModal.handleSuccess(result.txHash); dispatch(pushVault(tempVault)); diff --git a/apps/dapp/src/components/ManageVaults/InspectVault.tsx b/apps/dapp/src/components/ManageVaults/InspectVault.tsx index e3917600..e9e0a21e 100644 --- a/apps/dapp/src/components/ManageVaults/InspectVault.tsx +++ b/apps/dapp/src/components/ManageVaults/InspectVault.tsx @@ -68,7 +68,7 @@ export const InspectVault = ({ Strategies: {selectedVault.assets.map((asset: Asset, index: number) => ( - + {(strategy: Strategy, index: number) => ( @@ -109,6 +109,15 @@ export const InspectVault = ({ ))} + + Fees: + + Defindex fee: {(selectedVault.fees[0]! / 100).toLocaleString('en-US', { style: 'decimal', maximumFractionDigits: 2 })} % + + + Vault fee: {(selectedVault.fees[1]! / 100).toLocaleString('en-US', { style: 'decimal', maximumFractionDigits: 2 })} % + + {(address && selectedVault.userBalance) && User balance: diff --git a/apps/dapp/src/components/ManageVaults/ManageVaults.tsx b/apps/dapp/src/components/ManageVaults/ManageVaults.tsx index aee2fc75..9a903fa6 100644 --- a/apps/dapp/src/components/ManageVaults/ManageVaults.tsx +++ b/apps/dapp/src/components/ManageVaults/ManageVaults.tsx @@ -137,6 +137,8 @@ export const ManageVaults = () => { } + + {/* Interact with vault */} { + + {/* Inspect vault */} { inspectModal.setIsOpen(e.open) }} - size={'lg'} + size={'xl'} placement={'center'} > @@ -162,6 +166,8 @@ export const ManageVaults = () => { onClose={() => { inspectModal.setIsOpen(false) }} /> + + {/* Edit vault */} { editModal.setIsOpen(e.open) }} @@ -171,6 +177,8 @@ export const ManageVaults = () => { + + {/* Transaction status modal */} { txModal.setIsOpen(e.open) }} diff --git a/apps/dapp/src/hooks/useVault.ts b/apps/dapp/src/hooks/useVault.ts index 4ce916aa..3b227756 100644 --- a/apps/dapp/src/hooks/useVault.ts +++ b/apps/dapp/src/hooks/useVault.ts @@ -24,6 +24,7 @@ export enum VaultMethod { GETIDLEFUNDS = "fetch_current_idle_funds", GETINVESTEDFUNDS = "fetch_current_invested_funds", SETFEERECIEVER = "set_fee_receiver", + GETFEES = "get_fees", } const isObject = (val: unknown) => typeof val === 'object' && val !== null && !Array.isArray(val); @@ -67,7 +68,8 @@ export const useVault = (vaultAddress?: string | undefined) => { TVL, totalSupply, idleFunds, - investedFunds + investedFunds, + fees ] = await Promise.all([ getVaultManager(vaultAddress), getVaultEmergencyManager(vaultAddress), @@ -77,7 +79,8 @@ export const useVault = (vaultAddress?: string | undefined) => { getTVL(vaultAddress), getVaultTotalSupply(vaultAddress), getIdleFunds(vaultAddress), - getInvestedFunds(vaultAddress) + getInvestedFunds(vaultAddress), + getFees(vaultAddress) ]); for (let asset of assets){ const symbol = await getTokenSymbol(asset.address, sorobanContext); @@ -96,6 +99,7 @@ export const useVault = (vaultAddress?: string | undefined) => { totalSupply: totalSupply || 0, idleFunds: idleFunds || [], investedFunds: investedFunds || [], + fees: fees || [50,0], } return newData } catch (error) { @@ -201,6 +205,14 @@ export const useVault = (vaultAddress?: string | undefined) => { console.error(error); } } + const getFees = async (vaultAddress: string) => { + try { + const fees = await vault(VaultMethod.GETFEES, vaultAddress, undefined, false).then((res: any) => scValToNative(res)); + return fees || [50,0]; + } catch (error) { + console.error(error); + } + } const vaultInfo = getVaultInfo(vaultAddress!); return { @@ -215,6 +227,7 @@ export const useVault = (vaultAddress?: string | undefined) => { getUserBalance, getTVL, getIdleFunds, - getInvestedFunds, + getInvestedFunds, + getFees }; } \ No newline at end of file diff --git a/apps/dapp/src/store/lib/types.ts b/apps/dapp/src/store/lib/types.ts index a699867a..6b21e4e3 100644 --- a/apps/dapp/src/store/lib/types.ts +++ b/apps/dapp/src/store/lib/types.ts @@ -39,7 +39,8 @@ export interface VaultData { TVL: number; totalSupply: number; idleFunds: AssetAmmount[]; - investedFunds: AssetAmmount[] + investedFunds: AssetAmmount[]; + fees: number[]; userBalance?: number; } From 0f28107f3514b9afd9b04cd7eb0a04b066d7922d Mon Sep 17 00:00:00 2001 From: Matias Poblete <86752543+MattPoblete@users.noreply.github.com> Date: Thu, 28 Nov 2024 14:27:16 -0300 Subject: [PATCH 38/50] =?UTF-8?q?=E2=9C=A8Display=20balances=20and=20resul?= =?UTF-8?q?ts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/tests/testTwoStrategiesVault.ts | 66 ++++++++++++++++++- apps/contracts/src/tests/vault.ts | 47 ++++++++++++- 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/apps/contracts/src/tests/testTwoStrategiesVault.ts b/apps/contracts/src/tests/testTwoStrategiesVault.ts index e2285976..d8f22be6 100644 --- a/apps/contracts/src/tests/testTwoStrategiesVault.ts +++ b/apps/contracts/src/tests/testTwoStrategiesVault.ts @@ -10,7 +10,7 @@ import { randomBytes } from "crypto"; import { AddressBook } from "../utils/address_book.js"; import { airdropAccount, invokeContract, invokeCustomContract } from "../utils/contract.js"; import { config } from "../utils/env_config.js"; -import { ActionType, AssetInvestmentAllocation, depositToVault, Instruction, investVault, rebalanceVault } from "./vault.js"; +import { ActionType, AssetInvestmentAllocation, depositToVault, getVaultBalanceInStrategy, Instruction, investVault, rebalanceVault, fetchParsedCurrentIdleFunds, fetchCurrentInvestedFunds } from "./vault.js"; const soroswapUSDC = new Address("CAAFIHB4I7WQMJMKC22CZVQNNX7EONWSOMT6SUXK6I3G3F6J4XFRWNDI"); @@ -110,6 +110,8 @@ export async function deployVaultTwoStrategies(addressBook: AddressBook) { const network = process.argv[2]; const addressBook = AddressBook.loadFromFile(network); +const hodl_strategy = addressBook.getContractId("hodl_strategy"); +const fixed_apr_strategy = addressBook.getContractId("fixed_apr_strategy"); const xlm: Asset = Asset.native() const loadedConfig = config(network); @@ -138,7 +140,7 @@ const mintToken = async () => { await mintToken(); // Step 1: Deposit to vault and capture initial balances -const { user, balanceBefore: depositBalanceBefore, result: depositResult, balanceAfter: depositBalanceAfter } +const { user, balanceBefore: depositBalanceBefore, result: depositResult, balanceAfter: depositBalanceAfter, status: depositStatus } = await depositToVault(deployedVault, [initialAmount], testUser); console.log(" -- ") console.log(" -- ") @@ -146,6 +148,14 @@ console.log("Step 1: Deposited to Vault using user:", user.publicKey(), "with ba console.log(" -- ") console.log(" -- ") + + +const idleFundsAfterDeposit = await fetchParsedCurrentIdleFunds(deployedVault, user); +const investedFundsAfterDeposit = await fetchCurrentInvestedFunds(deployedVault, user); + +const hodlBalanceBeforeInvest = await getVaultBalanceInStrategy(hodl_strategy, deployedVault, user); +const fixedBalanceBeforeInvest = await getVaultBalanceInStrategy(fixed_apr_strategy, deployedVault, user); + // Step 2: Invest in vault idle funds const investParams: AssetInvestmentAllocation[] = [ { @@ -167,12 +177,19 @@ const manager = loadedConfig.getUser("DEFINDEX_MANAGER_SECRET_KEY"); const investResult = await investVault(deployedVault, investParams, manager) console.log('🚀 « investResult:', investResult); +const idleFundsAfterInvest = await fetchParsedCurrentIdleFunds(deployedVault, user); +const investedFundsAfterInvest = await fetchCurrentInvestedFunds(deployedVault, user); + +const afterInvestHodlBalance = await getVaultBalanceInStrategy(hodl_strategy, deployedVault, user); +const afterInvestFixedBalance = await getVaultBalanceInStrategy(fixed_apr_strategy, deployedVault, user); + // 10000 USDC -> Total Balance // 1500 USDC -> Hodl Strategy // 2000 USDC -> Fixed Strategy // 6500 USDC -> Idle // Step 3: Rebalance Vault + const rebalanceParams: Instruction[] = [ { action: ActionType.Withdraw, @@ -192,6 +209,8 @@ const rebalanceParams: Instruction[] = [ console.log('🚀 « rebalanceParams:', rebalanceParams); +// this should leave us with: + // this should leave us with: // 10000 USDC -> Total Balance // 1000 USDC -> Hodl Strategy @@ -199,4 +218,45 @@ console.log('🚀 « rebalanceParams:', rebalanceParams); // 3500 USDC -> Idle const rebalanceResult = await rebalanceVault(deployedVault, rebalanceParams, manager); -console.log('🚀 « rebalanceResult:', rebalanceResult); \ No newline at end of file +console.log('🚀 « rebalanceResult:', rebalanceResult) + +const idleFundsAfterRebalance = await fetchParsedCurrentIdleFunds(deployedVault, user); +const investedFundsAfterRebalance = await fetchCurrentInvestedFunds(deployedVault, user); + +const afterRebalanceHodlBalance = await getVaultBalanceInStrategy(hodl_strategy, deployedVault, user); +const afterRebalanceFixedBalance = await getVaultBalanceInStrategy(fixed_apr_strategy, deployedVault, user); + +console.table({ + hodlStrategy: { + 'Balance before invest': hodlBalanceBeforeInvest, + 'Balance after invest': afterInvestHodlBalance, + 'Balance after rebalance': afterRebalanceHodlBalance + }, + fixedStrategy: { + 'Balance before invest': fixedBalanceBeforeInvest, + 'Balance after invest': afterInvestFixedBalance, + 'Balance after rebalance': afterRebalanceFixedBalance + }, + 'Invested funds': { + 'Balance before invest': investedFundsAfterDeposit[0].amount, + 'Balance after invest': investedFundsAfterInvest[0].amount, + 'Balance after rebalance': investedFundsAfterRebalance[0].amount + }, + 'Idle funds': { + 'Balance before invest': idleFundsAfterDeposit[0].amount, + 'Balance after invest': idleFundsAfterInvest[0].amount, + 'Balance after rebalance': idleFundsAfterRebalance[0].amount + } +}) + +console.table({ + deposit: { + 'Status': depositStatus ? '🟢 Success' : '🔴 Failed', + }, + invest: { + 'Status': investResult.status ? '🟢 Success' : '🔴 Failed', + }, + rebalance: { + 'Status': rebalanceResult.status ? '🟢 Success' : '🔴 Failed', + } +}) \ No newline at end of file diff --git a/apps/contracts/src/tests/vault.ts b/apps/contracts/src/tests/vault.ts index 87eb311a..4260782b 100644 --- a/apps/contracts/src/tests/vault.ts +++ b/apps/contracts/src/tests/vault.ts @@ -75,7 +75,7 @@ export async function depositToVault(deployedVault: string, amount: number[], us throw error; } - return { user: newUser, balanceBefore, result, balanceAfter }; + return { user: newUser, balanceBefore, result, balanceAfter, status:true }; } @@ -191,6 +191,21 @@ export async function fetchCurrentIdleFunds(deployedVault: string, user: Keypair throw error; } } + +export async function fetchParsedCurrentIdleFunds(deployedVault: string, user: Keypair) { + try { + const res = await invokeCustomContract(deployedVault, "fetch_current_idle_funds", [], user); + const funds = scValToNative(res.returnValue); + const mappedFunds = Object.entries(funds).map(([key, value]) => ({ + address: key, + amount: value, + })); + return mappedFunds; + } catch (error) { + console.error("Error:", error); + throw error; + } +} export interface AssetInvestmentAllocation { asset: Address; strategy_investments: { amount: bigint, strategy: Address }[]; @@ -239,7 +254,7 @@ export async function investVault( manager ); console.log("Investment successful:", scValToNative(investResult.returnValue)); - return investResult; + return {result: investResult, status: true}; } catch (error) { console.error("Investment failed:", error); throw error; @@ -337,7 +352,7 @@ export async function rebalanceVault(deployedVault: string, instructions: Instru manager ); console.log("Rebalance successful:", scValToNative(investResult.returnValue)); - return investResult; + return {result: investResult, status: true}; } catch (error) { console.error("Rebalance failed:", error); throw error; @@ -436,4 +451,30 @@ function mapSwapDetailsExactOut(details: SwapDetailsExactOut) { ), }), ]; +} + +export async function getVaultBalanceInStrategy(strategyAddress: string, vaultAddress: string, user: Keypair) { + const address = new Address(vaultAddress); + try { + const res = await invokeCustomContract(strategyAddress, "balance",[address.toScVal()],user) + return scValToNative(res.returnValue); + } catch (error) { + console.error('🔴 « error:', error); + return 0; + } + } + +export async function fetchCurrentInvestedFunds(deployedVault:string, user:Keypair) { + try { + const res = await invokeCustomContract(deployedVault, "fetch_current_invested_funds", [], user); + const funds = scValToNative(res.returnValue); + const mappedFunds = Object.entries(funds).map(([key, value]) => ({ + address: key, + amount: value, + })); + return mappedFunds; + } catch (error) { + console.error("Error:", error); + throw error; + } } \ No newline at end of file From c28a428ecf48d1be8bed9d7d7e3af42abd9c3e1b Mon Sep 17 00:00:00 2001 From: chopan123 Date: Thu, 28 Nov 2024 18:04:32 -0300 Subject: [PATCH 39/50] fees: explained better --- .../02-state-of-the-art/01-yearn-finance.md | 9 +++ .../02-contracts/01-vault-contract.md | 62 ++++++++++--------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/apps/docs/10-whitepaper/02-state-of-the-art/01-yearn-finance.md b/apps/docs/10-whitepaper/02-state-of-the-art/01-yearn-finance.md index 13744f2f..b41f750c 100644 --- a/apps/docs/10-whitepaper/02-state-of-the-art/01-yearn-finance.md +++ b/apps/docs/10-whitepaper/02-state-of-the-art/01-yearn-finance.md @@ -240,3 +240,12 @@ performance_fees = total_fees - protocol_fees = 18 Fees are collected when a strategy reports gains or losses via the report() function. During the report, the strategy will calculate the gains since the last report and then calculate the fees based on the gains. This fees are then distributed as shares of the vault. Then, fees are collected per strategy. + +Accountant reports the fees or refunds to the vault, from the gains or losses of the strategy. Then, the vault will calculate the fees and the protocol fees and then distribute the fees to the vault manager and the protocol fee recipient. This accountant is an interface and apparently it depends on the vault. + +Yearn burns shares when there is fees or losses. When there is a loss and there is still fees not paid, the vault will burn shares to pay the fees. + +The Vaults utilizes several mechanisms to mitigate price per share (pps) fluctuations and manipulation: +1. Internal accounting is used instead of balanceOf() to keep track of the vault's debt and idle. +2. A profit locking machenism designed by V3 Vaults locks profits or accountant's refunds by issuing new shares to the vault itself that are slowly burnt over the unlock perior. +3. In the event of losses or fees, the vault will always try to offset them by butning locked shares it owns. the price per share is expected to decrease only when excess losses or fees occur upon processing a report, or a loss occurs upon force revoking a strategy. diff --git a/apps/docs/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract.md b/apps/docs/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract.md index d0b94513..52eedf19 100644 --- a/apps/docs/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract.md +++ b/apps/docs/10-whitepaper/03-the-defindex-approach/02-contracts/01-vault-contract.md @@ -147,29 +147,28 @@ The fees collected are from the gains of the strategies. Thus, it is a performan ### Fee Collection Methodology -The fee collection process locks the fees in the vault until they are distributed. These fees are got from the gains of the strategies. +The DeFindex fee collection process is designed to track fees in the vault until distribution, with fees originating from the strategy gains. This ensures an organized and accountable fee handling system. -Let's see an example of how this works. -1. The fees for the DeFindex Protocol Fee Receiver is set to 5% and the fees for the Vault Fee Receiver is set to 15%. -2. A user deposits 100 USDC into the vault that only has one strategy. -3. The strategy gains 10 USDC. -4. The strategy reports the gains to the vault. -5. The vault collects the fees from the gains. In this case, 2 USDC (20% of 10 USDC). -6. The vault distributes the fees to the protocol and the manager. +#### General Overview +Fees are charged on a per-strategy basis, meaning each strategy independently calculates its gains and the corresponding fees. These fees are then collected and distributed to the protocol and manager. The fee percentages can vary, and the vault adjusts total assets accordingly by deducting fees from the strategy gains. -Now the total assets of the vault are 100 USDC + 10 USDC - 2 USDC = 108 USDC. +#### Detailed Workflow -And the 2 USDC are locked in the vault, for future distributions. +1. **Fee Structure Example**: + - Protocol Fee Receiver: 5% + - Vault Fee Receiver: 15% -These fees are collected in a per strategy basis. So, if there are multiple strategies, each strategy will have its own fees. +2. **Execution Example**: + - A user deposits 100 USDC into a vault with one strategy. + - The strategy earns 10 USDC in gains. + - The vault collects 20% of the gains as fees (2 USDC). + - Fees are distributed between the protocol (0.5 USDC) and the manager (1.5 USDC). + - The total assets of the vault become \(100 + 10 - 2 = 108\) USDC. -In order to account for the fees, as fees depend on the gains of the strategy, we need to track the gains of the strategy. -So, we create a function called `report()` that will keep record of the gains and losses of the strategy. -In this function, we will: -- store gains and losses from the strategy since the last time we checked. - -Let's see a pseudocode for this function: +#### Strategy Gains Tracking +Since fees depend on strategy performance, gains and losses must be tracked meticulously. To achieve this, a `report()` function is implemented to log the gains or losses since the last update. +**Pseudocode for Tracking Gains and Losses**: ```rust fn report(strategy: Address) -> (u256, u256) { let current_balance = get_current_balance(strategy); @@ -186,29 +185,34 @@ fn report_all_strategies() { } } ``` -This report_all_strategies() function is called at the beginning whenever the manager wants to rebalance the DeFindex, or a user wants to deposit or withdraw. -Then, at the end of a rebalance, deposit or withdrawal function call, the store_prev_balance() function is called to update the previous balance of the strategy. - -Then another function will be called to distribute the fees to the protocol and the manager. And it will set the gains and losses to 0. +- **Usage**: The `report_all_strategies()` function is invoked during key operations such as rebalancing, deposits, or withdrawals to ensure accurate gain tracking. -Here is a pseudocode for the fee distribution function: +#### Fee Distribution +Once gains are tracked, fees are calculated and distributed accordingly. After distribution, the gains and losses for each strategy are reset to 0. +**Pseudocode for Fee Distribution**: ```rust fn distribute_fees() { for strategy in strategies { let gains_or_losses = get_gains_or_losses(strategy); - let protocol_fee = gains_or_losses * protocol_fee_receiver/MAX_BPS; - let vault_fee = gains_or_losses * vault_fee_receiver/MAX_BPS; - transfer_from_strategy(strategy.asset, protocol_fee_receiver, protocol_fee); - transfer_from_strategy(strategy.asset, vault_fee_receiver, vault_fee); - reset_gains_or_losses(strategy); + if gains_or_losses > 0 { + let protocol_fee = gains_or_losses * protocol_fee_receiver / MAX_BPS; + let vault_fee = gains_or_losses * vault_fee_receiver / MAX_BPS; + transfer_from_strategy(strategy.asset, protocol_fee_receiver, protocol_fee); + transfer_from_strategy(strategy.asset, vault_fee_receiver, vault_fee); + reset_gains_or_losses(strategy); + } } } ``` -**Note:** in order to show the current balance for users, we should discount the fees (if there is gains) from the total assets of the Vaults. - This function is public and can be called by anyone. +#### Displaying User Balances +To provide users with an accurate view of their balances, the vault deducts any outstanding fees from the total assets when reporting current balances. + +By following this structured methodology, DeFindex ensures transparent and fair fee collection, tracking, and distribution processes. + + It is expected that the Fee Receiver is associated with the manager, allowing the entity managing the Vault to be compensated through the Fee Receiver. In other words, the Fee Receiver could be the manager using the same address, or it could be a different entity such as a streaming contract, a DAO, or another party.