From f69aca20d68f3b04d5130faca8a91135a9f6390a Mon Sep 17 00:00:00 2001 From: Lars Lubkoll <11710767+lubkoll@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:27:43 +0000 Subject: [PATCH 1/2] Remove dex-router --- .../contracts/cl-vault/schema/cl-vault.json | 23 ----- .../cl-vault/schema/raw/execute.json | 23 ----- .../contracts/cl-vault/src/contract.rs | 2 + .../osmosis/contracts/cl-vault/src/msg.rs | 5 -- .../osmosis/contracts/cl-vault/src/query.rs | 7 +- .../osmosis/contracts/cl-vault/src/state.rs | 5 +- .../contracts/cl-vault/src/vault/admin.rs | 32 +------ .../contracts/cl-vault/src/vault/deposit.rs | 86 ++++--------------- .../contracts/cl-vault/src/vault/range.rs | 12 ++- .../contracts/cl-vault/src/vault/swap.rs | 41 +-------- .../cl-vault/tests/test-tube/any_deposit.rs | 8 +- .../cl-vault/tests/test-tube/setup.rs | 18 +--- 12 files changed, 38 insertions(+), 224 deletions(-) diff --git a/smart-contracts/osmosis/contracts/cl-vault/schema/cl-vault.json b/smart-contracts/osmosis/contracts/cl-vault/schema/cl-vault.json index 31409d9dd..d5c0df4aa 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/schema/cl-vault.json +++ b/smart-contracts/osmosis/contracts/cl-vault/schema/cl-vault.json @@ -338,29 +338,6 @@ }, "additionalProperties": false }, - { - "description": "Update the dex router address.", - "type": "object", - "required": [ - "update_dex_router" - ], - "properties": { - "update_dex_router": { - "type": "object", - "properties": { - "address": { - "description": "The new dex router address.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Build tick exponent cache", "type": "object", diff --git a/smart-contracts/osmosis/contracts/cl-vault/schema/raw/execute.json b/smart-contracts/osmosis/contracts/cl-vault/schema/raw/execute.json index cc4f7079e..50be98af8 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/schema/raw/execute.json +++ b/smart-contracts/osmosis/contracts/cl-vault/schema/raw/execute.json @@ -229,29 +229,6 @@ }, "additionalProperties": false }, - { - "description": "Update the dex router address.", - "type": "object", - "required": [ - "update_dex_router" - ], - "properties": { - "update_dex_router": { - "type": "object", - "properties": { - "address": { - "description": "The new dex router address.", - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, { "description": "Build tick exponent cache", "type": "object", diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/contract.rs b/smart-contracts/osmosis/contracts/cl-vault/src/contract.rs index 67b8d339a..0fea53c02 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/contract.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/contract.rs @@ -201,6 +201,8 @@ pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result Result { + let dex_router_item: Item = Item::new("dex_router"); + dex_router_item.remove(deps.storage); // VaultConfig #[cw_serde] struct OldVaultConfig { diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/msg.rs b/smart-contracts/osmosis/contracts/cl-vault/src/msg.rs index ce323ccc2..123260d5c 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/msg.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/msg.rs @@ -54,11 +54,6 @@ pub enum AdminExtensionExecuteMsg { /// The metadata updates. updates: Metadata, }, - /// Update the dex router address. - UpdateDexRouter { - /// The new dex router address. - address: Option, - }, /// Build tick exponent cache BuildTickCache {}, } diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/query.rs b/smart-contracts/osmosis/contracts/cl-vault/src/query.rs index c0f17a5b3..9c76602cb 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/query.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/query.rs @@ -1,9 +1,8 @@ use crate::helpers::coinlist::CoinList; use crate::helpers::getters::get_unused_balances; use crate::math::tick::verify_tick_exp_cache; -use crate::state::DEX_ROUTER; use crate::state::{ - PoolConfig, ADMIN_ADDRESS, METADATA, POOL_CONFIG, POSITION, SHARES, VAULT_DENOM, + PoolConfig, ADMIN_ADDRESS, METADATA, POOL_CONFIG, POSITION, SHARES, VAULT_CONFIG, VAULT_DENOM, }; use crate::vault::concentrated_liquidity::get_position; use crate::ContractError; @@ -106,10 +105,10 @@ pub fn query_metadata(deps: Deps) -> Result { } pub fn query_dex_router(deps: Deps) -> Result { - let dex_router = DEX_ROUTER.load(deps.storage)?; + let config = VAULT_CONFIG.load(deps.storage)?; Ok(DexRouterResponse { - dex_router: dex_router.to_string(), + dex_router: config.dex_router.to_string(), }) } diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/state.rs b/smart-contracts/osmosis/contracts/cl-vault/src/state.rs index 6c86ad60b..18197a93a 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/state.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/state.rs @@ -1,6 +1,6 @@ use crate::vault::merge::CurrentMergeWithdraw; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Coin, Decimal, Decimal256, Uint128}; +use cosmwasm_std::{Addr, Decimal, Decimal256, Uint128}; use cw_storage_plus::{Deque, Item, Map}; use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; @@ -72,8 +72,7 @@ pub struct CurrentDeposit { pub sender: Addr, } -pub const CURRENT_SWAP_ANY_DEPOSIT: Item<(Coin, Addr, (Uint128, Uint128))> = - Item::new("current_swap_any_deposit"); +pub const CURRENT_SWAP_RECIPIENT: Item = Item::new("current_swap_recipient"); pub const DEX_ROUTER: Item = Item::new("dex_router"); diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/vault/admin.rs b/smart-contracts/osmosis/contracts/cl-vault/src/vault/admin.rs index 6f176075d..a81a19065 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/admin.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/vault/admin.rs @@ -1,8 +1,6 @@ use crate::error::assert_admin; use crate::math::tick::build_tick_exp_cache; -use crate::state::{ - Metadata, VaultConfig, ADMIN_ADDRESS, DEX_ROUTER, METADATA, RANGE_ADMIN, VAULT_CONFIG, -}; +use crate::state::{Metadata, VaultConfig, ADMIN_ADDRESS, METADATA, RANGE_ADMIN, VAULT_CONFIG}; use crate::{msg::AdminExtensionExecuteMsg, ContractError}; use cosmwasm_std::{Decimal, DepsMut, MessageInfo, Response, StdError}; use cw_utils::nonpayable; @@ -25,9 +23,6 @@ pub(crate) fn execute_admin( AdminExtensionExecuteMsg::UpdateRangeAdmin { address } => { execute_update_range_admin(deps, info, address) } - AdminExtensionExecuteMsg::UpdateDexRouter { address } => { - execute_update_dex_router(deps, info, address) - } AdminExtensionExecuteMsg::BuildTickCache {} => execute_build_tick_exp_cache(deps, info), } } @@ -80,31 +75,6 @@ pub fn execute_update_range_admin( .add_attribute("new_admin", &new_admin)) } -/// Updates the dex router address -pub fn execute_update_dex_router( - deps: DepsMut, - info: MessageInfo, - address: Option, -) -> Result { - nonpayable(&info).map_err(|_| ContractError::NonPayable {})?; - assert_admin(deps.storage, &info.sender)?; - - match address.clone() { - Some(address) => { - let new_router = deps.api.addr_validate(&address)?; - DEX_ROUTER.save(deps.storage, &new_router.clone())?; - } - None => { - DEX_ROUTER.remove(deps.storage); - } - } - - Ok(Response::new() - .add_attribute("method", "execute") - .add_attribute("action", "update_dex_router") - .add_attribute("new_router", address.unwrap_or_default().to_string())) -} - /// Updates the configuration of the contract. /// /// This function first checks if the message sender is nonpayable. If the sender sent funds, a `ContractError::NonPayable` error is returned. diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs b/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs index ed32f19b0..fad872231 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs @@ -3,14 +3,14 @@ use crate::{ helpers::{ getters::{ get_depositable_tokens, get_single_sided_deposit_0_to_1_swap_amount, - get_single_sided_deposit_1_to_0_swap_amount, get_twap_price, get_value_wrt_asset0, - DepositInfo, + get_single_sided_deposit_1_to_0_swap_amount, get_twap_price, get_unused_pair_balances, + get_value_wrt_asset0, DepositInfo, }, msgs::refund_bank_msg, }, query::{query_total_assets, query_total_vault_token_supply}, reply::Replies, - state::{CURRENT_SWAP_ANY_DEPOSIT, DEX_ROUTER, POOL_CONFIG, SHARES, VAULT_CONFIG, VAULT_DENOM}, + state::{CURRENT_SWAP_RECIPIENT, POOL_CONFIG, SHARES, VAULT_CONFIG, VAULT_DENOM}, vault::{ concentrated_liquidity::{get_cl_pool_info, get_position}, swap::{estimate_swap_min_out_amount, swap_msg}, @@ -18,12 +18,10 @@ use crate::{ ContractError, }; use cosmwasm_std::{ - attr, coin, Addr, Coin, Decimal, DepsMut, Env, Fraction, MessageInfo, Response, SubMsg, - SubMsgResult, Uint128, Uint256, -}; -use osmosis_std::types::osmosis::{ - poolmanager::v1beta1::MsgSwapExactAmountInResponse, tokenfactory::v1beta1::MsgMint, + attr, coin, Addr, Decimal, DepsMut, Env, Fraction, MessageInfo, Response, SubMsg, SubMsgResult, + Uint128, Uint256, }; +use osmosis_std::types::osmosis::tokenfactory::v1beta1::MsgMint; pub(crate) fn execute_exact_deposit( mut deps: DepsMut, @@ -69,7 +67,7 @@ pub(crate) fn execute_any_deposit( pool_config.clone().token0, pool_config.clone().token1, )?; - let (token_in, out_denom, remainder, price) = if !deposit_info.base_refund.amount.is_zero() { + let (token_in, out_denom, price) = if !deposit_info.base_refund.amount.is_zero() { let token_in_amount = if pool_details.current_tick > position.upper_tick { deposit_info.base_refund.amount } else { @@ -81,15 +79,7 @@ pub(crate) fn execute_any_deposit( )? }; let token_in = coin(token_in_amount.into(), pool_config.token0.clone()); - let remainder = coin( - deposit_info - .base_refund - .amount - .checked_sub(token_in.amount)? - .into(), - pool_config.token0.clone(), - ); - (token_in, pool_config.token1.clone(), remainder, twap_price) + (token_in, pool_config.token1.clone(), twap_price) } else { let token_in_amount = if pool_details.current_tick < position.lower_tick { deposit_info.quote_refund.amount @@ -102,40 +92,21 @@ pub(crate) fn execute_any_deposit( )? }; let token_in = coin(token_in_amount.into(), pool_config.token1.clone()); - let remainder = coin( - deposit_info - .quote_refund - .amount - .checked_sub(token_in.amount)? - .into(), - pool_config.token1.clone(), - ); ( token_in, pool_config.token0.clone(), - remainder, twap_price.inv().expect("Invalid price"), ) }; - CURRENT_SWAP_ANY_DEPOSIT.save( - deps.storage, - &( - remainder, - recipient.clone(), - (deposit_info.base_deposit, deposit_info.quote_deposit), - ), - )?; + CURRENT_SWAP_RECIPIENT.save(deps.storage, &recipient)?; let token_out_min_amount = estimate_swap_min_out_amount(token_in.amount, price, max_slippage)?; - let dex_router = DEX_ROUTER.may_load(deps.storage)?; let swap_msg = swap_msg( - env.contract.address, - pool_config.pool_id, + vault_config.dex_router, token_in.clone(), coin(token_out_min_amount.into(), out_denom.clone()), None, // TODO: check this None - dex_router, )?; Ok(Response::new() @@ -154,46 +125,21 @@ pub(crate) fn execute_any_deposit( pub fn handle_any_deposit_swap_reply( mut deps: DepsMut, env: Env, - data: SubMsgResult, + _data: SubMsgResult, ) -> Result { - // Attempt to directly parse the data to MsgSwapExactAmountInResponse outside of the match - let resp: MsgSwapExactAmountInResponse = data.try_into()?; - - let (remainder, recipient, deposit_amount_in_ratio) = - CURRENT_SWAP_ANY_DEPOSIT.load(deps.storage)?; - CURRENT_SWAP_ANY_DEPOSIT.remove(deps.storage); + let recipient = CURRENT_SWAP_RECIPIENT.load(deps.storage)?; + CURRENT_SWAP_RECIPIENT.remove(deps.storage); let pool_config = POOL_CONFIG.load(deps.storage)?; - let (balance0, balance1): (Uint128, Uint128) = if remainder.denom == pool_config.token0 { - ( - remainder.amount, - Uint128::new(resp.token_out_amount.parse()?), - ) - } else { - ( - Uint128::new(resp.token_out_amount.parse()?), - remainder.amount, - ) - }; - - let coins_to_mint_for = ( - Coin { - denom: pool_config.token0.clone(), - amount: balance0 + deposit_amount_in_ratio.0, - }, - Coin { - denom: pool_config.token1.clone(), - amount: balance1 + deposit_amount_in_ratio.1, - }, - ); + let balances = get_unused_pair_balances(&deps, &env, &pool_config)?; execute_deposit( &mut deps, env, recipient, DepositInfo { - base_deposit: coins_to_mint_for.0.amount, - quote_deposit: coins_to_mint_for.1.amount, + base_deposit: balances[0].amount, + quote_deposit: balances[1].amount, base_refund: coin(0u128, pool_config.token0), quote_refund: coin(0u128, pool_config.token1), }, diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/vault/range.rs b/smart-contracts/osmosis/contracts/cl-vault/src/vault/range.rs index d816ac1a5..b733b900b 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/range.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/vault/range.rs @@ -7,8 +7,8 @@ use crate::{ math::tick::{price_to_tick, tick_to_price}, reply::Replies, state::{ - ModifyRangeState, Position, SwapDepositMergeState, DEX_ROUTER, MODIFY_RANGE_STATE, - POOL_CONFIG, POSITION, SWAP_DEPOSIT_MERGE_STATE, + ModifyRangeState, Position, SwapDepositMergeState, MODIFY_RANGE_STATE, POOL_CONFIG, + POSITION, SWAP_DEPOSIT_MERGE_STATE, VAULT_CONFIG, }, vault::{ concentrated_liquidity::{ @@ -215,14 +215,12 @@ pub fn handle_withdraw_position_reply(deps: DepsMut, env: Env) -> Result, ) -> Result { let vault_config = VAULT_CONFIG.load(deps.storage)?; - let dex_router = DEX_ROUTER.may_load(deps.storage)?; let twap_price = get_twap_price( &deps.querier, @@ -102,12 +101,10 @@ fn prepare_swap_msg( estimate_swap_min_out_amount(token_in.amount, twap_price, vault_config.swap_max_slippage)?; let swap_msg = swap_msg( - env.clone().contract.address, - pool_id, + vault_config.dex_router, token_in, coin(token_out_min_amount.into(), token_out_denom), forced_swap_route, - dex_router, )?; Ok(swap_msg) @@ -124,42 +121,12 @@ pub fn estimate_swap_min_out_amount( } pub fn swap_msg( - sender: Addr, - pool_id: u64, + dex_router: Addr, token_in: Coin, min_receive: Coin, forced_swap_route: Option>, - dex_router: Option, ) -> Result { - if let Some(dex_router) = dex_router { - cw_dex_execute_swap_operations_msg(dex_router, forced_swap_route, token_in, min_receive) - } else { - let pool_route = SwapAmountInRoute { - pool_id, - token_out_denom: min_receive.denom, - }; - Ok(osmosis_swap_exact_amount_in_msg( - sender, - pool_route, - token_in, - min_receive.amount, - )) - } -} - -fn osmosis_swap_exact_amount_in_msg( - sender: Addr, - pool_route: SwapAmountInRoute, - token_in: Coin, - token_out_min_amount: Uint128, -) -> CosmosMsg { - osmosis_std::types::osmosis::poolmanager::v1beta1::MsgSwapExactAmountIn { - sender: sender.to_string(), - routes: vec![pool_route], - token_in: Some(token_in.into()), - token_out_min_amount: token_out_min_amount.to_string(), - } - .into() + cw_dex_execute_swap_operations_msg(dex_router, forced_swap_route, token_in, min_receive) } fn cw_dex_execute_swap_operations_msg( diff --git a/smart-contracts/osmosis/contracts/cl-vault/tests/test-tube/any_deposit.rs b/smart-contracts/osmosis/contracts/cl-vault/tests/test-tube/any_deposit.rs index d72d4f76f..8a991cdb7 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/tests/test-tube/any_deposit.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/tests/test-tube/any_deposit.rs @@ -1,6 +1,6 @@ use crate::setup::{ - fixture_default, fixture_dex_router, ACCOUNTS_INIT_BALANCE, ACCOUNTS_NUM, DENOM_BASE, - DENOM_QUOTE, MAX_SLIPPAGE_HIGH, PERFORMANCE_FEE_DEFAULT, + fixture_dex_router, ACCOUNTS_INIT_BALANCE, ACCOUNTS_NUM, DENOM_BASE, DENOM_QUOTE, + MAX_SLIPPAGE_HIGH, PERFORMANCE_FEE_DEFAULT, }; use cl_vault::msg::ExecuteMsg; @@ -25,8 +25,8 @@ fn test_any_deposit() { ]; for (amount_base, amount_quote) in test_cases { - let (app, contract_address, vault_pool_id, admin, ..) = - fixture_default(PERFORMANCE_FEE_DEFAULT); + let (app, contract_address, _dex_router, vault_pool_id, _pools, admin, ..) = + fixture_dex_router(PERFORMANCE_FEE_DEFAULT); do_and_verify_any_deposit( app, diff --git a/smart-contracts/osmosis/contracts/cl-vault/tests/test-tube/setup.rs b/smart-contracts/osmosis/contracts/cl-vault/tests/test-tube/setup.rs index 129ba21f0..fc7feb825 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/tests/test-tube/setup.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/tests/test-tube/setup.rs @@ -1,10 +1,6 @@ #![allow(dead_code)] -use cl_vault::{ - helpers::generic::sort_tokens, - msg::{AdminExtensionExecuteMsg, ExtensionExecuteMsg, InstantiateMsg}, - state::VaultConfig, -}; +use cl_vault::{helpers::generic::sort_tokens, msg::InstantiateMsg, state::VaultConfig}; use cosmwasm_std::{coin, Addr, Attribute, Coin, Decimal, Uint128}; use dex_router_osmosis::msg::{ExecuteMsg as DexExecuteMsg, InstantiateMsg as DexInstantiate}; use osmosis_std::types::{ @@ -467,18 +463,6 @@ fn init_test_contract_with_dex_router_and_swap_pools( ) .unwrap(); - wasm.execute( - &contract_cl.data.address, - &cw_vault_multi_standard::VaultStandardExecuteMsg::VaultExtension( - ExtensionExecuteMsg::Admin(AdminExtensionExecuteMsg::UpdateDexRouter { - address: Some(contract_dex_router.data.address.clone()), - }), - ), - &[], - &admin, - ) - .unwrap(); - ( app, Addr::unchecked(contract_cl.data.address), From ed68c611d4f87c93e5b2573c6c2e3c999fb3d0f6 Mon Sep 17 00:00:00 2001 From: Lars Lubkoll <11710767+lubkoll@users.noreply.github.com> Date: Tue, 27 Aug 2024 13:21:32 +0000 Subject: [PATCH 2/2] Fix swap-reply and add unit-tests --- .../osmosis/contracts/cl-vault/src/error.rs | 4 + .../osmosis/contracts/cl-vault/src/state.rs | 11 +- .../contracts/cl-vault/src/test_helpers.rs | 17 +- .../contracts/cl-vault/src/vault/deposit.rs | 358 +++++++++++++++++- 4 files changed, 371 insertions(+), 19 deletions(-) diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/error.rs b/smart-contracts/osmosis/contracts/cl-vault/src/error.rs index 281a6efea..06d1f4395 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/error.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/error.rs @@ -6,6 +6,7 @@ use cosmwasm_std::{ }; use cw_utils::PaymentError; use prost::DecodeError; +use quasar_types::pool_pair::PoolPairError; use std::num::{ParseIntError, TryFromIntError}; use thiserror::Error; @@ -158,6 +159,9 @@ pub enum ContractError { #[error("{0}")] TryFromIntError(#[from] TryFromIntError), + + #[error("{0}")] + PoolPair(#[from] PoolPairError), } pub fn assert_deposits(funds: &[Coin], config: &PoolConfig) -> Result<(), ContractError> { diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/state.rs b/smart-contracts/osmosis/contracts/cl-vault/src/state.rs index 18197a93a..76aa516d3 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/state.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/state.rs @@ -1,8 +1,9 @@ use crate::vault::merge::CurrentMergeWithdraw; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Decimal, Decimal256, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Decimal256, Uint128}; use cw_storage_plus::{Deque, Item, Map}; use osmosis_std::types::osmosis::poolmanager::v1beta1::SwapAmountInRoute; +use quasar_types::pool_pair::PoolPair; #[cw_serde] pub struct Metadata { @@ -72,7 +73,13 @@ pub struct CurrentDeposit { pub sender: Addr, } -pub const CURRENT_SWAP_RECIPIENT: Item = Item::new("current_swap_recipient"); +#[cw_serde] +pub struct CurrentSwap { + pub recipient: Addr, + pub vault_balance: PoolPair, +} + +pub const CURRENT_SWAP_INFO: Item = Item::new("current_swap_recipient"); pub const DEX_ROUTER: Item = Item::new("dex_router"); diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/test_helpers.rs b/smart-contracts/osmosis/contracts/cl-vault/src/test_helpers.rs index fca9fc6fa..1a19a7be9 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/test_helpers.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/test_helpers.rs @@ -30,6 +30,9 @@ pub const POSITION_ID: u64 = 101; pub const BASE_DENOM: &str = "base"; pub const QUOTE_DENOM: &str = "quote"; pub const TEST_VAULT_DENOM: &str = "uqsr"; +pub const INSTANTIATE_BASE_DEPOSIT_AMOUNT: u128 = 100; +pub const INSTANTIATE_QUOTE_DEPOSIT_AMOUNT: u128 = 100; +pub const TEST_VAULT_TOKEN_SUPPLY: u128 = 100_000; pub struct QuasarQuerier { position: FullPositionBreakdown, @@ -89,7 +92,7 @@ impl Querier for QuasarQuerier { to_json_binary(&QuerySupplyOfResponse { amount: Some(OsmoCoin { denom, - amount: 100000.to_string(), + amount: TEST_VAULT_TOKEN_SUPPLY.to_string(), }), }) .unwrap(), @@ -268,8 +271,8 @@ pub fn get_init_msg(admin: &str) -> InstantiateMsg { }, vault_token_subdenom: "utestvault".to_string(), range_admin: admin.to_string(), - initial_lower_tick: 1, - initial_upper_tick: 100, + initial_lower_tick: 100, + initial_upper_tick: 1000, thesis: "Test thesis".to_string(), name: "Contract".to_string(), } @@ -277,7 +280,13 @@ pub fn get_init_msg(admin: &str) -> InstantiateMsg { pub fn instantiate_contract(mut deps: DepsMut, env: Env, admin: &str) { let msg = get_init_msg(admin); - let info = mock_info(admin, &[coin(100, BASE_DENOM), coin(100, QUOTE_DENOM)]); + let info = mock_info( + admin, + &[ + coin(INSTANTIATE_BASE_DEPOSIT_AMOUNT, BASE_DENOM), + coin(INSTANTIATE_QUOTE_DEPOSIT_AMOUNT, QUOTE_DENOM), + ], + ); assert!(instantiate(deps.branch(), env, info, msg).is_ok()); VAULT_DENOM .save(deps.storage, &TEST_VAULT_DENOM.to_string()) diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs b/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs index fad872231..2bc86c2c5 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs @@ -3,14 +3,14 @@ use crate::{ helpers::{ getters::{ get_depositable_tokens, get_single_sided_deposit_0_to_1_swap_amount, - get_single_sided_deposit_1_to_0_swap_amount, get_twap_price, get_unused_pair_balances, + get_single_sided_deposit_1_to_0_swap_amount, get_twap_price, get_unused_pair, get_value_wrt_asset0, DepositInfo, }, msgs::refund_bank_msg, }, query::{query_total_assets, query_total_vault_token_supply}, reply::Replies, - state::{CURRENT_SWAP_RECIPIENT, POOL_CONFIG, SHARES, VAULT_CONFIG, VAULT_DENOM}, + state::{CurrentSwap, CURRENT_SWAP_INFO, POOL_CONFIG, SHARES, VAULT_CONFIG, VAULT_DENOM}, vault::{ concentrated_liquidity::{get_cl_pool_info, get_position}, swap::{estimate_swap_min_out_amount, swap_msg}, @@ -22,6 +22,7 @@ use cosmwasm_std::{ Uint128, Uint256, }; use osmosis_std::types::osmosis::tokenfactory::v1beta1::MsgMint; +use quasar_types::pool_pair::PoolPair; pub(crate) fn execute_exact_deposit( mut deps: DepsMut, @@ -98,7 +99,29 @@ pub(crate) fn execute_any_deposit( twap_price.inv().expect("Invalid price"), ) }; - CURRENT_SWAP_RECIPIENT.save(deps.storage, &recipient)?; + let unused = get_unused_pair(&deps.as_ref(), &env.contract.address, &pool_config)?; + let base_funds = deposit_info + .base_deposit + .checked_add(deposit_info.base_refund.amount)?; + let quote_funds = deposit_info + .quote_deposit + .checked_add(deposit_info.quote_refund.amount)?; + CURRENT_SWAP_INFO.save( + deps.storage, + &CurrentSwap { + recipient, + vault_balance: PoolPair::new( + coin( + unused.base.amount.checked_sub(base_funds)?.into(), + unused.base.denom, + ), + coin( + unused.quote.amount.checked_sub(quote_funds)?.into(), + unused.quote.denom, + ), + ), + }, + )?; let token_out_min_amount = estimate_swap_min_out_amount(token_in.amount, price, max_slippage)?; @@ -127,19 +150,20 @@ pub fn handle_any_deposit_swap_reply( env: Env, _data: SubMsgResult, ) -> Result { - let recipient = CURRENT_SWAP_RECIPIENT.load(deps.storage)?; - CURRENT_SWAP_RECIPIENT.remove(deps.storage); + let info: CurrentSwap = CURRENT_SWAP_INFO.load(deps.storage)?; + CURRENT_SWAP_INFO.remove(deps.storage); let pool_config = POOL_CONFIG.load(deps.storage)?; - let balances = get_unused_pair_balances(&deps, &env, &pool_config)?; + let balances = get_unused_pair(&deps.as_ref(), &env.contract.address, &pool_config)?; + let user_balances = balances.checked_sub(&info.vault_balance)?; execute_deposit( &mut deps, env, - recipient, + info.recipient, DepositInfo { - base_deposit: balances[0].amount, - quote_deposit: balances[1].amount, + base_deposit: user_balances.base.amount, + quote_deposit: user_balances.quote.amount, base_refund: coin(0u128, pool_config.token0), quote_refund: coin(0u128, pool_config.token1), }, @@ -194,7 +218,6 @@ fn execute_deposit( .try_into()? }; - // save the shares in the user map SHARES.update( deps.storage, recipient.clone(), @@ -243,13 +266,18 @@ mod tests { use std::str::FromStr; use cosmwasm_std::{ - testing::{mock_env, mock_info}, - Addr, BankMsg, Decimal256, Fraction, Uint256, + testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}, + Addr, BankMsg, CosmosMsg, Decimal256, Fraction, Reply, SubMsgResponse, Uint256, WasmMsg, }; use crate::{ + contract::{execute, reply}, helpers::msgs::refund_bank_msg, - test_helpers::{instantiate_contract, mock_deps_with_querier, BASE_DENOM, QUOTE_DENOM}, + msg::ExecuteMsg, + test_helpers::{ + instantiate_contract, mock_deps_with_querier, mock_deps_with_querier_with_balance, + BASE_DENOM, QUOTE_DENOM, TEST_VAULT_DENOM, TEST_VAULT_TOKEN_SUPPLY, + }, }; use super::*; @@ -349,4 +377,308 @@ mod tests { } ) } + + const ADMIN: &str = "admin"; + const SENDER: &str = "sender"; + + #[test] + fn exact_deposit_with_single_wrong_denom_fails() { + let mut deps = mock_deps_with_querier(); + let env = mock_env(); + instantiate_contract(deps.as_mut(), env.clone(), ADMIN); + + let info = mock_info(SENDER, &[coin(1, "other_denom".to_string())]); + let err = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ExactDeposit { recipient: None }, + ) + .unwrap_err(); + assert_eq!(err, ContractError::IncorrectDepositFunds); + } + + #[test] + fn exact_deposit_with_more_than_two_assets_fails() { + let mut deps = mock_deps_with_querier(); + let env = mock_env(); + instantiate_contract(deps.as_mut(), env.clone(), ADMIN); + + let info = mock_info( + SENDER, + &[ + coin(1, BASE_DENOM.to_string()), + coin(1, QUOTE_DENOM.to_string()), + coin(1, "other_denom".to_string()), + ], + ); + let err = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::ExactDeposit { recipient: None }, + ) + .unwrap_err(); + assert_eq!(err, ContractError::IncorrectDepositFunds); + } + + #[test] + fn successful_exact_deposit_mints_fund_tokens_according_to_share_of_assets() { + let current_deposit_amount = 100u128; + let deposit_amount = 50; + let env = mock_env(); + let fund_shares = 50000u64; + let mut deps = mock_deps_with_querier_with_balance( + 100, + 100, + 0, + &[( + MOCK_CONTRACT_ADDR, + &[ + coin(current_deposit_amount + deposit_amount, BASE_DENOM), + coin(current_deposit_amount + deposit_amount, QUOTE_DENOM), + coin(fund_shares.into(), TEST_VAULT_DENOM), + ], + )], + ); + + instantiate_contract(deps.as_mut(), env.clone(), ADMIN); + + let info = mock_info( + SENDER, + &[ + coin(deposit_amount, BASE_DENOM.to_string()), + coin(deposit_amount, QUOTE_DENOM), + ], + ); + let response = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::ExactDeposit { recipient: None }, + ) + .unwrap(); + assert_eq!(response.messages.len(), 1); + + let expected_minted_tokens = TEST_VAULT_TOKEN_SUPPLY / 4; + let msg = response.messages[0].msg.clone(); + match msg { + CosmosMsg::Stargate { type_url: _, value } => { + let m: MsgMint = value.try_into().unwrap(); + assert_eq!(m.sender, env.contract.address.to_string()); + assert_eq!( + m.amount.as_ref().unwrap().amount, + expected_minted_tokens.to_string() + ); + assert_eq!( + m.amount.as_ref().unwrap().denom, + TEST_VAULT_DENOM.to_string() + ); + assert_eq!(m.mint_to_address, env.contract.address.to_string()); + } + _ => panic!("unreachable"), + } + } + + #[test] + fn any_deposit_with_single_wrong_denom_fails() { + let mut deps = mock_deps_with_querier(); + let env = mock_env(); + instantiate_contract(deps.as_mut(), env.clone(), ADMIN); + + let info = mock_info(SENDER, &[coin(1, "other_denom".to_string())]); + let err = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::AnyDeposit { + amount: Uint128::zero(), + asset: String::default(), + recipient: None, + max_slippage: Decimal::percent(90), + }, + ) + .unwrap_err(); + assert_eq!(err, ContractError::IncorrectDepositFunds); + } + + #[test] + fn any_deposit_with_more_than_two_assets_fails() { + let mut deps = mock_deps_with_querier(); + let env = mock_env(); + instantiate_contract(deps.as_mut(), env.clone(), ADMIN); + + let info = mock_info( + SENDER, + &[ + coin(1, BASE_DENOM.to_string()), + coin(1, QUOTE_DENOM.to_string()), + coin(1, "other_denom".to_string()), + ], + ); + let err = execute( + deps.as_mut(), + env, + info, + ExecuteMsg::AnyDeposit { + amount: Uint128::zero(), + asset: String::default(), + recipient: None, + max_slippage: Decimal::percent(90), + }, + ) + .unwrap_err(); + assert_eq!(err, ContractError::IncorrectDepositFunds); + } + + #[test] + fn successful_exact_any_deposit_mints_fund_tokens_according_to_share_of_assets() { + let current_deposit_amount = 100u128; + let deposit_amount = 50; + let env = mock_env(); + let mut deps = mock_deps_with_querier_with_balance( + 100, + 100, + 0, + &[( + MOCK_CONTRACT_ADDR, + &[ + coin(current_deposit_amount + deposit_amount, BASE_DENOM), + coin(current_deposit_amount + deposit_amount, QUOTE_DENOM), + coin(TEST_VAULT_TOKEN_SUPPLY, TEST_VAULT_DENOM), + ], + )], + ); + + instantiate_contract(deps.as_mut(), env.clone(), ADMIN); + + let info = mock_info( + SENDER, + &[ + coin(deposit_amount, BASE_DENOM.to_string()), + coin(deposit_amount, QUOTE_DENOM), + ], + ); + let response = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::AnyDeposit { + amount: Uint128::zero(), + asset: String::default(), + recipient: None, + max_slippage: Decimal::percent(90), + }, + ) + .unwrap(); + assert_eq!(response.messages.len(), 1); + + let expected_minted_tokens = TEST_VAULT_TOKEN_SUPPLY / 4; + let msg = response.messages[0].msg.clone(); + match msg { + CosmosMsg::Stargate { type_url: _, value } => { + let m: MsgMint = value.try_into().unwrap(); + assert_eq!(m.sender, env.contract.address.to_string()); + assert_eq!( + m.amount.as_ref().unwrap().amount, + expected_minted_tokens.to_string() + ); + assert_eq!( + m.amount.as_ref().unwrap().denom, + TEST_VAULT_DENOM.to_string() + ); + assert_eq!(m.mint_to_address, env.contract.address.to_string()); + } + _ => panic!("unreachable"), + } + } + + #[test] + fn successful_inexact_any_deposit_mints_fund_tokens_according_to_share_of_assets() { + let current_deposit_amount = 100u128; + let base_deposit_amount = 50; + let quote_deposit_amount = 100; + let env = mock_env(); + let mut deps = mock_deps_with_querier_with_balance( + 100, + 100, + 549, + &[( + MOCK_CONTRACT_ADDR, + &[ + coin(current_deposit_amount + base_deposit_amount, BASE_DENOM), + coin(current_deposit_amount + quote_deposit_amount, QUOTE_DENOM), + coin(TEST_VAULT_TOKEN_SUPPLY, TEST_VAULT_DENOM), + ], + )], + ); + + instantiate_contract(deps.as_mut(), env.clone(), ADMIN); + + let info = mock_info( + SENDER, + &[ + coin(base_deposit_amount, BASE_DENOM.to_string()), + coin(quote_deposit_amount, QUOTE_DENOM), + ], + ); + let response = execute( + deps.as_mut(), + env.clone(), + info.clone(), + ExecuteMsg::AnyDeposit { + amount: Uint128::zero(), + asset: String::default(), + recipient: None, + max_slippage: Decimal::percent(90), + }, + ) + .unwrap(); + assert_eq!(response.messages.len(), 1); + + let msg = response.messages[0].msg.clone(); + match msg { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: _, + msg: _, + funds, + }) => { + assert_eq!(funds.len(), 1); + assert_eq!(funds[0].denom, QUOTE_DENOM); + assert_eq!(funds[0].amount, Uint128::from(25u64)); + } + _ => panic!("unreachable"), + } + + let response = reply( + deps.as_mut(), + env.clone(), + Reply { + id: Replies::AnyDepositSwap.into(), + result: SubMsgResult::Ok(SubMsgResponse { + events: vec![], + data: None, + }), + }, + ) + .unwrap(); + let expected_minted_tokens = 37_250; + let msg = response.messages[0].msg.clone(); + match msg { + CosmosMsg::Stargate { type_url: _, value } => { + let m: MsgMint = value.try_into().unwrap(); + assert_eq!(m.sender, env.contract.address.to_string()); + assert_eq!( + m.amount.as_ref().unwrap().amount, + expected_minted_tokens.to_string() + ); + assert_eq!( + m.amount.as_ref().unwrap().denom, + TEST_VAULT_DENOM.to_string() + ); + assert_eq!(m.mint_to_address, env.contract.address.to_string()); + } + _ => panic!("unreachable"), + } + } }