Skip to content

Commit

Permalink
feat(smart-contracts): add reverse simulate swap operations with tests
Browse files Browse the repository at this point in the history
  • Loading branch information
nseguias committed Apr 29, 2024
1 parent 75794fe commit b760043
Show file tree
Hide file tree
Showing 5 changed files with 284 additions and 23 deletions.
14 changes: 7 additions & 7 deletions contracts/liquidity_hub/pool-manager/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::error::ContractError;
use crate::helpers::simulate_swap_operations;
use crate::helpers::{reverse_simulate_swap_operations, 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};
Expand Down Expand Up @@ -228,12 +228,12 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result<Binary, ContractErro
offer_amount,
operations,
)?)?),
// QueryMsg::ReverseSimulateSwapOperations {
// ask_amount,
// operations,
// } => Ok(to_binary(&queries::reverse_simulate_swap_operations(
// deps, env, ask_amount, operations,
// )?)?),
QueryMsg::ReverseSimulateSwapOperations {
ask_amount,
operations,
} => Ok(to_json_binary(&reverse_simulate_swap_operations(
deps, ask_amount, operations,
)?)?),
QueryMsg::SwapRoute {
offer_asset_denom,
ask_asset_denom,
Expand Down
38 changes: 33 additions & 5 deletions contracts/liquidity_hub/pool-manager/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -620,11 +620,39 @@ pub fn simulate_swap_operations(
token_out_denom: _,
pool_identifier,
} => {
let res = query_simulation(
deps,
coin(offer_amount.u128(), token_in_denom),
pool_identifier,
)?;
let res =
query_simulation(deps, coin(amount.u128(), token_in_denom), pool_identifier)?;
amount = res.return_amount;
}
}
}

Ok(SimulateSwapOperationsResponse { amount })
}

/// This function iterates over the swap operations in the reverse order,
/// simulates each swap to get the final amount after all the swaps.
pub fn reverse_simulate_swap_operations(
deps: Deps,
ask_amount: Uint128,
operations: Vec<SwapOperation>,
) -> Result<SimulateSwapOperationsResponse, ContractError> {
let operations_len = operations.len();
if operations_len == 0 {
return Err(ContractError::NoSwapOperationsProvided {});
}

let mut amount = ask_amount;

for operation in operations.into_iter().rev() {
match operation {
SwapOperation::WhaleSwap {
token_in_denom: _,
token_out_denom,
pool_identifier,
} => {
let res =
query_simulation(deps, coin(amount.u128(), token_out_denom), pool_identifier)?;
amount = res.return_amount;
}
}
Expand Down
190 changes: 190 additions & 0 deletions contracts/liquidity_hub/pool-manager/src/tests/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1558,6 +1558,196 @@ mod router {
);
});
}

#[test]
fn query_swap_operations() {
let mut suite = TestingSuite::default_with_balances(vec![
coin(1_000_000_000u128, "uwhale".to_string()),
coin(1_000_000_000u128, "uluna".to_string()),
coin(1_000_000_000u128, "uusd".to_string()),
]);
let creator = suite.creator();
let _other = suite.senders[1].clone();
let _unauthorized = suite.senders[2].clone();

// Asset infos with uwhale and uluna
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(1_000, "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(1_000, "uusd")],
|result| {
result.unwrap();
},
);

// Lets try to add liquidity
suite.provide_liquidity(
creator.clone(),
"whale-uluna".to_string(),
None,
None,
vec![
Coin {
denom: "uwhale".to_string(),
amount: Uint128::from(1_000_000u128),
},
Coin {
denom: "uluna".to_string(),
amount: Uint128::from(1_000_000u128),
},
],
|result| {
// ensure we got 999,000 in the response (1m - initial liquidity amount)
let result = result.unwrap();
assert!(result.has_event(&Event::new("wasm").add_attribute("share", "999000")));
},
);

// Lets try to add liquidity
suite.provide_liquidity(
creator.clone(),
"uluna-uusd".to_string(),
None,
None,
vec![
Coin {
denom: "uluna".to_string(),
amount: Uint128::from(1_000_000u128),
},
Coin {
denom: "uusd".to_string(),
amount: Uint128::from(1_000_000u128),
},
],
|result| {
// ensure we got 999,000 in the response (1m - initial liquidity amount)
let result = result.unwrap();
assert!(result.has_event(&Event::new("wasm").add_attribute("share", "999000")));
},
);

// Prepare the swap operations, we want to go from WHALE -> UUSD
// We will use the uluna-uusd pair as the intermediary pool

let 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(),
},
];

// simulating (reverse) swap operations should return the correct same amount as the pools are balanced
// going from whale -> uusd should return 974 uusd
// going from uusd -> whale should return 974 whale
suite.query_simulate_swap_operations(
Uint128::new(1_000),
swap_operations.clone(),
|result| {
let result = result.unwrap();
assert_eq!(result.amount.u128(), 974);
},
);
suite.query_reverse_simulate_swap_operations(
Uint128::new(1_000),
swap_operations.clone(),
|result| {
let result = result.unwrap();
assert_eq!(result.amount.u128(), 974);
},
);

// execute the swap operations to unbalance the pools
// sold 10_000 whale for some uusd, so the price of whale should go down
suite.execute_swap_operations(
creator.clone(),
swap_operations.clone(),
None,
None,
None,
vec![coin(10_000u128, "uwhale".to_string())],
|result| {
result.unwrap();
},
);

// now to get 1_000 uusd we should swap more whale than before
suite.query_reverse_simulate_swap_operations(
Uint128::new(1_000),
swap_operations.clone(),
|result| {
let result = result.unwrap();
assert_eq!(result.amount.u128(), 1_006);
},
);

// and if simulate swap operations with 1_000 more whale we should get even less uusd than before
suite.query_simulate_swap_operations(
Uint128::new(1_000),
swap_operations.clone(),
|result| {
let result = result.unwrap();
assert_eq!(result.amount.u128(), 935);
},
);
}
}

mod swapping {
Expand Down
43 changes: 40 additions & 3 deletions contracts/liquidity_hub/pool-manager/src/tests/suite.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use cosmwasm_std::testing::MockStorage;
use white_whale_std::pool_manager::{
Config, FeatureToggle, PairInfoResponse, SwapOperation, SwapRouteCreatorResponse,
SwapRouteResponse, SwapRoutesResponse,
Config, FeatureToggle, PairInfoResponse, ReverseSimulateSwapOperationsResponse, SimulateSwapOperationsResponse, SwapOperation, SwapRouteCreatorResponse, SwapRouteResponse, SwapRoutesResponse
};
use white_whale_std::pool_manager::{InstantiateMsg, PairInfo};

Expand All @@ -17,7 +16,7 @@ use white_whale_std::fee::PoolFee;
use white_whale_std::incentive_manager::PositionsResponse;
use white_whale_std::lp_common::LP_SYMBOL;
use white_whale_std::pool_network::asset::{AssetInfo, PairType};
use white_whale_std::pool_network::pair::{ReverseSimulationResponse, SimulationResponse};
use white_whale_std::pool_manager::{ReverseSimulationResponse, SimulationResponse};
use white_whale_testing::multi_test::stargate_mock::StargateMock;

fn contract_pool_manager() -> Box<dyn Contract<Empty>> {
Expand Down Expand Up @@ -609,6 +608,44 @@ impl TestingSuite {
self
}

pub(crate) fn query_simulate_swap_operations(
&mut self,
offer_amount: Uint128,
operations: Vec<SwapOperation>,
result: impl Fn(StdResult<SimulateSwapOperationsResponse>),
) -> &mut Self {
let pair_info_response: StdResult<SimulateSwapOperationsResponse> = self.app.wrap().query_wasm_smart(
&self.pool_manager_addr,
&white_whale_std::pool_manager::QueryMsg::SimulateSwapOperations {
offer_amount,
operations,
},
);

result(pair_info_response);

self
}

pub(crate) fn query_reverse_simulate_swap_operations(
&mut self,
ask_amount: Uint128,
operations: Vec<SwapOperation>,
result: impl Fn(StdResult<ReverseSimulateSwapOperationsResponse>),
) -> &mut Self {
let pair_info_response: StdResult<ReverseSimulateSwapOperationsResponse> = self.app.wrap().query_wasm_smart(
&self.pool_manager_addr,
&white_whale_std::pool_manager::QueryMsg::ReverseSimulateSwapOperations {
ask_amount,
operations,
},
);

result(pair_info_response);

self
}

pub(crate) fn query_amount_of_lp_token(
&mut self,
identifier: String,
Expand Down
22 changes: 14 additions & 8 deletions packages/white-whale-std/src/pool_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,19 +248,20 @@ pub enum QueryMsg {
#[returns(SwapRoutesResponse)]
SwapRoutes {},

// /// Simulates swap operations.
/// Simulates swap operations.
#[returns(SimulateSwapOperationsResponse)]
SimulateSwapOperations {
offer_amount: Uint128,
operations: Vec<SwapOperation>,
},
// /// 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)]
// ReverseSimulateSwapOperations {
// ask_amount: Uint128,
// operations: Vec<SwapOperation>,
// },
/// Simulates a reverse swap operations, i.e. given the ask asset, how much of the offer asset
/// is needed to perform the swap.
#[returns(ReverseSimulateSwapOperationsResponse)]
ReverseSimulateSwapOperations {
ask_amount: Uint128,
operations: Vec<SwapOperation>,
},

#[returns(PairInfoResponse)]
Pair { pair_identifier: String },
/// Retrieves the creator of the swap routes that can then remove them.
Expand Down Expand Up @@ -335,6 +336,11 @@ pub struct SimulateSwapOperationsResponse {
pub amount: Uint128,
}

#[cw_serde]
pub struct ReverseSimulateSwapOperationsResponse {
pub amount: Uint128,
}

#[cw_serde]
pub struct SwapRouteCreatorResponse {
pub creator: String,
Expand Down

0 comments on commit b760043

Please sign in to comment.