Skip to content

Commit

Permalink
feat: Finish Swap usecase and Lp withdrawal, update pool manager to u…
Browse files Browse the repository at this point in the history
…se new method, update tests
  • Loading branch information
0xFable committed Apr 26, 2024
1 parent 5a6c9d4 commit 30712fb
Show file tree
Hide file tree
Showing 13 changed files with 249 additions and 95 deletions.
106 changes: 31 additions & 75 deletions contracts/liquidity_hub/bonding-manager/src/commands.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
use cosmwasm_std::{
ensure, to_json_binary, Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, Env, MessageInfo,
Order, Response, StdError, StdResult, Timestamp, Uint128, Uint64, WasmMsg,
ensure, Addr, BankMsg, Coin, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, Order, Response,
StdError, StdResult, Timestamp, Uint128, Uint64,
};
use white_whale_std::constants::LP_SYMBOL;
use white_whale_std::pool_manager::PairInfoResponse;
use white_whale_std::pool_network::asset;

use white_whale_std::bonding_manager::Bond;
Expand Down Expand Up @@ -188,6 +186,7 @@ pub(crate) fn update_config(
deps: DepsMut,
info: MessageInfo,
owner: Option<String>,
pool_manager_addr: Option<String>,
unbonding_period: Option<Uint64>,
growth_rate: Option<Decimal>,
) -> Result<Response, ContractError> {
Expand All @@ -197,6 +196,10 @@ pub(crate) fn update_config(
return Err(ContractError::Unauthorized {});
}

if let Some(pool_manager_addr) = pool_manager_addr {
config.pool_manager_addr = deps.api.addr_validate(&pool_manager_addr)?;
}

if let Some(owner) = owner {
config.owner = deps.api.addr_validate(&owner)?;
}
Expand All @@ -215,6 +218,7 @@ pub(crate) fn update_config(
Ok(Response::default().add_attributes(vec![
("action", "update_config".to_string()),
("owner", config.owner.to_string()),
("pool_manager_addr", config.pool_manager_addr.to_string()),
("unbonding_period", config.unbonding_period.to_string()),
("growth_rate", config.growth_rate.to_string()),
]))
Expand Down Expand Up @@ -320,93 +324,45 @@ pub(crate) fn fill_rewards(
info: MessageInfo,
) -> Result<Response, ContractError> {
{
// Use aggregate_coins to get the total amount of new coins
// Finding the most recent EpochID
let most_recent_epoch_id = EPOCHS
.keys(deps.storage, None, None, Order::Descending)
.next()
.unwrap()?;

let config = CONFIG.load(deps.storage)?;
let distribution_denom = config.distribution_denom.clone();

let mut messages: Vec<CosmosMsg> = vec![];
// Verify coins are coming
// swap non-whale to whale
// Search info funds for LP tokens, LP tokens will contain LP_SYMBOL from lp_common and the string .pair.
let _lp_tokens = info
let mut whale = info
.funds
.iter()
.filter(|coin| coin.denom.contains(".pair.") | coin.denom.contains(LP_SYMBOL));
// LP tokens have the format "{pair_label}.pair.{identifier}.{LP_SYMBOL}", get the identifier and not the LP SYMBOL
// let _pair_identifier = lp_tokens
// .map(|coin| coin.denom.split(".pair.").collect::<Vec<&str>>()[1])
// .next()
// .unwrap();

// // if LP Tokens ,verify and withdraw then swap to whale
// let lp_withdrawal_msg = white_whale_std::pool_manager::ExecuteMsg::WithdrawLiquidity { pair_identifier: pair_identifier.to_string() };
// messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
// contract_addr: ,
// msg: to_json_binary(&lp_withdrawal_msg)?,
// funds: vec![],
// }));

let pool_identifier = "whale-uusdc".to_string();
let pool_query = white_whale_std::pool_manager::QueryMsg::Pair {
pair_identifier: pool_identifier.clone(),
};
let resp: PairInfoResponse = deps
.querier
.query_wasm_smart("contract2".to_string(), &pool_query)?;
let mut skip_swap = false;
// Check pair 'assets' and if either one has 0 amount then don't do swaps
resp.pair_info.assets.iter().for_each(|asset| {
if asset.amount.is_zero() {
skip_swap = true;
}
});
// Suggested method for swaps
// Loop over the assets in info.funds
// If whale is in the fund object then skip that
// Everything else either gets swapped or if its an LP token withdrawn and then swapped
// For each swapped coin we need to simulate swap operations and get the route from SwapRoutes
// For each swapped coin if there is no funds found in the pool found via SwapRoutes, skip it. e.g newly made pools
// Might need to add a reply to the contract as if doing it only in this method we can only save the simulation amount in the state
// Alternatively we could add a reply and try to get the actual amount swapped from there.

if !skip_swap {
let swap_operations = vec![white_whale_std::pool_manager::SwapOperation::WhaleSwap {
token_in_denom: info.funds[0].denom.to_string(),
token_out_denom: "uwhale".to_string(),
pool_identifier,
}];
let msg = white_whale_std::pool_manager::ExecuteMsg::ExecuteSwapOperations {
operations: swap_operations,
minimum_receive: None,
to: None,
max_spread: None,
};
let binary_msg = to_json_binary(&msg)?;
let wrapped_msg = WasmMsg::Execute {
contract_addr: "contract2".to_string(),
msg: binary_msg,
funds: info.funds.to_vec(),
};
messages.push(wrapped_msg.into());
}
// Note: Might need to convert back to ints and use that for ranking to get the most recent ID
// Note: After swap,
// TODO: Remove hardcode below after more testing
.find(|coin| coin.denom.eq(distribution_denom.as_str()))
.unwrap()
.to_owned();
// Each of these helpers will add messages to the messages vector
// and may increment the whale Coin above with the result of the swaps
helpers::handle_lp_tokens(&info, &config, &mut messages)?;
helpers::swap_coins_to_main_token(
info,
&deps,
config,
&mut whale,
&distribution_denom,
&mut messages,
)?;
// Add the whale to the funds, the whale figure now should be the result
// of all the LP token withdrawals and swaps
// Because we are using minimum receive, it is possible the contract can accumulate micro amounts of whale if we get more than what the swap query returned
// If this became an issue would could look at replys instead of the query
EPOCHS.update(
deps.storage,
&most_recent_epoch_id,
|bucket| -> StdResult<_> {
let mut bucket = bucket.unwrap_or_default();
bucket.available = asset::aggregate_coins(
bucket.available,
vec![Coin {
denom: "uwhale".to_string(),
amount: Uint128::new(1000u128),
}],
)?;
bucket.available = asset::aggregate_coins(bucket.available, vec![whale.clone()])?;
Ok(bucket)
},
)?;
Expand Down
14 changes: 12 additions & 2 deletions contracts/liquidity_hub/bonding-manager/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use cosmwasm_std::{ensure, entry_point, Coin};
use cosmwasm_std::{ensure, entry_point, Addr, Coin};
use cosmwasm_std::{to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
use cw2::{get_contract_version, set_contract_version};
use cw_utils::PaymentError;
Expand Down Expand Up @@ -38,6 +38,8 @@ pub fn instantiate(

let config = Config {
owner: deps.api.addr_validate(info.sender.as_str())?,
pool_manager_addr: Addr::unchecked(""),
distribution_denom: msg.distribution_denom,
unbonding_period: msg.unbonding_period,
growth_rate: msg.growth_rate,
bonding_assets: msg.bonding_assets.clone(),
Expand Down Expand Up @@ -116,9 +118,17 @@ pub fn execute(
}
ExecuteMsg::UpdateConfig {
owner,
pool_manager_addr,
unbonding_period,
growth_rate,
} => commands::update_config(deps, info, owner, unbonding_period, growth_rate),
} => commands::update_config(
deps,
info,
owner,
pool_manager_addr,
unbonding_period,
growth_rate,
),
ExecuteMsg::FillRewards { .. } => commands::fill_rewards(deps, env, info),
ExecuteMsg::FillRewardsCoin => commands::fill_rewards(deps, env, info),
ExecuteMsg::Claim { .. } => commands::claim(deps, env, info),
Expand Down
118 changes: 117 additions & 1 deletion contracts/liquidity_hub/bonding-manager/src/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
use cosmwasm_std::{Coin, Decimal, DepsMut, Env, MessageInfo, StdResult, Timestamp, Uint64};
use cosmwasm_std::{
to_json_binary, Coin, CosmosMsg, Decimal, DepsMut, Env, MessageInfo, StdResult, Timestamp,
Uint64, WasmMsg,
};
use white_whale_std::bonding_manager::{ClaimableEpochsResponse, EpochResponse};
use white_whale_std::constants::LP_SYMBOL;
use white_whale_std::epoch_manager::epoch_manager::EpochConfig;
use white_whale_std::pool_manager::{
PairInfoResponse, SimulateSwapOperationsResponse, SwapRouteResponse,
};

use crate::error::ContractError;
use crate::queries::{get_claimable_epochs, get_current_epoch};
Expand Down Expand Up @@ -90,6 +97,115 @@ pub fn calculate_epoch(
Ok(epoch)
}

// Used in FillRewards to search the funds for LP tokens and withdraw them
// If we do get some LP tokens to withdraw they could be swapped to whale in the reply
pub fn handle_lp_tokens(
info: &MessageInfo,
config: &white_whale_std::bonding_manager::Config,
messages: &mut Vec<CosmosMsg>,
) -> Result<(), ContractError> {
let lp_tokens: Vec<&Coin> = info
.funds
.iter()
.filter(|coin| coin.denom.contains(".pair.") | coin.denom.contains(LP_SYMBOL))
.collect();
for lp_token in lp_tokens {
// LP tokens have the format "{pair_label}.pair.{identifier}.{LP_SYMBOL}", get the identifier and not the LP SYMBOL
let pair_identifier = lp_token.denom.split(".pair.").collect::<Vec<&str>>()[1];

// if LP Tokens ,verify and withdraw then swap to whale
let lp_withdrawal_msg = white_whale_std::pool_manager::ExecuteMsg::WithdrawLiquidity {
pair_identifier: pair_identifier.to_string(),
};
messages.push(CosmosMsg::Wasm(WasmMsg::Execute {
contract_addr: config.pool_manager_addr.to_string(),
msg: to_json_binary(&lp_withdrawal_msg)?,
funds: vec![lp_token.clone()],
}));
}
Ok(())
}

// Used in FillRewards to search the funds for coins that are neither LP tokens nor whale and swap them to whale
pub fn swap_coins_to_main_token(
info: MessageInfo,
deps: &DepsMut,
config: white_whale_std::bonding_manager::Config,
whale: &mut Coin,
distribution_denom: &String,
messages: &mut Vec<CosmosMsg>,
) -> Result<(), ContractError> {
let coins_to_swap: Vec<&Coin> = info
.funds
.iter()
.filter(|coin| {
!coin.denom.contains(".pair.")
& !coin.denom.contains(LP_SYMBOL)
& !coin.denom.eq(distribution_denom)
})
.collect();
for coin in coins_to_swap {
let swap_route_query = white_whale_std::pool_manager::QueryMsg::SwapRoute {
offer_asset_denom: coin.denom.to_string(),
ask_asset_denom: distribution_denom.to_string(),
};

// Query for the routes and pool
let swap_routes: SwapRouteResponse = deps
.querier
.query_wasm_smart(config.pool_manager_addr.to_string(), &swap_route_query)?;

// check if the pool has any assets, if not skip the swap
// Note we are only checking the first operation here. Might be better to another loop to check all operations
let pool_query = white_whale_std::pool_manager::QueryMsg::Pair {
pair_identifier: swap_routes
.swap_route
.swap_operations
.first()
.unwrap()
.get_pool_identifer(),
};
let resp: PairInfoResponse = deps
.querier
.query_wasm_smart(config.pool_manager_addr.to_string(), &pool_query)?;
let mut skip_swap = false;
// Check pair 'assets' and if either one has 0 amount then don't do swaps
resp.pair_info.assets.iter().for_each(|asset| {
if asset.amount.is_zero() {
skip_swap = true;
}
});

let simulate: SimulateSwapOperationsResponse = deps.querier.query_wasm_smart(
config.pool_manager_addr.to_string(),
&white_whale_std::pool_manager::QueryMsg::SimulateSwapOperations {
offer_amount: coin.amount,
operations: swap_routes.swap_route.swap_operations.clone(),
},
)?;
// Add the simulate amount received to the whale amount, if the swap fails this should also be rolled back
whale.amount = whale.amount.checked_add(simulate.amount)?;

if !skip_swap {
// 1% max spread for the swap
let msg = white_whale_std::pool_manager::ExecuteMsg::ExecuteSwapOperations {
operations: swap_routes.swap_route.swap_operations.clone(),
minimum_receive: Some(simulate.amount),
to: None,
max_spread: Some(Decimal::percent(1)),
};
let binary_msg = to_json_binary(&msg)?;
let wrapped_msg = WasmMsg::Execute {
contract_addr: config.pool_manager_addr.to_string(),
msg: binary_msg,
funds: vec![coin.clone()],
};
messages.push(wrapped_msg.into());
}
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
11 changes: 9 additions & 2 deletions contracts/liquidity_hub/bonding-manager/src/tests/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ fn test_bond_successfully() {
pool_fees.clone(),
white_whale_std::pool_network::asset::PairType::ConstantProduct,
Some("whale-uusdc".to_string()),
vec![coin(1000, "uusdc")],
vec![coin(1000, "uwhale")],
|result| {
result.unwrap();
},
Expand Down Expand Up @@ -240,6 +240,13 @@ fn test_bond_successfully() {
});

robot.claim(sender, |res| {
println!("{:?}", res);
let result = res.unwrap();
println!("{:?}", result);
assert!(result.events.iter().any(|event| {
event
.attributes
.iter()
.any(|attr| attr.key == "amount" && attr.value == "390uwhale")
}));
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ fn test_instantiate_successfully() {
)
.assert_config(Config {
owner: Addr::unchecked("owner"),
pool_manager_addr: Addr::unchecked("contract2"),
distribution_denom: "uwhale".to_string(),
unbonding_period: Uint64::new(1_000u64),
growth_rate: Decimal::one(),
grace_period: Uint64::new(21u64),
Expand Down
Loading

0 comments on commit 30712fb

Please sign in to comment.