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 1a19a7be9..b830fb799 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/test_helpers.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/test_helpers.rs @@ -60,6 +60,10 @@ impl QuasarQuerier { bank: BankQuerier::new(balances), } } + + pub fn update_balances(&mut self, balances: &[(&str, &[Coin])]) { + self.bank = BankQuerier::new(balances); + } } impl Querier for QuasarQuerier { @@ -167,48 +171,68 @@ impl Querier for QuasarQuerier { } } +fn get_quasar_querier_with_balances( + position_base_amount: u128, + position_quote_amount: u128, + current_tick: i64, + position_lower_tick: i64, + position_upper_tick: i64, + balances: &[(&str, &[Coin])], +) -> QuasarQuerier { + QuasarQuerier::new_with_balances( + FullPositionBreakdown { + position: Some(OsmoPosition { + position_id: POSITION_ID, + address: MOCK_CONTRACT_ADDR.to_string(), + pool_id: POOL_ID, + lower_tick: position_lower_tick, + upper_tick: position_upper_tick, + join_time: None, + liquidity: "1000000.1".to_string(), + }), + asset0: Some(OsmoCoin { + denom: BASE_DENOM.to_string(), + amount: position_base_amount.to_string(), + }), + asset1: Some(OsmoCoin { + denom: QUOTE_DENOM.to_string(), + amount: position_quote_amount.to_string(), + }), + claimable_spread_rewards: vec![ + OsmoCoin { + denom: BASE_DENOM.to_string(), + amount: "100".to_string(), + }, + OsmoCoin { + denom: QUOTE_DENOM.to_string(), + amount: "100".to_string(), + }, + ], + claimable_incentives: vec![], + forfeited_incentives: vec![], + }, + current_tick, + balances, + ) +} + pub fn mock_deps_with_querier_with_balance( position_base_amount: u128, position_quote_amount: u128, current_tick: i64, + position_lower_tick: i64, + position_upper_tick: i64, balances: &[(&str, &[Coin])], ) -> OwnedDeps { OwnedDeps { storage: MockStorage::default(), api: MockApi::default(), - querier: QuasarQuerier::new_with_balances( - FullPositionBreakdown { - position: Some(OsmoPosition { - position_id: POSITION_ID, - address: MOCK_CONTRACT_ADDR.to_string(), - pool_id: POOL_ID, - lower_tick: 100, - upper_tick: 1000, - join_time: None, - liquidity: "1000000.1".to_string(), - }), - asset0: Some(OsmoCoin { - denom: BASE_DENOM.to_string(), - amount: position_base_amount.to_string(), - }), - asset1: Some(OsmoCoin { - denom: QUOTE_DENOM.to_string(), - amount: position_quote_amount.to_string(), - }), - claimable_spread_rewards: vec![ - OsmoCoin { - denom: BASE_DENOM.to_string(), - amount: "100".to_string(), - }, - OsmoCoin { - denom: QUOTE_DENOM.to_string(), - amount: "100".to_string(), - }, - ], - claimable_incentives: vec![], - forfeited_incentives: vec![], - }, + querier: get_quasar_querier_with_balances( + position_base_amount, + position_quote_amount, current_tick, + position_lower_tick, + position_upper_tick, balances, ), custom_query_type: PhantomData, 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 2bc86c2c5..0a5d153f1 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/vault/deposit.rs @@ -432,6 +432,8 @@ mod tests { 100, 100, 0, + 100, + 1000, &[( MOCK_CONTRACT_ADDR, &[ @@ -540,6 +542,8 @@ mod tests { 100, 100, 0, + 100, + 1000, &[( MOCK_CONTRACT_ADDR, &[ @@ -595,19 +599,26 @@ mod tests { #[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 current_vault_base_balance = 150u128; + let current_vault_quote_balance = 100u128; + let base_deposit_amount = 150; let quote_deposit_amount = 100; + let current_price = Decimal::percent(200); let env = mock_env(); let mut deps = mock_deps_with_querier_with_balance( 100, - 100, - 549, + 200, + 1_000_000, + 900_000, + 1_101_000, &[( MOCK_CONTRACT_ADDR, &[ - coin(current_deposit_amount + base_deposit_amount, BASE_DENOM), - coin(current_deposit_amount + quote_deposit_amount, QUOTE_DENOM), + coin(current_vault_base_balance + base_deposit_amount, BASE_DENOM), + coin( + current_vault_quote_balance + quote_deposit_amount, + QUOTE_DENOM, + ), coin(TEST_VAULT_TOKEN_SUPPLY, TEST_VAULT_DENOM), ], )], @@ -636,6 +647,124 @@ mod tests { .unwrap(); assert_eq!(response.messages.len(), 1); + let expected_swap_amount = 50u128; + 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, BASE_DENOM); + assert_eq!(funds[0].amount.u128(), expected_swap_amount); + } + _ => panic!("unreachable"), + } + let swap_receive_amount: u128 = Uint128::from(expected_swap_amount) + .checked_mul_floor(current_price) + .unwrap() + .into(); + deps.querier.update_balances(&[( + MOCK_CONTRACT_ADDR, + &[ + coin( + current_vault_base_balance + base_deposit_amount - expected_swap_amount, + BASE_DENOM, + ), + coin( + current_vault_quote_balance + quote_deposit_amount + swap_receive_amount, + QUOTE_DENOM, + ), + coin(TEST_VAULT_TOKEN_SUPPLY, TEST_VAULT_DENOM), + ], + )]); + + 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 = 50_000; + 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_one_sided_position_base_only( + ) { + let current_vault_base_balance = 100u128; + let current_vault_quote_balance = 100u128; + let base_deposit_amount = 50; + let quote_deposit_amount = 100; + let position_base_amount = 100; + let current_price = Decimal::percent(200); + let env = mock_env(); + let mut deps = mock_deps_with_querier_with_balance( + position_base_amount, + 0, + 1_000_000, + 10_000_000, + 20_000_000, + &[( + MOCK_CONTRACT_ADDR, + &[ + coin(current_vault_base_balance + base_deposit_amount, BASE_DENOM), + coin( + current_vault_quote_balance + 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 expected_swap_amount = 100u128; let msg = response.messages[0].msg.clone(); match msg { CosmosMsg::Wasm(WasmMsg::Execute { @@ -645,10 +774,147 @@ mod tests { }) => { assert_eq!(funds.len(), 1); assert_eq!(funds[0].denom, QUOTE_DENOM); - assert_eq!(funds[0].amount, Uint128::from(25u64)); + assert_eq!(funds[0].amount, Uint128::from(expected_swap_amount)); + } + _ => panic!("unreachable"), + } + + let swap_receive_amount: u128 = Uint128::from(expected_swap_amount) + .checked_div_floor(current_price) + .unwrap() + .into(); + deps.querier.update_balances(&[( + MOCK_CONTRACT_ADDR, + &[ + coin( + current_vault_base_balance + base_deposit_amount + swap_receive_amount, + BASE_DENOM, + ), + coin( + current_vault_quote_balance + quote_deposit_amount - expected_swap_amount, + QUOTE_DENOM, + ), + coin(TEST_VAULT_TOKEN_SUPPLY, TEST_VAULT_DENOM), + ], + )]); + + 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 = 40_000; + 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_one_sided_position_quote_only( + ) { + let current_vault_base_balance = 100u128; + let current_vault_quote_balance = 100u128; + let base_deposit_amount = 50; + let quote_deposit_amount = 100; + let position_quote_amount = 100; + let current_price = Decimal::percent(200); + let env = mock_env(); + let mut deps = mock_deps_with_querier_with_balance( + 0, + position_quote_amount, + 1_000_000, // price = 2.0 + 100, + 1000, + &[( + MOCK_CONTRACT_ADDR, + &[ + coin(current_vault_base_balance + base_deposit_amount, BASE_DENOM), + coin( + current_vault_quote_balance + 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 expected_swap_amount = 50u128; + 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, BASE_DENOM); + assert_eq!(funds[0].amount, Uint128::from(expected_swap_amount)); + } + _ => panic!("unreachable"), + } + + let swap_receive_amount: u128 = Uint128::from(expected_swap_amount) + .checked_mul_floor(current_price) + .unwrap() + .into(); + deps.querier.update_balances(&[( + MOCK_CONTRACT_ADDR, + &[ + coin( + current_vault_base_balance + base_deposit_amount - expected_swap_amount, + BASE_DENOM, + ), + coin( + current_vault_quote_balance + quote_deposit_amount + swap_receive_amount, + QUOTE_DENOM, + ), + coin(TEST_VAULT_TOKEN_SUPPLY, TEST_VAULT_DENOM), + ], + )]); let response = reply( deps.as_mut(), @@ -662,7 +928,7 @@ mod tests { }, ) .unwrap(); - let expected_minted_tokens = 37_250; + let expected_minted_tokens = 50_000; let msg = response.messages[0].msg.clone(); match msg { CosmosMsg::Stargate { type_url: _, value } => { 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 b733b900b..56203eb3e 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/range.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/vault/range.rs @@ -374,6 +374,8 @@ mod tests { 100_000, 100_000, 0, + 100, + 1000, &[( MOCK_CONTRACT_ADDR, &[coin(11000, BASE_DENOM), coin(11234, QUOTE_DENOM)], diff --git a/smart-contracts/osmosis/contracts/cl-vault/src/vault/withdraw.rs b/smart-contracts/osmosis/contracts/cl-vault/src/vault/withdraw.rs index cefab1a9b..17d3ca853 100644 --- a/smart-contracts/osmosis/contracts/cl-vault/src/vault/withdraw.rs +++ b/smart-contracts/osmosis/contracts/cl-vault/src/vault/withdraw.rs @@ -179,6 +179,8 @@ mod tests { 100_000, 100_000, 0, + 100, + 1000, &[( MOCK_CONTRACT_ADDR, &[coin(2000, BASE_DENOM), coin(3000, QUOTE_DENOM)],