diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/contract.rs b/smart-contracts/osmosis/contracts/cl-vault/src/contract.rs index 62264466a..56928c0e4 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/contract.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/contract.rs @@ -13,7 +13,7 @@ use crate::helpers::prepend::prepend_claim_msg; use crate::instantiate::{ handle_create_denom_reply, handle_instantiate, handle_instantiate_create_position_reply, }; -use crate::msg::{ExecuteMsg, InstantiateMsg, MigrateMsg, ModifyRangeMsg, QueryMsg}; +use crate::msg::{DepositType, ExecuteMsg, InstantiateMsg, MigrateMsg, ModifyRangeMsg, QueryMsg}; use crate::query::{ query_assets_from_shares, query_dex_router, query_info, query_metadata, query_pool, query_position, query_total_assets, query_total_vault_token_supply, query_user_assets, @@ -32,14 +32,13 @@ use crate::state::{ #[allow(deprecated)] use crate::state::{Position, OLD_POSITION, POSITION}; use crate::vault::admin::execute_admin; -use crate::vault::any_deposit::{execute_any_deposit, handle_any_deposit_swap_reply}; use crate::vault::autocompound::{ execute_autocompound, execute_migration_step, handle_autocompound_reply, }; +use crate::vault::deposit::{execute_deposit, handle_any_deposit_swap_reply}; use crate::vault::distribution::{ execute_collect_rewards, handle_collect_incentives_reply, handle_collect_spread_rewards_reply, }; -use crate::vault::exact_deposit::execute_exact_deposit; use crate::vault::merge::{ execute_merge_position, handle_merge_create_position_reply, handle_merge_withdraw_position_reply, @@ -80,9 +79,15 @@ pub fn execute( asset: _, recipient, max_slippage, - } => execute_any_deposit(deps, env, info, recipient, max_slippage), + } => execute_deposit( + deps, + env, + info, + recipient, + DepositType::Any { max_slippage }, + ), cw_vault_multi_standard::VaultStandardExecuteMsg::ExactDeposit { recipient } => { - execute_exact_deposit(deps, env, info, recipient) + execute_deposit(deps, env, info, recipient, DepositType::Exact) } cw_vault_multi_standard::VaultStandardExecuteMsg::Redeem { recipient, amount } => { prepend_claim_msg( @@ -97,10 +102,10 @@ pub fn execute( } crate::msg::ExtensionExecuteMsg::Authz(msg) => match msg { crate::msg::AuthzExtension::ExactDeposit {} => { - execute_exact_deposit(deps, env, info, None) + execute_deposit(deps, env, info, None, DepositType::Exact) } crate::msg::AuthzExtension::AnyDeposit { max_slippage } => { - execute_any_deposit(deps, env, info, None, max_slippage) + execute_deposit(deps, env, info, None, DepositType::Any { max_slippage }) } crate::msg::AuthzExtension::Redeem { amount } => prepend_claim_msg( &env, diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/helpers/getters.rs b/smart-contracts/osmosis/contracts/cl-vault/src/helpers/getters.rs index e6ba63f4a..ea76e3987 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/helpers/getters.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/helpers/getters.rs @@ -1,5 +1,6 @@ use crate::math::tick::tick_to_price; use crate::state::{PoolConfig, RANGE_ADMIN}; +use crate::vault::range::SwapDirection; use std::str::FromStr; use osmosis_std::shim::Timestamp as OsmoTimestamp; @@ -245,6 +246,40 @@ pub fn get_tokens_provided( Ok(tokens_provided) } +pub fn get_swap_amount_and_direction( + balance0: Uint128, + balance1: Uint128, + current_tick: i64, + target_lower_tick: i64, + target_upper_tick: i64, +) -> Result<(Uint128, SwapDirection), ContractError> { + if !balance0.is_zero() { + let token_in_amount = if current_tick > target_upper_tick { + balance0 + } else { + get_single_sided_deposit_0_to_1_swap_amount( + balance0, + target_lower_tick, + current_tick, + target_upper_tick, + )? + }; + Ok((token_in_amount, SwapDirection::ZeroToOne)) + } else { + let token_in_amount = if current_tick < target_lower_tick { + balance1 + } else { + get_single_sided_deposit_1_to_0_swap_amount( + balance1, + target_lower_tick, + current_tick, + target_upper_tick, + )? + }; + Ok((token_in_amount, SwapDirection::OneToZero)) + } +} + #[cfg(test)] mod tests { use std::collections::HashMap; diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/msg.rs b/smart-contracts/osmosis/contracts/cl-vault/src/msg.rs index be196a7db..5d4356f47 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/msg.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/msg.rs @@ -173,6 +173,11 @@ pub struct InstantiateMsg { pub initial_upper_tick: i64, } +pub enum DepositType { + Exact, + Any { max_slippage: Decimal }, +} + #[cw_serde] pub struct MigrateMsg { pub dex_router: Addr, diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/vault/any_deposit.rs b/smart-contracts/osmosis/contracts/cl-vault/src/vault/any_deposit.rs deleted file mode 100644 index 5ec61379d..000000000 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/any_deposit.rs +++ /dev/null @@ -1,354 +0,0 @@ -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; -use osmosis_std::types::osmosis::tokenfactory::v1beta1::MsgMint; - -use crate::helpers::assert::must_pay_one_or_two; -use crate::helpers::getters::{ - get_asset0_value, get_depositable_tokens, get_single_sided_deposit_0_to_1_swap_amount, - get_single_sided_deposit_1_to_0_swap_amount, get_twap_price, -}; -use crate::helpers::msgs::swap_msg; -use crate::query::query_total_vault_token_supply; -use crate::reply::Replies; -use crate::state::{PoolConfig, CURRENT_SWAP_ANY_DEPOSIT}; -use crate::vault::concentrated_liquidity::get_cl_pool_info; -use crate::vault::range::SwapDirection; -use crate::vault::swap::SwapParams; -use crate::{ - query::query_total_assets, - state::{POOL_CONFIG, SHARES, VAULT_DENOM}, - vault::concentrated_liquidity::get_position, - ContractError, -}; - -use super::swap::SwapCalculationResult; - -pub fn execute_any_deposit( - mut deps: DepsMut, - env: Env, - info: MessageInfo, - recipient: Option, - max_slippage: Decimal, -) -> Result { - // Unwrap recipient or use caller's address - let recipient = recipient.map_or(Ok(info.sender.clone()), |x| deps.api.addr_validate(&x))?; - - let pool_config = POOL_CONFIG.load(deps.storage)?; - let pool_details = get_cl_pool_info(&deps.querier, pool_config.pool_id)?; - let position = get_position(deps.storage, &deps.querier)? - .position - .ok_or(ContractError::MissingPosition {})?; - - let (token0, token1) = must_pay_one_or_two( - &info, - (pool_config.token0.clone(), pool_config.token1.clone()), - )?; - - // get the amount of funds we can deposit from this ratio - let (deposit_amount_in_ratio, swappable_amount): ((Uint128, Uint128), (Uint128, Uint128)) = - get_depositable_tokens(deps.branch(), token0.clone(), token1.clone())?; - - // Swap logic - // TODO_FUTURE: Optimize this if conditions - if !swappable_amount.0.is_zero() { - let (swap_amount, swap_direction) = ( - // range is above current tick - if pool_details.current_tick > position.upper_tick { - swappable_amount.0 - } else { - get_single_sided_deposit_0_to_1_swap_amount( - swappable_amount.0, - position.lower_tick, - pool_details.current_tick, - position.upper_tick, - )? - }, - SwapDirection::ZeroToOne, - ); - let swap_calc_result = calculate_swap_amount( - deps, - &env, - pool_config, - swap_direction, - swap_amount, - swappable_amount, - deposit_amount_in_ratio, - max_slippage, - &recipient, - )?; - - // rest minting logic remains same - Ok(Response::new() - .add_submessage(SubMsg::reply_on_success( - swap_calc_result.swap_msg, - Replies::AnyDepositSwap.into(), - )) - .add_attributes(vec![ - attr("method", "execute"), - attr("action", "any_deposit"), - attr( - "token_in", - format!("{}{}", swap_amount, swap_calc_result.token_in_denom), - ), - attr( - "token_out_min", - format!("{}", swap_calc_result.token_out_min_amount), - ), - ])) - } else if !swappable_amount.1.is_zero() { - let (swap_amount, swap_direction) = ( - // current tick is above range - if pool_details.current_tick < position.lower_tick { - swappable_amount.1 - } else { - get_single_sided_deposit_1_to_0_swap_amount( - swappable_amount.1, - position.lower_tick, - pool_details.current_tick, - position.upper_tick, - )? - }, - SwapDirection::OneToZero, - ); - let swap_calc_result = calculate_swap_amount( - deps, - &env, - pool_config, - swap_direction, - swap_amount, - swappable_amount, - deposit_amount_in_ratio, - max_slippage, - &recipient, - )?; - - // rest minting logic remains same - Ok(Response::new() - .add_submessage(SubMsg::reply_on_success( - swap_calc_result.swap_msg, - Replies::AnyDepositSwap.into(), - )) - .add_attributes(vec![ - attr("method", "execute"), - attr("action", "any_deposit"), - attr( - "token_in", - format!("{}{}", swap_amount, swap_calc_result.token_in_denom), - ), - attr( - "token_out_min", - format!("{}", swap_calc_result.token_out_min_amount), - ), - ])) - } else { - let (mint_msg, user_shares) = - mint_msg_user_shares(deps, &env, &deposit_amount_in_ratio, &recipient)?; - - Ok(Response::new() - .add_attribute("method", "execute") - .add_attribute("action", "any_deposit") - .add_attribute("amount0", deposit_amount_in_ratio.0) - .add_attribute("amount1", deposit_amount_in_ratio.1) - .add_message(mint_msg) - .add_attribute("mint_shares_amount", user_shares) - .add_attribute("receiver", recipient.as_str())) - } -} - -pub fn handle_any_deposit_swap_reply( - mut deps: DepsMut, - env: Env, - data: SubMsgResult, -) -> Result { - // Attempt to directly parse the data to MsgSwapExactAmountInResponse outside of the match - let resp: MsgSwapExactAmountInResponse = data.try_into()?; - - let (swap_direction, left_over_amount, recipient, deposit_amount_in_ratio) = - CURRENT_SWAP_ANY_DEPOSIT.load(deps.storage)?; - - let pool_config = POOL_CONFIG.load(deps.storage)?; - - // get post swap balances to create positions with - let (balance0, balance1): (Uint128, Uint128) = match swap_direction { - SwapDirection::ZeroToOne => ( - left_over_amount, - Uint128::new(resp.token_out_amount.parse()?), - ), - SwapDirection::OneToZero => ( - Uint128::new(resp.token_out_amount.parse()?), - left_over_amount, - ), - }; - - // Create the tuple for minting coins - 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 (mint_msg, user_shares) = mint_msg_user_shares( - deps.branch(), - &env, - &(coins_to_mint_for.0.amount, coins_to_mint_for.1.amount), - &recipient, - )?; - - CURRENT_SWAP_ANY_DEPOSIT.remove(deps.storage); - - Ok(Response::new() - .add_attribute("method", "reply") - .add_attribute("action", "handle_any_deposit_swap") - .add_attribute("amount0", balance0) - .add_attribute("amount1", balance1) - .add_message(mint_msg) - .add_attribute("mint_shares_amount", user_shares) - .add_attribute("receiver", recipient.as_str())) -} - -fn mint_msg_user_shares( - deps: DepsMut, - env: &Env, - deposit_amount_in_ratio: &(Uint128, Uint128), - recipient: &Addr, -) -> Result<(MsgMint, Uint128), ContractError> { - // calculate the amount of shares we can mint for this - let total_assets = query_total_assets(deps.as_ref(), env.clone())?; - let total_assets_value = get_asset0_value( - deps.storage, - &deps.querier, - total_assets.token0.amount, - total_assets.token1.amount, - )?; - - let vault_denom = VAULT_DENOM.load(deps.storage)?; - let total_vault_shares: Uint256 = query_total_vault_token_supply(deps.as_ref())?.total.into(); - - let user_value = get_asset0_value( - deps.storage, - &deps.querier, - deposit_amount_in_ratio.0, - deposit_amount_in_ratio.1, - )?; - - // total_vault_shares.is_zero() should never be zero. This should ideally always enter the else and we are just sanity checking. - let user_shares: Uint128 = if total_vault_shares.is_zero() { - user_value - } else { - total_vault_shares - .checked_mul(user_value.into())? - .checked_div(total_assets_value.into())? - .try_into()? - }; - - // save the shares in the user map - SHARES.update( - deps.storage, - recipient.clone(), - |old| -> Result { - if let Some(existing_user_shares) = old { - Ok(user_shares + existing_user_shares) - } else { - Ok(user_shares) - } - }, - )?; - - // TODO the locking of minted shares is a band-aid for giving out rewards to users, - // once tokenfactory has send hooks, we can remove the lockup and have the users - // own the shares in their balance - // we mint shares to the contract address here, so we can lock those shares for the user later in the same call - // this is blocked by Osmosis v17 update - let mint_msg = MsgMint { - sender: env.clone().contract.address.to_string(), - amount: Some(coin(user_shares.into(), vault_denom).into()), - mint_to_address: env.clone().contract.address.to_string(), - }; - - Ok((mint_msg, user_shares)) -} - -#[allow(clippy::too_many_arguments)] -fn calculate_swap_amount( - deps: DepsMut, - env: &Env, - pool_config: PoolConfig, - swap_direction: SwapDirection, - token_in_amount: Uint128, - swappable_amount: (Uint128, Uint128), - deposit_amount_in_ratio: (Uint128, Uint128), - max_slippage: Decimal, - recipient: &Addr, -) -> Result { - // TODO check that this math is right with spot price (numerators, denominators) if taken by legacy gamm module instead of poolmanager - // TODO check on the twap_window_seconds (taking hardcoded value for now) - let twap_price = get_twap_price(deps.storage, &deps.querier, env, 24u64)?; - let (token_in_denom, token_out_denom, token_out_ideal_amount, left_over_amount) = - match swap_direction { - SwapDirection::ZeroToOne => ( - &pool_config.token0, - &pool_config.token1, - token_in_amount - .checked_multiply_ratio(twap_price.numerator(), twap_price.denominator()), - swappable_amount.0.checked_sub(token_in_amount)?, - ), - SwapDirection::OneToZero => ( - &pool_config.token1, - &pool_config.token0, - token_in_amount - .checked_multiply_ratio(twap_price.denominator(), twap_price.numerator()), - swappable_amount.1.checked_sub(token_in_amount)?, - ), - }; - - CURRENT_SWAP_ANY_DEPOSIT.save( - deps.storage, - &( - swap_direction, - left_over_amount, - recipient.clone(), - deposit_amount_in_ratio, - ), - )?; - - let token_out_min_amount = token_out_ideal_amount? - .checked_multiply_ratio(max_slippage.numerator(), max_slippage.denominator())?; - - if !pool_config.pool_contains_token(token_in_denom) { - return Err(ContractError::BadTokenForSwap { - base_token: pool_config.token0, - quote_token: pool_config.token1, - }); - } - - // generate a swap message with recommended path as the current - // pool on which the vault is running - let swap_msg = swap_msg( - &deps, - env, - SwapParams { - pool_id: pool_config.pool_id, - token_in_amount, - token_out_min_amount, - token_in_denom: token_in_denom.clone(), - token_out_denom: token_out_denom.clone(), - forced_swap_route: None, // TODO: check this None - }, - )?; - - Ok(SwapCalculationResult { - swap_msg, - token_in_denom: token_in_denom.to_string(), - token_out_min_amount, - token_in_amount, - position_id: None, - }) -} diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/vault/exact_deposit.rs b/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs similarity index 59% rename from smart-contracts/osmosis/contracts/cl-vault/src/vault/exact_deposit.rs rename to smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs index 8378b3e28..fb4ee79be 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/exact_deposit.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs @@ -1,42 +1,72 @@ -use cosmwasm_std::{coin, DepsMut, Env, MessageInfo, Response, Uint128, Uint256}; +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; use osmosis_std::types::osmosis::tokenfactory::v1beta1::MsgMint; use crate::helpers::assert::must_pay_one_or_two; -use crate::helpers::getters::{get_asset0_value, get_depositable_tokens}; -use crate::helpers::msgs::refund_bank_msg; +use crate::helpers::getters::{ + get_asset0_value, get_depositable_tokens, get_swap_amount_and_direction, get_twap_price, +}; +use crate::helpers::msgs::{refund_bank_msg, swap_msg}; +use crate::msg::DepositType; use crate::query::query_total_vault_token_supply; +use crate::reply::Replies; +use crate::state::{PoolConfig, CURRENT_SWAP_ANY_DEPOSIT}; use crate::{ query::query_total_assets, state::{POOL_CONFIG, SHARES, VAULT_DENOM}, ContractError, }; -/// Try to deposit as much user funds as we can in the current ratio of the vault and -/// refund the rest to the caller. -pub(crate) fn execute_exact_deposit( +use super::concentrated_liquidity::{get_cl_pool_info, get_position}; +use super::range::SwapDirection; +use super::swap::{SwapCalculationResult, SwapParams}; + +pub fn execute_deposit( mut deps: DepsMut, env: Env, info: MessageInfo, recipient: Option, + deposit_type: DepositType, ) -> Result { - // Unwrap recipient or use caller's address let recipient = recipient.map_or(Ok(info.sender.clone()), |x| deps.api.addr_validate(&x))?; - let pool = POOL_CONFIG.load(deps.storage)?; - let (token0, token1) = must_pay_one_or_two(&info, (pool.token0.clone(), pool.token1.clone()))?; + let pool_config = POOL_CONFIG.load(deps.storage)?; + let (token0, token1) = must_pay_one_or_two( + &info, + (pool_config.token0.clone(), pool_config.token1.clone()), + )?; - // get the amount of funds we can deposit from this ratio let (deposit, refund): ((Uint128, Uint128), (Uint128, Uint128)) = get_depositable_tokens(deps.branch(), token0.clone(), token1.clone())?; - let vault_denom = VAULT_DENOM.load(deps.storage)?; - let total_vault_shares: Uint256 = query_total_vault_token_supply(deps.as_ref())?.total.into(); - - let user_value = get_asset0_value(deps.storage, &deps.querier, deposit.0, deposit.1)?; - let refund_value = get_asset0_value(deps.storage, &deps.querier, refund.0, refund.1)?; + match deposit_type { + DepositType::Exact => { + handle_exact_deposit(deps, env, recipient, pool_config, deposit, refund) + } + DepositType::Any { max_slippage } => handle_any_deposit( + deps, + env, + recipient, + pool_config, + deposit, + refund, + max_slippage, + ), + } +} - // calculate the amount of shares we can mint for this +fn handle_exact_deposit( + deps: DepsMut, + env: Env, + recipient: Addr, + pool_config: PoolConfig, + deposit: (Uint128, Uint128), + refund: (Uint128, Uint128), +) -> Result { let total_assets = query_total_assets(deps.as_ref(), env.clone())?; let total_assets_value = get_asset0_value( deps.storage, @@ -45,7 +75,12 @@ pub(crate) fn execute_exact_deposit( total_assets.token1.amount, )?; - // total_vault_shares.is_zero() should never be zero. This should ideally always enter the else and we are just sanity checking. + let vault_denom = VAULT_DENOM.load(deps.storage)?; + let total_vault_shares: Uint256 = query_total_vault_token_supply(deps.as_ref())?.total.into(); + + let user_value = get_asset0_value(deps.storage, &deps.querier, deposit.0, deposit.1)?; + let refund_value = get_asset0_value(deps.storage, &deps.querier, refund.0, refund.1)?; + let user_shares: Uint128 = if total_vault_shares.is_zero() { user_value } else { @@ -60,7 +95,6 @@ pub(crate) fn execute_exact_deposit( .try_into()? }; - // save the shares in the user map SHARES.update( deps.storage, recipient.clone(), @@ -73,11 +107,6 @@ pub(crate) fn execute_exact_deposit( }, )?; - // TODO the locking of minted shares is a band-aid for giving out rewards to users, - // once tokenfactory has send hooks, we can remove the lockup and have the users - // own the shares in their balance - // we mint shares to the contract address here, so we can lock those shares for the user later in the same call - // this is blocked by Osmosis v17 update let mint_msg = MsgMint { sender: env.clone().contract.address.to_string(), amount: Some(coin(user_shares.into(), vault_denom).into()), @@ -95,8 +124,8 @@ pub(crate) fn execute_exact_deposit( if let Some((bank_msg, bank_attr)) = refund_bank_msg( recipient, - Some(coin(refund.0.u128(), pool.token0)), - Some(coin(refund.1.u128(), pool.token1)), + Some(coin(refund.0.u128(), pool_config.token0)), + Some(coin(refund.1.u128(), pool_config.token1)), )? { resp = resp.add_message(bank_msg).add_attributes(bank_attr); } @@ -104,6 +133,266 @@ pub(crate) fn execute_exact_deposit( Ok(resp) } +fn handle_any_deposit( + deps: DepsMut, + env: Env, + recipient: Addr, + pool_config: PoolConfig, + deposit: (Uint128, Uint128), + refund: (Uint128, Uint128), + max_slippage: Decimal, +) -> Result { + let position = get_position(deps.storage, &deps.querier)? + .position + .ok_or(ContractError::MissingPosition {})?; + + let pool_details = get_cl_pool_info(&deps.querier, pool_config.pool_id)?; + if !refund.0.is_zero() || !refund.1.is_zero() { + let (swap_amount, swap_direction) = get_swap_amount_and_direction( + refund.0, + refund.1, + pool_details.current_tick, + position.lower_tick, + position.upper_tick, + ) + .unwrap(); + + let swap_calc_result = calculate_swap_amount( + deps, + &env, + pool_config, + swap_direction, + swap_amount, + refund, + deposit, + max_slippage, + &recipient, + )?; + + return Ok(Response::new() + .add_submessage(SubMsg::reply_on_success( + swap_calc_result.swap_msg, + Replies::AnyDepositSwap.into(), + )) + .add_attributes(vec![ + attr("method", "execute"), + attr("action", "any_deposit"), + attr( + "token_in", + format!("{}{}", swap_amount, swap_calc_result.token_in_denom), + ), + attr( + "token_out_min", + format!("{}", swap_calc_result.token_out_min_amount), + ), + ])); + } + + let (mint_msg, user_shares) = mint_msg_user_shares(deps, &env, &deposit, &recipient)?; + + Ok(Response::new() + .add_attribute("method", "execute") + .add_attribute("action", "any_deposit") + .add_attribute("amount0", deposit.0) + .add_attribute("amount1", deposit.1) + .add_message(mint_msg) + .add_attribute("mint_shares_amount", user_shares) + .add_attribute("receiver", recipient.as_str())) +} + +pub fn handle_any_deposit_swap_reply( + mut deps: DepsMut, + env: Env, + data: SubMsgResult, +) -> Result { + // Attempt to directly parse the data to MsgSwapExactAmountInResponse outside of the match + let resp: MsgSwapExactAmountInResponse = data.try_into()?; + + let (swap_direction, left_over_amount, recipient, deposit_amount_in_ratio) = + CURRENT_SWAP_ANY_DEPOSIT.load(deps.storage)?; + + let pool_config = POOL_CONFIG.load(deps.storage)?; + + // get post swap balances to create positions with + let (balance0, balance1): (Uint128, Uint128) = match swap_direction { + SwapDirection::ZeroToOne => ( + left_over_amount, + Uint128::new(resp.token_out_amount.parse()?), + ), + SwapDirection::OneToZero => ( + Uint128::new(resp.token_out_amount.parse()?), + left_over_amount, + ), + }; + + // Create the tuple for minting coins + 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 (mint_msg, user_shares) = mint_msg_user_shares( + deps.branch(), + &env, + &(coins_to_mint_for.0.amount, coins_to_mint_for.1.amount), + &recipient, + )?; + + CURRENT_SWAP_ANY_DEPOSIT.remove(deps.storage); + + Ok(Response::new() + .add_attribute("method", "reply") + .add_attribute("action", "handle_any_deposit_swap") + .add_attribute("amount0", balance0) + .add_attribute("amount1", balance1) + .add_message(mint_msg) + .add_attribute("mint_shares_amount", user_shares) + .add_attribute("receiver", recipient.as_str())) +} + +fn mint_msg_user_shares( + deps: DepsMut, + env: &Env, + deposit_amount_in_ratio: &(Uint128, Uint128), + recipient: &Addr, +) -> Result<(MsgMint, Uint128), ContractError> { + // calculate the amount of shares we can mint for this + let total_assets = query_total_assets(deps.as_ref(), env.clone())?; + let total_assets_value = get_asset0_value( + deps.storage, + &deps.querier, + total_assets.token0.amount, + total_assets.token1.amount, + )?; + + let vault_denom = VAULT_DENOM.load(deps.storage)?; + let total_vault_shares: Uint256 = query_total_vault_token_supply(deps.as_ref())?.total.into(); + + let user_value = get_asset0_value( + deps.storage, + &deps.querier, + deposit_amount_in_ratio.0, + deposit_amount_in_ratio.1, + )?; + + // total_vault_shares.is_zero() should never be zero. This should ideally always enter the else and we are just sanity checking. + let user_shares: Uint128 = if total_vault_shares.is_zero() { + user_value + } else { + total_vault_shares + .checked_mul(user_value.into())? + .checked_div(total_assets_value.into())? + .try_into()? + }; + + // save the shares in the user map + SHARES.update( + deps.storage, + recipient.clone(), + |old| -> Result { + if let Some(existing_user_shares) = old { + Ok(user_shares + existing_user_shares) + } else { + Ok(user_shares) + } + }, + )?; + + // TODO the locking of minted shares is a band-aid for giving out rewards to users, + // once tokenfactory has send hooks, we can remove the lockup and have the users + // own the shares in their balance + // we mint shares to the contract address here, so we can lock those shares for the user later in the same call + // this is blocked by Osmosis v17 update + let mint_msg = MsgMint { + sender: env.clone().contract.address.to_string(), + amount: Some(coin(user_shares.into(), vault_denom).into()), + mint_to_address: env.clone().contract.address.to_string(), + }; + + Ok((mint_msg, user_shares)) +} + +#[allow(clippy::too_many_arguments)] +fn calculate_swap_amount( + deps: DepsMut, + env: &Env, + pool_config: PoolConfig, + swap_direction: SwapDirection, + token_in_amount: Uint128, + swappable_amount: (Uint128, Uint128), + deposit_amount_in_ratio: (Uint128, Uint128), + max_slippage: Decimal, + recipient: &Addr, +) -> Result { + let twap_price = get_twap_price(deps.storage, &deps.querier, env, 24u64)?; + let (token_in_denom, token_out_denom, token_out_ideal_amount, left_over_amount) = + match swap_direction { + SwapDirection::ZeroToOne => ( + &pool_config.token0, + &pool_config.token1, + token_in_amount + .checked_multiply_ratio(twap_price.numerator(), twap_price.denominator()), + swappable_amount.0.checked_sub(token_in_amount)?, + ), + SwapDirection::OneToZero => ( + &pool_config.token1, + &pool_config.token0, + token_in_amount + .checked_multiply_ratio(twap_price.denominator(), twap_price.numerator()), + swappable_amount.1.checked_sub(token_in_amount)?, + ), + }; + + CURRENT_SWAP_ANY_DEPOSIT.save( + deps.storage, + &( + swap_direction, + left_over_amount, + recipient.clone(), + deposit_amount_in_ratio, + ), + )?; + + let token_out_min_amount = token_out_ideal_amount? + .checked_multiply_ratio(max_slippage.numerator(), max_slippage.denominator())?; + + if !pool_config.pool_contains_token(token_in_denom) { + return Err(ContractError::BadTokenForSwap { + base_token: pool_config.token0, + quote_token: pool_config.token1, + }); + } + + // generate a swap message with recommended path as the current + // pool on which the vault is running + let swap_msg = swap_msg( + &deps, + env, + SwapParams { + pool_id: pool_config.pool_id, + token_in_amount, + token_out_min_amount, + token_in_denom: token_in_denom.clone(), + token_out_denom: token_out_denom.clone(), + forced_swap_route: None, // TODO: check this None + }, + )?; + + Ok(SwapCalculationResult { + swap_msg, + token_in_denom: token_in_denom.to_string(), + token_out_min_amount, + token_in_amount, + position_id: None, + }) +} + #[cfg(test)] mod tests { use std::{marker::PhantomData, str::FromStr}; @@ -303,7 +592,7 @@ mod tests { // ) // .unwrap(); - execute_exact_deposit( + execute_deposit( deps.as_mut(), env, MessageInfo { @@ -311,6 +600,7 @@ mod tests { funds: vec![coin(100, "token0"), coin(100, "token1")], }, None, + DepositType::Exact, ) .unwrap(); diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/vault/mod.rs b/smart-contracts/osmosis/contracts/cl-vault/src/vault/mod.rs index b4aa809cb..1b628ec8d 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/mod.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/vault/mod.rs @@ -1,9 +1,8 @@ pub mod admin; -pub mod any_deposit; pub mod autocompound; pub mod concentrated_liquidity; +pub mod deposit; pub mod distribution; -pub mod exact_deposit; pub mod merge; pub mod range; pub mod swap; 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 cdfc29700..e44d34d58 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/range.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/vault/range.rs @@ -2,9 +2,8 @@ use crate::{ helpers::{ assert::assert_range_admin, getters::{ - get_single_sided_deposit_0_to_1_swap_amount, - get_single_sided_deposit_1_to_0_swap_amount, get_tokens_provided, get_twap_price, - get_unused_pair_balances, + get_swap_amount_and_direction, + get_tokens_provided, get_twap_price, get_unused_pair_balances, }, msgs::swap_msg, }, @@ -340,36 +339,14 @@ fn calculate_swap_amount( ) -> Result, ContractError> { //TODO: further optimizations can be made by increasing the swap amount by half of our expected slippage, // to reduce the total number of non-deposited tokens that we will then need to refund - let (token_in_amount, swap_direction) = if !balance0.is_zero() { - ( - // range is above current tick - if current_tick > target_upper_tick { - balance0 - } else { - get_single_sided_deposit_0_to_1_swap_amount( - balance0, - target_lower_tick, - current_tick, - target_upper_tick, - )? - }, - SwapDirection::ZeroToOne, - ) - } else if !balance1.is_zero() { - ( - // current tick is above range - if current_tick < target_lower_tick { - balance1 - } else { - get_single_sided_deposit_1_to_0_swap_amount( - balance1, - target_lower_tick, - current_tick, - target_upper_tick, - )? - }, - SwapDirection::OneToZero, - ) + let (token_in_amount, swap_direction) = if !balance0.is_zero() || !balance1.is_zero() { + get_swap_amount_and_direction( + balance0, + balance1, + current_tick, + target_lower_tick, + target_upper_tick, + )? } else { // Load the current Position to extract join_time and claim_after which is unchangeable in this context let position = POSITION.load(deps.storage)?;