From a8f2513a71e5ddcb0a3f683ebe763ac479c4862f Mon Sep 17 00:00:00 2001 From: nahem Date: Fri, 19 Apr 2024 14:29:40 +0200 Subject: [PATCH 01/12] feat(smart-contracts): implement add & remove swap routes logic --- .../pool-manager/src/contract.rs | 14 +++- .../liquidity_hub/pool-manager/src/error.rs | 10 +++ .../liquidity_hub/pool-manager/src/helpers.rs | 54 +++++++++++- .../liquidity_hub/pool-manager/src/queries.rs | 2 - .../pool-manager/src/router/commands.rs | 83 ++++++++++++++++++- .../src/tests/integration_tests.rs | 2 - .../pool-manager/src/tests/suite.rs | 5 -- .../terraswap_router/src/error.rs | 1 + packages/white-whale-std/src/lib.rs | 3 +- packages/white-whale-std/src/pool_manager.rs | 10 ++- 10 files changed, 164 insertions(+), 20 deletions(-) diff --git a/contracts/liquidity_hub/pool-manager/src/contract.rs b/contracts/liquidity_hub/pool-manager/src/contract.rs index 44ec8b86f..91fc1c47a 100644 --- a/contracts/liquidity_hub/pool-manager/src/contract.rs +++ b/contracts/liquidity_hub/pool-manager/src/contract.rs @@ -1,5 +1,6 @@ use crate::error::ContractError; use crate::queries::{get_swap_route, get_swap_routes}; +use crate::router::commands::{add_swap_routes, remove_swap_routes}; use crate::state::{Config, MANAGER_CONFIG, PAIRS, PAIR_COUNTER}; use crate::{liquidity, manager, queries, router, swap}; #[cfg(not(feature = "library"))] @@ -146,7 +147,12 @@ pub fn execute( // max_spread, // ) // } - ExecuteMsg::AddSwapRoutes { swap_routes: _ } => Ok(Response::new()), + ExecuteMsg::AddSwapRoutes { swap_routes } => { + add_swap_routes(deps, env, info.sender, swap_routes) + } + ExecuteMsg::RemoveSwapRoutes { swap_routes } => { + remove_swap_routes(deps, env, info.sender, swap_routes) + } ExecuteMsg::UpdateConfig { whale_lair_addr, pool_creation_fee, @@ -191,13 +197,13 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result Ok(to_json_binary(&queries::query_simulation( deps, - env, + // env, offer_asset, - ask_asset, + // ask_asset, pair_identifier, )?)?), QueryMsg::ReverseSimulation { diff --git a/contracts/liquidity_hub/pool-manager/src/error.rs b/contracts/liquidity_hub/pool-manager/src/error.rs index a90ff549b..d6e8a4b99 100644 --- a/contracts/liquidity_hub/pool-manager/src/error.rs +++ b/contracts/liquidity_hub/pool-manager/src/error.rs @@ -7,6 +7,7 @@ use cw_ownable::OwnershipError; use cw_utils::PaymentError; use semver::Version; use thiserror::Error; +use white_whale_std::pool_manager::SwapRoute; #[derive(Error, Debug, PartialEq)] pub enum ContractError { @@ -147,6 +148,15 @@ pub enum ContractError { #[error("Funds for {denom} were missing when performing swap")] MissingNativeSwapFunds { denom: String }, + + #[error("Swap route already exists for {offer_asset} - {ask_asset}")] + SwapRouteAlreadyExists { + offer_asset: String, + ask_asset: String, + }, + + #[error("Invalid swap route: {0}")] + InvalidSwapRoute(SwapRoute), } impl From for ContractError { diff --git a/contracts/liquidity_hub/pool-manager/src/helpers.rs b/contracts/liquidity_hub/pool-manager/src/helpers.rs index 1da0dfc31..9799b2f0b 100644 --- a/contracts/liquidity_hub/pool-manager/src/helpers.rs +++ b/contracts/liquidity_hub/pool-manager/src/helpers.rs @@ -2,13 +2,18 @@ use std::cmp::Ordering; use std::ops::Mul; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Coin, Decimal, Decimal256, StdError, StdResult, Storage, Uint128, Uint256}; +use cosmwasm_std::{ + coin, Addr, Coin, Decimal, Decimal256, Deps, Env, StdError, StdResult, Storage, Uint128, + Uint256, +}; use white_whale_std::fee::PoolFee; +use white_whale_std::pool_manager::{SimulateSwapOperationsResponse, SwapOperation}; use white_whale_std::pool_network::asset::{Asset, AssetInfo, PairType}; use crate::error::ContractError; use crate::math::Decimal256Helper; +use crate::queries::query_simulation; pub const INSTANTIATE_REPLY_ID: u64 = 1; @@ -578,3 +583,50 @@ pub fn instantiate_fees( ], ) } + +/// This function compares the address of the message sender with the contract admin +/// address. This provides a convenient way to verify if the sender +/// is the admin in a single line. +pub fn assert_admin(deps: Deps, env: &Env, sender: &Addr) -> Result<(), ContractError> { + let contract_info = deps + .querier + .query_wasm_contract_info(env.contract.address.clone())?; + if let Some(admin) = contract_info.admin { + if sender != deps.api.addr_validate(admin.as_str())? { + return Err(ContractError::Unauthorized {}); + } + } + Ok(()) +} + +pub fn simulate_swap_operations( + deps: Deps, + offer_amount: Uint128, + operations: Vec, +) -> Result { + let operations_len = operations.len(); + if operations_len == 0 { + return Err(ContractError::NoSwapOperationsProvided {}); + } + + let mut amount = offer_amount; + + for operation in operations.into_iter() { + match operation { + SwapOperation::WhaleSwap { + token_in_denom, + token_out_denom: _, + pool_identifier, + } => { + let res = query_simulation( + deps, + coin(offer_amount.u128(), token_in_denom), + pool_identifier, + )?; + amount = res.return_amount; + } + } + } + + Ok(SimulateSwapOperationsResponse { amount }) +} diff --git a/contracts/liquidity_hub/pool-manager/src/queries.rs b/contracts/liquidity_hub/pool-manager/src/queries.rs index 36498988e..f12f91de5 100644 --- a/contracts/liquidity_hub/pool-manager/src/queries.rs +++ b/contracts/liquidity_hub/pool-manager/src/queries.rs @@ -47,9 +47,7 @@ pub fn query_asset_decimals( // Simulate a swap with the provided asset to determine the amount of the other asset that would be received pub fn query_simulation( deps: Deps, - _env: Env, offer_asset: Coin, - _ask_asset: Coin, pair_identifier: String, ) -> Result { let pair_info = get_pair_by_identifier(&deps, &pair_identifier)?; diff --git a/contracts/liquidity_hub/pool-manager/src/router/commands.rs b/contracts/liquidity_hub/pool-manager/src/router/commands.rs index dab3a8935..53100f68b 100644 --- a/contracts/liquidity_hub/pool-manager/src/router/commands.rs +++ b/contracts/liquidity_hub/pool-manager/src/router/commands.rs @@ -1,9 +1,15 @@ use cosmwasm_std::{ - attr, coin, Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, MessageInfo, Response, Uint128, + attr, coin, Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Response, + Uint128, }; -use white_whale_std::pool_manager::SwapOperation; +use white_whale_std::pool_manager::{SwapOperation, SwapRoute}; -use crate::{state::MANAGER_CONFIG, swap::perform_swap::perform_swap, ContractError}; +use crate::{ + helpers::{assert_admin, simulate_swap_operations}, + state::{MANAGER_CONFIG, SWAP_ROUTES}, + swap::perform_swap::perform_swap, + ContractError, +}; /// Checks that the output of each [`SwapOperation`] acts as the input of the next swap. fn assert_operations(operations: Vec) -> Result<(), ContractError> { @@ -161,3 +167,74 @@ pub fn execute_swap_operations( ]) .add_attributes(swap_attributes)) } + +pub fn add_swap_routes( + deps: DepsMut, + env: Env, + sender: Addr, + swap_routes: Vec, +) -> Result { + assert_admin(deps.as_ref(), &env, &sender)?; + + let mut attributes = vec![]; + + for swap_route in swap_routes { + simulate_swap_operations( + deps.as_ref(), + Uint128::one(), + swap_route.clone().swap_operations, + ) + .map_err(|_| ContractError::InvalidSwapRoute(swap_route.clone()))?; + + // TODO: do we need to derivate the key from the pair identifier too? + let swap_route_key = + SWAP_ROUTES.key((&swap_route.offer_asset_denom, &swap_route.ask_asset_denom)); + + // Add the swap route if it does not exist, otherwise return an error + if swap_route_key.may_load(deps.storage)?.is_some() { + return Err(ContractError::SwapRouteAlreadyExists { + offer_asset: swap_route.offer_asset_denom, + ask_asset: swap_route.ask_asset_denom, + }); + } + swap_route_key.save(deps.storage, &swap_route.clone().swap_operations)?; + + attributes.push(attr("swap_route", swap_route.clone().to_string())); + } + + Ok(Response::new() + .add_attribute("action", "add_swap_routes") + .add_attributes(attributes)) +} + +pub fn remove_swap_routes( + deps: DepsMut, + env: Env, + sender: Addr, + swap_routes: Vec, +) -> Result { + assert_admin(deps.as_ref(), &env, &sender)?; + + let mut attributes = vec![]; + + for swap_route in swap_routes { + // TODO: do we need to derivate the key from the pair identifier too? + let swap_route_key = + SWAP_ROUTES.key((&swap_route.offer_asset_denom, &swap_route.ask_asset_denom)); + + // Remove the swap route if it exists + if swap_route_key.has(deps.storage) { + swap_route_key.remove(deps.storage); + attributes.push(attr("swap_route", swap_route.clone().to_string())); + } else { + return Err(ContractError::NoSwapRouteForAssets { + offer_asset: swap_route.offer_asset_denom, + ask_asset: swap_route.ask_asset_denom, + }); + } + } + + Ok(Response::new() + .add_attribute("action", "remove_swap_routes") + .add_attributes(attributes)) +} diff --git a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs index a5bb1c29c..5065fcea1 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs @@ -1290,7 +1290,6 @@ mod swapping { denom: "uwhale".to_string(), amount: Uint128::from(1000u128), }, - "uluna".to_string(), |result| { // Ensure that the return amount is 1_000 minus spread assert_eq!( @@ -1496,7 +1495,6 @@ mod swapping { denom: "uwhale".to_string(), amount: Uint128::from(1000u128), }, - "uluna".to_string(), |result| { println!("{:?}", result); *simulated_return_amount.borrow_mut() = result.unwrap().return_amount; diff --git a/contracts/liquidity_hub/pool-manager/src/tests/suite.rs b/contracts/liquidity_hub/pool-manager/src/tests/suite.rs index c82c668c6..00495e787 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/suite.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/suite.rs @@ -425,17 +425,12 @@ impl TestingSuite { &mut self, pair_identifier: String, offer_asset: Coin, - ask_asset: String, result: impl Fn(StdResult), ) -> &mut Self { let pair_info_response: StdResult = self.app.wrap().query_wasm_smart( &self.pool_manager_addr, &white_whale_std::pool_manager::QueryMsg::Simulation { offer_asset, - ask_asset: Coin { - amount: Uint128::zero(), - denom: ask_asset, - }, pair_identifier, }, ); diff --git a/contracts/liquidity_hub/pool-network/terraswap_router/src/error.rs b/contracts/liquidity_hub/pool-network/terraswap_router/src/error.rs index 3341af83d..4799fa404 100644 --- a/contracts/liquidity_hub/pool-network/terraswap_router/src/error.rs +++ b/contracts/liquidity_hub/pool-network/terraswap_router/src/error.rs @@ -26,6 +26,7 @@ pub enum ContractError { #[error("Invalid swap route: {0}")] InvalidSwapRoute(SwapRoute), + #[error("No swap route found for {offer_asset} -> {ask_asset}")] NoSwapRouteForAssets { offer_asset: String, diff --git a/packages/white-whale-std/src/lib.rs b/packages/white-whale-std/src/lib.rs index b2baca35b..a8b38b5a2 100644 --- a/packages/white-whale-std/src/lib.rs +++ b/packages/white-whale-std/src/lib.rs @@ -1,5 +1,6 @@ pub mod common; +pub mod coin; pub mod constants; pub mod epoch_manager; pub mod fee; @@ -12,8 +13,6 @@ pub mod pool_manager; pub mod pool_network; pub mod token_factory; -pub mod coin; - #[cfg(any( feature = "token_factory", feature = "osmosis_token_factory", diff --git a/packages/white-whale-std/src/pool_manager.rs b/packages/white-whale-std/src/pool_manager.rs index 9ddd957c8..86f5c9ec1 100644 --- a/packages/white-whale-std/src/pool_manager.rs +++ b/packages/white-whale-std/src/pool_manager.rs @@ -184,6 +184,8 @@ pub enum ExecuteMsg { // }, /// Adds swap routes to the router. AddSwapRoutes { swap_routes: Vec }, + /// Removes swap routes from the router. + RemoveSwapRoutes { swap_routes: Vec }, /// Updates the configuration of the contract. /// If a field is not specified (i.e., set to `None`), it will not be modified. UpdateConfig { @@ -216,7 +218,7 @@ pub enum QueryMsg { #[returns(SimulationResponse)] Simulation { offer_asset: Coin, - ask_asset: Coin, + // ask_asset: Coin, pair_identifier: String, }, /// Simulates a reverse swap, i.e. given the ask asset, how much of the offer asset is needed to @@ -312,3 +314,9 @@ pub struct FeatureToggle { pub deposits_enabled: bool, pub swaps_enabled: bool, } + +// We define a custom struct for each query response +#[cw_serde] +pub struct SimulateSwapOperationsResponse { + pub amount: Uint128, +} From dbcf946c0b7eee721dab72231c8e7a10d52ead1d Mon Sep 17 00:00:00 2001 From: nahem Date: Fri, 19 Apr 2024 14:40:07 +0200 Subject: [PATCH 02/12] chore(smart-contracts): clean commented lines that are not used --- contracts/liquidity_hub/pool-manager/src/contract.rs | 3 --- packages/white-whale-std/src/pool_manager.rs | 1 - 2 files changed, 4 deletions(-) diff --git a/contracts/liquidity_hub/pool-manager/src/contract.rs b/contracts/liquidity_hub/pool-manager/src/contract.rs index 91fc1c47a..82106f1e0 100644 --- a/contracts/liquidity_hub/pool-manager/src/contract.rs +++ b/contracts/liquidity_hub/pool-manager/src/contract.rs @@ -197,13 +197,10 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result Ok(to_json_binary(&queries::query_simulation( deps, - // env, offer_asset, - // ask_asset, pair_identifier, )?)?), QueryMsg::ReverseSimulation { diff --git a/packages/white-whale-std/src/pool_manager.rs b/packages/white-whale-std/src/pool_manager.rs index 86f5c9ec1..f344676d0 100644 --- a/packages/white-whale-std/src/pool_manager.rs +++ b/packages/white-whale-std/src/pool_manager.rs @@ -218,7 +218,6 @@ pub enum QueryMsg { #[returns(SimulationResponse)] Simulation { offer_asset: Coin, - // ask_asset: Coin, pair_identifier: String, }, /// Simulates a reverse swap, i.e. given the ask asset, how much of the offer asset is needed to From 5b8f22abda335d88483c821102bd1dabae8f6c39 Mon Sep 17 00:00:00 2001 From: nahem Date: Fri, 19 Apr 2024 19:05:01 +0200 Subject: [PATCH 03/12] feat(smart-contracts): add tests for add & remove swap routes --- .../src/tests/integration_tests.rs | 291 ++++++++++++++++++ .../pool-manager/src/tests/suite.rs | 38 ++- 2 files changed, 328 insertions(+), 1 deletion(-) diff --git a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs index 5065fcea1..2e14a41f3 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs @@ -317,6 +317,7 @@ mod pair_creation_failures { mod router { use cosmwasm_std::Event; + use white_whale_std::pool_manager::SwapRoute; use super::*; #[test] @@ -1181,6 +1182,296 @@ mod router { }, ); } + + #[test] + fn add_swap_routes() { + let mut suite = TestingSuite::default_with_balances(vec![ + coin(1_000_000_001u128, "uwhale".to_string()), + coin(1_000_000_000u128, "uluna".to_string()), + coin(1_000_000_001u128, "uusd".to_string()), + ]); + let creator = suite.creator(); + let _other = suite.senders[1].clone(); + let _unauthorized = suite.senders[2].clone(); + + let first_pair = vec!["uwhale".to_string(), "uluna".to_string()]; + let second_pair = vec!["uluna".to_string(), "uusd".to_string()]; + + #[cfg(not(feature = "osmosis"))] + let pool_fees = PoolFee { + protocol_fee: Fee { + share: Decimal::bps(50), // 0.5% + }, + swap_fee: Fee { + share: Decimal::bps(50), // 0.5% + }, + burn_fee: Fee { + share: Decimal::bps(50), // 0.5% + }, + extra_fees: vec![], + }; + #[cfg(feature = "osmosis")] + let pool_fees = PoolFee { + protocol_fee: Fee { + share: Decimal::bps(50), + }, + swap_fee: Fee { + share: Decimal::bps(50), + }, + burn_fee: Fee { + share: Decimal::bps(50), + }, + osmosis_fee: Fee { + share: Decimal::bps(50), + }, + extra_fees: vec![], + }; + + // Create a pair + suite + .instantiate_default() + .create_pair( + creator.clone(), + first_pair, + vec![6u8, 6u8], + pool_fees.clone(), + white_whale_std::pool_network::asset::PairType::ConstantProduct, + Some("whale-uluna".to_string()), + vec![coin(1000, "uusd")], + |result| { + result.unwrap(); + }, + ) + .create_pair( + creator.clone(), + second_pair, + vec![6u8, 6u8], + pool_fees, + white_whale_std::pool_network::asset::PairType::ConstantProduct, + Some("uluna-uusd".to_string()), + vec![coin(1000, "uusd")], + |result| { + result.unwrap(); + }, + ) + .provide_liquidity( + creator.clone(), + "whale-uluna".to_string(), + vec![ + Coin { + denom: "uwhale".to_string(), + amount: Uint128::from(1000000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::from(1000000u128), + }, + ], + |result| { + result.unwrap(); + }, + ) + .provide_liquidity( + creator.clone(), + "uluna-uusd".to_string(), + vec![ + Coin { + denom: "uluna".to_string(), + amount: Uint128::from(1000000u128), + }, + Coin { + denom: "uusd".to_string(), + amount: Uint128::from(1000000u128), + }, + ], + |result| { + result.unwrap(); + }, + ); + + // Lets try to add a swap route + let swap_route_1 = SwapRoute { + offer_asset_denom: "uwhale".to_string(), + ask_asset_denom: "uusd".to_string(), + swap_operations: vec![ + white_whale_std::pool_manager::SwapOperation::WhaleSwap { + token_in_denom: "uwhale".to_string(), + token_out_denom: "uluna".to_string(), + pool_identifier: "whale-uluna".to_string(), + }, + white_whale_std::pool_manager::SwapOperation::WhaleSwap { + token_in_denom: "uluna".to_string(), + token_out_denom: "uusd".to_string(), + pool_identifier: "uluna-uusd".to_string(), + }, + ], + }; + + suite.add_swap_routes(creator.clone(), vec![swap_route_1.clone()], |result| { + assert!(result.unwrap().events.into_iter().any(|attr| { + attr.attributes + .iter() + .any(|attr| attr.value == "add_swap_routes") + })); + }); + + // Let's query for the swap route + suite.query_swap_routes(|result| { + assert_eq!(result.unwrap().swap_routes[0], swap_route_1); + }); + } + + #[test] + fn remove_swap_routes() { + let mut suite = TestingSuite::default_with_balances(vec![ + coin(1_000_000_001u128, "uwhale".to_string()), + coin(1_000_000_000u128, "uluna".to_string()), + coin(1_000_000_001u128, "uusd".to_string()), + ]); + let creator = suite.creator(); + let _other = suite.senders[1].clone(); + let _unauthorized = suite.senders[2].clone(); + + let first_pair = vec!["uwhale".to_string(), "uluna".to_string()]; + let second_pair = vec!["uluna".to_string(), "uusd".to_string()]; + + #[cfg(not(feature = "osmosis"))] + let pool_fees = PoolFee { + protocol_fee: Fee { + share: Decimal::bps(50), // 0.5% + }, + swap_fee: Fee { + share: Decimal::bps(50), // 0.5% + }, + burn_fee: Fee { + share: Decimal::bps(50), // 0.5% + }, + extra_fees: vec![], + }; + #[cfg(feature = "osmosis")] + let pool_fees = PoolFee { + protocol_fee: Fee { + share: Decimal::bps(50), + }, + swap_fee: Fee { + share: Decimal::bps(50), + }, + burn_fee: Fee { + share: Decimal::bps(50), + }, + osmosis_fee: Fee { + share: Decimal::bps(50), + }, + extra_fees: vec![], + }; + + // Create a pair + suite + .instantiate_default() + .create_pair( + creator.clone(), + first_pair, + vec![6u8, 6u8], + pool_fees.clone(), + white_whale_std::pool_network::asset::PairType::ConstantProduct, + Some("whale-uluna".to_string()), + vec![coin(1000, "uusd")], + |result| { + result.unwrap(); + }, + ) + .create_pair( + creator.clone(), + second_pair, + vec![6u8, 6u8], + pool_fees, + white_whale_std::pool_network::asset::PairType::ConstantProduct, + Some("uluna-uusd".to_string()), + vec![coin(1000, "uusd")], + |result| { + result.unwrap(); + }, + ) + .provide_liquidity( + creator.clone(), + "whale-uluna".to_string(), + vec![ + Coin { + denom: "uwhale".to_string(), + amount: Uint128::from(1000000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::from(1000000u128), + }, + ], + |result| { + result.unwrap(); + }, + ) + .provide_liquidity( + creator.clone(), + "uluna-uusd".to_string(), + vec![ + Coin { + denom: "uluna".to_string(), + amount: Uint128::from(1000000u128), + }, + Coin { + denom: "uusd".to_string(), + amount: Uint128::from(1000000u128), + }, + ], + |result| { + result.unwrap(); + }, + ); + + // Lets try to add a swap route + let swap_route_1 = SwapRoute { + offer_asset_denom: "uwhale".to_string(), + ask_asset_denom: "uusd".to_string(), + swap_operations: vec![ + white_whale_std::pool_manager::SwapOperation::WhaleSwap { + token_in_denom: "uwhale".to_string(), + token_out_denom: "uluna".to_string(), + pool_identifier: "whale-uluna".to_string(), + }, + white_whale_std::pool_manager::SwapOperation::WhaleSwap { + token_in_denom: "uluna".to_string(), + token_out_denom: "uusd".to_string(), + pool_identifier: "uluna-uusd".to_string(), + }, + ], + }; + + suite.add_swap_routes(creator.clone(), vec![swap_route_1.clone()], |result| { + assert!(result.unwrap().events.into_iter().any(|attr| { + attr.attributes + .iter() + .any(|attr| attr.value == "add_swap_routes") + })); + }); + + // Let's query for the swap route + suite.query_swap_routes(|result| { + assert_eq!(result.unwrap().swap_routes[0], swap_route_1); + }); + + // Lets try to remove the swap route + suite.remove_swap_routes(creator.clone(), vec![swap_route_1.clone()], |result| { + assert!(result.unwrap().events.into_iter().any(|attr| { + attr.attributes + .iter() + .any(|attr| attr.value == "remove_swap_routes") + })); + }); + + // Let's query for the swap route + suite.query_swap_routes(|result| { + assert_eq!(result.unwrap().swap_routes.len(), 0); + }); + } } mod swapping { diff --git a/contracts/liquidity_hub/pool-manager/src/tests/suite.rs b/contracts/liquidity_hub/pool-manager/src/tests/suite.rs index 00495e787..af1195d19 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/suite.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/suite.rs @@ -362,6 +362,42 @@ impl TestingSuite { self } + + /// Adds swap routes to the pool manager contract. + #[track_caller] + pub(crate) fn add_swap_routes( + &mut self, + sender: Addr, + swap_routes: Vec, + result: impl Fn(Result), + ) -> &mut Self { + result(self.app.execute_contract( + sender, + self.pool_manager_addr.clone(), + &white_whale_std::pool_manager::ExecuteMsg::AddSwapRoutes { swap_routes }, + &[], + )); + + self + } + + /// Removes swap routes from the pool manager contract. + #[track_caller] + pub(crate) fn remove_swap_routes( + &mut self, + sender: Addr, + swap_routes: Vec, + result: impl Fn(Result), + ) -> &mut Self { + result(self.app.execute_contract( + sender, + self.pool_manager_addr.clone(), + &white_whale_std::pool_manager::ExecuteMsg::RemoveSwapRoutes { swap_routes }, + &[], + )); + + self + } } /// queries @@ -545,7 +581,7 @@ impl TestingSuite { } /// Retrieves the swap routes for a given pair of assets. - pub(crate) fn _query_swap_routes( + pub(crate) fn query_swap_routes( &mut self, result: impl Fn(StdResult), ) -> &mut Self { From 3faf786db445d683edc56b09d87ab0c47ed270ab Mon Sep 17 00:00:00 2001 From: nahem Date: Fri, 19 Apr 2024 19:14:00 +0200 Subject: [PATCH 04/12] feat(smart-contracts): make adding routes permissionless --- contracts/liquidity_hub/pool-manager/src/router/commands.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/liquidity_hub/pool-manager/src/router/commands.rs b/contracts/liquidity_hub/pool-manager/src/router/commands.rs index 53100f68b..26a5428b6 100644 --- a/contracts/liquidity_hub/pool-manager/src/router/commands.rs +++ b/contracts/liquidity_hub/pool-manager/src/router/commands.rs @@ -174,8 +174,6 @@ pub fn add_swap_routes( sender: Addr, swap_routes: Vec, ) -> Result { - assert_admin(deps.as_ref(), &env, &sender)?; - let mut attributes = vec![]; for swap_route in swap_routes { From b6422fa5d9e1f529714bf4d20a207b1e9b586c01 Mon Sep 17 00:00:00 2001 From: nahem Date: Fri, 19 Apr 2024 19:32:13 +0200 Subject: [PATCH 05/12] feat(smart-contracts): add tests for add & remove swap route failing scenarios --- .../pool-manager/src/router/commands.rs | 5 +++-- .../src/tests/integration_tests.rs | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/contracts/liquidity_hub/pool-manager/src/router/commands.rs b/contracts/liquidity_hub/pool-manager/src/router/commands.rs index 26a5428b6..d5f12cc0a 100644 --- a/contracts/liquidity_hub/pool-manager/src/router/commands.rs +++ b/contracts/liquidity_hub/pool-manager/src/router/commands.rs @@ -170,8 +170,9 @@ pub fn execute_swap_operations( pub fn add_swap_routes( deps: DepsMut, - env: Env, - sender: Addr, + // TODO: still need to save the swap route creator into state + _env: Env, + _sender: Addr, swap_routes: Vec, ) -> Result { let mut attributes = vec![]; diff --git a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs index 2e14a41f3..0f20e7cda 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs @@ -1315,6 +1315,17 @@ mod router { })); }); + // Re-add the same swap route should fail + suite.add_swap_routes(creator.clone(), vec![swap_route_1.clone()], |result| { + assert_eq!( + result.unwrap_err().downcast_ref::(), + Some(&ContractError::SwapRouteAlreadyExists { + offer_asset: "uwhale".to_string(), + ask_asset: "uusd".to_string() + }) + ); + }); + // Let's query for the swap route suite.query_swap_routes(|result| { assert_eq!(result.unwrap().swap_routes[0], swap_route_1); @@ -1471,6 +1482,17 @@ mod router { suite.query_swap_routes(|result| { assert_eq!(result.unwrap().swap_routes.len(), 0); }); + + // Re-remove the same swap route should fail + suite.remove_swap_routes(creator.clone(), vec![swap_route_1.clone()], |result| { + assert_eq!( + result.unwrap_err().downcast_ref::(), + Some(&ContractError::NoSwapRouteForAssets { + offer_asset: "uwhale".to_string(), + ask_asset: "uusd".to_string() + }) + ); + }); } } From f14fb74a3b8c912b79ed2913af3b84162c470fe7 Mon Sep 17 00:00:00 2001 From: kaimen-sano Date: Mon, 22 Apr 2024 22:59:46 +1200 Subject: [PATCH 06/12] fix(pool-manager): send protocol fees via FillRewards Rather than a BankMsg::Send --- .../pool-manager/src/router/commands.rs | 15 +++++++++------ .../pool-manager/src/swap/commands.rs | 17 +++++++++++------ .../pool-manager/src/tests/integration_tests.rs | 2 +- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/contracts/liquidity_hub/pool-manager/src/router/commands.rs b/contracts/liquidity_hub/pool-manager/src/router/commands.rs index dab3a8935..26771f763 100644 --- a/contracts/liquidity_hub/pool-manager/src/router/commands.rs +++ b/contracts/liquidity_hub/pool-manager/src/router/commands.rs @@ -1,7 +1,9 @@ use cosmwasm_std::{ - attr, coin, Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, MessageInfo, Response, Uint128, + attr, coin, to_json_binary, Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, MessageInfo, + Response, Uint128, WasmMsg, }; use white_whale_std::pool_manager::SwapOperation; +use white_whale_std::whale_lair; use crate::{state::MANAGER_CONFIG, swap::perform_swap::perform_swap, ContractError}; @@ -112,12 +114,13 @@ pub fn execute_swap_operations( amount: vec![swap_result.burn_fee_asset], })); } - - // todo this should be not a BankMsg but a fill_rewards msg if !swap_result.protocol_fee_asset.amount.is_zero() { - fee_messages.push(CosmosMsg::Bank(BankMsg::Send { - to_address: config.whale_lair_addr.to_string(), - amount: vec![swap_result.protocol_fee_asset], + fee_messages.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: config.whale_lair_addr.to_string(), + msg: to_json_binary(&whale_lair::ExecuteMsg::FillRewards { + assets: vec![swap_result.protocol_fee_asset.clone()], + })?, + funds: vec![swap_result.protocol_fee_asset.clone()], })); } diff --git a/contracts/liquidity_hub/pool-manager/src/swap/commands.rs b/contracts/liquidity_hub/pool-manager/src/swap/commands.rs index c597a6498..66ae981b4 100644 --- a/contracts/liquidity_hub/pool-manager/src/swap/commands.rs +++ b/contracts/liquidity_hub/pool-manager/src/swap/commands.rs @@ -1,9 +1,12 @@ use crate::{state::MANAGER_CONFIG, ContractError}; -use cosmwasm_std::{Addr, BankMsg, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response}; +use cosmwasm_std::{ + to_json_binary, Addr, BankMsg, Coin, CosmosMsg, DepsMut, Env, MessageInfo, Response, WasmMsg, +}; pub const MAX_ASSETS_PER_POOL: usize = 4; use cosmwasm_std::Decimal; +use white_whale_std::whale_lair; use super::perform_swap::perform_swap; @@ -52,18 +55,20 @@ pub fn swap( amount: vec![swap_result.return_asset.clone()], })); } + // then we add the fees if !swap_result.burn_fee_asset.amount.is_zero() { messages.push(CosmosMsg::Bank(BankMsg::Burn { amount: vec![swap_result.burn_fee_asset.clone()], })); } - - //todo this should be not a BankMsg but a fill_rewards msg if !swap_result.protocol_fee_asset.amount.is_zero() { - messages.push(CosmosMsg::Bank(BankMsg::Send { - to_address: config.whale_lair_addr.to_string(), - amount: vec![swap_result.protocol_fee_asset.clone()], + messages.push(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: config.whale_lair_addr.to_string(), + msg: to_json_binary(&whale_lair::ExecuteMsg::FillRewards { + assets: vec![swap_result.protocol_fee_asset.clone()], + })?, + funds: vec![swap_result.protocol_fee_asset.clone()], })); } diff --git a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs index ccadf96f4..4adfa37be 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs @@ -1743,7 +1743,7 @@ mod swapping { }, ); - // Verify fee collection by querying the address of the fee_collector and checking its balance + // Verify fee collection by querying the address of the whale lair and checking its balance // Should be 297 uLUNA suite.query_balance( suite.whale_lair_addr.to_string(), From 25bec4783683136ffd5366b6574fa9982a5252fc Mon Sep 17 00:00:00 2001 From: nahem Date: Tue, 23 Apr 2024 11:59:34 +0200 Subject: [PATCH 07/12] feat(smart-contracts): make add swap route permissionless & remove swap routes permissioned --- .../pool-manager/src/contract.rs | 14 +++++-- .../liquidity_hub/pool-manager/src/queries.rs | 26 +++++++++++-- .../pool-manager/src/router/commands.rs | 27 ++++++++------ .../liquidity_hub/pool-manager/src/state.rs | 12 +++++- .../src/tests/integration_tests.rs | 37 +++++++++++++++---- .../pool-manager/src/tests/suite.rs | 24 +++++++++++- packages/white-whale-std/src/pool_manager.rs | 11 ++++++ 7 files changed, 124 insertions(+), 27 deletions(-) diff --git a/contracts/liquidity_hub/pool-manager/src/contract.rs b/contracts/liquidity_hub/pool-manager/src/contract.rs index 82106f1e0..aacf11c3e 100644 --- a/contracts/liquidity_hub/pool-manager/src/contract.rs +++ b/contracts/liquidity_hub/pool-manager/src/contract.rs @@ -1,5 +1,5 @@ use crate::error::ContractError; -use crate::queries::{get_swap_route, get_swap_routes}; +use crate::queries::{get_swap_route, get_swap_route_creator, get_swap_routes}; use crate::router::commands::{add_swap_routes, remove_swap_routes}; use crate::state::{Config, MANAGER_CONFIG, PAIRS, PAIR_COUNTER}; use crate::{liquidity, manager, queries, router, swap}; @@ -148,10 +148,10 @@ pub fn execute( // ) // } ExecuteMsg::AddSwapRoutes { swap_routes } => { - add_swap_routes(deps, env, info.sender, swap_routes) + add_swap_routes(deps, info.sender, swap_routes) } ExecuteMsg::RemoveSwapRoutes { swap_routes } => { - remove_swap_routes(deps, env, info.sender, swap_routes) + remove_swap_routes(deps, info.sender, swap_routes) } ExecuteMsg::UpdateConfig { whale_lair_addr, @@ -242,6 +242,14 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result Ok(to_json_binary(&PairInfoResponse { pair_info: PAIRS.load(deps.storage, &pair_identifier)?, })?), + QueryMsg::SwapRouteCreator { + offer_asset_denom, + ask_asset_denom, + } => Ok(to_json_binary(&get_swap_route_creator( + deps, + offer_asset_denom, + ask_asset_denom, + )?)?), } } diff --git a/contracts/liquidity_hub/pool-manager/src/queries.rs b/contracts/liquidity_hub/pool-manager/src/queries.rs index f12f91de5..dc45103ff 100644 --- a/contracts/liquidity_hub/pool-manager/src/queries.rs +++ b/contracts/liquidity_hub/pool-manager/src/queries.rs @@ -3,7 +3,8 @@ use std::cmp::Ordering; use cosmwasm_std::{Coin, Decimal256, Deps, Env, Fraction, Order, StdResult, Uint128}; use white_whale_std::pool_manager::{ - AssetDecimalsResponse, Config, SwapRoute, SwapRouteResponse, SwapRoutesResponse, + AssetDecimalsResponse, Config, SwapRoute, SwapRouteCreatorResponse, SwapRouteResponse, + SwapRoutesResponse, }; use white_whale_std::pool_network::{ asset::PairType, @@ -256,7 +257,7 @@ pub fn get_swap_routes(deps: Deps) -> Result Ok(SwapRoute { offer_asset_denom, ask_asset_denom, - swap_operations, + swap_operations: swap_operations.swap_operations, }) }) .collect::>>()?; @@ -282,11 +283,30 @@ pub fn get_swap_route( swap_route: SwapRoute { offer_asset_denom, ask_asset_denom, - swap_operations, + swap_operations: swap_operations.swap_operations, }, }) } +pub fn get_swap_route_creator( + deps: Deps, + offer_asset_denom: String, + ask_asset_denom: String, +) -> Result { + let swap_route_key = SWAP_ROUTES.key((&offer_asset_denom, &ask_asset_denom)); + + let swap_operations = + swap_route_key + .load(deps.storage) + .map_err(|_| ContractError::NoSwapRouteForAssets { + offer_asset: offer_asset_denom.clone(), + ask_asset: ask_asset_denom.clone(), + })?; + Ok(SwapRouteCreatorResponse { + creator: swap_operations.creator, + }) +} + // TODO: May need to remove this for a new implementation, router swap operation queries // pub fn simulate_swap_operations( // deps: Deps, diff --git a/contracts/liquidity_hub/pool-manager/src/router/commands.rs b/contracts/liquidity_hub/pool-manager/src/router/commands.rs index d5f12cc0a..81e92f193 100644 --- a/contracts/liquidity_hub/pool-manager/src/router/commands.rs +++ b/contracts/liquidity_hub/pool-manager/src/router/commands.rs @@ -1,12 +1,11 @@ use cosmwasm_std::{ - attr, coin, Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Response, - Uint128, + attr, coin, Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, MessageInfo, Response, Uint128, }; use white_whale_std::pool_manager::{SwapOperation, SwapRoute}; use crate::{ - helpers::{assert_admin, simulate_swap_operations}, - state::{MANAGER_CONFIG, SWAP_ROUTES}, + helpers::simulate_swap_operations, + state::{SwapOperations, MANAGER_CONFIG, SWAP_ROUTES}, swap::perform_swap::perform_swap, ContractError, }; @@ -170,9 +169,7 @@ pub fn execute_swap_operations( pub fn add_swap_routes( deps: DepsMut, - // TODO: still need to save the swap route creator into state - _env: Env, - _sender: Addr, + sender: Addr, swap_routes: Vec, ) -> Result { let mut attributes = vec![]; @@ -196,7 +193,13 @@ pub fn add_swap_routes( ask_asset: swap_route.ask_asset_denom, }); } - swap_route_key.save(deps.storage, &swap_route.clone().swap_operations)?; + swap_route_key.save( + deps.storage, + &SwapOperations { + creator: sender.to_string(), + swap_operations: swap_route.clone().swap_operations, + }, + )?; attributes.push(attr("swap_route", swap_route.clone().to_string())); } @@ -208,12 +211,9 @@ pub fn add_swap_routes( pub fn remove_swap_routes( deps: DepsMut, - env: Env, sender: Addr, swap_routes: Vec, ) -> Result { - assert_admin(deps.as_ref(), &env, &sender)?; - let mut attributes = vec![]; for swap_route in swap_routes { @@ -223,6 +223,11 @@ pub fn remove_swap_routes( // Remove the swap route if it exists if swap_route_key.has(deps.storage) { + // only contract owner or route creator can remove the swap route + let creator = swap_route_key.load(deps.storage)?.creator; + if !cw_ownable::is_owner(deps.storage, &sender)? && sender != creator { + return Err(ContractError::Unauthorized {}); + } swap_route_key.remove(deps.storage); attributes.push(attr("swap_route", swap_route.clone().to_string())); } else { diff --git a/contracts/liquidity_hub/pool-manager/src/state.rs b/contracts/liquidity_hub/pool-manager/src/state.rs index d88423aa4..d766a1d26 100644 --- a/contracts/liquidity_hub/pool-manager/src/state.rs +++ b/contracts/liquidity_hub/pool-manager/src/state.rs @@ -1,3 +1,4 @@ +use cosmwasm_schema::cw_serde; use cosmwasm_std::Deps; use cw_storage_plus::{Index, IndexList, IndexedMap, Item, Map, UniqueIndex}; use white_whale_std::pool_manager::{PairInfo, SwapOperation}; @@ -33,8 +34,15 @@ pub fn get_pair_by_identifier( .ok_or(ContractError::UnExistingPair {}) } -// Swap routes are used to establish defined routes for a given fee token to a desired fee token and is used for fee collection -pub const SWAP_ROUTES: Map<(&str, &str), Vec> = Map::new("swap_routes"); +// Swap routes are used to establish defined routes for a given fee +// token to a desired fee token and is used for fee collection +#[cw_serde] +pub struct SwapOperations { + // creator of the swap route, can remove it later + pub creator: String, + pub swap_operations: Vec, +} +pub const SWAP_ROUTES: Map<(&str, &str), SwapOperations> = Map::new("swap_routes"); pub use white_whale_std::pool_manager::Config; pub const MANAGER_CONFIG: Item = Item::new("manager_config"); diff --git a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs index 0f20e7cda..e9932d736 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs @@ -317,7 +317,7 @@ mod pair_creation_failures { mod router { use cosmwasm_std::Event; - use white_whale_std::pool_manager::SwapRoute; + use white_whale_std::pool_manager::{SwapRoute, SwapRouteCreatorResponse}; use super::*; #[test] @@ -1326,10 +1326,19 @@ mod router { ); }); - // Let's query for the swap route + // Let's query all swap routes suite.query_swap_routes(|result| { assert_eq!(result.unwrap().swap_routes[0], swap_route_1); }); + + // Let;s query for the swap route creator + suite.query_swap_route_creator( + "uwhale".to_string(), + "uusd".to_string(), + |result: Result| { + assert_eq!(result.unwrap().creator, creator); + }, + ); } #[test] @@ -1340,8 +1349,8 @@ mod router { coin(1_000_000_001u128, "uusd".to_string()), ]); let creator = suite.creator(); - let _other = suite.senders[1].clone(); - let _unauthorized = suite.senders[2].clone(); + let other = suite.senders[1].clone(); + let unauthorized = suite.senders[2].clone(); let first_pair = vec!["uwhale".to_string(), "uluna".to_string()]; let second_pair = vec!["uluna".to_string(), "uusd".to_string()]; @@ -1456,7 +1465,7 @@ mod router { ], }; - suite.add_swap_routes(creator.clone(), vec![swap_route_1.clone()], |result| { + suite.add_swap_routes(other.clone(), vec![swap_route_1.clone()], |result| { assert!(result.unwrap().events.into_iter().any(|attr| { attr.attributes .iter() @@ -1469,8 +1478,22 @@ mod router { assert_eq!(result.unwrap().swap_routes[0], swap_route_1); }); - // Lets try to remove the swap route - suite.remove_swap_routes(creator.clone(), vec![swap_route_1.clone()], |result| { + // Let;s query for the swap route creator + suite.query_swap_route_creator( + "uwhale".to_string(), + "uusd".to_string(), + |result: Result| { + assert_eq!(result.unwrap().creator, other); + }, + ); + + // Removing a swap route as a non-route-creator & non-admin should fail + suite.remove_swap_routes(unauthorized.clone(), vec![swap_route_1.clone()], |result| { + assert!(result.is_err()); + }); + + // Lets try to remove the swap route as the swap route creator + suite.remove_swap_routes(other.clone(), vec![swap_route_1.clone()], |result| { assert!(result.unwrap().events.into_iter().any(|attr| { attr.attributes .iter() diff --git a/contracts/liquidity_hub/pool-manager/src/tests/suite.rs b/contracts/liquidity_hub/pool-manager/src/tests/suite.rs index af1195d19..d0b1e6bfd 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/suite.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/suite.rs @@ -1,6 +1,7 @@ use cosmwasm_std::testing::MockStorage; use white_whale_std::pool_manager::{ - Config, FeatureToggle, PairInfoResponse, SwapOperation, SwapRouteResponse, SwapRoutesResponse, + Config, FeatureToggle, PairInfoResponse, SwapOperation, SwapRouteCreatorResponse, + SwapRouteResponse, SwapRoutesResponse, }; use white_whale_std::pool_manager::{InstantiateMsg, PairInfo}; @@ -594,4 +595,25 @@ impl TestingSuite { self } + + /// Retrieves the swap route creator for a given pair of assets. + pub(crate) fn query_swap_route_creator( + &mut self, + offer_asset_denom: String, + ask_asset_denom: String, + result: impl Fn(StdResult), + ) -> &mut Self { + let swap_route_creator_response: StdResult = + self.app.wrap().query_wasm_smart( + &self.pool_manager_addr, + &white_whale_std::pool_manager::QueryMsg::SwapRouteCreator { + offer_asset_denom, + ask_asset_denom, + }, + ); + + result(swap_route_creator_response); + + self + } } diff --git a/packages/white-whale-std/src/pool_manager.rs b/packages/white-whale-std/src/pool_manager.rs index f344676d0..1bff85a1f 100644 --- a/packages/white-whale-std/src/pool_manager.rs +++ b/packages/white-whale-std/src/pool_manager.rs @@ -254,6 +254,12 @@ pub enum QueryMsg { // }, #[returns(PairInfoResponse)] Pair { pair_identifier: String }, + /// Retrieves the creator of the swap routes that can then remove them. + #[returns(SwapRouteCreatorResponse)] + SwapRouteCreator { + offer_asset_denom: String, + ask_asset_denom: String, + }, } #[cw_serde] @@ -319,3 +325,8 @@ pub struct FeatureToggle { pub struct SimulateSwapOperationsResponse { pub amount: Uint128, } + +#[cw_serde] +pub struct SwapRouteCreatorResponse { + pub creator: String, +} From 2540006aa0ac279acee3318b0b017c86ffd0a7fc Mon Sep 17 00:00:00 2001 From: nahem Date: Tue, 23 Apr 2024 12:05:09 +0200 Subject: [PATCH 08/12] chore(schemas): regenerate schemas --- .../pool-manager/schema/pool-manager.json | 69 +++++++++++++++++-- .../pool-manager/schema/raw/execute.json | 25 +++++++ .../pool-manager/schema/raw/query.json | 30 ++++++-- .../raw/response_to_swap_route_creator.json | 14 ++++ 4 files changed, 130 insertions(+), 8 deletions(-) create mode 100644 contracts/liquidity_hub/pool-manager/schema/raw/response_to_swap_route_creator.json diff --git a/contracts/liquidity_hub/pool-manager/schema/pool-manager.json b/contracts/liquidity_hub/pool-manager/schema/pool-manager.json index 6e4527c7c..23e77ea8f 100644 --- a/contracts/liquidity_hub/pool-manager/schema/pool-manager.json +++ b/contracts/liquidity_hub/pool-manager/schema/pool-manager.json @@ -288,6 +288,31 @@ }, "additionalProperties": false }, + { + "description": "Removes swap routes from the router.", + "type": "object", + "required": [ + "remove_swap_routes" + ], + "properties": { + "remove_swap_routes": { + "type": "object", + "required": [ + "swap_routes" + ], + "properties": { + "swap_routes": { + "type": "array", + "items": { + "$ref": "#/definitions/SwapRoute" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Updates the configuration of the contract. If a field is not specified (i.e., set to `None`), it will not be modified.", "type": "object", @@ -704,14 +729,10 @@ "simulation": { "type": "object", "required": [ - "ask_asset", "offer_asset", "pair_identifier" ], "properties": { - "ask_asset": { - "$ref": "#/definitions/Coin" - }, "offer_asset": { "$ref": "#/definitions/Coin" }, @@ -815,6 +836,32 @@ }, "additionalProperties": false }, + { + "description": "Retrieves the creator of the swap routes that can then remove them.", + "type": "object", + "required": [ + "swap_route_creator" + ], + "properties": { + "swap_route_creator": { + "type": "object", + "required": [ + "ask_asset_denom", + "offer_asset_denom" + ], + "properties": { + "ask_asset_denom": { + "type": "string" + }, + "offer_asset_denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Query the contract's ownership information", "type": "object", @@ -1382,6 +1429,20 @@ } } }, + "swap_route_creator": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SwapRouteCreatorResponse", + "type": "object", + "required": [ + "creator" + ], + "properties": { + "creator": { + "type": "string" + } + }, + "additionalProperties": false + }, "swap_routes": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "SwapRoutesResponse", diff --git a/contracts/liquidity_hub/pool-manager/schema/raw/execute.json b/contracts/liquidity_hub/pool-manager/schema/raw/execute.json index a268cfc0f..ccbbc66ea 100644 --- a/contracts/liquidity_hub/pool-manager/schema/raw/execute.json +++ b/contracts/liquidity_hub/pool-manager/schema/raw/execute.json @@ -245,6 +245,31 @@ }, "additionalProperties": false }, + { + "description": "Removes swap routes from the router.", + "type": "object", + "required": [ + "remove_swap_routes" + ], + "properties": { + "remove_swap_routes": { + "type": "object", + "required": [ + "swap_routes" + ], + "properties": { + "swap_routes": { + "type": "array", + "items": { + "$ref": "#/definitions/SwapRoute" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Updates the configuration of the contract. If a field is not specified (i.e., set to `None`), it will not be modified.", "type": "object", diff --git a/contracts/liquidity_hub/pool-manager/schema/raw/query.json b/contracts/liquidity_hub/pool-manager/schema/raw/query.json index e25110fff..8e3f22764 100644 --- a/contracts/liquidity_hub/pool-manager/schema/raw/query.json +++ b/contracts/liquidity_hub/pool-manager/schema/raw/query.json @@ -52,14 +52,10 @@ "simulation": { "type": "object", "required": [ - "ask_asset", "offer_asset", "pair_identifier" ], "properties": { - "ask_asset": { - "$ref": "#/definitions/Coin" - }, "offer_asset": { "$ref": "#/definitions/Coin" }, @@ -163,6 +159,32 @@ }, "additionalProperties": false }, + { + "description": "Retrieves the creator of the swap routes that can then remove them.", + "type": "object", + "required": [ + "swap_route_creator" + ], + "properties": { + "swap_route_creator": { + "type": "object", + "required": [ + "ask_asset_denom", + "offer_asset_denom" + ], + "properties": { + "ask_asset_denom": { + "type": "string" + }, + "offer_asset_denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Query the contract's ownership information", "type": "object", diff --git a/contracts/liquidity_hub/pool-manager/schema/raw/response_to_swap_route_creator.json b/contracts/liquidity_hub/pool-manager/schema/raw/response_to_swap_route_creator.json new file mode 100644 index 000000000..825c4ef49 --- /dev/null +++ b/contracts/liquidity_hub/pool-manager/schema/raw/response_to_swap_route_creator.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SwapRouteCreatorResponse", + "type": "object", + "required": [ + "creator" + ], + "properties": { + "creator": { + "type": "string" + } + }, + "additionalProperties": false +} From 70976da5bf1604ffbb6ff02ba1e99469cd1c4874 Mon Sep 17 00:00:00 2001 From: nahem Date: Tue, 23 Apr 2024 12:18:47 +0200 Subject: [PATCH 09/12] chore(smart-contracts): add test for query a specific swap route --- .../src/tests/integration_tests.rs | 18 +++++++++++++++--- .../pool-manager/src/tests/suite.rs | 2 +- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs index e9932d736..bac0820a6 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs @@ -1473,9 +1473,21 @@ mod router { })); }); - // Let's query for the swap route + // Let's query for all swap routes suite.query_swap_routes(|result| { - assert_eq!(result.unwrap().swap_routes[0], swap_route_1); + assert_eq!(result.unwrap().swap_routes[0], swap_route_1.clone()); + }); + + // Let's query for the swap route + suite.query_swap_route("uwhale".to_string(), "uusd".to_string(), |result| { + assert_eq!( + result.unwrap().swap_route, + SwapRoute { + offer_asset_denom: "uwhale".to_string(), + ask_asset_denom: "uusd".to_string(), + swap_operations: swap_route_1.swap_operations.clone(), + } + ); }); // Let;s query for the swap route creator @@ -1501,7 +1513,7 @@ mod router { })); }); - // Let's query for the swap route + // Let's query for all swap routes suite.query_swap_routes(|result| { assert_eq!(result.unwrap().swap_routes.len(), 0); }); diff --git a/contracts/liquidity_hub/pool-manager/src/tests/suite.rs b/contracts/liquidity_hub/pool-manager/src/tests/suite.rs index d0b1e6bfd..3a8c99746 100644 --- a/contracts/liquidity_hub/pool-manager/src/tests/suite.rs +++ b/contracts/liquidity_hub/pool-manager/src/tests/suite.rs @@ -562,7 +562,7 @@ impl TestingSuite { } /// Retrieves a swap route for a given pair of assets. - pub(crate) fn _query_swap_route( + pub(crate) fn query_swap_route( &mut self, offer_asset_denom: String, ask_asset_denom: String, From 61f9235fed586afbffe45c192ceabc8ac4b680d3 Mon Sep 17 00:00:00 2001 From: nahem Date: Tue, 23 Apr 2024 13:57:00 +0200 Subject: [PATCH 10/12] feat(smart-contracts): add a query for simulate swap operations --- .../liquidity_hub/pool-manager/src/contract.rs | 18 +++++++++--------- .../liquidity_hub/pool-manager/src/helpers.rs | 2 ++ packages/white-whale-std/src/pool_manager.rs | 10 +++++----- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/contracts/liquidity_hub/pool-manager/src/contract.rs b/contracts/liquidity_hub/pool-manager/src/contract.rs index aacf11c3e..535e8bf64 100644 --- a/contracts/liquidity_hub/pool-manager/src/contract.rs +++ b/contracts/liquidity_hub/pool-manager/src/contract.rs @@ -1,4 +1,5 @@ use crate::error::ContractError; +use crate::helpers::simulate_swap_operations; use crate::queries::{get_swap_route, get_swap_route_creator, get_swap_routes}; use crate::router::commands::{add_swap_routes, remove_swap_routes}; use crate::state::{Config, MANAGER_CONFIG, PAIRS, PAIR_COUNTER}; @@ -214,15 +215,14 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result Ok(to_binary(&queries::simulate_swap_operations( - // deps, - // env, - // offer_amount, - // operations, - // )?)?), + QueryMsg::SimulateSwapOperations { + offer_amount, + operations, + } => Ok(to_json_binary(&simulate_swap_operations( + deps, + offer_amount, + operations, + )?)?), // QueryMsg::ReverseSimulateSwapOperations { // ask_amount, // operations, diff --git a/contracts/liquidity_hub/pool-manager/src/helpers.rs b/contracts/liquidity_hub/pool-manager/src/helpers.rs index 9799b2f0b..c1ae5a73f 100644 --- a/contracts/liquidity_hub/pool-manager/src/helpers.rs +++ b/contracts/liquidity_hub/pool-manager/src/helpers.rs @@ -599,6 +599,8 @@ pub fn assert_admin(deps: Deps, env: &Env, sender: &Addr) -> Result<(), Contract Ok(()) } +/// This function iterates over the swap operations, simulates each swap +/// to get the final amount after all the swaps. pub fn simulate_swap_operations( deps: Deps, offer_amount: Uint128, diff --git a/packages/white-whale-std/src/pool_manager.rs b/packages/white-whale-std/src/pool_manager.rs index 1bff85a1f..25dfc83ee 100644 --- a/packages/white-whale-std/src/pool_manager.rs +++ b/packages/white-whale-std/src/pool_manager.rs @@ -240,11 +240,11 @@ pub enum QueryMsg { SwapRoutes {}, // /// Simulates swap operations. - // #[returns(SimulateSwapOperationsResponse)] - // SimulateSwapOperations { - // offer_amount: Uint128, - // operations: Vec, - // }, + #[returns(SimulateSwapOperationsResponse)] + SimulateSwapOperations { + offer_amount: Uint128, + operations: Vec, + }, // /// Simulates a reverse swap operations, i.e. given the ask asset, how much of the offer asset // /// is needed to perform the swap. // #[returns(SimulateSwapOperationsResponse)] From fb352847046a4fdbcd236b562679f753df338328 Mon Sep 17 00:00:00 2001 From: nahem Date: Tue, 23 Apr 2024 13:58:55 +0200 Subject: [PATCH 11/12] chore(schemas): regenerate schemas --- .../pool-manager/schema/pool-manager.json | 81 +++++++++++++++++++ .../pool-manager/schema/raw/query.json | 61 ++++++++++++++ .../response_to_simulate_swap_operations.json | 20 +++++ 3 files changed, 162 insertions(+) create mode 100644 contracts/liquidity_hub/pool-manager/schema/raw/response_to_simulate_swap_operations.json diff --git a/contracts/liquidity_hub/pool-manager/schema/pool-manager.json b/contracts/liquidity_hub/pool-manager/schema/pool-manager.json index 23e77ea8f..f937bcb35 100644 --- a/contracts/liquidity_hub/pool-manager/schema/pool-manager.json +++ b/contracts/liquidity_hub/pool-manager/schema/pool-manager.json @@ -815,6 +815,34 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "simulate_swap_operations" + ], + "properties": { + "simulate_swap_operations": { + "type": "object", + "required": [ + "offer_amount", + "operations" + ], + "properties": { + "offer_amount": { + "$ref": "#/definitions/Uint128" + }, + "operations": { + "type": "array", + "items": { + "$ref": "#/definitions/SwapOperation" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -893,6 +921,39 @@ } } }, + "SwapOperation": { + "oneOf": [ + { + "type": "object", + "required": [ + "whale_swap" + ], + "properties": { + "whale_swap": { + "type": "object", + "required": [ + "pool_identifier", + "token_in_denom", + "token_out_denom" + ], + "properties": { + "pool_identifier": { + "type": "string" + }, + "token_in_denom": { + "type": "string" + }, + "token_out_denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -1320,6 +1381,26 @@ } } }, + "simulate_swap_operations": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SimulateSwapOperationsResponse", + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "simulation": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "SimulationResponse", diff --git a/contracts/liquidity_hub/pool-manager/schema/raw/query.json b/contracts/liquidity_hub/pool-manager/schema/raw/query.json index 8e3f22764..3e230a047 100644 --- a/contracts/liquidity_hub/pool-manager/schema/raw/query.json +++ b/contracts/liquidity_hub/pool-manager/schema/raw/query.json @@ -138,6 +138,34 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "simulate_swap_operations" + ], + "properties": { + "simulate_swap_operations": { + "type": "object", + "required": [ + "offer_amount", + "operations" + ], + "properties": { + "offer_amount": { + "$ref": "#/definitions/Uint128" + }, + "operations": { + "type": "array", + "items": { + "$ref": "#/definitions/SwapOperation" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "type": "object", "required": [ @@ -216,6 +244,39 @@ } } }, + "SwapOperation": { + "oneOf": [ + { + "type": "object", + "required": [ + "whale_swap" + ], + "properties": { + "whale_swap": { + "type": "object", + "required": [ + "pool_identifier", + "token_in_denom", + "token_out_denom" + ], + "properties": { + "pool_identifier": { + "type": "string" + }, + "token_in_denom": { + "type": "string" + }, + "token_out_denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/contracts/liquidity_hub/pool-manager/schema/raw/response_to_simulate_swap_operations.json b/contracts/liquidity_hub/pool-manager/schema/raw/response_to_simulate_swap_operations.json new file mode 100644 index 000000000..b15e18ee0 --- /dev/null +++ b/contracts/liquidity_hub/pool-manager/schema/raw/response_to_simulate_swap_operations.json @@ -0,0 +1,20 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "SimulateSwapOperationsResponse", + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false, + "definitions": { + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} From 4f0d553bceadebfd1c6c94b7a4cfb00b7234b9ee Mon Sep 17 00:00:00 2001 From: nahem Date: Tue, 23 Apr 2024 14:02:22 +0200 Subject: [PATCH 12/12] chore(smart-contracts): remove commented TODOs --- .../liquidity_hub/pool-manager/src/router/commands.rs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/contracts/liquidity_hub/pool-manager/src/router/commands.rs b/contracts/liquidity_hub/pool-manager/src/router/commands.rs index c293b1c2f..2f4b662cb 100644 --- a/contracts/liquidity_hub/pool-manager/src/router/commands.rs +++ b/contracts/liquidity_hub/pool-manager/src/router/commands.rs @@ -83,10 +83,7 @@ pub fn execute_swap_operations( for operation in operations { match operation { SwapOperation::WhaleSwap { - // TODO: do we need to use token_in_denom? - token_in_denom: _, - pool_identifier, - .. + pool_identifier, .. } => { // inside assert_operations() we have already checked that // the output of each swap is the input of the next swap. @@ -185,7 +182,6 @@ pub fn add_swap_routes( ) .map_err(|_| ContractError::InvalidSwapRoute(swap_route.clone()))?; - // TODO: do we need to derivate the key from the pair identifier too? let swap_route_key = SWAP_ROUTES.key((&swap_route.offer_asset_denom, &swap_route.ask_asset_denom)); @@ -220,7 +216,6 @@ pub fn remove_swap_routes( let mut attributes = vec![]; for swap_route in swap_routes { - // TODO: do we need to derivate the key from the pair identifier too? let swap_route_key = SWAP_ROUTES.key((&swap_route.offer_asset_denom, &swap_route.ask_asset_denom));