diff --git a/smart-contracts/contracts/cl-vault/src/contract.rs b/smart-contracts/contracts/cl-vault/src/contract.rs index a42e3e65a..3673f9845 100644 --- a/smart-contracts/contracts/cl-vault/src/contract.rs +++ b/smart-contracts/contracts/cl-vault/src/contract.rs @@ -11,10 +11,9 @@ use crate::query::{ use crate::reply::Replies; use crate::rewards::{ execute_collect_rewards, execute_distribute_rewards, handle_collect_incentives_reply, - handle_collect_spread_rewards_reply, CoinList, + handle_collect_spread_rewards_reply, }; -use crate::state::{RewardsStatus, CURRENT_TOTAL_SUPPLY, DISTRIBUTED_REWARDS, REWARDS_STATUS}; use crate::vault::admin::{execute_admin, execute_build_tick_exp_cache}; use crate::vault::claim::execute_claim_user_rewards; use crate::vault::deposit::{execute_exact_deposit, handle_deposit_create_position_reply}; @@ -29,7 +28,7 @@ use crate::vault::range::{ use crate::vault::withdraw::{execute_withdraw, handle_withdraw_user_reply}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; -use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, Uint128}; +use cosmwasm_std::{to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response}; use cw2::set_contract_version; // version info for migration info @@ -186,9 +185,6 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result Result { - REWARDS_STATUS.save(deps.storage, &RewardsStatus::Ready)?; - DISTRIBUTED_REWARDS.save(deps.storage, &CoinList::new())?; - CURRENT_TOTAL_SUPPLY.save(deps.storage, &Uint128::zero())?; +pub fn migrate(_deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result { Ok(Response::new().add_attribute("migrate", "successful")) } diff --git a/smart-contracts/contracts/cl-vault/src/helpers.rs b/smart-contracts/contracts/cl-vault/src/helpers.rs index b25c43c02..422554023 100644 --- a/smart-contracts/contracts/cl-vault/src/helpers.rs +++ b/smart-contracts/contracts/cl-vault/src/helpers.rs @@ -160,6 +160,39 @@ pub fn get_twap_price( // )) // } +const SCALE_FACTOR: u128 = 10u128.pow(12); + +fn scale_if_needed( + cur_price_sqrt: Decimal256, + upper_price_sqrt: Decimal256, + lower_price_sqrt: Decimal256, + current_price: Decimal256, +) -> (bool, Decimal256, Decimal256, Decimal256, Decimal256) { + let scale_up_threshold = Decimal256::from_ratio(1u128, SCALE_FACTOR); + let product = cur_price_sqrt * upper_price_sqrt * lower_price_sqrt; + + // Scale the square root prices and current price only if needed + if product <= scale_up_threshold { + let scale_factor = Decimal256::from_atomics(SCALE_FACTOR, 0).unwrap(); + + ( + true, + cur_price_sqrt.checked_mul(scale_factor).unwrap(), + upper_price_sqrt.checked_mul(scale_factor).unwrap(), + lower_price_sqrt.checked_mul(scale_factor).unwrap(), + current_price.checked_mul(scale_factor).unwrap(), + ) + } else { + ( + false, + cur_price_sqrt, + upper_price_sqrt, + lower_price_sqrt, + current_price, + ) + } +} + // this math is straight from the readme pub fn get_single_sided_deposit_0_to_1_swap_amount( token0_balance: Uint128, @@ -167,11 +200,6 @@ pub fn get_single_sided_deposit_0_to_1_swap_amount( current_tick: i64, upper_tick: i64, ) -> Result { - // TODO error here if this condition holds - // if current_tick < lower_tick { - // return ; - // } - let lower_price = tick_to_price(lower_tick)?; let current_price = tick_to_price(current_tick)?; let upper_price = tick_to_price(upper_tick)?; @@ -180,10 +208,13 @@ pub fn get_single_sided_deposit_0_to_1_swap_amount( let lower_price_sqrt = lower_price.sqrt(); let upper_price_sqrt = upper_price.sqrt(); - // let pool_metadata_constant: Decimal256 = cur_price_sqrt - // .checked_mul(lower_price_sqrt)? - // .checked_mul(cur_price_sqrt.checked_sub(lower_price_sqrt)?)? - // .checked_div(upper_price_sqrt.checked_sub(cur_price_sqrt)?)?; + let (is_scale_needed, cur_price_sqrt, upper_price_sqrt, lower_price_sqrt, current_price) = + scale_if_needed( + cur_price_sqrt, + upper_price_sqrt, + lower_price_sqrt, + current_price, + ); let pool_metadata_constant: Decimal256 = (upper_price_sqrt .checked_mul(cur_price_sqrt)? @@ -193,7 +224,16 @@ pub fn get_single_sided_deposit_0_to_1_swap_amount( let spot_price_over_pool_metadata_constant = current_price.checked_div(pool_metadata_constant)?; - let denominator = Decimal256::one().checked_add(spot_price_over_pool_metadata_constant)?; + // Adjust spot_price_over_pool_metadata_constant back to its original scale if scaling was applied + let adjusted_spot_price_over_pool_metadata_constant = if is_scale_needed { + let underscale_factor = Decimal256::from_atomics(SCALE_FACTOR, 0).unwrap(); + spot_price_over_pool_metadata_constant.checked_div(underscale_factor)? + } else { + spot_price_over_pool_metadata_constant + }; + + let denominator = + Decimal256::one().checked_add(adjusted_spot_price_over_pool_metadata_constant)?; let swap_amount: Uint128 = Uint256::from(token0_balance) .multiply_ratio(denominator.denominator(), denominator.numerator()) @@ -216,6 +256,14 @@ pub fn get_single_sided_deposit_1_to_0_swap_amount( let lower_price_sqrt = lower_price.sqrt(); let upper_price_sqrt = upper_price.sqrt(); + let (is_scale_needed, cur_price_sqrt, upper_price_sqrt, lower_price_sqrt, current_price) = + scale_if_needed( + cur_price_sqrt, + upper_price_sqrt, + lower_price_sqrt, + current_price, + ); + let pool_metadata_constant: Decimal256 = (upper_price_sqrt .checked_mul(cur_price_sqrt)? .checked_mul(cur_price_sqrt.checked_sub(lower_price_sqrt)?))? @@ -224,7 +272,16 @@ pub fn get_single_sided_deposit_1_to_0_swap_amount( let pool_metadata_constant_over_spot_price: Decimal256 = pool_metadata_constant.checked_div(current_price)?; - let denominator = Decimal256::one().checked_add(pool_metadata_constant_over_spot_price)?; + // Adjust spot_price_over_pool_metadata_constant back to its original scale if scaling was applied + let adjusted_pool_metadata_over_spot_price_constant = if is_scale_needed { + let underscale_factor = Decimal256::from_atomics(SCALE_FACTOR, 0).unwrap(); + pool_metadata_constant_over_spot_price.checked_div(underscale_factor)? + } else { + pool_metadata_constant_over_spot_price + }; + + let denominator = + Decimal256::one().checked_add(adjusted_pool_metadata_over_spot_price_constant)?; let swap_amount: Uint128 = Uint256::from(token1_balance) .multiply_ratio(denominator.denominator(), denominator.numerator()) @@ -463,6 +520,62 @@ mod tests { use super::*; + #[test] + fn test_scale_if_needed_variants() { + // Adjusted small and large values for testing + let small_value = Decimal256::from_ratio(1u128, 10u128.pow(5)); // 1e-5 to ensure product is below 1e-12 + let large_value = Decimal256::from_ratio(1u128, 10u128.pow(3)); // 1e-3 or larger to ensure product is above 1e-12 + + // Scenario 1: All Values Below Threshold + let (needs_scaling_below, _, _, _, _) = + scale_if_needed(small_value, small_value, small_value, small_value); + assert_eq!( + needs_scaling_below, true, + "Scaling should be needed for all values below threshold" + ); + + // Scenario 2: All Values Above Threshold + let (needs_scaling_above, _, _, _, _) = + scale_if_needed(large_value, large_value, large_value, large_value); + assert_eq!( + needs_scaling_above, false, + "Scaling should not be needed for all values above threshold" + ); + + // Scenario 3: Mixed Values - Some below, some above threshold + let (needs_scaling_mixed, _, _, _, _) = + scale_if_needed(small_value, large_value, small_value, large_value); + assert_eq!( + needs_scaling_mixed, true, + "Scaling should be needed for mixed values with any below threshold" + ); + + // Scenario 4: Boundary Condition - Exactly at Threshold + // Assuming the threshold can be represented exactly, otherwise adjust the threshold or values accordingly + let threshold_value = Decimal256::from_ratio(1u128, SCALE_FACTOR); // Adjust based on actual threshold + let (needs_scaling_boundary, _, _, _, _) = scale_if_needed( + threshold_value, + threshold_value, + threshold_value, + threshold_value, + ); + // The expected result here depends on how you define "less than" in scale_if_needed + // If using <, this should be false. If using <=, adjust the assertion accordingly. + assert_eq!( + needs_scaling_boundary, true, + "Scaling should be needed exactly at threshold" + ); + + // Scenario 5: Zero and Near-Zero Values - Ensure handling of zero properly + let zero_value = Decimal256::zero(); + let (needs_scaling_zero, _, _, _, _) = + scale_if_needed(zero_value, zero_value, zero_value, zero_value); + assert_eq!( + needs_scaling_zero, true, + "Scaling should be needed for zero values" + ); + } + #[test] fn must_pay_one_or_two_works_ordered() { let expected0 = coin(100, "uatom"); diff --git a/smart-contracts/contracts/cl-vault/src/instantiate.rs b/smart-contracts/contracts/cl-vault/src/instantiate.rs index 5bfe32400..7f33321c2 100644 --- a/smart-contracts/contracts/cl-vault/src/instantiate.rs +++ b/smart-contracts/contracts/cl-vault/src/instantiate.rs @@ -20,7 +20,7 @@ use crate::state::{ DISTRIBUTED_REWARDS, METADATA, POOL_CONFIG, POSITION, RANGE_ADMIN, REWARDS_STATUS, STRATEGIST_REWARDS, VAULT_CONFIG, VAULT_DENOM, }; -use crate::vault::concentrated_liquidity::create_position; +use crate::vault::concentrated_liquidity::get_create_position_msg; use crate::ContractError; pub fn handle_instantiate( @@ -86,7 +86,7 @@ pub fn handle_instantiate( // in order to create the initial position, we need some funds to throw in there, these funds should be seen as burned let (initial0, initial1) = must_pay_one_or_two(&info, (pool.token0, pool.token1))?; - let create_position_msg = create_position( + let create_position_msg = get_create_position_msg( deps, &env, msg.initial_lower_tick, diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/admin.rs b/smart-contracts/contracts/cl-vault/src/test_tube/admin.rs index 4c44ff450..fc74886d0 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/admin.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/admin.rs @@ -1,11 +1,26 @@ #[cfg(test)] mod tests { - use crate::test_tube::initialize::initialize::default_init; + use cosmwasm_std::coin; + + use crate::test_tube::initialize::initialize::{default_init, TOKENS_PROVIDED_AMOUNT}; + + const DENOM_BASE: &str = "uatom"; + const DENOM_QUOTE: &str = "uosmo"; #[test] #[ignore] fn range_admin_update_works() { - let (_app, _contract_address, _cl_pool_id, _admin) = default_init(); + let (_app, _contract, _cl_pool_id, _admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + ) + .unwrap(); // change the range admin and verify that it works } } diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs b/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs index 19ffc3d71..cd899c161 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/deposit_withdraw.rs @@ -1,13 +1,13 @@ #[cfg(test)] mod tests { - use cosmwasm_std::{assert_approx_eq, Coin, Uint128}; + use cosmwasm_std::{assert_approx_eq, coin, Coin, Uint128}; use osmosis_test_tube::{Account, Module, Wasm}; use crate::{ msg::{ExecuteMsg, ExtensionQueryMsg, QueryMsg}, query::{AssetsBalanceResponse, TotalAssetsResponse, UserSharesBalanceResponse}, - test_tube::initialize::initialize::default_init, + test_tube::initialize::initialize::{default_init, TOKENS_PROVIDED_AMOUNT}, }; const INITIAL_BALANCE_AMOUNT: u128 = 340282366920938463463374607431768211455u128; @@ -17,7 +17,17 @@ mod tests { #[test] #[ignore] fn single_deposit_withdraw_works() { - let (app, contract_address, _cl_pool_id, _admin) = default_init(); + let (app, contract, _cl_pool_id, _admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + ) + .unwrap(); let wasm = Wasm::new(&app); // Create Alice account @@ -29,12 +39,12 @@ mod tests { .unwrap(); let vault_assets_before: TotalAssetsResponse = wasm - .query(contract_address.as_str(), &QueryMsg::TotalAssets {}) + .query(contract.as_str(), &QueryMsg::TotalAssets {}) .unwrap(); // TODO: Check this -> Certain deposit amounts do not work here due to an off by one error in Osmosis cl code. The value here is chosen to specifically work wasm.execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::ExactDeposit { recipient: None }, &[ Coin::new(1_000_000_000_000_000, DENOM_BASE), @@ -47,7 +57,7 @@ mod tests { // Get shares for Alice from vault contract and assert let shares: UserSharesBalanceResponse = wasm .query( - contract_address.as_str(), + contract.as_str(), &QueryMsg::VaultExtension(ExtensionQueryMsg::Balances( crate::msg::UserBalanceQueryMsg::UserSharesBalance { user: alice.address(), @@ -60,7 +70,7 @@ mod tests { // Get user_assets for Alice from vault contract and assert let user_assets: AssetsBalanceResponse = wasm .query( - contract_address.as_str(), + contract.as_str(), &QueryMsg::VaultExtension(ExtensionQueryMsg::Balances( crate::msg::UserBalanceQueryMsg::UserAssetsBalance { user: alice.address(), @@ -85,7 +95,7 @@ mod tests { // Get vault assets and assert let vault_assets: TotalAssetsResponse = wasm - .query(contract_address.as_str(), &QueryMsg::TotalAssets {}) + .query(contract.as_str(), &QueryMsg::TotalAssets {}) .unwrap(); assert_approx_eq!( vault_assets.token0.amount, @@ -110,7 +120,7 @@ mod tests { let _withdraw = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::Redeem { recipient: None, amount: shares.balance, @@ -126,7 +136,17 @@ mod tests { #[test] #[ignore] fn multiple_deposit_withdraw_works() { - let (app, contract_address, _cl_pool_id, _admin) = default_init(); + let (app, contract, _cl_pool_id, _admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + ) + .unwrap(); let wasm = Wasm::new(&app); // Create Alice account @@ -139,13 +159,13 @@ mod tests { // Get vaults assets before doing anything for future assertions let vault_assets_before: TotalAssetsResponse = wasm - .query(contract_address.as_str(), &QueryMsg::TotalAssets {}) + .query(contract.as_str(), &QueryMsg::TotalAssets {}) .unwrap(); // Loop 3 times to do multiple deposits as Alice for _ in 0..3 { wasm.execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::ExactDeposit { recipient: None }, &[ Coin::new(1_000_000_000_000_000_000, DENOM_BASE), @@ -159,7 +179,7 @@ mod tests { // Get Alice shares from vault contract let shares: UserSharesBalanceResponse = wasm .query( - contract_address.as_str(), + contract.as_str(), &QueryMsg::VaultExtension(ExtensionQueryMsg::Balances( crate::msg::UserBalanceQueryMsg::UserSharesBalance { user: alice.address(), @@ -172,7 +192,7 @@ mod tests { // Get Alice assets from vault contract let user_assets: AssetsBalanceResponse = wasm .query( - contract_address.as_str(), + contract.as_str(), &QueryMsg::VaultExtension(ExtensionQueryMsg::Balances( crate::msg::UserBalanceQueryMsg::UserAssetsBalance { user: alice.address(), @@ -196,7 +216,7 @@ mod tests { let user_assets_again: AssetsBalanceResponse = wasm .query( - contract_address.as_str(), + contract.as_str(), &QueryMsg::ConvertToAssets { amount: shares.balance, }, @@ -214,7 +234,7 @@ mod tests { ); let vault_assets: TotalAssetsResponse = wasm - .query(contract_address.as_str(), &QueryMsg::TotalAssets {}) + .query(contract.as_str(), &QueryMsg::TotalAssets {}) .unwrap(); assert_approx_eq!( @@ -239,7 +259,7 @@ mod tests { let _withdraw = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::Redeem { recipient: None, amount: shares.balance, @@ -254,7 +274,17 @@ mod tests { #[test] #[ignore] fn multiple_deposit_withdraw_unused_funds_works() { - let (app, contract_address, _cl_pool_id, _admin) = default_init(); + let (app, contract, _cl_pool_id, _admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + ) + .unwrap(); //let bank = Bank::new(&app); let wasm = Wasm::new(&app); @@ -304,7 +334,7 @@ mod tests { // depositing for user in &users { wasm.execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::ExactDeposit { recipient: None }, &[ Coin::new(deposit_amount, DENOM_BASE), @@ -321,7 +351,7 @@ mod tests { for user in users { let user_shares: UserSharesBalanceResponse = wasm .query( - contract_address.as_str(), + contract.as_str(), &QueryMsg::VaultExtension(ExtensionQueryMsg::Balances( crate::msg::UserBalanceQueryMsg::UserSharesBalance { user: user.address(), @@ -332,13 +362,13 @@ mod tests { // let _balances = bank // .query_all_balances(&QueryAllBalancesRequest { - // address: contract_address.to_string(), + // address: contract.to_string(), // pagination: None, // }) // .unwrap(); // let pos_id: PositionResponse = wasm // .query( - // contract_address.as_str(), + // contract.as_str(), // &QueryMsg::VaultExtension(ExtensionQueryMsg::ConcentratedLiquidity( // crate::msg::ClQueryMsg::Position {}, // )), @@ -352,7 +382,7 @@ mod tests { // withdrawing wasm.execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::Redeem { recipient: None, amount: user_shares.balance, diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs b/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs index ebd4cbcc2..a6a03f9d4 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/initialize.rs @@ -2,7 +2,7 @@ pub mod initialize { use std::str::FromStr; - use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; + use cosmwasm_std::{coin, Addr, Coin, Decimal, StdError, Uint128}; use cw_vault_multi_standard::VaultInfoResponse; use osmosis_std::types::cosmos::base::v1beta1; use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::{ @@ -21,47 +21,61 @@ pub mod initialize { SigningAccount, TokenFactory, Wasm, }; - use crate::helpers::sort_tokens; use crate::msg::{ ClQueryMsg, ExecuteMsg, ExtensionQueryMsg, InstantiateMsg, ModifyRangeMsg, QueryMsg, }; use crate::query::PoolResponse; use crate::state::VaultConfig; - const ADMIN_BALANCE_AMOUNT: u128 = 340282366920938463463374607431768211455u128; - const TOKENS_PROVIDED_AMOUNT: &str = "1000000000000"; - const DENOM_BASE: &str = "uatom"; - const DENOM_QUOTE: &str = "uosmo"; + pub(crate) const ADMIN_BALANCE_AMOUNT: u128 = 340282366920938463463374607431768211455u128; + pub(crate) const TOKENS_PROVIDED_AMOUNT: u128 = 1_000_000_000_000; + pub(crate) const DENOM_BASE: &str = "uatom"; + pub(crate) const DENOM_QUOTE: &str = "uosmo"; + pub(crate) const ACCOUNTS_INIT_BALANCE: u128 = 1_000_000_000_000_000; - pub fn default_init() -> (OsmosisTestApp, Addr, u64, SigningAccount) { - init_test_contract( + // Define init variants here + + pub fn default_init( + pool_tokens: Vec, + vault_tokens: Vec, + ) -> Result<(OsmosisTestApp, Addr, u64, SigningAccount), StdError> { + if pool_tokens.len() > 2 { + return Err(StdError::generic_err("More than two tokens provided")); + } + + // Prepare admin balances, including "uosmo" if neither of the provided tokens is "uosmo" + let mut admin_balances = pool_tokens + .iter() + .map(|coin| Coin::new(ADMIN_BALANCE_AMOUNT, coin.denom.clone())) + .collect::>(); + + if !pool_tokens.iter().any(|coin| coin.denom == "uosmo") { + admin_balances.push(Coin::new(ADMIN_BALANCE_AMOUNT, "uosmo".to_string())); + } + + let (app, contract, cl_pool_id, admin) = init_test_contract( "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", - &[ - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_BASE), - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_QUOTE), - ], + &admin_balances, MsgCreateConcentratedPool { sender: "overwritten".to_string(), - denom0: DENOM_BASE.to_string(), - denom1: DENOM_QUOTE.to_string(), + denom0: pool_tokens + .get(0) + .map_or(String::new(), |coin| coin.denom.clone()), + denom1: pool_tokens + .get(1) + .map_or(String::new(), |coin| coin.denom.clone()), tick_spacing: 100, spread_factor: Decimal::from_str("0.01").unwrap().atomics().to_string(), }, -5000000, // 0.5 spot price 500000, // 1.5 spot price - vec![ - v1beta1::Coin { - denom: DENOM_BASE.to_string(), - amount: TOKENS_PROVIDED_AMOUNT.to_string(), - }, - v1beta1::Coin { - denom: DENOM_QUOTE.to_string(), - amount: TOKENS_PROVIDED_AMOUNT.to_string(), - }, - ], + pool_tokens, + vault_tokens, Uint128::zero(), Uint128::zero(), - ) + ); + + Ok((app, contract, cl_pool_id, admin)) } pub fn init_test_contract( @@ -70,7 +84,8 @@ pub mod initialize { pool: MsgCreateConcentratedPool, lower_tick: i64, upper_tick: i64, - mut tokens_provided: Vec, + mut pool_tokens: Vec, + vault_tokens: Vec, token_min_amount0: Uint128, token_min_amount1: Uint128, ) -> (OsmosisTestApp, Addr, u64, SigningAccount) { @@ -115,16 +130,16 @@ pub mod initialize { let pool: Pool = Pool::decode(pools.pools[0].value.as_slice()).unwrap(); // Sort tokens alphabetically by denom name or Osmosis will return an error - tokens_provided.sort_by(|a, b| a.denom.cmp(&b.denom)); // can't use helpers.rs::sort_tokens() due to different Coin type + pool_tokens.sort_by(|a, b| a.denom.cmp(&b.denom)); // can't use helpers.rs::sort_tokens() due to different Coin type - // Create a first position in the pool with the admin user + // Create a first position in the pool with the admin user to simulate liquidity availability on the CL Pool cl.create_position( MsgCreatePosition { pool_id: pool.id, sender: admin.address(), lower_tick, upper_tick, - tokens_provided: tokens_provided.clone(), + tokens_provided: osmosis_std::cosmwasm_to_proto_coins(pool_tokens.clone()), token_min_amount0: token_min_amount0.to_string(), token_min_amount1: token_min_amount1.to_string(), }, @@ -133,14 +148,21 @@ pub mod initialize { .unwrap(); // Get and assert spot price is 1.0 - let spot_price = pm + let spot_price: osmosis_std::types::osmosis::poolmanager::v1beta1::SpotPriceResponse = pm .query_spot_price(&SpotPriceRequest { - base_asset_denom: tokens_provided[0].denom.to_string(), - quote_asset_denom: tokens_provided[1].denom.to_string(), + base_asset_denom: pool_tokens[0].denom.to_string(), + quote_asset_denom: pool_tokens[1].denom.to_string(), pool_id: pool.id, }) .unwrap(); - assert_eq!(spot_price.spot_price, "1.000000000000000000"); + // Assuming tokens_provided[0].amount and tokens_provided[1].amount are String + let tokens_provided_0_amount: u128 = pool_tokens[0].amount.u128(); + let tokens_provided_1_amount: u128 = pool_tokens[1].amount.u128(); + // Assuming `spot_price.spot_price` is a string representation of a float. + let spot_price_float: f64 = spot_price.spot_price.parse().unwrap(); + let division_result: f64 = + tokens_provided_1_amount as f64 / tokens_provided_0_amount as f64; + assert!((spot_price_float - division_result).abs() < f64::EPSILON); // Increment the app time for twaps to function, this is needed to do not fail on querying a twap for a timeframe higher than the chain existence app.increase_time(1000000); @@ -166,7 +188,7 @@ pub mod initialize { }, Some(admin.address().as_str()), Some("cl-vault"), - sort_tokens(vec![coin(1000, pool.token0), coin(1000, pool.token1)]).as_ref(), + vault_tokens.as_ref(), &admin, ) .unwrap(); @@ -177,7 +199,18 @@ pub mod initialize { #[test] #[ignore] fn default_init_works() { - let (app, contract_address, cl_pool_id, admin) = default_init(); + let (app, contract_address, cl_pool_id, admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + ) + .unwrap(); + let wasm = Wasm::new(&app); let cl = ConcentratedLiquidity::new(&app); let tf = TokenFactory::new(&app); @@ -216,8 +249,8 @@ pub mod initialize { // Create Alice account let alice = app .init_account(&[ - Coin::new(1_000_000_000_000, DENOM_BASE), - Coin::new(1_000_000_000_000, DENOM_QUOTE), + Coin::new(1_000_000_000_000, "uatom"), + Coin::new(1_000_000_000_000, "uosmo"), ]) .unwrap(); @@ -227,10 +260,10 @@ pub mod initialize { sender: alice.address(), routes: vec![SwapAmountInRoute { pool_id: cl_pool_id, - token_out_denom: DENOM_BASE.to_string(), + token_out_denom: "uatom".to_string(), }], token_in: Some(v1beta1::Coin { - denom: DENOM_QUOTE.to_string(), + denom: "uosmo".to_string(), amount: "1000".to_string(), }), token_out_min_amount: "1".to_string(), diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs b/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs index 57f4cd61f..26da00fda 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/proptest.rs @@ -1,13 +1,10 @@ #[cfg(test)] mod tests { - use cosmwasm_std::{Addr, Coin, Decimal, Uint128}; + use cosmwasm_std::{coin, Addr, Coin, Decimal, Uint128}; use osmosis_std::types::cosmos::bank::v1beta1::{QueryBalanceRequest, QueryBalanceResponse}; use osmosis_std::types::cosmwasm::wasm::v1::MsgExecuteContractResponse; + use osmosis_std::types::osmosis::concentratedliquidity::poolmodel::concentrated::v1beta1::MsgCreateConcentratedPool; use osmosis_std::types::osmosis::concentratedliquidity::v1beta1::PositionByIdRequest; - use osmosis_std::types::{ - cosmos::base::v1beta1, - osmosis::concentratedliquidity::poolmodel::concentrated::v1beta1::MsgCreateConcentratedPool, - }; use osmosis_test_tube::{ Account, Bank, ConcentratedLiquidity, ExecuteResponse, Module, OsmosisTestApp, SigningAccount, Wasm, @@ -479,14 +476,12 @@ mod tests { initial_lower_tick, initial_upper_tick, vec![ - v1beta1::Coin { - denom: DENOM_BASE.to_string(), - amount: "1000000000000000000".to_string(), - }, - v1beta1::Coin { - denom: DENOM_QUOTE.to_string(), - amount: "1000000000000000000".to_string(), - }, + coin(1_000_000_000_000_000_000,DENOM_BASE.to_string()), + coin(1_000_000_000_000_000_000, DENOM_QUOTE.to_string()) + ], + vec![ + coin(1_000_000,DENOM_BASE.to_string()), + coin(1_000_000, DENOM_QUOTE.to_string()) ], Uint128::zero(), Uint128::zero(), diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/range.rs b/smart-contracts/contracts/cl-vault/src/test_tube/range.rs index b06900ed2..f5e270afb 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/range.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/range.rs @@ -1,67 +1,185 @@ #[cfg(test)] mod test { - use std::str::FromStr; - - use cosmwasm_std::{coin, Coin, Decimal, Uint128}; + use cosmwasm_std::{coin, Decimal, Uint128}; use osmosis_std::types::{ cosmos::base::v1beta1, osmosis::{ - concentratedliquidity::{ - poolmodel::concentrated::v1beta1::MsgCreateConcentratedPool, - v1beta1::{MsgCreatePosition, Pool, PoolsRequest}, - }, + concentratedliquidity::v1beta1::{MsgCreatePosition, Pool, PoolsRequest}, poolmanager::v1beta1::{MsgSwapExactAmountIn, SwapAmountInRoute}, }, }; use osmosis_test_tube::{Account, ConcentratedLiquidity, Module, PoolManager, Wasm}; + use prost::Message; + use std::str::FromStr; use crate::{ msg::{ExecuteMsg, ModifyRangeMsg, QueryMsg}, query::PositionResponse, - test_tube::initialize::initialize::init_test_contract, + test_tube::initialize::initialize::{ + default_init, DENOM_BASE, DENOM_QUOTE, TOKENS_PROVIDED_AMOUNT, + }, }; - use prost::Message; + /// # Test: move_range_works_dym_usdc + /// + /// This test case initializes a Concentrated Liquidity (CL) pool with 18DEC and USDC tokens + /// to simulate a real-world scenario on the blockchain with a specific spot price. The purpose + /// of this test is to ensure that operations such as moving the range within the CL pool function + /// correctly, especially when dealing with tokens of different decimal precisions. + /// + /// ## Initialization Parameters: + /// - 18DEC Token (u18dec): 18 decimal places, represented as `1000000000000000000u18dec` (for 1 18DEC). + /// - USDC Token (uusdc): 6 decimal places, represented as `7250000uusdc` (to establish a spot price of 7.25 USDC for 1 18DEC). + /// + /// The test initializes a CL pool with these tokens to establish a spot price of 7.25 18DEC/USD. + /// This spot price accurately reflects the real-world ratio between 18DEC and USDC on the mainnet, + /// adjusted for the blockchain's unit representation. + /// + /// ## Decimal Precision and Spot Price Consideration: + /// The significant difference in decimal places between 18DEC (18 decimals) and USDC (6 decimals) + /// necessitates precise calculation to ensure the spot price is accurately represented in the + /// blockchain's terms. The chosen amounts of `1000000000000000000u18dec` for 18DEC and `7250000uusdc` + /// for USDC effectively establish a starting spot price of 7.25 18DEC/USD in the CL pool, accurately + /// representing the spot price in a manner that does not require adjustment for decimal places in + /// the context of Osmosis' handling of token amounts. + /// + /// Spot price it would be: `spot_price = 7250000 / 1000000000000000000`, + /// calculating the spot price in raw integer format without adjusting for decimal places, representing the USDC required to purchase one unit of 18DEC. + #[test] + #[ignore] + fn move_range_works_18dec_usdc() { + let (app, contract, cl_pool_id, admin) = default_init( + vec![ + coin(1_000_000_000_000_000_000, "udym"), + coin(7_250_000, "uusdc"), + ], + vec![coin(1_000_000, "udym"), coin(1_000_000, "uusdc")], + ) + .unwrap(); + let wasm = Wasm::new(&app); + let cl = ConcentratedLiquidity::new(&app); - const ADMIN_BALANCE_AMOUNT: u128 = 340282366920938463463374607431768211455u128; - const TOKENS_PROVIDED_AMOUNT: &str = "1000000000000"; - const DENOM_BASE: &str = "uatom"; - const DENOM_QUOTE: &str = "uosmo"; + // Create a second position in the pool with the admin user (wide position) to simulate liquidity availability on the CL Pool + cl.create_position( + MsgCreatePosition { + pool_id: cl_pool_id, + sender: admin.address(), + lower_tick: -108000000, // min tick + upper_tick: 342000000, // max tick + tokens_provided: vec![ + v1beta1::Coin { + denom: "udym".to_string(), + amount: "1000000000000000000000000000000".to_string(), + }, + v1beta1::Coin { + denom: "uusdc".to_string(), + amount: "7250000000000000000".to_string(), + }, + ], + token_min_amount0: Uint128::zero().to_string(), + token_min_amount1: Uint128::zero().to_string(), + }, + &admin, + ) + .unwrap(); + + // Two sided re-range (50% 50%) + let _result = wasm + .execute( + contract.as_str(), + &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ModifyRange( + ModifyRangeMsg { + lower_price: Decimal::from_str("0.00000000000675").unwrap(), + upper_price: Decimal::from_str("0.0000000000075").unwrap(), + max_slippage: Decimal::bps(1), + ratio_of_swappable_funds_to_use: Decimal::one(), + twap_window_seconds: 45, + }, + )), + &[], + &admin, + ) + .unwrap(); + + // Create a first position in the pool with the admin user + cl.create_position( + MsgCreatePosition { + pool_id: cl_pool_id, + sender: admin.address(), + lower_tick: -10800000, + upper_tick: -1000000, + tokens_provided: vec![ + v1beta1::Coin { + denom: "udym".to_string(), + amount: 100_000_000_000u128.to_string(), + }, + v1beta1::Coin { + denom: "uusdc".to_string(), + amount: 100_000_000_000u128.to_string(), + }, + ], + token_min_amount0: Uint128::zero().to_string(), + token_min_amount1: Uint128::zero().to_string(), + }, + &admin, + ) + .unwrap(); + + // One-sided re-range (above current tick, 100% token0) + let _result = wasm + .execute( + contract.as_str(), + &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ModifyRange( + ModifyRangeMsg { + lower_price: Decimal::from_str("0.0000000000075").unwrap(), + upper_price: Decimal::from_str("0.000000000008").unwrap(), + max_slippage: Decimal::from_str("0.00000000001").unwrap(), + ratio_of_swappable_funds_to_use: Decimal::one(), + twap_window_seconds: 45, + }, + )), + &[], + &admin, + ) + .unwrap(); + + // One-sided re-range (below current tick, 100% token1) + let _result = wasm + .execute( + contract.as_str(), + &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ModifyRange( + ModifyRangeMsg { + lower_price: Decimal::from_str("0.000000000006").unwrap(), + upper_price: Decimal::from_str("0.000000000007").unwrap(), + max_slippage: Decimal::bps(9500), + ratio_of_swappable_funds_to_use: Decimal::one(), + twap_window_seconds: 45, + }, + )), + &[], + &admin, + ) + .unwrap(); + } #[test] #[ignore] fn move_range_works() { - let (app, contract, cl_pool_id, admin) = init_test_contract( - // TODO: Evaluate creating a default_init() variant i.e. out_of_range_init() - "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", - &[ - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_BASE), - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_QUOTE), + let (app, contract, cl_pool_id, admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), ], - MsgCreateConcentratedPool { - sender: "overwritten".to_string(), - denom0: DENOM_BASE.to_string(), - denom1: DENOM_QUOTE.to_string(), - tick_spacing: 100, - spread_factor: Decimal::from_str("0.0001").unwrap().atomics().to_string(), - }, - 21205000, - 27448000, vec![ - v1beta1::Coin { - denom: DENOM_BASE.to_string(), - amount: TOKENS_PROVIDED_AMOUNT.to_string(), - }, - v1beta1::Coin { - denom: DENOM_QUOTE.to_string(), - amount: TOKENS_PROVIDED_AMOUNT.to_string(), - }, + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), ], - Uint128::zero(), - Uint128::zero(), - ); + ) + .unwrap(); + let wasm = Wasm::new(&app); let cl = ConcentratedLiquidity::new(&app); + let pm = PoolManager::new(&app); // Create a second position (in range) in the pool with the admin user to allow for swapping during update range operation cl.create_position( @@ -87,18 +205,10 @@ mod test { ) .unwrap(); - let alice = app - .init_account(&[ - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_BASE), - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_QUOTE), - ]) - .unwrap(); - // do a swap to move the cur tick - let pm = PoolManager::new(&app); pm.swap_exact_amount_in( MsgSwapExactAmountIn { - sender: alice.address(), + sender: admin.address(), routes: vec![SwapAmountInRoute { pool_id: cl_pool_id, token_out_denom: DENOM_BASE.to_string(), @@ -109,7 +219,7 @@ mod test { }), token_out_min_amount: "1".to_string(), }, - &alice, + &admin, ) .unwrap(); @@ -132,7 +242,7 @@ mod test { ModifyRangeMsg { lower_price: Decimal::from_str("400").unwrap(), upper_price: Decimal::from_str("1466").unwrap(), - max_slippage: Decimal::bps(9500), + max_slippage: Decimal::bps(5000), // this max slippage allows for a very large amount of price impact, mostly relevant here since the pool is illiquid ratio_of_swappable_funds_to_use: Decimal::one(), twap_window_seconds: 45, }, @@ -155,35 +265,18 @@ mod test { #[test] #[ignore] fn move_range_same_single_side_works() { - let (app, contract, cl_pool_id, admin) = init_test_contract( - // TODO: Evaluate creating a default_init() variant i.e. out_of_range_init() - "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", - &[ - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_BASE), - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_QUOTE), + let (app, contract, cl_pool_id, admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), ], - MsgCreateConcentratedPool { - sender: "overwritten".to_string(), - denom0: DENOM_BASE.to_string(), - denom1: DENOM_QUOTE.to_string(), - tick_spacing: 100, - spread_factor: Decimal::from_str("0.0001").unwrap().atomics().to_string(), - }, - 21205000, - 27448000, vec![ - v1beta1::Coin { - denom: DENOM_BASE.to_string(), - amount: TOKENS_PROVIDED_AMOUNT.to_string(), - }, - v1beta1::Coin { - denom: DENOM_QUOTE.to_string(), - amount: TOKENS_PROVIDED_AMOUNT.to_string(), - }, + coin(100_000_000, DENOM_BASE.to_string()), + coin(100_000_000, DENOM_QUOTE.to_string()), ], - Uint128::zero(), - Uint128::zero(), - ); + ) + .unwrap(); + let wasm = Wasm::new(&app); let cl = ConcentratedLiquidity::new(&app); let pm = PoolManager::new(&app); @@ -212,17 +305,10 @@ mod test { ) .unwrap(); - let alice = app - .init_account(&[ - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_BASE), - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_QUOTE), - ]) - .unwrap(); - // do a swap to move the cur tick pm.swap_exact_amount_in( MsgSwapExactAmountIn { - sender: alice.address(), + sender: admin.address(), routes: vec![SwapAmountInRoute { pool_id: cl_pool_id, token_out_denom: DENOM_BASE.to_string(), @@ -233,7 +319,7 @@ mod test { }), token_out_min_amount: "1".to_string(), }, - &alice, + &admin, ) .unwrap(); @@ -271,42 +357,18 @@ mod test { */ #[test] #[ignore] - fn test_swap_math_poc() { - let (app, _contract, _cl_pool_id, _admin) = init_test_contract( - // TODO: Evaluate using default_init() - "./test-tube-build/wasm32-unknown-unknown/release/cl_vault.wasm", - &[ - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_BASE), - Coin::new(ADMIN_BALANCE_AMOUNT, DENOM_QUOTE), + fn test_swap_math() { + let (app, _contract, _cl_pool_id, admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), ], - MsgCreateConcentratedPool { - sender: "overwritten".to_string(), - denom0: DENOM_BASE.to_string(), //token0 is uatom - denom1: DENOM_QUOTE.to_string(), //token1 is uosmo - tick_spacing: 100, - spread_factor: Decimal::from_str("0.0001").unwrap().atomics().to_string(), - }, - 30500000, // 4500 - 31500000, // 5500 vec![ - v1beta1::Coin { - denom: DENOM_BASE.to_string(), - amount: "1000000".to_string(), - }, - v1beta1::Coin { - denom: DENOM_QUOTE.to_string(), - amount: "1000000".to_string(), - }, + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), ], - Uint128::zero(), - Uint128::zero(), - ); - let alice = app - .init_account(&[ - Coin::new(1_000_000_000_000, DENOM_BASE), - Coin::new(1_000_000_000_000, DENOM_QUOTE), - ]) - .unwrap(); + ) + .unwrap(); let cl = ConcentratedLiquidity::new(&app); @@ -317,7 +379,7 @@ mod test { // create a basic position on the pool let initial_position = MsgCreatePosition { pool_id: pool.id, - sender: alice.address(), + sender: admin.address(), lower_tick: 30500000, upper_tick: 31500000, tokens_provided: vec![ @@ -327,6 +389,6 @@ mod test { token_min_amount0: "0".to_string(), token_min_amount1: "0".to_string(), }; - let _position = cl.create_position(initial_position, &alice).unwrap(); + let _position = cl.create_position(initial_position, &admin).unwrap(); } } diff --git a/smart-contracts/contracts/cl-vault/src/test_tube/rewards.rs b/smart-contracts/contracts/cl-vault/src/test_tube/rewards.rs index 839d47d11..253e981f9 100644 --- a/smart-contracts/contracts/cl-vault/src/test_tube/rewards.rs +++ b/smart-contracts/contracts/cl-vault/src/test_tube/rewards.rs @@ -1,13 +1,13 @@ #[cfg(test)] mod tests { - use std::ops::Mul; - use crate::msg::ExecuteMsg; use crate::test_tube::helpers::{ get_event_attributes_by_ty_and_key, get_event_value_amount_numeric, }; - use crate::test_tube::initialize::initialize::default_init; - use cosmwasm_std::{assert_approx_eq, Coin, Uint128}; + use crate::test_tube::initialize::initialize::{ + default_init, ACCOUNTS_INIT_BALANCE, DENOM_BASE, DENOM_QUOTE, TOKENS_PROVIDED_AMOUNT, + }; + use cosmwasm_std::{assert_approx_eq, coin, Coin, Uint128}; use osmosis_std::types::cosmos::base::v1beta1::Coin as OsmoCoin; use osmosis_std::types::osmosis::poolmanager::v1beta1::{ MsgSwapExactAmountIn, SwapAmountInRoute, @@ -15,19 +15,26 @@ mod tests { use osmosis_test_tube::RunnerError::ExecuteError; use osmosis_test_tube::{Account, Module, PoolManager, Wasm}; - const DENOM_BASE: &str = "uatom"; - const DENOM_QUOTE: &str = "uosmo"; const ACCOUNTS_NUM: u64 = 10; - const ACCOUNTS_INIT_BALANCE: u128 = 1_000_000_000_000_000; const DEPOSIT_AMOUNT: u128 = 5_000_000; const SWAPS_NUM: usize = 10; const SWAPS_AMOUNT: &str = "1000000000"; - const DISTRIBUTION_CYCLES: usize = 25; + const DISTRIBUTION_CYCLES: usize = 10; #[test] #[ignore] fn test_rewards_single_distribute_claim() { - let (app, contract_address, cl_pool_id, _admin) = default_init(); + let (app, contract, cl_pool_id, _admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + ) + .unwrap(); // Initialize accounts let accounts = app @@ -45,7 +52,7 @@ mod tests { for account in &accounts { let _ = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::ExactDeposit { recipient: None }, &[ Coin::new(DEPOSIT_AMOUNT, DENOM_BASE), @@ -83,7 +90,7 @@ mod tests { let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::one(), // this is ignored the first time but lets pass it anyway for now }), @@ -110,7 +117,7 @@ mod tests { for _ in 0..(ACCOUNTS_NUM - 1) { let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::one(), // this is ignored the first time but lets pass it anyway for now }), @@ -131,7 +138,7 @@ mod tests { // Collect one more time to finish, even if we extra deposited with one more user we expect the distribution to finish let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::one(), }), @@ -153,7 +160,7 @@ mod tests { // Adjust the number of distribute actions as needed let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension( crate::msg::ExtensionExecuteMsg::DistributeRewards { amount_of_users: Uint128::one(), // hardcoding 1 @@ -177,7 +184,7 @@ mod tests { // Distribute one more time to finish, even if we extra deposited with one more user we expect the distribution to finish let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::DistributeRewards { amount_of_users: Uint128::one(), }), @@ -199,7 +206,7 @@ mod tests { for account in &accounts { let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ClaimRewards {}), &[], account, @@ -220,7 +227,17 @@ mod tests { #[test] #[ignore] fn test_rewards_single_distribute_claim_cycles() { - let (app, contract_address, cl_pool_id, _admin) = default_init(); + let (app, contract, cl_pool_id, _admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + ) + .unwrap(); // Initialize accounts let accounts = app @@ -243,7 +260,7 @@ mod tests { for account in &accounts { let _ = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::ExactDeposit { recipient: None }, &[ Coin::new(DEPOSIT_AMOUNT, DENOM_BASE), @@ -277,7 +294,7 @@ mod tests { let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::new(1), }), @@ -302,7 +319,7 @@ mod tests { for _ in 0..(ACCOUNTS_NUM - 1) { let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension( crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::new(1), @@ -321,7 +338,7 @@ mod tests { // Collect one more time to finish, even if we extra deposited with one more user we expect the distribution to finish let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::one(), }), @@ -339,7 +356,7 @@ mod tests { // Adjust the number of distribute actions as needed let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension( crate::msg::ExtensionExecuteMsg::DistributeRewards { amount_of_users: Uint128::one(), // hardcoding 1 @@ -362,7 +379,7 @@ mod tests { // Distribute one more time to finish, even if we extra deposited with one more user we expect the distribution to finish let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension( crate::msg::ExtensionExecuteMsg::DistributeRewards { amount_of_users: Uint128::one(), @@ -382,7 +399,7 @@ mod tests { for account in &accounts { let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension( crate::msg::ExtensionExecuteMsg::ClaimRewards {}, ), @@ -406,7 +423,17 @@ mod tests { #[test] #[ignore] fn test_rewards_single_distribute_claim_no_rewards_works() { - let (app, contract_address, _cl_pool_id, _admin) = default_init(); + let (app, contract, _cl_pool_id, _admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + ) + .unwrap(); // Initialize accounts let accounts = app @@ -424,7 +451,7 @@ mod tests { for account in &accounts { let _ = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::ExactDeposit { recipient: None }, &[ Coin::new(DEPOSIT_AMOUNT, DENOM_BASE), @@ -441,7 +468,7 @@ mod tests { // Collect and Distribute Rewards (there should be anything) let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::one(), }), @@ -468,7 +495,7 @@ mod tests { // Try to collect one more time, this should be closing the process and set to Ready as there are not rewards let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::one(), }), @@ -488,7 +515,7 @@ mod tests { // Distribute just one time, as there are no rewards we expect this to clear the state even if 1 user < 10 users let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::DistributeRewards { amount_of_users: Uint128::one(), }), @@ -506,7 +533,17 @@ mod tests { #[test] #[ignore] fn test_rewards_single_distribute_claim_deposit_between() { - let (app, contract_address, cl_pool_id, _admin) = default_init(); + let (app, contract, cl_pool_id, _admin) = default_init( + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + vec![ + coin(TOKENS_PROVIDED_AMOUNT, DENOM_BASE.to_string()), + coin(TOKENS_PROVIDED_AMOUNT, DENOM_QUOTE.to_string()), + ], + ) + .unwrap(); // Initialize accounts let accounts = app @@ -524,7 +561,7 @@ mod tests { for account in &accounts { let _ = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::ExactDeposit { recipient: None }, &[ Coin::new(DEPOSIT_AMOUNT, DENOM_BASE), @@ -560,9 +597,9 @@ mod tests { .unwrap(); } - let result = wasm + let _result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::one(), // this is ignored the first time but lets pass it anyway for now }), @@ -571,18 +608,11 @@ mod tests { ) .unwrap(); - // Extract 'tokens_out' attribute value for 'total_collect_spread_rewards' - let tokens_out_spread_rewards = get_event_attributes_by_ty_and_key( - &result, - "total_collect_spread_rewards", - vec!["tokens_out"], - ); - // Collect init for _ in 0..(ACCOUNTS_NUM - 1) { let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::one(), // this is ignored the first time but lets pass it anyway for now }), @@ -600,7 +630,7 @@ mod tests { for account in &accounts { let _ = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::ExactDeposit { recipient: None }, &[ Coin::new(DEPOSIT_AMOUNT, DENOM_BASE), @@ -614,7 +644,7 @@ mod tests { // Collect one more time to finish, even if we extra deposited with one more user we expect the distribution to finish let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards { amount_of_users: Uint128::one(), }), @@ -632,7 +662,7 @@ mod tests { // Adjust the number of distribute actions as needed let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension( crate::msg::ExtensionExecuteMsg::DistributeRewards { amount_of_users: Uint128::one(), // hardcoding 1 @@ -652,7 +682,7 @@ mod tests { // Distribute one more time to finish, even if we extra deposited with one more user we expect the distribution to finish let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::DistributeRewards { amount_of_users: Uint128::one(), }), @@ -671,7 +701,7 @@ mod tests { for account in &accounts { let result = wasm .execute( - contract_address.as_str(), + contract.as_str(), &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ClaimRewards {}), &[], account, @@ -684,32 +714,37 @@ mod tests { rewards_received.push(coin_received_u128); } - // Assert that 'tokens_out' values for events are empty - assert_ne!(tokens_out_spread_rewards[0].value, "".to_string()); - let tokens_out_spread_rewards_u128: u128 = - get_event_value_amount_numeric(&tokens_out_spread_rewards[0].value); - let rewards_less_performance_fee = (tokens_out_spread_rewards_u128 as f64 * 0.8) as u64; - let expected_rewards_per_user = rewards_less_performance_fee / (ACCOUNTS_NUM + 1); // hardcoding +1 due to test logic, we will deposit once more with a single account doubling its shares amount - let expected_rewards_per_user_double = expected_rewards_per_user.mul(2); + // Assert rewards - let double_rewards_value: Vec = rewards_received + let max_reward = *rewards_received .iter() - .filter(|&&x| x > expected_rewards_per_user as u128) - .cloned() - .collect(); - let single_rewards_count = rewards_received + .max() + .expect("There should be at least one reward"); + let max_count = rewards_received .iter() - .filter(|&&x| x == expected_rewards_per_user as u128) + .filter(|&&x| x == max_reward) .count(); - assert_approx_eq!( - double_rewards_value[0], - expected_rewards_per_user_double as u128, - "0.005" + assert_eq!( + max_count, 1, + "There should be exactly one account with the highest reward." ); + + let common_reward = rewards_received + .iter() + .filter(|&&x| x != max_reward) + .next() + .expect("There should be a common lower reward value"); + + let common_count = rewards_received + .iter() + .filter(|&&x| x == *common_reward) + .count(); + assert_eq!( - single_rewards_count, 9, - "There should be exactly one account with double rewards." + common_count, + rewards_received.len() - 1, + "All other rewards should be the same lower value." ); } @@ -725,7 +760,7 @@ mod tests { // #[test] // #[ignore] // fn test_rewards_single_distribute_claim_max_users(users in 10..u64::MAX) { - // let (app, contract_address, cl_pool_id, _admin) = default_init(); + // let (app, contract, cl_pool_id, _admin) = default_init(); // // Initialize accounts // let accounts = app @@ -743,7 +778,7 @@ mod tests { // for account in &accounts { // let _ = wasm // .execute( - // contract_address.as_str(), + // contract.as_str(), // &ExecuteMsg::ExactDeposit { recipient: None }, // &[ // Coin::new(DEPOSIT_AMOUNT, DENOM_BASE), @@ -782,7 +817,7 @@ mod tests { // // Collect and Distribute Rewards // let result = wasm // .execute( - // contract_address.as_str(), + // contract.as_str(), // &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::CollectRewards {}), // &[], // claimer, @@ -811,7 +846,7 @@ mod tests { // // Adjust the number of distribute actions as needed // let result = wasm // .execute( - // contract_address.as_str(), + // contract.as_str(), // &ExecuteMsg::VaultExtension( // crate::msg::ExtensionExecuteMsg::DistributeRewards { // amount_of_users: Uint128::new(1), // hardcoding 1 @@ -842,7 +877,7 @@ mod tests { // for account in &extra_accounts { // let _ = wasm // .execute( - // contract_address.as_str(), + // contract.as_str(), // &ExecuteMsg::ExactDeposit { recipient: None }, // &[ // Coin::new(DEPOSIT_AMOUNT, DENOM_BASE), @@ -856,7 +891,7 @@ mod tests { // // Distribute one more time to finish, even if we extra deposited with one more user we expect the distribution to finish // let result = wasm // .execute( - // contract_address.as_str(), + // contract.as_str(), // &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::DistributeRewards { // amount_of_users: Uint128::new(1), // }), @@ -877,7 +912,7 @@ mod tests { // for account in &accounts { // let result = wasm // .execute( - // contract_address.as_str(), + // contract.as_str(), // &ExecuteMsg::VaultExtension(crate::msg::ExtensionExecuteMsg::ClaimRewards {}), // &[], // account, diff --git a/smart-contracts/contracts/cl-vault/src/vault/concentrated_liquidity.rs b/smart-contracts/contracts/cl-vault/src/vault/concentrated_liquidity.rs index f02caa266..096a50a68 100644 --- a/smart-contracts/contracts/cl-vault/src/vault/concentrated_liquidity.rs +++ b/smart-contracts/contracts/cl-vault/src/vault/concentrated_liquidity.rs @@ -12,7 +12,7 @@ use crate::{ ContractError, }; -pub fn create_position( +pub fn get_create_position_msg( deps: DepsMut, env: &Env, lower_tick: i64, @@ -43,6 +43,7 @@ pub fn create_position( // An sdk.Int in the Go code token_min_amount1: token_min_amount1.to_string(), }; + Ok(create_position) } @@ -165,7 +166,7 @@ mod tests { let token_min_amount0 = Uint128::new(1000); let token_min_amount1 = Uint128::new(2000); - let result = create_position( + let result = get_create_position_msg( deps_mut, &env, lower_tick, diff --git a/smart-contracts/contracts/cl-vault/src/vault/deposit.rs b/smart-contracts/contracts/cl-vault/src/vault/deposit.rs index 4ef615f4e..8017733c4 100644 --- a/smart-contracts/contracts/cl-vault/src/vault/deposit.rs +++ b/smart-contracts/contracts/cl-vault/src/vault/deposit.rs @@ -19,7 +19,7 @@ use crate::{ msg::{ExecuteMsg, MergePositionMsg}, reply::Replies, state::{CurrentDeposit, CURRENT_DEPOSIT, POOL_CONFIG, POSITION, SHARES, VAULT_DENOM}, - vault::concentrated_liquidity::{create_position, get_position}, + vault::concentrated_liquidity::{get_create_position_msg, get_position}, ContractError, }; @@ -76,7 +76,7 @@ pub(crate) fn execute_exact_deposit( coins_to_send.push(token1.clone()); } - let create_position_msg = create_position( + let create_position_msg = get_create_position_msg( deps, &env, position.lower_tick, diff --git a/smart-contracts/contracts/cl-vault/src/vault/merge.rs b/smart-contracts/contracts/cl-vault/src/vault/merge.rs index 52ebd792a..1cd61fcaf 100644 --- a/smart-contracts/contracts/cl-vault/src/vault/merge.rs +++ b/smart-contracts/contracts/cl-vault/src/vault/merge.rs @@ -16,7 +16,7 @@ use crate::{ msg::MergePositionMsg, reply::Replies, state::{CurrentMergePosition, CURRENT_MERGE, CURRENT_MERGE_POSITION, POOL_CONFIG}, - vault::concentrated_liquidity::create_position, + vault::concentrated_liquidity::get_create_position_msg, ContractError, }; @@ -159,7 +159,7 @@ pub fn handle_merge_withdraw_reply( // this is expected to panic if tokens is an empty vec![] // tokens should never be an empty vec![] as this would mean that all the current positions // are returning zero tokens and this would fail on osmosis side - let position = create_position( + let position = get_create_position_msg( deps, &env, range.lower_tick, diff --git a/smart-contracts/contracts/cl-vault/src/vault/range.rs b/smart-contracts/contracts/cl-vault/src/vault/range.rs index 1093021db..2d2630da8 100644 --- a/smart-contracts/contracts/cl-vault/src/vault/range.rs +++ b/smart-contracts/contracts/cl-vault/src/vault/range.rs @@ -24,7 +24,7 @@ use crate::{ ModifyRangeState, Position, SwapDepositMergeState, MODIFY_RANGE_STATE, POOL_CONFIG, POSITION, RANGE_ADMIN, SWAP_DEPOSIT_MERGE_STATE, }, - vault::concentrated_liquidity::create_position, + vault::concentrated_liquidity::get_create_position_msg, vault::concentrated_liquidity::get_position, vault::merge::MergeResponse, vault::swap::swap, @@ -188,18 +188,17 @@ pub fn handle_withdraw_position_reply( let pool_details = get_cl_pool_info(&deps.querier, pool_config.pool_id)?; // if only one token is being deposited, and we are moving into a position where any amount of the other token is needed, - // creating the position here will fail because liquidityNeeded is calculated as 0 on chain level + // creating the position here will fail because liquidityNeeded is calculated as 0 on the chain module level // we can fix this by going straight into a swap-deposit-merge before creating any positions - // todo: Check if needs LTE or just LT - // 0 token0 and current_tick > lower_tick - // 0 token1 and current_tick < upper_tick - // if (lower < current < upper) && amount0 == 0 || amount1 == 0 - // also onesided but wrong token - // bad complexity demon, grug no like - if (amount0.is_zero() && pool_details.current_tick < modify_range_state.upper_tick) - || (amount1.is_zero() && pool_details.current_tick > modify_range_state.lower_tick) - { + // we swap token 1 to token 0 if we don't have any token 0 and the upper price is above the current price + let should_swap_1_to_0 = + amount0.is_zero() && pool_details.current_tick < modify_range_state.upper_tick; + // we swap token 0 to token 1 if we don't have any token 1 and the lower price is below the current price + let should_swap_0_to_1 = + amount1.is_zero() && pool_details.current_tick > modify_range_state.lower_tick; + + if should_swap_1_to_0 || should_swap_0_to_1 { do_swap_deposit_merge( deps, env, @@ -212,7 +211,7 @@ pub fn handle_withdraw_position_reply( ) } else { // we can naively re-deposit up to however much keeps the proportion of tokens the same. Then swap & re-deposit the proper ratio with the remaining tokens - let create_position_msg = create_position( + let create_position_msg = get_create_position_msg( deps, &env, modify_range_state.lower_tick, @@ -461,7 +460,7 @@ fn handle_swap_success( amount: balance1, }); } - let create_position_msg = create_position( + let create_position_msg = get_create_position_msg( deps, &env, swap_deposit_merge_state.target_lower_tick,