diff --git a/Scarb.toml b/Scarb.toml index 230d363..442a8f6 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -98,4 +98,4 @@ block_id.number = "1256209" [profile.dev.cairo] unstable-add-statements-code-locations-debug-info = true unstable-add-statements-functions-debug-info = true -inlining-strategy = "avoid" +inlining-strategy = "avoid" \ No newline at end of file diff --git a/scripts/vesu_rebalance/actions.ts b/scripts/vesu_rebalance/actions.ts index deb0b48..d366366 100644 --- a/scripts/vesu_rebalance/actions.ts +++ b/scripts/vesu_rebalance/actions.ts @@ -17,15 +17,18 @@ async function main() { // const acc = getAccount(ACCOUNT_NAME); const acc = new Account(getRpcProvider(), process.env.ADDRESS!, process.env.PK!, "1", '0x3'); - const depositCalls = await vesuRebalance.depositCall( - { - tokenInfo: vesuRebalance.asset(), - amount: new Web3Number(0.01, 6) - }, ContractAddr.from(acc.address) - ); - console.log(depositCalls) - const gas = await acc.estimateInvokeFee(depositCalls); - console.log(`Estimated gas: `, gas); + // const depositCalls = await vesuRebalance.depositCall( + // { + // tokenInfo: vesuRebalance.asset(), + // amount: new Web3Number(0.01, 6) + // }, ContractAddr.from(acc.address) + // ); + // console.log(depositCalls) + // const gas = await acc.estimateInvokeFee(depositCalls); + // console.log(`Estimated gas: `, gas); + + const fees = await vesuRebalance.getFee(await vesuRebalance.getPools()); + console.log(`Fees: ${JSON.stringify(fees)}`); // const tx = await acc.execute(depositCalls); // console.log(tx.transaction_hash); // await getRpcProvider().waitForTransaction(tx.transaction_hash, { @@ -79,7 +82,7 @@ async function harvest() { const call = await vesuRebalance.harvest(riskAcc); calls.push(...call); } - const _calls = [...calls.slice(0, 3)]; // ! TODO Ensure I switch to all calls later + const _calls = [...calls.slice(0, 4)]; const gas = await riskAcc.estimateInvokeFee(_calls); const tx = await riskAcc.execute(_calls); console.log(`Harvest tx: ${tx.transaction_hash}`); diff --git a/src/helpers/constants.cairo b/src/helpers/constants.cairo index 85d9d05..7170578 100644 --- a/src/helpers/constants.cairo +++ b/src/helpers/constants.cairo @@ -5,12 +5,16 @@ pub const BASIS_POINTS_FACTOR: u32 = 10000; pub const TWO_POWER_128: u128 = 0xffffffffffffffffffffffffffffffff; pub const TWO_POWER_256: u256 = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; -pub const MAX_SLIPPAGE_BPS: u32 = 300; // 3% +pub const MAX_SLIPPAGE_BPS: u32 = 300; // 3% pub fn VESU_SINGLETON_ADDRESS() -> ContractAddress { contract_address_const::<0x2545b2e5d519fc230e9cd781046d3a64e092114f07e44771e0d719d148725ef>() } +pub fn EKUBO_FEE_COLLECTOR() -> ContractAddress { + contract_address_const::<0x053c69eDcB1a65A8687Ec7Ad8fC23fD7cc815186fE4380bb6A5bf31df52361FF>() +} + pub fn RE7_XSTRK_POOL() -> ContractAddress { contract_address_const::<0x52fb52363939c3aa848f8f4ac28f0a51379f8d1b971d8444de25fbd77d8f161>() } diff --git a/src/strategies/cl_vault/test.cairo b/src/strategies/cl_vault/test.cairo index d026beb..f0fc801 100644 --- a/src/strategies/cl_vault/test.cairo +++ b/src/strategies/cl_vault/test.cairo @@ -225,7 +225,7 @@ pub mod test_cl_vault { calldata.append(constants::EKUBO_CORE().into()); calldata.append(constants::ORACLE_OURS().into()); let fee_settings = FeeSettings { - fee_bps: fee_bps, fee_collector: contract_address_const::<0x123>() + fee_bps: fee_bps, fee_collector: constants::EKUBO_FEE_COLLECTOR() }; fee_settings.serialize(ref calldata); let (address, _) = clVault.deploy(@calldata).expect('ClVault deploy failed'); @@ -261,7 +261,7 @@ pub mod test_cl_vault { calldata.append(constants::EKUBO_CORE().into()); calldata.append(constants::ORACLE_OURS().into()); let fee_settings = FeeSettings { - fee_bps: fee_bps, fee_collector: contract_address_const::<0x123>() + fee_bps: fee_bps, fee_collector: constants::EKUBO_FEE_COLLECTOR() }; fee_settings.serialize(ref calldata); let (address, _) = clVault.deploy(@calldata).expect('ClVault deploy failed'); @@ -299,6 +299,175 @@ pub mod test_cl_vault { stop_cheat_caller_address(constants::XSTRK_ADDRESS()); } + fn ekubo_deposit() -> (IClVaultDispatcher, u256) { + let amount = 10 * pow::ten_pow(18); + let this = get_contract_address(); + /// println!("this: {:?}", this); + + // approve the necessary tokens linked with liquidity to be created + let (clVault, _) = deploy_cl_vault(); + assert(clVault.get_settings().contract_nft_id == 0, 'nft id not zero on deploy'); + vault_init(amount * 2); + ERC20Helper::approve(constants::ETH_ADDRESS(), clVault.contract_address, amount * 2); + ERC20Helper::approve(constants::WST_ADDRESS(), clVault.contract_address, amount * 2); + /// println!("clVault.contract_address: {:?}", clVault.contract_address); + + // deposit once + let expected_shares1 = clVault.convert_to_shares(amount, amount); + let shares1 = clVault.deposit(amount, amount, this); + assert(shares1 > 0, 'invalid shares minted'); + assert(shares1 == expected_shares1, 'invalid shares minted'); + + return (clVault, shares1); + } + + fn ekubo_deposit_xstrk() -> (IClVaultDispatcher, u256) { + let amount = 500000 * pow::ten_pow(18); + vault_init_xstrk_pool(amount * 3); + + let this = get_contract_address(); + let (clVault, _) = deploy_cl_vault_xstrk(); + ERC20Helper::approve(constants::STRK_ADDRESS(), clVault.contract_address, amount); + ERC20Helper::approve(constants::XSTRK_ADDRESS(), clVault.contract_address, amount); + + let expected_shares1 = clVault.convert_to_shares(amount, amount); + let shares = clVault.deposit(amount, amount, this); + assert(shares > 0, 'invalid shares minted'); + assert(shares == expected_shares1, 'invalid shares minted'); + + return (clVault, shares); + } + + fn ekubo_withdraw_xstrk(clVault: IClVaultDispatcher, withdraw_amount: u256) { + let this = get_contract_address(); + let amount = 500000 * pow::ten_pow(18); + let vault_shares = ERC20Helper::balanceOf(clVault.contract_address, this); + let strk_before_withdraw = ERC20Helper::balanceOf(constants::STRK_ADDRESS(), this); + let xstrk_before_withdraw = ERC20Helper::balanceOf(constants::XSTRK_ADDRESS(), this); + clVault.withdraw(withdraw_amount, this); + + let shares_bal = ERC20Helper::balanceOf(clVault.contract_address, this); + assert(shares_bal == (vault_shares - withdraw_amount), 'invalid shares minted'); + let partial_strk_bal = ERC20Helper::balanceOf(constants::STRK_ADDRESS(), this); + let partial_xstrk_bal = ERC20Helper::balanceOf(constants::XSTRK_ADDRESS(), this); + assert(partial_strk_bal > strk_before_withdraw, 'eth not withdrawn'); + assert(partial_xstrk_bal > xstrk_before_withdraw, 'wst not withdrawn'); + let vault_bal0 = ERC20Helper::balanceOf( + constants::STRK_ADDRESS(), clVault.contract_address + ); + let vault_bal1 = ERC20Helper::balanceOf( + constants::XSTRK_ADDRESS(), clVault.contract_address + ); + /// println!("vault bal0: {:?}", vault_bal0); + /// println!("vault bal1: {:?}", vault_bal1); + assert( + safe_decimal_math::is_under_by_percent_bps(vault_bal0, amount, 1), 'invalid token bal' + ); + assert( + safe_decimal_math::is_under_by_percent_bps(vault_bal1, amount, 1), 'invalid token bal' + ); + } + + fn ekubo_withdraw(clVault: IClVaultDispatcher, withdraw_amount: u256) { + let this = get_contract_address(); + let position = clVault.get_position(); + let liquidity_256: u256 = position.liquidity.into(); + let vault_shares = ERC20Helper::balanceOf(clVault.contract_address, this); + let eth_before_withdraw = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), this); + let wst_before_withdraw = ERC20Helper::balanceOf(constants::WST_ADDRESS(), this); + clVault.withdraw(withdraw_amount, this); + + let shares_bal = ERC20Helper::balanceOf(clVault.contract_address, this); + assert(shares_bal == (vault_shares - withdraw_amount), 'invalid shares minted'); + let partial_eth_bal = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), this); + let partial_wst_bal = ERC20Helper::balanceOf(constants::WST_ADDRESS(), this); + assert(partial_eth_bal > eth_before_withdraw, 'eth not withdrawn'); + assert(partial_wst_bal > wst_before_withdraw, 'wst not withdrawn'); + let liquidity_256_after_withdraw: u256 = clVault.get_position().liquidity.into(); + assert( + liquidity_256_after_withdraw == (liquidity_256 - withdraw_amount), + 'invalid liquidity removed' + ); + assert( + ERC20Helper::balanceOf(constants::ETH_ADDRESS(), clVault.contract_address) == 0, + 'invalid token bal' + ); + assert( + ERC20Helper::balanceOf(constants::WST_ADDRESS(), clVault.contract_address) == 0, + 'invalid token bal' + ); + } + + fn ekubo_swaps() { + let pool_price_before = IEkuboDispatcher { contract_address: constants::EKUBO_POSITIONS() } + .get_pool_price(get_pool_key()) + .tick + .mag; + let mut x = 1; + loop { + x += 1; + let eth_route = get_eth_wst_route(); + ekubo_swap( + eth_route, constants::ETH_ADDRESS(), constants::WST_ADDRESS(), 400000000000000000 + ); + + let wst_route = get_wst_eth_route(); + ekubo_swap( + wst_route, constants::WST_ADDRESS(), constants::ETH_ADDRESS(), 400000000000000000 + ); + if x == 50 { + break; + } + }; + /// println!("fifth swap passed"); + + let pool_price_after = IEkuboDispatcher { contract_address: constants::EKUBO_POSITIONS() } + .get_pool_price(get_pool_key()) + .tick + .mag; + + /// println!("pool price before: {:?}", pool_price_before); + /// println!("pool price after: {:?}", pool_price_after); + assert(pool_price_before != pool_price_after, 'invalid swap pool'); + } + + fn ekubo_swaps_xstrk() { + let pool_price_before = IEkuboDispatcher { contract_address: constants::EKUBO_POSITIONS() } + .get_pool_price(get_pool_key_xstrk()) + .tick + .mag; + + let mut x = 1; + loop { + x += 1; + let eth_route = get_strk_xstrk_route(); + ekubo_swap( + eth_route, + constants::STRK_ADDRESS(), + constants::XSTRK_ADDRESS(), + 5000000000000000000000 + ); + + let wst_route = get_xstrk_strk_route(); + ekubo_swap( + wst_route, + constants::XSTRK_ADDRESS(), + constants::STRK_ADDRESS(), + 500000000000000000000 + ); + if x == 50 { + break; + } + }; + + let pool_price_after = IEkuboDispatcher { contract_address: constants::EKUBO_POSITIONS() } + .get_pool_price(get_pool_key_xstrk()) + .tick + .mag; + + assert(pool_price_before != pool_price_after, 'invalid swap pool'); + } + #[test] #[fork("mainnet_1134787")] fn test_clVault_constructer() { @@ -326,23 +495,8 @@ pub mod test_cl_vault { #[test] #[fork("mainnet_1134787")] fn test_ekubo_deposit() { - let amount = 10 * pow::ten_pow(18); + let (clVault, _) = ekubo_deposit(); let this = get_contract_address(); - /// println!("this: {:?}", this); - - // approve the necessary tokens linked with liquidity to be created - let (clVault, _) = deploy_cl_vault(); - assert(clVault.get_settings().contract_nft_id == 0, 'nft id not zero on deploy'); - vault_init(amount); - ERC20Helper::approve(constants::ETH_ADDRESS(), clVault.contract_address, amount * 2); - ERC20Helper::approve(constants::WST_ADDRESS(), clVault.contract_address, amount * 2); - /// println!("clVault.contract_address: {:?}", clVault.contract_address); - - // deposit once - let expected_shares1 = clVault.convert_to_shares(amount, amount); - let shares1 = clVault.deposit(amount, amount, this); - assert(shares1 > 0, 'invalid shares minted'); - assert(shares1 == expected_shares1, 'invalid shares minted'); let settings: ClSettings = clVault.get_settings(); let nft_id: u64 = settings.contract_nft_id; let nft_id_u256: u256 = nft_id.into(); @@ -369,6 +523,7 @@ pub mod test_cl_vault { assert((cl_shares_bal) == (total_liquidity), 'invalid shares minted'); // deposit again + let amount = 10 * pow::ten_pow(18); vault_init(amount); let expected_shares2 = clVault.convert_to_shares(amount, amount); let shares2 = clVault.deposit(amount, amount, this); @@ -401,61 +556,23 @@ pub mod test_cl_vault { #[test] #[fork("mainnet_1134787")] fn test_ekubo_withdraw() { - let amount = 10 * pow::ten_pow(18); - + let (clVault, shares) = ekubo_deposit(); let this = get_contract_address(); - let (clVault, _) = deploy_cl_vault(); - ERC20Helper::approve(constants::ETH_ADDRESS(), clVault.contract_address, amount * 2); - ERC20Helper::approve(constants::WST_ADDRESS(), clVault.contract_address, amount * 2); - vault_init(amount); - - //deposit - let shares = clVault.deposit(amount, amount, this); - assert(shares > 0, 'invalid shares minted'); let position = clVault.get_position(); let liquidity_256: u256 = position.liquidity.into(); let vault_shares = ERC20Helper::balanceOf(clVault.contract_address, this); assert(liquidity_256 == vault_shares, 'invalid liquidity'); assert(shares == vault_shares, 'invalid liquidity'); - let eth_before_withdraw = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), this); - let wst_before_withdraw = ERC20Helper::balanceOf(constants::WST_ADDRESS(), this); - //withdraw partial - assert( - ERC20Helper::balanceOf(constants::ETH_ADDRESS(), clVault.contract_address) == 0, - 'invalid token bal' - ); - assert( - ERC20Helper::balanceOf(constants::WST_ADDRESS(), clVault.contract_address) == 0, - 'invalid token bal' - ); let withdraw_amount = liquidity_256 / 2; - clVault.withdraw(withdraw_amount, this); + ekubo_withdraw(clVault, withdraw_amount); - let shares_bal = ERC20Helper::balanceOf(clVault.contract_address, this); - assert(shares_bal == (shares - withdraw_amount), 'invalid shares minted'); + //withdraw full let partial_eth_bal = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), this); let partial_wst_bal = ERC20Helper::balanceOf(constants::WST_ADDRESS(), this); - assert(partial_eth_bal > eth_before_withdraw, 'eth not withdrawn'); - assert(partial_wst_bal > wst_before_withdraw, 'wst not withdrawn'); - let liquidity_256_after_withdraw: u256 = clVault.get_position().liquidity.into(); - assert( - liquidity_256_after_withdraw == (liquidity_256 - withdraw_amount), - 'invalid liquidity removed' - ); - assert( - ERC20Helper::balanceOf(constants::ETH_ADDRESS(), clVault.contract_address) == 0, - 'invalid token bal' - ); - assert( - ERC20Helper::balanceOf(constants::WST_ADDRESS(), clVault.contract_address) == 0, - 'invalid token bal' - ); - - //withdraw full let shares_left = ERC20Helper::balanceOf(clVault.contract_address, this); - clVault.withdraw(shares_left, this); + ekubo_withdraw(clVault, shares_left); let liquidity_left = clVault.get_position().liquidity; let neg_liq = liquidity_left / 1000; assert(neg_liq == 0, 'liquidity not 0'); @@ -464,15 +581,6 @@ pub mod test_cl_vault { let total_wst_bal = ERC20Helper::balanceOf(constants::WST_ADDRESS(), this); assert(total_eth_bal > partial_eth_bal, 'total eth not withdrawn'); assert(total_wst_bal > partial_wst_bal, 'total wst eth not withdrawn'); - assert( - ERC20Helper::balanceOf(constants::ETH_ADDRESS(), clVault.contract_address) == 0, - 'invalid token bal' - ); - assert( - ERC20Helper::balanceOf(constants::WST_ADDRESS(), clVault.contract_address) == 0, - 'invalid token bal' - ); - let shares_bal = ERC20Helper::balanceOf(clVault.contract_address, this); assert(shares_bal == 0, 'invalid shares minted'); } @@ -480,16 +588,7 @@ pub mod test_cl_vault { #[test] #[fork("mainnet_1134787")] fn test_handle_fees() { - let amount = 10 * pow::ten_pow(18); - vault_init(amount * 2); - - let this = get_contract_address(); - let (clVault, _) = deploy_cl_vault(); - ERC20Helper::approve(constants::ETH_ADDRESS(), clVault.contract_address, amount); - ERC20Helper::approve(constants::WST_ADDRESS(), clVault.contract_address, amount); - - //deposit - let _ = clVault.deposit(amount, amount, this); + let (clVault, _) = ekubo_deposit(); // check if function works with 0 fees let liquidity_before_fees = clVault.get_position().liquidity; @@ -498,49 +597,7 @@ pub mod test_cl_vault { assert(liquidity_after_fees == liquidity_before_fees, 'invalid liquidity'); /// println!("first handle fee passed"); - let pool_price_before = IEkuboDispatcher { contract_address: constants::EKUBO_POSITIONS() } - .get_pool_price(get_pool_key()) - .tick - .mag; - - let eth_route = get_eth_wst_route(); - ekubo_swap( - eth_route, constants::ETH_ADDRESS(), constants::WST_ADDRESS(), 1000000000000000000 - ); - /// println!("first swap passed"); - - let wst_route = get_wst_eth_route(); - ekubo_swap( - wst_route, constants::WST_ADDRESS(), constants::ETH_ADDRESS(), 400000000000000000 - ); - /// println!("second swap passed"); - - let wst_route = get_wst_eth_route(); - ekubo_swap( - wst_route, constants::WST_ADDRESS(), constants::ETH_ADDRESS(), 300000000000000000 - ); - /// println!("third swap passed"); - - let eth_route = get_eth_wst_route(); - ekubo_swap( - eth_route, constants::ETH_ADDRESS(), constants::WST_ADDRESS(), 100000000000000000 - ); - /// println!("fourth swap passed"); - - let eth_route = get_eth_wst_route(); - ekubo_swap( - eth_route, constants::ETH_ADDRESS(), constants::WST_ADDRESS(), 200000000000000000 - ); - /// println!("fifth swap passed"); - - let pool_price_after = IEkuboDispatcher { contract_address: constants::EKUBO_POSITIONS() } - .get_pool_price(get_pool_key()) - .tick - .mag; - - /// println!("pool price before: {:?}", pool_price_before); - /// println!("pool price after: {:?}", pool_price_after); - assert(pool_price_before != pool_price_after, 'invalid swap pool'); + ekubo_swaps(); //call handle fees and check how much fees was generated from collect fees let liquidity_before_fees = clVault.get_position().liquidity; @@ -553,16 +610,7 @@ pub mod test_cl_vault { #[test] #[fork("mainnet_1134787")] fn test_ekubo_rebalance() { - let amount = 10 * pow::ten_pow(18); - // deploy_avnu(); - vault_init(amount); - - let this = get_contract_address(); - let (clVault, _) = deploy_cl_vault(); - ERC20Helper::approve(constants::ETH_ADDRESS(), clVault.contract_address, amount); - ERC20Helper::approve(constants::WST_ADDRESS(), clVault.contract_address, amount); - - let _ = clVault.deposit(amount, amount, this); + let (clVault, _) = ekubo_deposit(); /// println!("deposit passed"); let old_bounds = clVault.get_settings().bounds_settings; @@ -606,7 +654,6 @@ pub mod test_cl_vault { clVault.rebalance(bounds, swap_params); /// println!("rebalance passed"); // assert total usd value is roughly same after rebalance - // assert bounds are updated and current liquidity > 0 let liquidity_after_rebalance = clVault.get_position().liquidity; assert(liquidity_after_rebalance > 0, 'invalid liquidity'); @@ -628,15 +675,8 @@ pub mod test_cl_vault { #[test] #[fork("mainnet_1134787")] fn test_strk_xstrk_pool() { - let amount = 500000 * pow::ten_pow(18); - vault_init_xstrk_pool(amount * 3); - + let (clVault, _) = ekubo_deposit_xstrk(); let this = get_contract_address(); - let (clVault, _) = deploy_cl_vault_xstrk(); - ERC20Helper::approve(constants::STRK_ADDRESS(), clVault.contract_address, amount); - ERC20Helper::approve(constants::XSTRK_ADDRESS(), clVault.contract_address, amount); - - let _ = clVault.deposit(amount, amount, this); let settings: ClSettings = clVault.get_settings(); let nft_id: u64 = settings.contract_nft_id; let nft_id_u256: u256 = nft_id.into(); @@ -646,47 +686,14 @@ pub mod test_cl_vault { assert(nft_disp.ownerOf(nft_id_u256) == clVault.contract_address, 'invalid owner'); assert( ERC20Helper::balanceOf(constants::STRK_ADDRESS(), clVault.contract_address) == 0, - 'invalid ETH amount' + 'invalid STRK amount' ); assert( ERC20Helper::balanceOf(constants::XSTRK_ADDRESS(), clVault.contract_address) == 0, - 'invalid WST amount' + 'invalid XSTRK amount' ); - let pool_price_before = IEkuboDispatcher { contract_address: constants::EKUBO_POSITIONS() } - .get_pool_price(get_pool_key_xstrk()) - .tick - .mag; - - let mut x = 1; - loop { - x += 1; - let eth_route = get_strk_xstrk_route(); - ekubo_swap( - eth_route, - constants::STRK_ADDRESS(), - constants::XSTRK_ADDRESS(), - 5000000000000000000000 - ); - - let wst_route = get_xstrk_strk_route(); - ekubo_swap( - wst_route, - constants::XSTRK_ADDRESS(), - constants::STRK_ADDRESS(), - 500000000000000000000 - ); - if x == 50 { - break; - } - }; - - let pool_price_after = IEkuboDispatcher { contract_address: constants::EKUBO_POSITIONS() } - .get_pool_price(get_pool_key_xstrk()) - .tick - .mag; - - assert(pool_price_before != pool_price_after, 'invalid swap pool'); + ekubo_swaps_xstrk(); //call handle fees and check how much fees was generated from collect fees let liquidity_before_fees = clVault.get_position().liquidity; @@ -694,9 +701,6 @@ pub mod test_cl_vault { let liquidity_after_fees = clVault.get_position().liquidity; assert(liquidity_after_fees >= liquidity_before_fees, 'invalid liquidity'); - - let strk_before_withdraw = ERC20Helper::balanceOf(constants::STRK_ADDRESS(), this); - let xstrk_before_withdraw = ERC20Helper::balanceOf(constants::XSTRK_ADDRESS(), this); /// println!("strk bal before withdraw {:?}", strk_before_withdraw); /// println!("xstrk bal before withdraw {:?}", xstrk_before_withdraw); @@ -705,51 +709,12 @@ pub mod test_cl_vault { let all_shares = ERC20Helper::balanceOf(clVault.contract_address, this); /// println!("all shares {:?}", all_shares); let withdraw_amount: u256 = all_shares / 2; - clVault.withdraw(withdraw_amount, this); - - let partial_strk_bal = ERC20Helper::balanceOf(constants::STRK_ADDRESS(), this); - let partial_xstrk_bal = ERC20Helper::balanceOf(constants::XSTRK_ADDRESS(), this); - /// println!("partial strk bal after withdraw {:?}", partial_strk_bal); - /// println!("partial xstrk bal after withdraw {:?}", partial_xstrk_bal); - assert(partial_strk_bal > strk_before_withdraw, 'strk not withdrawn'); - assert(partial_xstrk_bal > xstrk_before_withdraw, 'xstrk not withdrawn'); - let vault_bal0 = ERC20Helper::balanceOf( - constants::STRK_ADDRESS(), clVault.contract_address - ); - let vault_bal1 = ERC20Helper::balanceOf( - constants::XSTRK_ADDRESS(), clVault.contract_address - ); - /// println!("vault bal0: {:?}", vault_bal0); - /// println!("vault bal1: {:?}", vault_bal1); - assert( - safe_decimal_math::is_under_by_percent_bps(vault_bal0, amount, 1), 'invalid token bal' - ); - assert( - safe_decimal_math::is_under_by_percent_bps(vault_bal1, amount, 1), 'invalid token bal' - ); + ekubo_withdraw_xstrk(clVault, withdraw_amount); //withdraw full /// println!("withdraw full"); let shares_left = ERC20Helper::balanceOf(clVault.contract_address, this); - clVault.withdraw(shares_left, this); - let total_strk_bal = ERC20Helper::balanceOf(constants::STRK_ADDRESS(), this); - let total_xstrk_bal = ERC20Helper::balanceOf(constants::XSTRK_ADDRESS(), this); - assert(total_strk_bal > partial_strk_bal, 'total eth not withdrawn'); - assert(total_xstrk_bal > partial_xstrk_bal, 'total wst eth not withdrawn'); - let vault_bal0 = ERC20Helper::balanceOf( - constants::STRK_ADDRESS(), clVault.contract_address - ); - let vault_bal1 = ERC20Helper::balanceOf( - constants::XSTRK_ADDRESS(), clVault.contract_address - ); - /// println!("vault bal0: {:?}", vault_bal0); - /// println!("vault bal1: {:?}", vault_bal1); - assert( - safe_decimal_math::is_under_by_percent_bps(vault_bal0, amount, 1), 'invalid token bal' - ); - assert( - safe_decimal_math::is_under_by_percent_bps(vault_bal1, amount, 1), 'invalid token bal' - ); + ekubo_withdraw_xstrk(clVault, shares_left); } #[test] @@ -911,16 +876,11 @@ pub mod test_cl_vault { fn test_harvest_cl_vault() { let block = 100; start_cheat_block_number_global(block); - - let (clVault, _) = deploy_cl_vault(); let ekubo_defi_spring = test_utils::deploy_defi_spring_ekubo(); // deposit let amount = 10 * pow::ten_pow(18); - vault_init(amount * 2); - ERC20Helper::approve(constants::ETH_ADDRESS(), clVault.contract_address, amount * 2); - ERC20Helper::approve(constants::WST_ADDRESS(), clVault.contract_address, amount * 2); - let shares1 = clVault.deposit(amount, amount, get_contract_address()); + let (clVault, shares) = ekubo_deposit(); let rsDisp = IRewardShareDispatcher { contract_address: clVault.contract_address }; let (additional, last_block, pending_round_points) = rsDisp @@ -941,7 +901,7 @@ pub mod test_cl_vault { /// println!("shares2: {:?}", shares2); /// println!("pending_round_points: {:?}", pending_round_points); assert( - pending_round_points == (shares1 * 100).try_into().unwrap(), + pending_round_points == (shares * 100).try_into().unwrap(), 'invalid pending round points' ); @@ -951,7 +911,7 @@ pub mod test_cl_vault { let pre_bal_strk = ERC20Helper::balanceOf( constants::STRK_ADDRESS(), clVault.contract_address ); - let fee_collector = contract_address_const::<0x123>(); + let fee_collector = clVault.get_settings().fee_settings.fee_collector; let fee_pre = ERC20Helper::balanceOf(constants::STRK_ADDRESS(), fee_collector); let claim = Claim { id: 0, amount: pow::ten_pow(18).try_into().unwrap(), claimee: clVault.contract_address, @@ -1003,7 +963,7 @@ pub mod test_cl_vault { 'invalid pending round points' ); assert( - user_shares == (shares1 + shares2 + unminted.try_into().unwrap()), 'invalid user shares' + user_shares == (shares + shares2 + unminted.try_into().unwrap()), 'invalid user shares' ); // 100% withdraw @@ -1053,6 +1013,256 @@ pub mod test_cl_vault { stop_cheat_caller_address(clVault.contract_address); } + // additional tests + #[test] + #[fork("mainnet_1134787")] + fn test_cl_vault_all_functions() { + let this = get_contract_address(); + let (clVault, shares1) = ekubo_deposit(); + let settings: ClSettings = clVault.get_settings(); + let nft_id: u64 = settings.contract_nft_id; + let nft_id_u256: u256 = nft_id.into(); + let nft_disp = IEkuboNFTDispatcher { contract_address: settings.ekubo_positions_nft }; + + assert(nft_disp.ownerOf(nft_id_u256) == clVault.contract_address, 'invalid owner'); + assert( + ERC20Helper::balanceOf(constants::ETH_ADDRESS(), clVault.contract_address) == 0, + 'invalid ETH amount' + ); + assert( + ERC20Helper::balanceOf(constants::WST_ADDRESS(), clVault.contract_address) == 0, + 'invalid WST amount' + ); + + let cl_shares_bal = ERC20Helper::balanceOf(clVault.contract_address, this); + let total_liquidity: u256 = clVault.get_position().liquidity.into(); + assert((cl_shares_bal) == (total_liquidity), 'invalid shares minted'); + + let old_bounds = clVault.get_settings().bounds_settings; + let new_lower_bound: u128 = 169000; + let new_upper_bound: u128 = 180000; + let bounds = Bounds { + lower: i129 { mag: new_lower_bound, sign: false }, + upper: i129 { mag: new_upper_bound, sign: false } + }; + let mut eth_route = get_eth_wst_route(); + eth_route.percent = 1000000000000; + let pool_key = get_pool_key(); + let additional: Array = array![ + pool_key.token0.into(), // token0 + pool_key.token1.into(), // token1 + pool_key.fee.into(), // fee + pool_key.tick_spacing.into(), // tick space + pool_key.extension.into(), // extension + pow::ten_pow(70).try_into().unwrap(), // sqrt limit + ]; + eth_route.additional_swap_params = additional; + let routes: Array = array![eth_route.clone()]; + let swap_params = AvnuMultiRouteSwap { + token_from_address: eth_route.clone().token_from, + token_from_amount: 1701 * pow::ten_pow(18) / 1000, + token_to_address: eth_route.token_to, + token_to_amount: 0, + token_to_min_amount: 0, + beneficiary: clVault.contract_address, + integrator_fee_amount_bps: 0, + integrator_fee_recipient: contract_address_const::<0x00>(), + routes + }; + clVault.rebalance(bounds, swap_params); + let liquidity_after_rebalance = clVault.get_position().liquidity; + assert(liquidity_after_rebalance > 0, 'invalid liquidity'); + let bounds = clVault.get_settings().bounds_settings; + assert(bounds.lower.mag == new_lower_bound, 'invalid bound written'); + assert(bounds.upper.mag == new_upper_bound, 'invalid bound written'); + + // assert that old bounds have 0 liquidity + let position_key = PositionKey { + salt: clVault.get_settings().contract_nft_id, + owner: constants::EKUBO_POSITIONS(), + bounds: old_bounds + }; + let pos_old_bounds = IEkuboCoreDispatcher { contract_address: constants::EKUBO_CORE() } + .get_position(clVault.get_settings().pool_key, position_key); + assert(pos_old_bounds.liquidity == 0, 'Invalid liquidity rebalanced'); + + // handle fees + let liquidity_before_fees = clVault.get_position().liquidity; + clVault.handle_fees(); + let liquidity_after_fees = clVault.get_position().liquidity; + assert(liquidity_after_fees == liquidity_before_fees, 'invalid liquidity'); + + ekubo_swaps(); + + //call handle fees and check how much fees was generated from collect fees + let liquidity_before_fees = clVault.get_position().liquidity; + clVault.handle_fees(); + let liquidity_after_fees = clVault.get_position().liquidity; + + assert(liquidity_after_fees > liquidity_before_fees, 'invalid liquidity'); + + //withdraw + let eth_before_withdraw = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), this); + let wst_before_withdraw = ERC20Helper::balanceOf(constants::WST_ADDRESS(), this); + let withdraw_amount = 10 * pow::ten_pow(18); + clVault.withdraw(withdraw_amount, this); + + let shares_bal = ERC20Helper::balanceOf(clVault.contract_address, this); + assert(shares_bal == (shares1 - withdraw_amount), 'invalid shares minted'); + let partial_eth_bal = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), this); + let partial_wst_bal = ERC20Helper::balanceOf(constants::WST_ADDRESS(), this); + assert(partial_eth_bal > eth_before_withdraw, 'eth not withdrawn'); + assert(partial_wst_bal > wst_before_withdraw, 'wst not withdrawn'); + // println!("eth withdrawn {:?}", my_position.amount1); + // println!("wst withdrawn {:?}", my_position.amount0); + assert( + partial_eth_bal - eth_before_withdraw == 74145774494158597, 'incorrect eth withdrawn' + ); + assert( + partial_wst_bal - wst_before_withdraw == 29054157674144808, 'incorrect wst withdrawn' + ); + // partial = before + print value + } + + #[test] + #[fork("mainnet_1134787")] + fn test_ekubo_withdraw_entire_liquidity() { + let this = get_contract_address(); + let (clVault, shares) = ekubo_deposit(); + + // withdraw entire liquidity + ekubo_withdraw(clVault, shares); + + let shares_bal = ERC20Helper::balanceOf(clVault.contract_address, this); + assert(shares_bal == 0, 'invalid shares minted'); + let liquidity_after_withdraw = clVault.get_position().liquidity; + assert(liquidity_after_withdraw == 0, 'invalid liquidity'); + + // test next deposit + let amount = 10 * pow::ten_pow(18); + let this = get_contract_address(); + + assert(clVault.get_settings().contract_nft_id == 0, 'nft id not zero on deploy'); + vault_init(amount); + ERC20Helper::approve(constants::ETH_ADDRESS(), clVault.contract_address, amount * 2); + ERC20Helper::approve(constants::WST_ADDRESS(), clVault.contract_address, amount * 2); + let _ = clVault.deposit(amount, amount, this); + + let liquidity_after_deposit = clVault.get_position().liquidity; + let total_supply = ERC20Helper::total_supply(clVault.contract_address); + assert(liquidity_after_deposit.into() == total_supply, 'invalid total supply'); + } + + // zero deposit + // if we remove our cl vault assert of amount > 0 the deposit call passes + #[test] + #[fork("mainnet_1134787")] + #[should_panic(expected: ('amounts cannot be zero',))] + fn test_ekubo_zero_deposit() { + let zero_amount = 0; + let amount = 10 * pow::ten_pow(18); + let this = get_contract_address(); + let (clVault, _) = deploy_cl_vault(); + vault_init(amount); + ERC20Helper::approve(constants::ETH_ADDRESS(), clVault.contract_address, amount); + ERC20Helper::approve(constants::WST_ADDRESS(), clVault.contract_address, amount); + let _ = clVault.deposit(zero_amount, zero_amount, this); + } + + #[test] + #[fork("mainnet_1134787")] + fn test_ekubo_low_liquidity() { + let amount = 10 * pow::ten_pow(18); + let this = get_contract_address(); + + let (clVault, _) = deploy_cl_vault(); + assert(clVault.get_settings().contract_nft_id == 0, 'nft id not zero on deploy'); + vault_init(amount); + ERC20Helper::approve(constants::ETH_ADDRESS(), clVault.contract_address, amount * 2); + ERC20Helper::approve(constants::WST_ADDRESS(), clVault.contract_address, amount * 2); + + let eth_before = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), this); + let wst_before = ERC20Helper::balanceOf(constants::WST_ADDRESS(), this); + + let deposit_amount = 20; + let shares1 = clVault.deposit(deposit_amount, deposit_amount, this); + /// println!("shares {:?}", shares1); + assert(shares1 == 2225, 'invalid shares'); + + let eth_after_deposit = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), this); + let wst_after_deposit = ERC20Helper::balanceOf(constants::WST_ADDRESS(), this); + assert(eth_after_deposit == eth_before - 20, 'invalid eth balance'); + assert( + wst_after_deposit == wst_before - 4, 'invalid wst balance' + ); // based on current requirements + + clVault.withdraw(shares1, this); + let liquidity_left = clVault.get_position().liquidity; + // let neg_liq = liquidity_left / 1000; + assert(liquidity_left == 0, 'liquidity not 0'); + let shares2 = ERC20Helper::balanceOf(clVault.contract_address, this); + assert(shares2 == 0, 'invalid shares left'); + let total_supply = ERC20Helper::total_supply(clVault.contract_address); + assert(total_supply == 0, 'total supply not 0'); + + let eth_after_withdraw = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), this); + let wst_after_withdraw = ERC20Helper::balanceOf(constants::WST_ADDRESS(), this); + + assert(eth_after_withdraw == eth_before - 2, 'invalid eth balance'); + assert(wst_after_withdraw == wst_before - 2, 'invalid wst balance'); + + assert( + ERC20Helper::balanceOf(constants::ETH_ADDRESS(), clVault.contract_address) == 0, + 'invalid token bal' + ); + assert( + ERC20Helper::balanceOf(constants::WST_ADDRESS(), clVault.contract_address) == 0, + 'invalid token bal' + ); + } + + #[test] + #[fork("mainnet_1134787")] + fn test_ekubo_fee_ranges() { + let (clVault, _shares) = ekubo_deposit(); + + ekubo_swaps(); + + let fee_collector = clVault.get_settings().fee_settings.fee_collector; + let token0_bal_before = ERC20Helper::balanceOf(constants::WST_ADDRESS(), fee_collector); + let token1_bal_before = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), fee_collector); + let settings = clVault.get_settings(); + let token_info = IEkuboDispatcher { contract_address: constants::EKUBO_POSITIONS() } + .get_token_info(settings.contract_nft_id, settings.pool_key, settings.bounds_settings); + let fee0 = token_info.fees0; + let fee1 = token_info.fees1; + + let position = clVault.get_position(); + let liquidity_before: u256 = position.liquidity.into(); + + clVault.handle_fees(); + let token0_bal_after = ERC20Helper::balanceOf(constants::WST_ADDRESS(), fee_collector); + let token1_bal_after = ERC20Helper::balanceOf(constants::ETH_ADDRESS(), fee_collector); + + let fee0_strat = (fee0.into() * settings.fee_settings.fee_bps) / 10000; + let fee1_strat = (fee1.into() * settings.fee_settings.fee_bps) / 10000; + + let fee0_collector = token0_bal_after - token0_bal_before; + let fee1_collector = token1_bal_after - token1_bal_before; + + assert(fee0_strat == fee0_collector, 'invalid fee0 transfered'); + assert(fee1_strat == fee1_collector, 'invalid fee1 transfered'); + + let position = clVault.get_position(); + let liquidity_after: u256 = position.liquidity.into(); + + assert(liquidity_after > liquidity_before, 'additional liq not created'); + let liq_handle_fees0 = fee0.into() - fee0_strat.into() - token0_bal_after.into(); + let liq_handle_fees1 = fee1.into() - fee1_strat.into() - token1_bal_after.into(); + + assert(liq_handle_fees0 > 0, 'invalid liq0 created'); + assert(liq_handle_fees1 > 0, 'invalid liq1 created'); + } fn STRKETHAvnuSwapInfo(amount: u256, beneficiary: ContractAddress) -> AvnuMultiRouteSwap { let additional1: Array = array![ diff --git a/src/strategies/vesu_rebalance/test.cairo b/src/strategies/vesu_rebalance/test.cairo index 8145c0b..d8a1741 100644 --- a/src/strategies/vesu_rebalance/test.cairo +++ b/src/strategies/vesu_rebalance/test.cairo @@ -3,7 +3,6 @@ pub mod test_vesu_rebalance { use snforge_std::{ declare, ContractClassTrait, start_cheat_caller_address, stop_cheat_caller_address, start_cheat_block_number_global, start_cheat_block_timestamp_global, - stop_cheat_block_number_global, }; use starknet::contract_address::contract_address_const; use snforge_std::{DeclareResultTrait}; @@ -220,7 +219,7 @@ pub mod test_vesu_rebalance { /// println!("prev index before {:?}", prev_index_before); /// println!("prev index after {:?}", prev_index_after); assert( - prev_index_after <= prev_index_before + 1 && prev_index_after >= prev_index_before - 1, + prev_index_before - 1 <= prev_index_after && prev_index_after <= prev_index_before + 1, 'index not updated' ); @@ -639,7 +638,6 @@ pub mod test_vesu_rebalance { let (vesu_address, vesu_vault, vesu_erc4626) = deploy_usdc_vesu_vault(); ERC20Helper::approve(constants::USDC_ADDRESS(), vesu_address, amount); - let bal = ERC20Helper::balanceOf(constants::USDC_ADDRESS(), this); // first deposit let prev_index_before = vesu_vault.get_previous_index(); @@ -657,7 +655,6 @@ pub mod test_vesu_rebalance { start_cheat_block_number_global(block + 100000); start_cheat_block_timestamp_global(time + 100000); - let total_assets = vesu_erc4626.total_assets(); // second deposit let amount = 500 * pow::ten_pow(6); @@ -665,7 +662,6 @@ pub mod test_vesu_rebalance { let bal_before = ERC20Helper::balanceOf(USDC_VTOKEN_GENESIS(), fee_receiver); assert(bal_before == 0, 'invalid fee [1]'); ERC20Helper::approve(constants::USDC_ADDRESS(), vesu_address, amount); - let prev_index_before = vesu_vault.get_previous_index(); let _ = vesu_erc4626.deposit(amount, this); let allowed_pools = vesu_vault.get_allowed_pools(); let v_token = *allowed_pools.at(default_id.into()).v_token;