diff --git a/.gitignore b/.gitignore index c8dbb2c9b..fa8d2e844 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,8 @@ target/ # Code coverage stuff *.profraw +.history + # IDEs .vscode/ .idea/ diff --git a/Cargo.toml b/Cargo.toml index 90bfe5ba9..7b1bf3d4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,6 +3,7 @@ resolver = "2" members = [ # Packages "packages/shade_protocol", + "packages/shadeswap_shared", "packages/secretcli", "packages/multi_test", "packages/multi_derive", @@ -25,7 +26,9 @@ members = [ "contracts/liquidity_book/lb_factory", "contracts/liquidity_book/lb_pair", "contracts/liquidity_book/lb_token", - #"contracts/liquidity_book/router", + "contracts/liquidity_book/router", + "contracts/liquidity_book/tests", + # Staking #"contracts/basic_staking", diff --git a/contracts/liquidity_book/Liquidity Book.drawio.pdf b/contracts/liquidity_book/Liquidity_Book_structure.pdf similarity index 100% rename from contracts/liquidity_book/Liquidity Book.drawio.pdf rename to contracts/liquidity_book/Liquidity_Book_structure.pdf diff --git a/contracts/liquidity_book/lb_router/Cargo.toml b/contracts/liquidity_book/archive_lb_router/Cargo.toml similarity index 100% rename from contracts/liquidity_book/lb_router/Cargo.toml rename to contracts/liquidity_book/archive_lb_router/Cargo.toml diff --git a/contracts/liquidity_book/lb_router/src/bin/schema.rs b/contracts/liquidity_book/archive_lb_router/src/bin/schema.rs similarity index 100% rename from contracts/liquidity_book/lb_router/src/bin/schema.rs rename to contracts/liquidity_book/archive_lb_router/src/bin/schema.rs diff --git a/contracts/liquidity_book/lb_router/src/contract.rs b/contracts/liquidity_book/archive_lb_router/src/contract.rs similarity index 100% rename from contracts/liquidity_book/lb_router/src/contract.rs rename to contracts/liquidity_book/archive_lb_router/src/contract.rs diff --git a/contracts/liquidity_book/lb_router/src/error.rs b/contracts/liquidity_book/archive_lb_router/src/error.rs similarity index 100% rename from contracts/liquidity_book/lb_router/src/error.rs rename to contracts/liquidity_book/archive_lb_router/src/error.rs diff --git a/contracts/liquidity_book/lb_router/src/lib.rs b/contracts/liquidity_book/archive_lb_router/src/lib.rs similarity index 100% rename from contracts/liquidity_book/lb_router/src/lib.rs rename to contracts/liquidity_book/archive_lb_router/src/lib.rs diff --git a/contracts/liquidity_book/lb_router/src/msg.rs b/contracts/liquidity_book/archive_lb_router/src/msg.rs similarity index 100% rename from contracts/liquidity_book/lb_router/src/msg.rs rename to contracts/liquidity_book/archive_lb_router/src/msg.rs diff --git a/contracts/liquidity_book/lb_router/src/operations.rs b/contracts/liquidity_book/archive_lb_router/src/operations.rs similarity index 100% rename from contracts/liquidity_book/lb_router/src/operations.rs rename to contracts/liquidity_book/archive_lb_router/src/operations.rs diff --git a/contracts/liquidity_book/lb_router/src/prelude.rs b/contracts/liquidity_book/archive_lb_router/src/prelude.rs similarity index 100% rename from contracts/liquidity_book/lb_router/src/prelude.rs rename to contracts/liquidity_book/archive_lb_router/src/prelude.rs diff --git a/contracts/liquidity_book/lb_router/src/query.rs b/contracts/liquidity_book/archive_lb_router/src/query.rs similarity index 100% rename from contracts/liquidity_book/lb_router/src/query.rs rename to contracts/liquidity_book/archive_lb_router/src/query.rs diff --git a/contracts/liquidity_book/lb_router/src/state.rs b/contracts/liquidity_book/archive_lb_router/src/state.rs similarity index 100% rename from contracts/liquidity_book/lb_router/src/state.rs rename to contracts/liquidity_book/archive_lb_router/src/state.rs diff --git a/contracts/liquidity_book/lb_factory/src/bin/schema.rs b/contracts/liquidity_book/lb_factory/src/bin/schema.rs index b999be2be..4c00bfb50 100644 --- a/contracts/liquidity_book/lb_factory/src/bin/schema.rs +++ b/contracts/liquidity_book/lb_factory/src/bin/schema.rs @@ -1,6 +1,6 @@ use cosmwasm_schema::write_api; -use lb_factory::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use shade_protocol::liquidity_book::lb_factory::{ExecuteMsg, InstantiateMsg, QueryMsg}; fn main() { write_api! { diff --git a/contracts/liquidity_book/lb_factory/src/contract.rs b/contracts/liquidity_book/lb_factory/src/contract.rs index e557ffe2c..64ebf195b 100644 --- a/contracts/liquidity_book/lb_factory/src/contract.rs +++ b/contracts/liquidity_book/lb_factory/src/contract.rs @@ -1,13 +1,35 @@ #![allow(unused)] // For beginning only. use cosmwasm_std::{ - entry_point, to_binary, Addr, Binary, ContractInfo, CosmosMsg, Deps, DepsMut, Env, MessageInfo, - Reply, Response, StdError, StdResult, Storage, SubMsg, SubMsgResult, Timestamp, Uint256, + entry_point, + to_binary, + Addr, + Binary, + ContractInfo, + CosmosMsg, + Deps, + DepsMut, + Env, + MessageInfo, + Reply, + Response, + StdError, + StdResult, + Storage, + SubMsg, + SubMsgResult, + Timestamp, + Uint256, WasmMsg, }; -use shade_protocol::lb_libraries::{math, pair_parameter_helper, price_helper, tokens, types}; - use ethnum::U256; +use shade_protocol::{ + lb_libraries::{math, pair_parameter_helper, price_helper, tokens, types, viewing_keys}, + liquidity_book::{ + lb_factory::*, + lb_pair::ExecuteMsg::{ForceDecay as LbPairForceDecay, SetStaticFeeParameters}, + }, +}; use math::encoded_sample::EncodedSample; use pair_parameter_helper::PairParameters; @@ -15,14 +37,15 @@ use price_helper::PriceHelper; use tokens::TokenType; use types::{Bytes32, ContractInstantiationInfo, StaticFeeParameters}; -use crate::msg::*; -use crate::prelude::*; -use crate::state::*; -use crate::types::{LBPair, LBPairInformation, NextPairKey}; +use crate::{ + prelude::*, + state::*, + types::{LBPair, LBPairInformation, NextPairKey}, +}; pub static _OFFSET_IS_PRESET_OPEN: u8 = 255; pub static _MIN_BIN_STEP: u8 = 1; // 0.001% -pub static _MAX_FLASHLOAN_FEE: u8 = 10; // 10% +pub static _MAX_FLASHLOAN_FEE: u8 = 10 ^ 17; // 10% pub const INSTANTIATE_REPLY_ID: u64 = 1u64; @@ -54,8 +77,7 @@ pub fn instantiate( lb_pair_implementation: ContractInstantiationInfo::default(), lb_token_implementation: ContractInstantiationInfo::default(), }; - deps.api - .debug(format!("Contract was initialized by {}", info.sender).as_str()); + CONFIG.save(deps.storage, &state)?; // TODO: decide on response output and format @@ -78,7 +100,17 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> R token_y, active_id, bin_step, - } => try_create_lb_pair(deps, env, info, token_x, token_y, active_id, bin_step), + viewing_key, + } => try_create_lb_pair( + deps, + env, + info, + token_x, + token_y, + active_id, + bin_step, + viewing_key, + ), // ExecuteMsg::SetLBPairIgnored { // token_x, // token_y, @@ -113,32 +145,32 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> R try_set_preset_open_state(deps, env, info, bin_step, is_open) } ExecuteMsg::RemovePreset { bin_step } => try_remove_preset(deps, env, info, bin_step), - // ExecuteMsg::SetFeeParametersOnPair { - // token_x, - // token_y, - // bin_step, - // base_factor, - // filter_period, - // decay_period, - // reduction_factor, - // variable_fee_control, - // protocol_share, - // max_volatility_accumulator, - // } => try_set_fee_parameters_on_pair( - // deps, - // env, - // info, - // token_x, - // token_y, - // bin_step, - // base_factor, - // filter_period, - // decay_period, - // reduction_factor, - // variable_fee_control, - // protocol_share, - // max_volatility_accumulator, - // ), + ExecuteMsg::SetFeeParametersOnPair { + token_x, + token_y, + bin_step, + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + } => try_set_fee_parameters_on_pair( + deps, + env, + info, + token_x, + token_y, + bin_step, + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + ), ExecuteMsg::SetFeeRecipient { fee_recipient } => { try_set_fee_recipient(deps, env, info, fee_recipient) } @@ -239,6 +271,7 @@ fn try_create_lb_pair( token_y: TokenType, active_id: u32, bin_step: u16, + viewing_key: String, ) -> Result { let state = CONFIG.load(deps.storage)?; @@ -251,7 +284,7 @@ fn try_create_lb_pair( .map_err(|_| Error::BinStepHasNoPreset { bin_step })?; let is_owner = info.sender == state.owner; - if !_is_preset_open(preset.0 .0) && !is_owner { + if !_is_preset_open(preset.0.0) && !is_owner { return Err(Error::PresetIsLockedForUsers { user: info.sender, bin_step, @@ -279,7 +312,6 @@ fn try_create_lb_pair( // safety check, making sure that the price can be calculated PriceHelper::get_price_from_id(active_id, bin_step); - // We sort token for storage efficiency, only one input needs to be stored because they are sorted let (token_a, token_b) = _sort_tokens(token_x.clone(), token_y.clone()); // TODO: error if address doesn't exist on chain @@ -337,10 +369,11 @@ fn try_create_lb_pair( active_id, lb_token_implementation: state.lb_token_implementation, //TODO add viewing key - viewing_key: String::new(), + viewing_key, //TODO add pair_name pair_name: String::new(), entropy: String::new(), + protocol_fee_recipient: state.fee_recipient, })?, code_hash: state.lb_pair_implementation.code_hash.clone(), funds: vec![], @@ -353,6 +386,7 @@ fn try_create_lb_pair( token_b: token_b.clone(), bin_step, code_hash: state.lb_pair_implementation.code_hash, + is_open: is_owner, })?; Ok(Response::new().add_submessages(messages)) @@ -480,18 +514,6 @@ fn try_set_pair_preset( PRESETS.save(deps.storage, bin_step, &preset)?; - // TODO: add all this to the response - // emit PresetSet( - // binStep, - // baseFactor, - // filterPeriod, - // decayPeriod, - // reductionFactor, - // variableFeeControl, - // protocolShare, - // maxVolatilityAccumulator - // ); - Ok(Response::default().add_attribute_plaintext("set preset", bin_step.to_string())) } @@ -556,70 +578,75 @@ fn try_remove_preset( Ok(Response::default().add_attribute_plaintext("preset removed", bin_step.to_string())) } -// /// Function to set the fee parameters of a LBPair -// /// -// /// # Arguments -// /// -// /// * `token_x` - The address of the first token -// /// * `token_y` - The address of the second token -// /// * `bin_step` - The bin step in basis point, used to calculate the price -// /// * `base_factor` - The base factor, used to calculate the base fee, baseFee = baseFactor * binStep -// /// * `filter_period` - The period where the accumulator value is untouched, prevent spam -// /// * `decay_period` - The period where the accumulator value is decayed, by the reduction factor -// /// * `reduction_factor` - The reduction factor, used to calculate the reduction of the accumulator -// /// * `variable_fee_control` - The variable fee control, used to control the variable fee, can be 0 to disable it -// /// * `protocol_share` - The share of the fees received by the protocol -// /// * `max_volatility_accumulator` - The max value of volatility accumulator -// fn try_set_fee_parameters_on_pair( -// deps: DepsMut, -// env: Env, -// info: MessageInfo, -// token_x: TokenType, -// token_y: TokenType, -// bin_step: u16, -// base_factor: u16, -// filter_period: u16, -// decay_period: u16, -// reduction_factor: u16, -// variable_fee_control: u32, -// protocol_share: u16, -// max_volatility_accumulator: u32, -// ) -> Result { -// let state = CONFIG.load(deps.storage)?; -// only_owner(&info.sender, &state.owner)?; +/// Function to set the fee parameters of a LBPair +/// +/// # Arguments +/// +/// * `token_x` - The address of the first token +/// * `token_y` - The address of the second token +/// * `bin_step` - The bin step in basis point, used to calculate the price +/// * `base_factor` - The base factor, used to calculate the base fee, baseFee = baseFactor * binStep +/// * `filter_period` - The period where the accumulator value is untouched, prevent spam +/// * `decay_period` - The period where the accumulator value is decayed, by the reduction factor +/// * `reduction_factor` - The reduction factor, used to calculate the reduction of the accumulator +/// * `variable_fee_control` - The variable fee control, used to control the variable fee, can be 0 to disable it +/// * `protocol_share` - The share of the fees received by the protocol +/// * `max_volatility_accumulator` - The max value of volatility accumulator +fn try_set_fee_parameters_on_pair( + deps: DepsMut, + env: Env, + info: MessageInfo, + token_x: TokenType, + token_y: TokenType, + bin_step: u16, + base_factor: u16, + filter_period: u16, + decay_period: u16, + reduction_factor: u16, + variable_fee_control: u32, + protocol_share: u16, + max_volatility_accumulator: u32, +) -> Result { + let state = CONFIG.load(deps.storage)?; + only_owner(&info.sender, &state.owner)?; -// let (token_a, token_b) = _sort_tokens(token_x, token_y); -// let mut lb_pair = LB_PAIRS_INFO -// .load( -// deps.storage, -// ( -// token_a.unique_key().clone(), -// token_b.unique_key().clone(), -// bin_step, -// ), -// ) -// .map_err(|_| Error::LBPairNotCreated { -// token_x: token_a.unique_key(), -// token_y: token_b.unique_key(), -// bin_step, -// })? -// .lb_pair; - -// // TODO: this needs to actually send a message to the LBPair contract to change those parameters -// // lb_pair.setStaticFeeParameters( -// // base_factor, -// // filter_period, -// // decay_period, -// // reduction_factor, -// // variable_fee_control, -// // protocol_share, -// // max_volatility_accumulator -// // ); - -// todo!(); - -// Ok(Response::default().add_attribute_plaintext("status", "ok")) -// } + let (token_a, token_b) = _sort_tokens(token_x, token_y); + let mut lb_pair = LB_PAIRS_INFO + .load( + deps.storage, + ( + token_a.unique_key().clone(), + token_b.unique_key().clone(), + bin_step, + ), + ) + .map_err(|_| Error::LBPairNotCreated { + token_x: token_a.unique_key(), + token_y: token_b.unique_key(), + bin_step, + })? + .lb_pair; + + let mut response = Response::new(); + + let msg: CosmosMsg = SetStaticFeeParameters { + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + } + .to_cosmos_msg( + lb_pair.contract.code_hash, + lb_pair.contract.address.to_string(), + None, + )?; + + response = response.add_message(msg); + Ok(response) +} /// Function to set the recipient of the fees. This address needs to be able to receive SNIP20s. /// @@ -770,8 +797,33 @@ fn try_force_decay(deps: DepsMut, env: Env, info: MessageInfo, pair: LBPair) -> // TODO: I think this needs to send a message to the LBPair contract to execute the force decay. // pair.forceDecay(); - - todo!() + let (token_a, token_b) = _sort_tokens(pair.token_x, pair.token_y); + let mut lb_pair = LB_PAIRS_INFO + .load( + deps.storage, + ( + token_a.unique_key().clone(), + token_b.unique_key().clone(), + pair.bin_step, + ), + ) + .map_err(|_| Error::LBPairNotCreated { + token_x: token_a.unique_key(), + token_y: token_b.unique_key(), + bin_step: pair.bin_step, + })? + .lb_pair; + + let mut response = Response::new(); + + response = response.add_message(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: lb_pair.contract.address.to_string(), + code_hash: lb_pair.contract.code_hash, + msg: to_binary(&LbPairForceDecay {})?, + funds: vec![], + })); + + Ok(response) } fn only_owner(sender: &Addr, owner: &Addr) -> Result<()> { @@ -796,10 +848,10 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { QueryMsg::GetQuoteAssetAtIndex { index } => query_quote_asset_at_index(deps, index), QueryMsg::IsQuoteAsset { token } => query_is_quote_asset(deps, token), QueryMsg::GetLBPairInformation { - token_a, - token_b, + token_x, + token_y, bin_step, - } => query_lb_pair_information(deps, token_a, token_b, bin_step), + } => query_lb_pair_information(deps, token_x, token_y, bin_step), QueryMsg::GetPreset { bin_step } => query_preset(deps, bin_step), QueryMsg::GetAllBinSteps {} => query_all_bin_steps(deps), QueryMsg::GetOpenBinSteps {} => query_open_bin_steps(deps), @@ -937,7 +989,7 @@ fn query_number_of_quote_assets(deps: Deps) -> Result { /// * `asset` - The address of the quote asset at index `index`. // TODO: Unsure if this function is necessary. Not sure how to index the Keyset. fn query_quote_asset_at_index(deps: Deps, index: u32) -> Result { - let asset = todo!(); + let asset = QUOTE_ASSET_WHITELIST.get_at(deps.storage, index)?; let response = QuoteAssetAtIndexResponse { asset }; to_binary(&response).map_err(Error::CwErr) @@ -1138,7 +1190,7 @@ fn query_open_bin_steps(deps: Deps) -> Result { for result in iterator { let (bin_step, preset) = result.map_err(Error::CwErr)?; - if _is_preset_open(preset.0 .0) { + if _is_preset_open(preset.0.0) { open_bin_steps.push(bin_step) } } @@ -1233,7 +1285,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> StdResult { bin_step: lb_pair_key.bin_step, lb_pair: lb_pair.clone(), // TODO: get 'is_owner' from the create_lb_pair function - created_by_owner: true, + created_by_owner: lb_pair_key.is_open, ignored_for_routing: false, }, )?; diff --git a/contracts/liquidity_book/lb_factory/src/lib.rs b/contracts/liquidity_book/lb_factory/src/lib.rs index 4fe1880d3..e5a3a80d2 100644 --- a/contracts/liquidity_book/lb_factory/src/lib.rs +++ b/contracts/liquidity_book/lb_factory/src/lib.rs @@ -3,5 +3,4 @@ mod prelude; mod types; pub mod contract; -pub mod msg; pub mod state; diff --git a/contracts/liquidity_book/lb_factory/src/msg.rs b/contracts/liquidity_book/lb_factory/src/msg.rs deleted file mode 100644 index d47d1af87..000000000 --- a/contracts/liquidity_book/lb_factory/src/msg.rs +++ /dev/null @@ -1,230 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; -pub use lb_pair::InstantiateMsg as LBPairInstantiateMsg; -use shade_protocol::lb_libraries::{tokens::TokenType, types::ContractInstantiationInfo}; -use shade_protocol::liquidity_book::lb_pair; - -use crate::types::{LBPair, LBPairInformation}; - -#[cw_serde] -pub struct InstantiateMsg { - pub owner: Option, - pub fee_recipient: Addr, - pub flash_loan_fee: u8, -} - -#[cw_serde] -pub enum ExecuteMsg { - #[serde(rename = "set_lb_pair_implementation")] - SetLBPairImplementation { - lb_pair_implementation: ContractInstantiationInfo, - }, - #[serde(rename = "set_lb_token_implementation")] - SetLBTokenImplementation { - lb_token_implementation: ContractInstantiationInfo, - }, - #[serde(rename = "create_lb_pair")] - CreateLBPair { - token_x: TokenType, - token_y: TokenType, - // u24 - active_id: u32, - bin_step: u16, - }, - // #[serde(rename = "set_lb_pair_ignored")] - // SetLBPairIgnored { - // token_x: TokenType, - // token_y: TokenType, - // bin_step: u16, - // ignored: bool, - // }, - SetPairPreset { - bin_step: u16, - base_factor: u16, - filter_period: u16, - decay_period: u16, - reduction_factor: u16, - // u24 - variable_fee_control: u32, - protocol_share: u16, - // u24 - max_volatility_accumulator: u32, - is_open: bool, - }, - SetPresetOpenState { - bin_step: u16, - is_open: bool, - }, - RemovePreset { - bin_step: u16, - }, - // SetFeeParametersOnPair { - // token_x: TokenType, - // token_y: TokenType, - // bin_step: u16, - // base_factor: u16, - // filter_period: u16, - // decay_period: u16, - // reduction_factor: u16, - // // u24 - // variable_fee_control: u32, - // protocol_share: u16, - // // u24 - // max_volatility_accumulator: u32, - // }, - SetFeeRecipient { - fee_recipient: Addr, - }, - SetFlashLoanFee { - flash_loan_fee: u8, - }, - AddQuoteAsset { - asset: TokenType, - }, - RemoveQuoteAsset { - asset: TokenType, - }, - ForceDecay { - pair: LBPair, - }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(MinBinStepResponse)] - GetMinBinStep {}, - #[returns(FeeRecipientResponse)] - GetFeeRecipient {}, - #[returns(MaxFlashLoanFeeResponse)] - GetMaxFlashLoanFee {}, - #[returns(FlashLoanFeeResponse)] - GetFlashLoanFee {}, - #[returns(LBPairImplementationResponse)] - #[serde(rename = "get_lb_pair_implementation")] - GetLBPairImplementation {}, - #[returns(LBTokenImplementationResponse)] - #[serde(rename = "get_lb_token_implementation")] - GetLBTokenImplementation {}, - #[returns(NumberOfLBPairsResponse)] - #[serde(rename = "get_number_of_lb_pairs")] - GetNumberOfLBPairs {}, - #[returns(LBPairAtIndexResponse)] - #[serde(rename = "get_lb_pair_at_index")] - GetLBPairAtIndex { index: u32 }, - #[returns(NumberOfQuoteAssetsResponse)] - GetNumberOfQuoteAssets {}, - #[returns(QuoteAssetAtIndexResponse)] - GetQuoteAssetAtIndex { index: u32 }, - #[returns(IsQuoteAssetResponse)] - IsQuoteAsset { token: TokenType }, - #[returns(LBPairInformationResponse)] - #[serde(rename = "get_lb_pair_information")] - GetLBPairInformation { - token_a: TokenType, - token_b: TokenType, - bin_step: u16, - }, - #[returns(PresetResponse)] - GetPreset { bin_step: u16 }, - #[returns(AllBinStepsResponse)] - GetAllBinSteps {}, - #[returns(OpenBinStepsResponse)] - GetOpenBinSteps {}, - #[returns(AllLBPairsResponse)] - #[serde(rename = "get_all_lb_pairs")] - GetAllLBPairs { - token_x: TokenType, - token_y: TokenType, - }, -} - -// We define a custom struct for each query response -#[cw_serde] -pub struct MinBinStepResponse { - pub min_bin_step: u8, -} - -#[cw_serde] -pub struct FeeRecipientResponse { - pub fee_recipient: Addr, -} - -#[cw_serde] -pub struct MaxFlashLoanFeeResponse { - pub max_fee: u8, -} - -#[cw_serde] -pub struct FlashLoanFeeResponse { - pub flash_loan_fee: u8, -} - -#[cw_serde] -pub struct LBPairImplementationResponse { - pub lb_pair_implementation: ContractInstantiationInfo, -} - -#[cw_serde] -pub struct LBTokenImplementationResponse { - pub lb_token_implementation: ContractInstantiationInfo, -} - -#[cw_serde] -pub struct NumberOfLBPairsResponse { - pub lb_pair_number: u32, -} - -#[cw_serde] -pub struct LBPairAtIndexResponse { - pub lb_pair: LBPair, -} - -#[cw_serde] -pub struct NumberOfQuoteAssetsResponse { - pub number_of_quote_assets: u32, -} - -#[cw_serde] -pub struct QuoteAssetAtIndexResponse { - pub asset: Addr, -} - -#[cw_serde] -pub struct IsQuoteAssetResponse { - pub is_quote: bool, -} - -#[cw_serde] -pub struct LBPairInformationResponse { - pub lb_pair_information: LBPairInformation, -} - -#[cw_serde] -pub struct PresetResponse { - pub base_factor: u16, - pub filter_period: u16, - pub decay_period: u16, - pub reduction_factor: u16, - // u24 - pub variable_fee_control: u32, - pub protocol_share: u16, - // u24 - pub max_volatility_accumulator: u32, - pub is_open: bool, -} - -#[cw_serde] -pub struct AllBinStepsResponse { - pub bin_step_with_preset: Vec, -} - -#[cw_serde] -pub struct OpenBinStepsResponse { - pub open_bin_steps: Vec, -} - -#[cw_serde] -pub struct AllLBPairsResponse { - pub lb_pairs_available: Vec, -} diff --git a/contracts/liquidity_book/lb_factory/src/types.rs b/contracts/liquidity_book/lb_factory/src/types.rs index c1b3cab28..14cbe6cc0 100644 --- a/contracts/liquidity_book/lb_factory/src/types.rs +++ b/contracts/liquidity_book/lb_factory/src/types.rs @@ -12,4 +12,5 @@ pub struct NextPairKey { pub token_b: TokenType, pub bin_step: u16, pub code_hash: String, + pub is_open: bool, } diff --git a/contracts/liquidity_book/lb_pair/Cargo.toml b/contracts/liquidity_book/lb_pair/Cargo.toml index 74f392d81..93fe66a11 100644 --- a/contracts/liquidity_book/lb_pair/Cargo.toml +++ b/contracts/liquidity_book/lb_pair/Cargo.toml @@ -30,6 +30,7 @@ serde-json-wasm = { version = "0.5"} thiserror = { version = "1" } secret-storage-plus = { git = "https://github.com/securesecrets/secret-plus-utils", version = "0.13.4", optional = true } ethnum = { version = "1" } +shadeswap-shared = { version = "0.1.0", path = "../../../packages/shadeswap_shared", features = [] } [dev-dependencies] anyhow = "1" diff --git a/contracts/liquidity_book/lb_pair/src/contract.rs b/contracts/liquidity_book/lb_pair/src/contract.rs index b29c3636d..dce43d11f 100644 --- a/contracts/liquidity_book/lb_pair/src/contract.rs +++ b/contracts/liquidity_book/lb_pair/src/contract.rs @@ -3,19 +3,42 @@ use crate::{prelude::*, state::*}; use core::panic; use cosmwasm_std::{ - to_binary, Addr, Binary, ContractInfo, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Response, - StdError, StdResult, SubMsgResult, Timestamp, Uint128, Uint256, WasmMsg, + from_binary, + to_binary, + Addr, + Attribute, + BankMsg, + Binary, + Coin, + ContractInfo, + CosmosMsg, + Decimal, + Deps, + DepsMut, + Env, + MessageInfo, + Response, + StdError, + StdResult, + SubMsgResult, + Timestamp, + Uint128, + Uint256, + WasmMsg, }; + use ethnum::U256; use serde::Serialize; use shade_protocol::{ c_std::{shd_entry_point, Reply, SubMsg}, contract_interfaces::liquidity_book::{ - lb_pair::*, lb_token, lb_token::InstantiateMsg as LBTokenInstantiateMsg, + lb_pair::*, + lb_token, + lb_token::InstantiateMsg as LBTokenInstantiateMsg, }, lb_libraries::{ bin_helper::{self, BinHelper}, - constants::{self, SCALE_OFFSET}, + constants::{self, MAX_FEE, PRECISION, SCALE_OFFSET}, fee_helper::{self, FeeHelper}, lb_token::state_structs::{LbPair, TokenAmount, TokenIdBalance}, math::{ @@ -31,12 +54,17 @@ use shade_protocol::{ oracle_helper::{Oracle, OracleError, MAX_SAMPLE_LIFETIME}, pair_parameter_helper::PairParameters, price_helper::PriceHelper, - tokens, types, + tokens, + types, viewing_keys::{register_receive, set_viewing_key_msg, ViewingKey}, }, + liquidity_book::lb_pair::QueryMsg::GetPairInfo, snip20, + utils::pad_handle_result, + BLOCK_SIZE, }; -use std::collections::HashMap; +use shadeswap_shared::router::ExecuteMsgResponse; +use std::{collections::HashMap, ops::Sub}; use tokens::TokenType; use types::{Bytes32, LBPairInformation, MintArrays}; @@ -126,7 +154,9 @@ pub fn instantiate( msg.pair_parameters.max_volatility_accumulator, ) .unwrap(); - let pair_parameters = pair_parameters.set_active_id(msg.active_id)?; + let pair_parameters = pair_parameters + .set_active_id(msg.active_id)? + .update_id_reference(); //RegisterReceiving Token let mut messages = vec![]; @@ -143,7 +173,6 @@ pub fn instantiate( let state = State { creator: info.sender.clone(), - // TODO: the factory should be hardcoded? that makes deploying way harder factory: msg.factory, token_x: msg.token_x, token_y: msg.token_y, @@ -152,14 +181,15 @@ pub fn instantiate( reserves: [0u8; 32], protocol_fees: [0u8; 32], lb_token: ContractInfo { - address: Addr::unchecked("lb_token".to_string()), - code_hash: "lb_token".to_string(), - }, + address: Addr::unchecked("".to_string()), + code_hash: "".to_string(), + }, // intentionally keeping this empty will be filled in reply viewing_key, + protocol_fees_recipient: msg.protocol_fee_recipient, }; - deps.api - .debug(format!("Contract was initialized by {}", info.sender).as_str()); + // deps.api + // .debug(format!("Contract was initialized by {}", info.sender).as_str()); let tree = TreeUint24::new(); let oracle = Oracle { @@ -183,15 +213,45 @@ pub fn instantiate( } /////////////// EXECUTE /////////////// - +//TODO: add contract status #[shd_entry_point] pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> Result { match msg { - ExecuteMsg::Swap { - swap_for_y, + ExecuteMsg::Receive(msg) => { + let checked_addr = deps.api.addr_validate(&msg.from)?; + receiver_callback(deps, env, info, checked_addr, msg.amount, msg.msg) + } + ExecuteMsg::SwapTokens { to, - amount_received, - } => try_swap(deps, env, info, swap_for_y, to, amount_received), + offer, + expected_return, + padding, + } => { + let config = CONFIG.load(deps.storage)?; + if !offer.token.is_native_token() { + return Err(Error::UseReceiveInterface); + } + + offer + .token + .assert_sent_native_token_balance(&info, offer.amount)?; + + let checked_to = if let Some(to) = to { + deps.api.addr_validate(to.as_str())? + } else { + info.sender.clone() + }; + + let swap_for_y: bool; + if offer.token.unique_key() == config.token_x.unique_key() { + swap_for_y = true; + } else { + swap_for_y = false; + } + + try_swap(deps, env, info, swap_for_y, checked_to, offer.amount) + } + //TODO: Flash loan ExecuteMsg::FlashLoan {} => todo!(), ExecuteMsg::AddLiquidity { liquidity_parameters, @@ -204,7 +264,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> R try_increase_oracle_length(deps, env, info, new_length) } ExecuteMsg::SetStaticFeeParameters { - active_id, base_factor, filter_period, decay_period, @@ -216,7 +275,6 @@ pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> R deps, env, info, - active_id, base_factor, filter_period, decay_period, @@ -304,7 +362,6 @@ fn try_swap( } else { BinHelper::received_y(amounts_received) }; - // TODO: compare with 0 instead if amounts_left == [0u8; 32] { return Err(Error::InsufficientAmountIn); }; @@ -319,12 +376,10 @@ fn try_swap( params = params.update_references(&env.block.time)?; loop { - let bin_reserves = BIN_MAP.load(deps.storage, active_id).map_err(|_| { - Error::Generic(format!( - "could not get bin reserves for active id {}", - active_id - )) - })?; + let bin_reserves = BIN_MAP + .load(deps.storage, active_id) + .map_err(|_| Error::ZeroBinReserve { active_id })?; + if !BinHelper::is_empty(bin_reserves, !swap_for_y) { params = params.update_volatility_accumulator(active_id)?; @@ -338,18 +393,14 @@ fn try_swap( amounts_left, )?; - //Proposed Option - // if amounts_in_with_fees.iter().any(|&x| x != 0) {} - if amounts_in_with_fees > [0u8; 32] { + if U256::from_le_bytes(amounts_in_with_fees) > U256::ZERO { amounts_left = amounts_left.sub(amounts_in_with_fees); amounts_out = amounts_out.add(amounts_out_of_bin); let p_fees = total_fees .scalar_mul_div_basis_point_round_down(params.get_protocol_share().into())?; - // TODO: need a zero impl for Bytes32 or something... - //Proposed Option - // if p_fees.iter().any(|&x| x != 0) {} - if p_fees > [0u8; 32] { + + if U256::from_le_bytes(p_fees) > U256::ZERO { protocol_fees = protocol_fees.add(p_fees); amounts_in_with_fees = amounts_in_with_fees.sub(p_fees); } @@ -361,9 +412,6 @@ fn try_swap( .add(amounts_in_with_fees) .sub(amounts_out_of_bin), )?; - - // TODO: decide on the nature of the return message / event - return Ok(Response::default()); } } @@ -372,10 +420,9 @@ fn try_swap( } else { let next_id = _get_next_non_empty_bin(&tree, swap_for_y, active_id); - if next_id == 0 || next_id == (2u32 ^ 24 - 1) { + if next_id == 0 || next_id == (U24::MAX) { return Err(Error::OutOfLiquidity); } - active_id = next_id; } } @@ -386,7 +433,6 @@ fn try_swap( reserves = reserves.sub(amounts_out); - // TODO: review this part carefully. I might be mixing up oracle params and pair params. let mut oracle = ORACLE.load(deps.storage)?; params = oracle.update(&env.block.time, params, active_id)?; @@ -397,25 +443,39 @@ fn try_swap( Ok(state) })?; - // TODO: this will take some refactoring. need to create the submessage - // for the token transfer instead of using those functions let mut messages: Vec = Vec::new(); + let amount_out; if swap_for_y { - let msg = BinHelper::transfer_y(amounts_out, token_y, to); + amount_out = amounts_out.decode_y(); + let msg = BinHelper::transfer_y(amounts_out, token_y.clone(), to); if let Some(message) = msg { messages.push(message); } } else { - let msg = BinHelper::transfer_x(amounts_out, token_x, to); + amount_out = amounts_out.decode_x(); + let msg = BinHelper::transfer_x(amounts_out, token_x.clone(), to); if let Some(message) = msg { messages.push(message); } } - // TODO: decide on the nature of the return message / event - Ok(Response::default().add_messages(messages)) + Ok(Response::new() + .add_messages(messages) + .add_attributes(vec![ + Attribute::new("amount_in", amounts_received), + Attribute::new("amount_out", amount_out.to_string()), + Attribute::new("lp_fee_amount", "0".to_string()), // TODO FILL with correct parameters + Attribute::new("total_fee_amount", "0".to_string()), // TODO FILL with correct parameters + Attribute::new("shade_dao_fee_amount", "0".to_string()), // TODO FILL with correct parameters + Attribute::new("token_in_key", token_x.unique_key()), + Attribute::new("token_out_key", token_y.unique_key()), + ]) + .set_data(to_binary(&ExecuteMsgResponse::SwapResult { + amount_in: amounts_received, + amount_out: Uint128::from(amount_out), + })?)) } /// Flash loan tokens from the pool to a receiver contract and execute a callback function. @@ -471,35 +531,12 @@ pub fn try_add_liquidity( { return Err(Error::WrongPair); } - let mut transfer_messages = Vec::new(); - // 2- tokens checking and transfer - for (token, amount) in [ - (config.token_x.clone(), liquidity_parameters.amount_x), - (config.token_y.clone(), liquidity_parameters.amount_y), - ] - .iter() - { - match token { - TokenType::CustomToken { - contract_addr, - token_code_hash, - } => { - let msg = - token.transfer_from(*amount, info.sender.clone(), env.contract.address.clone()); - if let Some(m) = msg { - transfer_messages.push(m); - } - } - TokenType::NativeToken { .. } => { - token.assert_sent_native_token_balance(&info, *amount)?; - } - } - } - response = response.add_messages(transfer_messages); + // response = response.add_messages(transfer_messages); //3- Main function -> add_liquidity_internal - let response = add_liquidity_internal(deps, env, info, &liquidity_parameters, response)?; + let response = + add_liquidity_internal(deps, env, info, &config, &liquidity_parameters, response)?; Ok(response) } @@ -508,6 +545,7 @@ pub fn add_liquidity_internal( deps: DepsMut, env: Env, info: MessageInfo, + config: &State, liquidity_parameters: &LiquidityParameters, mut response: Response, ) -> Result { @@ -543,6 +581,7 @@ pub fn add_liquidity_internal( deps, env, info.clone(), + &config, info.sender.clone(), liquidity_configs, info.sender, @@ -670,6 +709,7 @@ fn mint( mut deps: DepsMut, env: Env, info: MessageInfo, + config: &State, to: Addr, liquidity_configs: Vec, refund_to: Addr, @@ -699,7 +739,6 @@ fn mint( let amounts_received = BinHelper::received(amount_received_x, amount_received_y); let mut messages: Vec = Vec::new(); - //TODO MINT TOKENS let (amounts_left) = _mint_bins( &mut deps, &env.block.time, @@ -716,32 +755,49 @@ fn mint( state.reserves = state.reserves.add(amounts_received.sub(amounts_left)); //Total liquidity of pool Ok(state) })?; - if amounts_left.iter().any(|&x| x != 0) { - if let Some(msgs) = BinHelper::transfer(amounts_left, token_x, token_y, refund_to) { - messages.extend(msgs); - }; - } + // if amounts_left.iter().any(|&x| x != 0) { + // if let Some(msgs) = BinHelper::transfer(amounts_left, token_x, token_y, refund_to) { + // messages.extend(msgs); + // }; + // } + + let (amount_left_x, amount_left_y) = amounts_left.decode(); - // // TODO: decide on the nature of the return message / event - // let transfer_batch = vec![ - // ("sender", info.sender.as_str()), - // // This is just to say that tokens are being newly minted. - // ("from", "0000000000000000000"), - // ("to", to.as_str()), - // ("ids", "arrays.ids"), - // ("amounts", "liquidity_minted"), - // ]; - // let deposited_to_bins = vec![ - // ("sender", info.sender.as_str()), - // ("to", to.as_str()), - // ("ids", "arrays.ids"), - // ("amounts", "arrays.amounts"), - // ]; + let mut transfer_messages = Vec::new(); + // 2- tokens checking and transfer + for (token, amount) in [ + ( + config.token_x.clone(), + amount_received_x.sub(Uint128::from(amount_left_x)), + ), + ( + config.token_y.clone(), + amount_received_y.sub(Uint128::from(amount_left_y)), + ), + ] + .iter() + { + match token { + TokenType::CustomToken { + contract_addr, + token_code_hash, + } => { + let msg = + token.transfer_from(*amount, info.sender.clone(), env.contract.address.clone()); + + if let Some(m) = msg { + transfer_messages.push(m); + } + } + TokenType::NativeToken { .. } => { + token.assert_sent_native_token_balance(&info, *amount)?; + } + } + } response = response - // .add_attributes(transfer_batch) - // .add_attributes(deposited_to_bins) - .add_messages(messages); + .add_messages(messages) + .add_messages(transfer_messages); Ok(( amounts_received, @@ -798,7 +854,6 @@ fn _mint_bins( amounts_left = amounts_left.sub(amounts_in); - // TODO: imo these are useless pls remove if not emits them with events mint_arrays.ids[index] = id.into(); mint_arrays.amounts[index] = amounts_in_to_bin; mint_arrays.liquidity_minted[index] = shares; @@ -857,7 +912,6 @@ fn _update_bin( let config = CONFIG.load(deps.storage)?; let price = PriceHelper::get_price_from_id(id, bin_step)?; - // TODO: this function needs to query the token contract for the total supply let total_supply = _query_total_supply( deps.as_ref(), id, @@ -910,21 +964,13 @@ fn _update_bin( state.pair_parameters = parameters; Ok(state) })?; - - // // TODO: figure out a way to return this to the 'try_mint' function to use in the response - // let composition_fees = vec![ - // ("sender", "info.sender"), - // ("id", "id"), - // ("fees", "fees"), - // ("protocol_c_fees", "protocol_c_fees"), - // ]; } } else { BinHelper::verify_amounts(amounts_in, active_id, id)?; } if shares == 0 || amounts_in_to_bin == [0u8; 32] { - return Err(Error::ZeroShares { id }); + return Err(Error::ZeroAmount { id }); } if total_supply == 0 { @@ -939,7 +985,6 @@ fn _update_bin( Ok((shares, amounts_in, amounts_in_to_bin)) } -//TODO: Move this to some library fn _query_total_supply(deps: Deps, id: u32, code_hash: String, address: Addr) -> Result { let msg = lb_token::QueryMsg::IdTotalBalance { id: id.to_string() }; @@ -957,7 +1002,6 @@ fn _query_total_supply(deps: Deps, id: u32, code_hash: String, address: Addr) -> Ok(total_supply_uint256.uint256_to_u256()) } -//TODO: Move this to some library fn query_token_symbol(deps: Deps, code_hash: String, address: Addr) -> Result { let msg = snip20::QueryMsg::TokenInfo {}; @@ -1016,7 +1060,7 @@ pub fn try_remove_liquidity( let (amount_x, amount_y, mut response) = remove_liquidity( deps, - env, + env.clone(), info.clone(), info.sender.clone(), amount_x_min, @@ -1025,20 +1069,6 @@ pub fn try_remove_liquidity( remove_liquidity_params.amounts, )?; - // response = response - // .add_attribute("action", "remove_liquidity") - // .add_attribute("to", info.sender.as_str()); - - // if is_wrong_order { - // response = response - // .add_attribute("amount_x", amount_y.u128().to_string()) - // .add_attribute("amount_y", amount_x.u128().to_string()); - // } else { - // response = response - // .add_attribute("amount_x", amount_x.u128().to_string()) - // .add_attribute("amount_y", amount_y.u128().to_string()); - // } - Ok(response) } @@ -1113,12 +1143,14 @@ fn burn( let amount_to_burn = amounts_to_burn[i]; if amount_to_burn.is_zero() { - return Err(Error::ZeroAmount { id }); + return Err(Error::ZeroShares { id }); } let bin_reserves = BIN_MAP .load(deps.storage, id) - .map_err(|_| Error::Generic(format!("could not get bin reserves for bin id {}", i)))?; + .map_err(|_| Error::ZeroBinReserve { + active_id: i as u32, + })?; let total_supply = _query_total_supply( deps.as_ref(), id, @@ -1155,6 +1187,10 @@ fn burn( if total_supply == amount_to_burn_u256 { BIN_MAP.remove(deps.storage, id); + BIN_TREE.update(deps.storage, |mut tree| -> StdResult<_> { + tree.remove(id); + Ok(tree) + })?; } else { BIN_MAP.save(deps.storage, id, &bin_reserves)?; } @@ -1180,63 +1216,22 @@ fn burn( let raw_msgs = BinHelper::transfer(amounts_out, token_x, token_y, info.sender.clone()); + CONFIG.update(deps.storage, |mut state| -> StdResult { + state.reserves = state.reserves.sub(amounts_out); + Ok(state) + })?; + if let Some(msgs) = raw_msgs { messages.extend(msgs) } - // let transfer_batch = vec![ - // ("sender", info.sender.as_str()), - // ("from", info.sender.as_str()), - // ("to", "0000000000000000000"), - // ("ids", "ids"), - // ("amounts", "amounts_to_burn"), - // ]; - // let withdrawn_from_bins = vec![ - // ("sender", info.sender.as_str()), - // ("to", info.sender.as_str()), - // ("ids", "ids"), - // ("amounts", "amounts"), - // ]; - - Ok(( - amounts, - Response::default() - // .add_attributes(transfer_batch) - // .add_attributes(withdrawn_from_bins) - .add_messages(messages), - )) -} - -fn _burn( - deps: &mut DepsMut, - code_hash: String, - contract_address: Addr, - from: Addr, - id: u32, - amount: Uint256, -) -> Result { - // TODO: Implement the burn logic for the provided `id` and `amount`. - // You might need to call the contract's token burning function or interact with the token's storage directly. - let msg = lb_token::ExecuteMsg::BurnTokens { - burn_tokens: vec![TokenAmount { - token_id: id.to_string(), - balances: vec![TokenIdBalance { - address: from, - amount, - }], - }], - memo: None, - padding: None, - } - .to_cosmos_msg(code_hash, contract_address.to_string(), None)?; - - Ok(msg) + Ok((amounts, Response::default().add_messages(messages))) } /// Collect the protocol fees from the pool. fn try_collect_protocol_fees(deps: DepsMut, env: Env, info: MessageInfo) -> Result { let state = CONFIG.load(deps.storage)?; - only_protocol_fee_recipient(&info.sender, &state.factory.address)?; + // only_protocol_fee_recipient(&info.sender, &state.factory.address)?; let token_x = state.token_x; let token_y = state.token_y; @@ -1245,8 +1240,6 @@ fn try_collect_protocol_fees(deps: DepsMut, env: Env, info: MessageInfo) -> Resu let protocol_fees = state.protocol_fees; - // TODO: this seems like a weird way to check if the protocol fees are non-zero - // Can probably refactor this. let (x, y) = protocol_fees.decode(); let ones = Bytes32::encode(if x > 0 { 1 } else { 0 }, if y > 0 { 1 } else { 0 }); @@ -1254,7 +1247,7 @@ fn try_collect_protocol_fees(deps: DepsMut, env: Env, info: MessageInfo) -> Resu //This is done to avoid completely draining the fees and possibly causing any issues with calculations that depend on non-zero values let collected_protocol_fees = protocol_fees.sub(ones); - if collected_protocol_fees != [0u8; 32] { + if U256::from_le_bytes(collected_protocol_fees) != U256::ZERO { // This is setting the protocol fees to the smallest possible values CONFIG.update(deps.storage, |mut state| -> StdResult { state.protocol_fees = ones; @@ -1265,25 +1258,27 @@ fn try_collect_protocol_fees(deps: DepsMut, env: Env, info: MessageInfo) -> Resu if collected_protocol_fees.iter().any(|&x| x != 0) { if let Some(msgs) = BinHelper::transfer( collected_protocol_fees, - token_x, - token_y, - info.sender.clone(), + token_x.clone(), + token_y.clone(), + state.protocol_fees_recipient, ) { messages.extend(msgs); }; } - // TODO: decide on the nature of the return message / event Ok(Response::default() .add_attribute( - "Collected Protocol Fees", - // TODO: figure out how to format the protocol fees - "collected_protocol_fees.decode()", + format!("Collected Protocol Fees for token {}", token_x.unique_key()), + collected_protocol_fees.decode_x().to_string(), + ) + .add_attribute( + format!("Collected Protocol Fees for token {}", token_y.unique_key()), + collected_protocol_fees.decode_y().to_string(), ) .add_attribute("Action performed by", info.sender.to_string()) .add_messages(messages)) } else { - Err(Error::Generic("???".to_string())) + Err(Error::NotEnoughFunds) } } @@ -1309,26 +1304,13 @@ fn try_increase_oracle_length( params = PairParameters::set_oracle_id(params, oracle_id); } - // TODO: I think this works but is kind of clunky - // ORACLE.update(deps.storage, |mut oracle| { - // let oracle = oracle - // .increase_length(oracle_id, new_length) - // .map_err(|err| StdError::GenericErr { - // msg: err.to_string(), - // })?; - // Ok(oracle) - // }); - ORACLE.update(deps.storage, |mut oracle| { oracle .increase_length(oracle_id, new_length) .map_err(|err| StdError::generic_err(err.to_string())) })?; - // TODO: decide on the nature of the return message / event - Ok(Response::default() - .add_attribute("Oracle Length Increased", new_length.to_string()) - .add_attribute("Action performed by", info.sender.to_string())) + Ok(Response::default().add_attribute("Oracle Length Increased to", new_length.to_string())) } /// Sets the static fee parameters of the pool. @@ -1348,7 +1330,6 @@ fn try_set_static_fee_parameters( deps: DepsMut, env: Env, info: MessageInfo, - active_id: u32, base_factor: u16, filter_period: u16, decay_period: u16, @@ -1373,6 +1354,14 @@ fn try_set_static_fee_parameters( max_volatility_accumulator, )?; + { + let total_fee = + params.get_base_fee(state.bin_step) + params.get_variable_fee(state.bin_step); + if total_fee > MAX_FEE { + return Err(Error::MaxTotalFeeExceeded {}); + } + } + CONFIG.update(deps.storage, |mut state| -> StdResult { state.pair_parameters = params; Ok(state) @@ -1397,9 +1386,12 @@ fn try_force_decay(deps: DepsMut, env: Env, info: MessageInfo) -> Result Result<()> { @@ -1409,26 +1401,69 @@ fn only_factory(sender: &Addr, factory: &Addr) -> Result<()> { Ok(()) } -// TODO: I think the factory has the protocol_fee_recipient, so we'll need to query it for that info -fn only_protocol_fee_recipient(sender: &Addr, factory: &Addr) -> Result<()> { - let protocol_fee_recipient = &Addr::unchecked("some address"); - - if sender != protocol_fee_recipient { - return Err(Error::OnlyFactory); - } - panic!("only_protocol_fee_recipient function incomplete") -} - fn serialize_or_err(data: &T) -> Result { serde_json_wasm::to_string(data).map_err(|_| Error::SerializationError) } +fn receiver_callback( + deps: DepsMut, + env: Env, + info: MessageInfo, + from: Addr, + amount: Uint128, + msg: Option, +) -> Result { + let msg = msg.ok_or_else(|| Error::ReceiverMsgEmpty)?; + + let config = CONFIG.load(deps.storage)?; + + let mut response = Response::new(); + match from_binary(&msg)? { + InvokeMsg::SwapTokens { + to, + expected_return, + padding: _, + } => { + // this check needs to be here instead of in execute() because it is impossible to (cleanly) distinguish between swaps and lp withdraws until this point + // if contract_status is FreezeAll, this fn will never be called, so only need to check LpWithdrawOnly here + //TODO: add status: check + // if contract_status == ContractStatus::LpWithdrawOnly { + // return Err(StdError::generic_err( + // "Transaction is blocked by contract status", + // )); + // } + + //validate recipient address + let checked_to = if let Some(to) = to { + deps.api.addr_validate(to.as_str())? + } else { + from + }; + + if info.sender != config.token_x.unique_key() + && info.sender != config.token_y.unique_key() + { + return Err(Error::NoMatchingTokenInPair); + } + let swap_for_y: bool; + if info.sender == config.token_x.unique_key() { + swap_for_y = true; + } else { + swap_for_y = false; + } + + response = try_swap(deps, env, info, swap_for_y, checked_to, amount)?; + } + }; + Ok(response) +} + /////////////// QUERY /////////////// -// TODO: refactor this like the LBFactory contract #[shd_entry_point] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { match msg { + QueryMsg::GetPairInfo {} => to_binary(&query_pair_info(deps)?).map_err(Error::CwErr), QueryMsg::GetFactory {} => to_binary(&query_factory(deps)?).map_err(Error::CwErr), QueryMsg::GetTokenX {} => to_binary(&query_token_x(deps)?).map_err(Error::CwErr), QueryMsg::GetTokenY {} => to_binary(&query_token_y(deps)?).map_err(Error::CwErr), @@ -1475,7 +1510,116 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { } QueryMsg::GetLbToken {} => to_binary(&query_lb_token(deps)?).map_err(Error::CwErr), QueryMsg::GetTokens {} => to_binary(&query_tokens(deps)?).map_err(Error::CwErr), + GetPairInfo {} => todo!(), + QueryMsg::SwapSimulation { offer, exclude_fee } => { + to_binary(&query_swap_simulation(deps, env, offer, exclude_fee)?).map_err(Error::CwErr) + } + } +} + +/// Returns the Liquidity Book Factory. +/// +/// # Returns +/// +/// * `factory` - The Liquidity Book Factory +fn query_pair_info(deps: Deps) -> Result { + let state = CONFIG.load(deps.storage)?; + + let (mut reserve_x, mut reserve_y) = state.reserves.decode(); + let (protocol_fee_x, protocol_fee_y) = state.protocol_fees.decode(); + + Ok( + shadeswap_shared::msg::amm_pair::QueryMsgResponse::GetPairInfo { + liquidity_token: shade_protocol::Contract { + address: state.lb_token.address, + code_hash: state.lb_token.code_hash, + }, + factory: Some(shade_protocol::Contract { + address: state.factory.address, + code_hash: state.factory.code_hash, + }), + pair: shadeswap_shared::core::TokenPair { + 0: state.token_x, + 1: state.token_y, + 2: false, + }, + amount_0: Uint128::from(reserve_x), + amount_1: Uint128::from(reserve_y), + total_liquidity: Uint128::default(), // no global liquidity, liquidity is calculated on per bin basis + contract_version: 1, // TODO set this like const AMM_PAIR_CONTRACT_VERSION: u32 = 1; + fee_info: shadeswap_shared::amm_pair::FeeInfo { + shade_dao_address: Addr::unchecked(""), // TODO set shade dao address + lp_fee: shadeswap_shared::core::Fee { + // TODO set this + nom: state.pair_parameters.get_base_fee_u64(state.bin_step), + denom: 1_000_000_000_000_000_000, + }, + shade_dao_fee: shadeswap_shared::core::Fee { + // TODO set this + nom: state.pair_parameters.get_base_fee_u64(state.bin_step), + denom: 1_000_000_000_000_000_000, + }, + stable_lp_fee: shadeswap_shared::core::Fee { + // TODO set this + nom: state.pair_parameters.get_base_fee_u64(state.bin_step), + denom: 1_000_000_000_000_000_000, + }, + stable_shade_dao_fee: shadeswap_shared::core::Fee { + // TODO set this + nom: state.pair_parameters.get_base_fee_u64(state.bin_step), + denom: 1_000_000_000_000_000_000, + }, + }, + stable_info: None, + }, + ) +} + +// / Returns the Liquidity Book Factory. +// / +// / # Returns +// / +// / * `factory` - The Liquidity Book Factory +fn query_swap_simulation( + deps: Deps, + env: Env, + offer: shade_protocol::lb_libraries::tokens::TokenAmount, + exclude_fee: Option, +) -> Result { + let state = CONFIG.load(deps.storage)?; + + let (mut reserve_x, mut reserve_y) = state.reserves.decode(); + let (protocol_fee_x, protocol_fee_y) = state.protocol_fees.decode(); + let mut swap_for_y = false; + match offer.token { + token if token == state.token_x => swap_for_y = true, + token if token == state.token_y => {} + _ => panic!("No such token"), + }; + + let res = query_swap_out(deps, env, offer.amount.into(), swap_for_y)?; + + if (res.amount_in_left.u128() > 0u128) { + return Err(Error::AmountInLeft { + amount_left_in: res.amount_in_left, + total_amount: offer.amount, + swapped_amount: res.amount_out, + }); } + + let price = Decimal::from_ratio(res.amount_out, offer.amount).to_string(); + + Ok( + shadeswap_shared::msg::amm_pair::QueryMsgResponse::SwapSimulation { + total_fee_amount: res.fee, + lp_fee_amount: res.fee, //TODO lpfee + shade_dao_fee_amount: res.fee, // dao fee + result: SwapResult { + return_amount: res.amount_out, + }, + price, + }, + ) } /// Returns the Liquidity Book Factory. @@ -1598,8 +1742,6 @@ fn query_active_id(deps: Deps) -> Result { /// * `bin_reserve_x` - The reserve of token X in the bin /// * `bin_reserve_y` - The reserve of token Y in the bin fn query_bin(deps: Deps, id: u32) -> Result { - // TODO: what should happen if the bin doesn't exist? - // TODO: this should be using the TreeUint24? but the tree doesn't have a direct 'get' method let bin: Bytes32 = BIN_MAP.load(deps.storage, id).unwrap_or([0u8; 32]); let (bin_reserve_x, bin_reserve_y) = bin.decode(); @@ -1761,7 +1903,13 @@ fn query_oracle_params(deps: Deps) -> Result { }) } else { // This happens if the oracle hasn't been used yet. - Err(Error::OracleErr(OracleError::InvalidOracleId)) + Ok(OracleParametersResponse { + sample_lifetime, + size: 0, + active_size: 0, + last_updated: 0, + first_timestamp: 0, + }) } } @@ -1813,9 +1961,7 @@ fn query_oracle_sample_at( cumulative_bin_crossed, }) } else { - Err(Error::Generic( - "time_of_last_update was later than look_up_timestamp".to_string(), - )) + Err(Error::LastUpdateTimestampGreaterThanLookupTimestamp) } } @@ -1892,7 +2038,6 @@ fn query_swap_in( params = params.update_references(&env.block.time)?; - // TODO: do something more idiomatic, like a 'while let Some(item) = iterator.next()' maybe loop { let bin_reserves = BIN_MAP .load(deps.storage, id) @@ -1930,8 +2075,7 @@ fn query_swap_in( break; } else { let next_id = _get_next_non_empty_bin(&tree, swap_for_y, id); - // TODO: or next_id == uint24::MAX - if next_id == 0 || next_id == u32::MAX { + if next_id == 0 || next_id == U24::MAX { break; } @@ -1983,11 +2127,8 @@ fn query_swap_out( params = params.update_references(&env.block.time)?; loop { - let bin_reserves = BIN_MAP.load(deps.storage, id).map_err(|_| { - Error::Generic(format!("could not get bin reserves for active id {}", id)) - })?; - - if BinHelper::is_empty(bin_reserves, !swap_for_y) { + let bin_reserves = BIN_MAP.load(deps.storage, id).unwrap_or_default(); + if !BinHelper::is_empty(bin_reserves, !swap_for_y) { params = params.update_volatility_accumulator(id)?; let (amounts_in_with_fees, amounts_out_of_bin, total_fees) = BinHelper::get_amounts( @@ -1998,8 +2139,8 @@ fn query_swap_out( id, amounts_in_left, )?; - // TODO: have a way to compare a packed_u128 with an integer? - if amounts_in_with_fees > [0u8; 32] { + + if U256::from_le_bytes(amounts_in_with_fees) > U256::ZERO { amounts_in_left = amounts_in_left.sub(amounts_in_with_fees); amount_out += Bytes32::decode_alt(&amounts_out_of_bin, !swap_for_y); @@ -2013,8 +2154,7 @@ fn query_swap_out( } else { let next_id = _get_next_non_empty_bin(&tree, swap_for_y, id); - // TODO: or next_id == uint24::MAX - if next_id == 0 || next_id == u32::MAX { + if next_id == 0 || next_id == U24::MAX { break; } diff --git a/contracts/liquidity_book/lb_pair/src/error.rs b/contracts/liquidity_book/lb_pair/src/error.rs index c1e88f37f..d70a57c2c 100644 --- a/contracts/liquidity_book/lb_pair/src/error.rs +++ b/contracts/liquidity_book/lb_pair/src/error.rs @@ -107,6 +107,18 @@ pub enum LBPairError { #[error("Wrong Pair")] WrongPair, + #[error("Use the receive interface")] + UseReceiveInterface, + + #[error("Receiver callback \"msg\" parameter cannot be empty.")] + ReceiverMsgEmpty, + + #[error("Not enough funds")] + NotEnoughFunds, + + #[error("No matching token in pair")] + NoMatchingTokenInPair, + #[error("Deadline exceeded. Deadline: {deadline}, Current timestamp: {current_timestamp}")] DeadlineExceeded { deadline: u64, @@ -116,9 +128,15 @@ pub enum LBPairError { #[error("Lengths mismatch")] LengthsMismatch, + #[error("time_of_last_update was later than look_up_timestamp")] + LastUpdateTimestampGreaterThanLookupTimestamp, + #[error("Id desired overflows. Id desired: {id_desired}, Id slippage: {id_slippage}")] IdDesiredOverflows { id_desired: u32, id_slippage: u32 }, + #[error("could not get bin reserves for active id: {active_id}")] + ZeroBinReserve { active_id: u32 }, + #[error("Delta id overflows. Delta Id: {delta_id}")] DeltaIdOverflows { delta_id: i64 }, @@ -128,6 +146,13 @@ pub enum LBPairError { #[error("Id overflows. Id: {id}")] IdOverflows { id: u32 }, + #[error("Amount left unswapped. : Amount Left In: {amount_left_in}, Total Amount: {total_amount}, swapped_amount: {swapped_amount}")] + AmountInLeft { + amount_left_in: Uint128, + total_amount: Uint128, + swapped_amount: Uint128, + }, + #[error("Id slippage caught. Active id desired: {active_id_desired}, Id slippage: {id_slippage}, Active id: {active_id}")] IdSlippageCaught { active_id_desired: u32, diff --git a/contracts/liquidity_book/lb_pair/src/state.rs b/contracts/liquidity_book/lb_pair/src/state.rs index 97da31b03..f1c4160aa 100644 --- a/contracts/liquidity_book/lb_pair/src/state.rs +++ b/contracts/liquidity_book/lb_pair/src/state.rs @@ -8,8 +8,14 @@ use math::tree_math::TreeUint24; //? use oracle_helper::Oracle; //? use types::Bytes32; //? -use shade_protocol::lb_libraries::viewing_keys::ViewingKey; -use shade_protocol::lb_libraries::{math, oracle_helper, pair_parameter_helper, tokens, types}; +use shade_protocol::lb_libraries::{ + math, + oracle_helper, + pair_parameter_helper, + tokens, + types, + viewing_keys::ViewingKey, +}; use tokens::TokenType; //? pub const CONFIG: Item = Item::new("config"); @@ -30,6 +36,7 @@ pub struct State { pub reserves: Bytes32, pub protocol_fees: Bytes32, pub lb_token: ContractInfo, + pub protocol_fees_recipient: Addr, } pub fn ephemeral_storage_w(storage: &mut dyn Storage) -> Singleton { diff --git a/contracts/liquidity_book/lb_pair/src/unittest/handle_test.rs b/contracts/liquidity_book/lb_pair/src/unittest/handle_test.rs index f38398eed..7a7cfa415 100644 --- a/contracts/liquidity_book/lb_pair/src/unittest/handle_test.rs +++ b/contracts/liquidity_book/lb_pair/src/unittest/handle_test.rs @@ -5,10 +5,14 @@ use cosmwasm_std::{Uint128, Uint256}; use shade_multi_test::interfaces::{lb_pair, lb_token, snip20}; use shade_protocol::contract_interfaces::liquidity_book::lb_token::QueryAnswer; -use super::test_helper::{init_addrs, remove_liquidity_parameters_helper}; use crate::unittest::test_helper::{ - assert_approx_eq_rel, init_lb_pair, liquidity_parameters_helper, - lp_tokens_tempate_for_100_sscrts, mint_increase_allowance_helper, + assert_approx_eq_rel, + init_addrs, + init_lb_pair, + liquidity_parameters_helper, + lp_tokens_tempate_for_100_sscrts, + mint_increase_allowance_helper, + remove_liquidity_parameters_helper, }; /*********************************************** @@ -28,7 +32,7 @@ fn test_init() -> Result<(), anyhow::Error> { let lb_token_info = lb_pair::lb_token_query(&app, &lb_pair_contract_info.clone().into())?; - let contract_info_lb_token = lb_token::contract_info_query(&app, &lb_token_info)?; + let contract_info_lb_token = lb_token::query_contract_info(&app, &lb_token_info)?; match contract_info_lb_token { QueryAnswer::TokenContractInfo { curators, .. } => { @@ -112,7 +116,7 @@ fn test_add_liquidity() -> Result<(), anyhow::Error> { let mut i = 0; for id in log_shares_array { - let liquidity = lb_token::id_balance_query(&app, &lb_token_info, id.0.to_string())?; + let liquidity = lb_token::query_id_balance(&app, &lb_token_info, id.0.to_string())?; match liquidity { shade_protocol::liquidity_book::lb_token::QueryAnswer::IdTotalBalance { amount } => { @@ -240,7 +244,7 @@ fn test_remove_liquidity() -> Result<(), anyhow::Error> { let mut i = 0; for (id, amt) in remove_liq_log { - let liquidity = lb_token::id_balance_query(&app, &lb_token_info, id.to_string())?; + let liquidity = lb_token::query_id_balance(&app, &lb_token_info, id.to_string())?; match liquidity { shade_protocol::liquidity_book::lb_token::QueryAnswer::IdTotalBalance { amount } => { diff --git a/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs b/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs index 39225b43e..a9ed1caad 100644 --- a/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs +++ b/contracts/liquidity_book/lb_pair/src/unittest/test_helper.rs @@ -2,12 +2,20 @@ use core::result::Result::Ok; use std::{any::Any, str::FromStr}; use cosmwasm_std::{ - Addr, BlockInfo, ContractInfo, StdError, StdResult, Timestamp, Uint128, Uint256, + Addr, + BlockInfo, + ContractInfo, + StdError, + StdResult, + Timestamp, + Uint128, + Uint256, }; use serde::de::Error; use shade_multi_test::{ interfaces::{ - lb_pair, snip20, + lb_pair, + snip20, utils::{DeployedContracts, SupportedContracts}, }, multi::lb_token::LbToken, @@ -33,27 +41,35 @@ impl Addrs { pub fn admin(&self) -> Addr { self.addrs[0].clone() } + pub fn user1(&self) -> Addr { self.addrs[1].clone() } + pub fn user2(&self) -> Addr { self.addrs[2].clone() } + pub fn user3(&self) -> Addr { self.addrs[3].clone() } + pub fn all(&self) -> Vec { self.addrs.clone() } + pub fn a_hash(&self) -> String { self.hashes[0].clone() } + pub fn b_hash(&self) -> String { self.hashes[1].clone() } + pub fn c_hash(&self) -> String { self.hashes[2].clone() } + pub fn _d_hash(&self) -> String { self.hashes[3].clone() } @@ -160,6 +176,7 @@ pub fn init_lb_pair() -> Result<(App, Contract, DeployedContracts), anyhow::Erro "viewing_key".to_string(), String::new(), String::new(), + addrs.admin(), )?; Ok((app, lb_pair, deployed_contracts)) diff --git a/contracts/liquidity_book/lb_quoter/Cargo.toml b/contracts/liquidity_book/lb_quoter/Cargo.toml deleted file mode 100644 index 92e235289..000000000 --- a/contracts/liquidity_book/lb_quoter/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "lb_quoter" -version = "0.1.0" -edition = "2021" -exclude = [ - # Those files are contract-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. - "contract.wasm", - "hash.txt", -] - -[lib] -crate-type = ["cdylib", "rlib"] - -[features] -default = [] -# for quicker tests, cargo test --lib -# for more explicit tests, cargo test --features=backtraces -backtraces = ["cosmwasm-std/backtraces"] - -[dependencies] -libraries={path = "../../libraries"} -cosmwasm-std = { git = "https://github.com/scrtlabs/cosmwasm", tag = "v1.1.9-secret", features = ["stargate"] } -cosmwasm-storage = { git = "https://github.com/scrtlabs/cosmwasm", tag = "v1.1.9-secret" } -cosmwasm-schema = { version = "1.2" } -# schemars = { version = "0.8" } -serde = { version = "1" } -# serde-json-wasm = { version = "0.5"} -thiserror = { version = "1" } -secret-toolkit = { git = "https://github.com/scrtlabs/secret-toolkit", tag = "v0.8.1", default-features = false, features = ["storage"] } -# cw-storage-plus = { version = "1", default-features = false, features = ["macro"] } -ethnum = { version = "1" } - -[dev-dependencies] -anyhow = "1" -# cw-multi-test = { version = "0.16.0", default-features = false } - -[[bin]] -name = "schema" diff --git a/contracts/liquidity_book/lb_quoter/src/bin/schema.rs b/contracts/liquidity_book/lb_quoter/src/bin/schema.rs deleted file mode 100644 index 5ed2a7b27..000000000 --- a/contracts/liquidity_book/lb_quoter/src/bin/schema.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cosmwasm_schema::write_api; - -use lb_quoter::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - execute: ExecuteMsg, - query: QueryMsg, - } -} diff --git a/contracts/liquidity_book/lb_quoter/src/contract.rs b/contracts/liquidity_book/lb_quoter/src/contract.rs deleted file mode 100644 index 82ae88526..000000000 --- a/contracts/liquidity_book/lb_quoter/src/contract.rs +++ /dev/null @@ -1,69 +0,0 @@ -#![allow(unused)] // For beginning only. - -use cosmwasm_std::{ - entry_point, to_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, - StdResult, Timestamp, Uint256, -}; -use ethnum::U256; - -use libraries::types::Bytes32; - -use crate::msg::*; -use crate::prelude::*; -use crate::state::*; - -/////////////// INSTANTIATE /////////////// - -#[entry_point] -pub fn instantiate( - deps: DepsMut, - _env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> StdResult { - // TODO: Only the factory should be allowed to instantiate this contract - // I think you can restrict that on code upload - - let state = State { - creator: info.sender.clone(), - }; - deps.api - .debug(format!("Contract was initialized by {}", info.sender).as_str()); - CONFIG.save(deps.storage, &state)?; - - Ok(Response::default()) -} - -/////////////// EXECUTE /////////////// - -#[entry_point] -pub fn execute(deps: DepsMut, env: Env, info: MessageInfo, msg: ExecuteMsg) -> Result { - match msg { - ExecuteMsg::Swap { swap_for_y, to } => try_swap(deps, env, info, swap_for_y, to), - } -} - -fn try_swap( - deps: DepsMut, - env: Env, - info: MessageInfo, - swap_for_y: bool, - to: Addr, -) -> Result { - todo!() -} - -#[entry_point] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> Result { - match msg { - QueryMsg::GetFactory {} => { - to_binary(&query_creator(deps)?).map_err(|err| Error::CwErr(err)) - } - } -} - -fn query_creator(deps: Deps) -> Result { - let state = CONFIG.load(deps.storage)?; - let factory = state.creator; - Ok(FactoryResponse { factory }) -} diff --git a/contracts/liquidity_book/lb_quoter/src/error.rs b/contracts/liquidity_book/lb_quoter/src/error.rs deleted file mode 100644 index d454d4cde..000000000 --- a/contracts/liquidity_book/lb_quoter/src/error.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! ### Custom Errors for LB_Factory contract. - -#![allow(unused)] // For beginning only. - -use libraries::bin_helper::BinError; -use libraries::fee_helper::FeeError; -use libraries::math::liquidity_configurations::LiquidityConfigurationsError; -use libraries::math::u128x128_math::U128x128MathError; -use libraries::math::u256x256_math::U256x256MathError; -use libraries::oracle_helper::OracleError; -use libraries::pair_parameter_helper::PairParametersError; - -#[derive(thiserror::Error, Debug)] -pub enum LBQuoterError { - #[error("Generic {0}")] - Generic(String), - - #[error(transparent)] - CwErr(#[from] cosmwasm_std::StdError), - - #[error(transparent)] - BinErr(#[from] BinError), - - #[error(transparent)] - FeeErr(#[from] FeeError), - - #[error(transparent)] - OracleErr(#[from] OracleError), - - #[error(transparent)] - ParamsErr(#[from] PairParametersError), - - #[error(transparent)] - LiquidityConfigErr(#[from] LiquidityConfigurationsError), - - #[error(transparent)] - U128Err(#[from] U128x128MathError), - - #[error(transparent)] - U256Err(#[from] U256x256MathError), -} diff --git a/contracts/liquidity_book/lb_quoter/src/lib.rs b/contracts/liquidity_book/lb_quoter/src/lib.rs deleted file mode 100644 index 7c9a41ed5..000000000 --- a/contracts/liquidity_book/lb_quoter/src/lib.rs +++ /dev/null @@ -1,6 +0,0 @@ -mod error; -mod prelude; - -pub mod contract; -pub mod msg; -pub mod state; diff --git a/contracts/liquidity_book/lb_quoter/src/msg.rs b/contracts/liquidity_book/lb_quoter/src/msg.rs deleted file mode 100644 index 430e219d8..000000000 --- a/contracts/liquidity_book/lb_quoter/src/msg.rs +++ /dev/null @@ -1,25 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::Addr; - -#[cw_serde] -pub struct InstantiateMsg { - pub factory: Addr, -} - -#[cw_serde] -pub enum ExecuteMsg { - Swap { swap_for_y: bool, to: Addr }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(FactoryResponse)] - GetFactory {}, -} - -// We define a custom struct for each query response -#[cw_serde] -pub struct FactoryResponse { - pub factory: Addr, -} diff --git a/contracts/liquidity_book/lb_quoter/src/prelude.rs b/contracts/liquidity_book/lb_quoter/src/prelude.rs deleted file mode 100644 index 45248831d..000000000 --- a/contracts/liquidity_book/lb_quoter/src/prelude.rs +++ /dev/null @@ -1,11 +0,0 @@ -// Use this crate's custom Error type -pub use crate::error::LBQuoterError as Error; - -// Force all Result types to use our Error type -pub type Result = core::result::Result; - -// Generic Wrapper tuple struct for newtype pattern. -// pub struct W(pub T); - -// Personal preference. -// pub use std::format as f; diff --git a/contracts/liquidity_book/lb_quoter/src/state.rs b/contracts/liquidity_book/lb_quoter/src/state.rs deleted file mode 100644 index 2b7f632cf..000000000 --- a/contracts/liquidity_book/lb_quoter/src/state.rs +++ /dev/null @@ -1,15 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Addr; - -use secret_toolkit::storage::{Item, Keymap}; - -use libraries::types::Bytes32; - -pub static CONFIG: Item = Item::new(b"config"); -pub static BIN_MAP: Keymap = Keymap::new(b"bins"); - -#[cw_serde] -pub struct State { - // TODO: use canonical addresses - pub creator: Addr, -} diff --git a/contracts/liquidity_book/lb_token/Cargo.toml b/contracts/liquidity_book/lb_token/Cargo.toml index 677930deb..3a758d707 100644 --- a/contracts/liquidity_book/lb_token/Cargo.toml +++ b/contracts/liquidity_book/lb_token/Cargo.toml @@ -37,7 +37,7 @@ backtraces = ["cosmwasm-std/backtraces"] serde = { version = "1.0.158", default-features = false, features = ["derive"] } schemars = "0.8.12" cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.1.10" } -cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.10" } +cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.1.10" , features=["iterator"]} secret-toolkit = { version = "0.9.0", default-features = false, features = [ "storage", "viewing-key", "permit", "serialization", #"crypto", "utils", diff --git a/contracts/liquidity_book/lb_token/src/contract.rs b/contracts/liquidity_book/lb_token/src/contract.rs index 84b7386b7..a183e332e 100644 --- a/contracts/liquidity_book/lb_token/src/contract.rs +++ b/contracts/liquidity_book/lb_token/src/contract.rs @@ -27,36 +27,38 @@ use secret_toolkit::{ viewing_key::{ViewingKey, ViewingKeyStore}, }; -use crate::state::{ - balances_r, blockinfo_r, contr_conf_r, get_receiver_hash, - permissions::{list_owner_permission_keys, may_load_any_permission}, - tkn_info_r, tkn_tot_supply_r, - txhistory::{get_txs, may_get_current_owner}, - PREFIX_REVOKED_PERMITS, -}; use crate::{ receiver::Snip1155ReceiveMsg, state::{ - balances_w, blockinfo_w, contr_conf_w, - permissions::{new_permission, update_permission}, - set_receiver_hash, tkn_info_w, tkn_tot_supply_w, - txhistory::{append_new_owner, store_burn, store_mint, store_transfer}, - RESPONSE_BLOCK_SIZE, + balances_r, balances_w, blockinfo_r, blockinfo_w, contr_conf_r, contr_conf_w, + get_receiver_hash, + permissions::{ + list_owner_permission_keys, may_load_any_permission, new_permission, update_permission, + }, + set_receiver_hash, tkn_info_r, tkn_info_w, tkn_tot_supply_r, tkn_tot_supply_w, + txhistory::{ + append_new_owner, get_txs, may_get_current_owner, store_burn, store_mint, + store_transfer, + }, + PREFIX_REVOKED_PERMITS, RESPONSE_BLOCK_SIZE, }, }; use secret_toolkit::permit::{validate, Permit, TokenPermissions}; -use shade_protocol::lb_libraries::lb_token::{ - expiration::Expiration, - metadata::Metadata, - permissions::{Permission, PermissionKey}, - state_structs::{ - ContractConfig, CurateTokenId, OwnerBalance, StoredTokenInfo, TknConfig, TokenAmount, - TokenInfoMsg, +use shade_protocol::{ + lb_libraries::lb_token::{ + expiration::Expiration, + metadata::Metadata, + permissions::{Permission, PermissionKey}, + state_structs::{ + ContractConfig, CurateTokenId, OwnerBalance, StoredTokenInfo, TknConfig, TokenAmount, + TokenInfoMsg, + }, + }, + liquidity_book::lb_token::{ + ExecuteAnswer, ExecuteMsg, InstantiateMsg, ResponseStatus::Success, SendAction, + TransferAction, }, -}; -use shade_protocol::liquidity_book::lb_token::{ - ExecuteAnswer, ExecuteMsg, InstantiateMsg, ResponseStatus::Success, SendAction, TransferAction, }; ///////////////////////////////////////////////////////////////////////////////// // Init @@ -113,8 +115,8 @@ pub fn instantiate( contr_conf_w(deps.storage).save(&config)?; let response = Response::new().set_data(to_binary(&env.contract.address)?); - deps.api - .debug(format!("Contract address {}", env.contract.address).as_str()); + // deps.api + // .debug(format!("Contract address {}", env.contract.address).as_str()); Ok(response) } @@ -321,8 +323,8 @@ fn try_mint_tokens( let curate_token = CurateTokenId { token_info: TokenInfoMsg { token_id: mint_token.token_id.clone(), - name: format!("{}-{}", &config.lb_pair_info.name, mint_token.token_id), - symbol: format!("{}", &config.lb_pair_info.symbol), + name: format!("LP-{}", &config.lb_pair_info.symbol), + symbol: format!("LP-{}", &config.lb_pair_info.symbol), token_config: TknConfig::Fungible { minters: Vec::new(), // No need for minter curator will be the minter decimals: config.lb_pair_info.decimals, @@ -364,6 +366,7 @@ fn try_mint_tokens( // check if sender is a minter // verify_minter(token_info_op.as_ref().unwrap(), &info)?; // add balances + for add_balance in mint_token.balances { exec_change_balance( deps.storage, @@ -409,8 +412,8 @@ fn try_burn_tokens( if token_info_op.is_none() { return Err(StdError::generic_err( - "token_id does not exist. Cannot burn non-existent `token_ids`. Use `curate_token_ids` to create tokens on new `token_ids`" - )); + "token_id does not exist. Cannot burn non-existent `token_ids`. Use `curate_token_ids` to create tokens on new `token_ids`", + )); } let token_info = token_info_op.clone().unwrap(); @@ -474,7 +477,7 @@ fn try_change_metadata( return Err(StdError::generic_err(format!( "token_id {} does not exist", token_id - ))) + ))); } Some(i) => i.token_config.flatten(), }; @@ -498,7 +501,7 @@ fn try_change_metadata( return Err(StdError::generic_err(format!( "unable to change the metadata for token_id {}", token_id - ))) + ))); } true => { let mut tkn_info = tkn_info_op.unwrap(); @@ -994,10 +997,12 @@ fn is_valid_name(name: &str) -> bool { fn is_valid_symbol(symbol: &str) -> bool { let len = symbol.len(); - let len_is_valid = (3..=50).contains(&len); + (3..=30).contains(&len) + // let len = symbol.len(); + // let len_is_valid = (3..=50).contains(&len); - // len_is_valid && symbol.bytes().all(|byte| (b'A'..=b'Z').contains(&byte)) - len_is_valid && symbol.bytes().all(|byte| byte.is_ascii_uppercase()) + // // len_is_valid && symbol.bytes().all(|byte| (b'A'..=b'Z').contains(&byte)) + // len_is_valid && symbol.bytes().all(|byte| byte.is_ascii_uppercase()) } fn verify_admin(contract_config: &ContractConfig, info: &MessageInfo) -> StdResult<()> { @@ -1077,9 +1082,9 @@ fn exec_curate_token_id( if initial_token.token_info.token_config.flatten().is_nft { if initial_token.balances.len() > 1 { return Err(StdError::generic_err(format!( - "token_id {} is an NFT; there can only be one NFT. Balances should only have one address", - initial_token.token_info.token_id - ))); + "token_id {} is an NFT; there can only be one NFT. Balances should only have one address", + initial_token.token_info.token_id + ))); } else if initial_token.balances[0].amount != Uint256::from(1_u64) { return Err(StdError::generic_err(format!( "token_id {} is an NFT; there can only be one NFT. Balances.amount must == 1", @@ -1100,6 +1105,7 @@ fn exec_curate_token_id( "Ticker symbol is not in expected format [A-Z]{3,6}", )); } + if initial_token.token_info.token_config.flatten().decimals > 18 { return Err(StdError::generic_err("Decimals must not exceed 18")); } @@ -1225,14 +1231,14 @@ fn impl_transfer( return Err(StdError::generic_err(format!( "Allowance has expired: {}", perm.trfer_allowance_exp - ))) + ))); } // not enough allowance to transfer amount Some(perm) if perm.trfer_allowance_perm < amount => { return Err(StdError::generic_err(format!( "Insufficient transfer allowance: {}", perm.trfer_allowance_perm - ))) + ))); } // success, so need to reduce allowance Some(mut perm) if perm.trfer_allowance_perm >= amount => { @@ -1259,7 +1265,7 @@ fn impl_transfer( true => { return Err(StdError::generic_err( "These tokens do not exist or you have no permission to transfer", - )) + )); } false => (), } @@ -1379,7 +1385,7 @@ fn exec_change_balance( Err(_e) => { return Err(StdError::generic_err( "total supply exceeds max allowed of 2^128", - )) + )); } }; tkn_tot_supply_w(storage).save(token_info.token_id.as_bytes(), &new_amount)?; @@ -1495,7 +1501,9 @@ fn permit_queries(deps: Deps, permit: Permit, query: QueryWithPermit) -> Result< if account != owner.as_str() && account != allowed_address.as_str() { return Err(StdError::generic_err(format!( "Cannot query permission. Requires permit for either owner {:?} or viewer||spender {:?}, got permit for {:?}", - owner.as_str(), allowed_address.as_str(), account.as_str() + owner.as_str(), + allowed_address.as_str(), + account.as_str() ))); } @@ -1658,7 +1666,7 @@ fn query_token_id_private_info(deps: Deps, viewer: &Addr, token_id: String) -> S None => { return Err(StdError::generic_err( "you do have have permission to view private token info", - )) + )); } Some(perm) => { let block: BlockInfo = @@ -1711,7 +1719,7 @@ fn query_balance(deps: Deps, owner: &Addr, viewer: &Addr, token_id: String) -> S None => { return Err(StdError::generic_err( "you do have have permission to view balance", - )) + )); } Some(perm) => { let block: BlockInfo = diff --git a/contracts/liquidity_book/lb_token/src/unittest/handletests.rs b/contracts/liquidity_book/lb_token/src/unittest/handletests.rs index 54d0dd188..7acc56dbd 100755 --- a/contracts/liquidity_book/lb_token/src/unittest/handletests.rs +++ b/contracts/liquidity_book/lb_token/src/unittest/handletests.rs @@ -9,8 +9,10 @@ use super::super::{ // state::{expiration::*, metadata::*, permissions::*, state_structs::*}, }; -use shade_protocol::lb_libraries::lb_token::{expiration::*, permissions::*, state_structs::*}; -use shade_protocol::liquidity_book::lb_token::*; +use shade_protocol::{ + lb_libraries::lb_token::{expiration::*, permissions::*, state_structs::*}, + liquidity_book::lb_token::*, +}; use cosmwasm_std::{from_binary, testing::*, to_binary, Addr, Response, StdResult, Uint256}; use secret_toolkit::{crypto::sha_256, permit::RevokedPermits}; @@ -871,8 +873,10 @@ fn test_transfer() -> StdResult<()> { // cannot transfer if not owner info.sender = addr2.clone(); let result = execute(deps.as_mut(), mock_env(), info.clone(), msg); - assert!(extract_error_msg(&result) - .contains("These tokens do not exist or you have no permission to transfer")); + assert!( + extract_error_msg(&result) + .contains("These tokens do not exist or you have no permission to transfer") + ); // transfer NFT "tkn2"; amount != 1 info.sender = addr2.clone(); @@ -1125,8 +1129,10 @@ fn test_batch_transfer_and_send_errors() -> StdResult<()> { }; let info = mock_info("addr0", &[]); let result = execute(deps.as_mut(), mock_env(), info, msg_batch_trans); - assert!(extract_error_msg(&result) - .contains("These tokens do not exist or you have no permission to transfer")); + assert!( + extract_error_msg(&result) + .contains("These tokens do not exist or you have no permission to transfer") + ); Ok(()) } @@ -1164,8 +1170,10 @@ fn test_transfer_permissions_fungible() -> StdResult<()> { padding: None, }; let mut result = execute(deps.as_mut(), mock_env(), info.clone(), msg_trnsf_0.clone()); - assert!(extract_error_msg(&result) - .contains("These tokens do not exist or you have no permission to transfer")); + assert!( + extract_error_msg(&result) + .contains("These tokens do not exist or you have no permission to transfer") + ); // cannot transfer with insufficient allowance info.sender = addr0.clone(); @@ -1325,8 +1333,10 @@ fn test_transfer_permissions_nft() -> StdResult<()> { info.clone(), msg1_trnsf_0.clone(), ); - assert!(extract_error_msg(&result) - .contains("These tokens do not exist or you have no permission to transfer")); + assert!( + extract_error_msg(&result) + .contains("These tokens do not exist or you have no permission to transfer") + ); assert_eq!( chk_bal(&deps.storage, "2", &addr2).unwrap(), Uint256::from(1u128) @@ -1437,8 +1447,10 @@ fn test_transfer_permissions_nft() -> StdResult<()> { padding: None, }; result = execute(deps.as_mut(), mock_env(), info, msg); - assert!(extract_error_msg(&result) - .contains("These tokens do not exist or you have no permission to transfer")); + assert!( + extract_error_msg(&result) + .contains("These tokens do not exist or you have no permission to transfer") + ); assert_eq!(chk_bal(&deps.storage, "2a", &addr1), None); assert_eq!( chk_bal(&deps.storage, "2a", &addr0).unwrap(), @@ -1471,23 +1483,18 @@ fn test_revoke_permission_sanity() -> StdResult<()> { let mut info = mock_info("addr0", &[]); execute(deps.as_mut(), mock_env(), info.clone(), msg0_perm_b)?; - let vks = generate_viewing_keys( - &mut deps, - mock_env(), - info.clone(), - vec![addr.a(), addr.b()], - )?; + let vks = generate_viewing_keys(&mut deps, mock_env(), info.clone(), vec![ + addr.a(), + addr.b(), + ])?; - let q_answer = from_binary::(&query( - deps.as_ref(), - mock_env(), - QueryMsg::Permission { + let q_answer = + from_binary::(&query(deps.as_ref(), mock_env(), QueryMsg::Permission { owner: addr.a(), allowed_address: addr.b(), key: vks.a(), token_id: "0".to_string(), - }, - )?)?; + })?)?; match q_answer { QueryAnswer::Permission(perm) => { assert_eq!(perm.unwrap().trfer_allowance_perm, Uint256::from(10u128)) @@ -1504,16 +1511,13 @@ fn test_revoke_permission_sanity() -> StdResult<()> { }; info.sender = addr.b(); execute(deps.as_mut(), mock_env(), info, msg_revoke)?; - let q_answer = from_binary::(&query( - deps.as_ref(), - mock_env(), - QueryMsg::Permission { + let q_answer = + from_binary::(&query(deps.as_ref(), mock_env(), QueryMsg::Permission { owner: addr.a(), allowed_address: addr.b(), key: vks.a(), token_id: "0".to_string(), - }, - )?)?; + })?)?; match q_answer { QueryAnswer::Permission(perm) => { assert_eq!(perm.unwrap().trfer_allowance_perm, Uint256::from(0u128)) diff --git a/contracts/liquidity_book/router/Cargo.toml b/contracts/liquidity_book/router/Cargo.toml index 3f7cac7b9..a559b0210 100644 --- a/contracts/liquidity_book/router/Cargo.toml +++ b/contracts/liquidity_book/router/Cargo.toml @@ -29,11 +29,10 @@ serde = { version = "1.0.114", default-features = false, features = [ "alloc" ] } schemars = "0.8.1" -cosmwasm-std = { version = "1.0.0", package = "secret-cosmwasm-std" } -cosmwasm-storage = { version = "1.0.0", package = "secret-cosmwasm-storage" } -shadeswap-shared = { git = "https://github.com/securesecrets/stableswap",version = "0.1.0", package = "shadeswap-shared" } +cosmwasm-std = { version = "1.0.0", package = "secret-cosmwasm-std" , features=["iterator"]} +cosmwasm-storage = { version = "1.0.0", package = "secret-cosmwasm-storage" , features=["iterator"]} +shadeswap-shared = { version = "0.1.0", path = "../../../packages/shadeswap_shared", features = [] } shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [] } [dev-dependencies] cosmwasm-schema = { git = "https://github.com/CosmWasm/cosmwasm"} -secret-multi-test = { git = "https://github.com/securesecrets/secret-plus-utils", version = "0.13.4" } -multi_test = { git = "https://github.com/securesecrets/stableswap", package = "multi_test" } \ No newline at end of file +secret-multi-test = { git = "https://github.com/securesecrets/secret-plus-utils", version = "0.13.4", features=["iterator"] } diff --git a/contracts/liquidity_book/router/src/contract.rs b/contracts/liquidity_book/router/src/contract.rs index 010515e02..9fae0dabb 100644 --- a/contracts/liquidity_book/router/src/contract.rs +++ b/contracts/liquidity_book/router/src/contract.rs @@ -1,23 +1,36 @@ -use cosmwasm_std::{ - entry_point, from_binary, to_binary, Addr, BankMsg, Binary, Coin, CosmosMsg, Deps, DepsMut, - Env, MessageInfo, Reply, Response, StdError, StdResult, Uint128, -}; -use shade_protocol::Contract; -use shadeswap_shared::admin::helpers::{validate_admin, AdminPermissions}; -use shadeswap_shared::router::{InitMsg, QueryMsgResponse}; -use shadeswap_shared::snip20::helpers::send_msg; -use shadeswap_shared::utils::{pad_handle_result, pad_query_result}; -use shadeswap_shared::{ - amm_pair::QueryMsgResponse as AMMPairQueryReponse, - core::{TokenAmount, TokenType}, - router::{ExecuteMsg, InvokeMsg, QueryMsg}, -}; - use crate::{ operations::{next_swap, refresh_tokens, swap_tokens_for_exact_tokens}, query, state::{config_r, config_w, registered_tokens_list_r, registered_tokens_list_w, Config}, }; +use cosmwasm_std::{ + entry_point, + from_binary, + to_binary, + Addr, + BankMsg, + Binary, + Coin, + CosmosMsg, + Deps, + DepsMut, + Env, + MessageInfo, + Reply, + Response, + StdError, + StdResult, + Uint128, +}; +use shade_protocol::{utils::liquidity_book::tokens::TokenType, Contract}; +use shadeswap_shared::{ + admin::helpers::{validate_admin, AdminPermissions}, + amm_pair::QueryMsgResponse as AMMPairQueryReponse, + core::TokenAmount, + router::{ExecuteMsg, InitMsg, InvokeMsg, QueryMsg, QueryMsgResponse}, + snip20::helpers::send_msg, + utils::{pad_handle_result, pad_query_result}, +}; /// Pad handle responses and log attributes to blocks /// of 256 bytes to prevent leaking info based on response size @@ -164,13 +177,11 @@ fn receiver_callback( path, recipient, } => { - let pair_contract_config = query::pair_contract_config( - &deps.querier, - Contract { + let pair_contract_config = + query::pair_contract_config(&deps.querier, Contract { address: deps.api.addr_validate(&path[0].addr.to_string())?, code_hash: path[0].code_hash.clone(), - }, - )?; + })?; match pair_contract_config { AMMPairQueryReponse::GetPairInfo { @@ -220,7 +231,7 @@ fn receiver_callback( return Err(StdError::generic_err(format!( "Could not retrieve PairInfo from {}", &path[0].addr - ))) + ))); } } } diff --git a/contracts/liquidity_book/router/src/operations.rs b/contracts/liquidity_book/router/src/operations.rs index b5fd747e3..0a12505a0 100644 --- a/contracts/liquidity_book/router/src/operations.rs +++ b/contracts/liquidity_book/router/src/operations.rs @@ -2,9 +2,9 @@ use cosmwasm_std::{ to_binary, Addr, Coin, CosmosMsg, DepsMut, Env, Response, StdError, StdResult, Storage, SubMsg, Uint128, WasmMsg, }; -use shade_protocol::Contract; +use shade_protocol::{utils::liquidity_book::tokens::TokenType, Contract}; use shadeswap_shared::{ - core::{TokenAmount, TokenType}, + core::TokenAmount, msg::amm_pair::{ ExecuteMsg as AMMPairExecuteMsg, InvokeMsg as AMMPairInvokeMsg, QueryMsgResponse as AMMPairQueryReponse, diff --git a/contracts/liquidity_book/router/src/query.rs b/contracts/liquidity_book/router/src/query.rs index 2380c131d..b32a1c5aa 100644 --- a/contracts/liquidity_book/router/src/query.rs +++ b/contracts/liquidity_book/router/src/query.rs @@ -4,12 +4,10 @@ use cosmwasm_std::{ to_binary, Binary, Deps, QuerierWrapper, QueryRequest, StdError, StdResult, Uint128, Uint256, WasmQuery, }; -use shade_protocol::Contract; +use shade_protocol::{liquidity_book::lb_pair::SwapResult, Contract}; use shadeswap_shared::{ core::TokenAmount, - msg::amm_pair::{ - QueryMsg as AMMPairQueryMsg, QueryMsgResponse as AMMPairQueryReponse, SwapResult, - }, + msg::amm_pair::{QueryMsg as AMMPairQueryMsg, QueryMsgResponse as AMMPairQueryReponse}, router::{Hop, QueryMsgResponse}, }; diff --git a/contracts/liquidity_book/router/src/state.rs b/contracts/liquidity_book/router/src/state.rs index a7e838b46..696e2fd4c 100644 --- a/contracts/liquidity_book/router/src/state.rs +++ b/contracts/liquidity_book/router/src/state.rs @@ -12,9 +12,9 @@ use cosmwasm_storage::Singleton; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; +use shade_protocol::utils::liquidity_book::tokens::TokenType; use shade_protocol::Contract; use shadeswap_shared::core::TokenAmount; -use shadeswap_shared::core::TokenType; use shadeswap_shared::router::Hop; pub static CONFIG: &[u8] = b"config"; diff --git a/contracts/liquidity_book/router/src/test.rs b/contracts/liquidity_book/router/src/test.rs index 50ed6f455..88f390dd1 100644 --- a/contracts/liquidity_book/router/src/test.rs +++ b/contracts/liquidity_book/router/src/test.rs @@ -25,6 +25,7 @@ pub mod tests { use serde::Deserialize; use serde::Serialize; use shade_protocol::snip20::Snip20ReceiveMsg; + use shade_protocol::utils::liquidity_book::tokens::TokenType; use shadeswap_shared::admin::ValidateAdminPermissionResponse; use shadeswap_shared::amm_pair::FeeInfo; use shadeswap_shared::core::TokenPair; @@ -46,7 +47,6 @@ pub mod tests { use shadeswap_shared::core::ContractInstantiationInfo; use shadeswap_shared::core::Fee; use shadeswap_shared::core::TokenAmount; - use shadeswap_shared::core::TokenType; use shadeswap_shared::router::ExecuteMsg; use shadeswap_shared::router::Hop; use shadeswap_shared::router::InitMsg; diff --git a/contracts/liquidity_book/router/tests/integration.rs b/contracts/liquidity_book/router/tests/integration.rs deleted file mode 100644 index d76cc2f6e..000000000 --- a/contracts/liquidity_book/router/tests/integration.rs +++ /dev/null @@ -1,879 +0,0 @@ -use cosmwasm_std::{to_binary, Addr, Empty}; -use secret_multi_test::{App, Contract, ContractWrapper, Executor}; -use shadeswap_shared::msg::router::{ExecuteMsg, InitMsg, QueryMsg}; - -#[cfg(not(target_arch = "wasm32"))] -#[test] -#[ignore = "broken"] -pub fn router_integration_tests() { - use cosmwasm_std::{Coin, ContractInfo, Uint128}; - use multi_test::admin::admin_help::init_admin_contract; - use multi_test::amm_pairs::amm_pairs_lib::amm_pairs_lib::{ - add_liquidity_to_amm_pairs, amm_pair_contract_store_in, create_amm_settings, - }; - use multi_test::help_lib::integration_help_lib::{ - configure_block_send_init_funds, convert_to_contract_link, create_token_pair, - create_token_pair_with_native, increase_allowance, mint_deposit_snip20, roll_blockchain, - set_viewing_key, snip20_lp_token_contract_store, snip_20_balance_query, TestingExt, - }; - use multi_test::staking::staking_lib::staking_lib::{ - create_staking_info_contract, staking_contract_store_in, - }; - use multi_test::util_addr::util_addr::{OWNER, STAKER_A}; - use router::contract::{execute, instantiate, query, reply}; - - use shade_protocol::snip20; - use shade_protocol::utils::asset::RawContract; - use shadeswap_shared::core::{ContractInstantiationInfo, TokenAmount}; - use shadeswap_shared::msg::amm_pair::InvokeMsg; - use shadeswap_shared::msg::router::QueryMsgResponse; - use shadeswap_shared::router::Hop; - - use multi_test::help_lib::integration_help_lib::generate_snip20_contract; - use shadeswap_shared::core::TokenType; - - use multi_test::factory::factory_lib::factory_lib::{ - create_amm_pairs_to_factory, init_factory, list_amm_pairs_from_factory, - }; - - pub fn router_contract_store() -> Box> { - let contract = - ContractWrapper::new_with_empty(execute, instantiate, query).with_reply(reply); - Box::new(contract) - } - - let staker_a_addr = Addr::unchecked(STAKER_A.to_owned()); - let owner_addr = Addr::unchecked(OWNER); - let mut router = App::default(); - - configure_block_send_init_funds(&mut router, &owner_addr, Uint128::new(100000000000000u128)); - // GENERATE TOKEN PAIRS & REWARD TOKEN - let token_0_contract = generate_snip20_contract( - &mut router, - "ETH".to_string(), - "ETH".to_string(), - 18, - &owner_addr, - ) - .unwrap(); - let token_1_contract = generate_snip20_contract( - &mut router, - "USDT".to_string(), - "USDT".to_string(), - 18, - &owner_addr, - ) - .unwrap(); - let reward_contract = generate_snip20_contract( - &mut router, - "RWD".to_string(), - "RWD".to_string(), - 18, - &owner_addr, - ) - .unwrap(); - - // MINT AND DEPOSIT FOR LIQUIDITY - mint_deposit_snip20( - &mut router, - &token_0_contract, - &owner_addr, - Uint128::new(10000000000u128), - &owner_addr, - ); - mint_deposit_snip20( - &mut router, - &token_1_contract, - &owner_addr, - Uint128::new(10000000000u128), - &owner_addr, - ); - mint_deposit_snip20( - &mut router, - &reward_contract, - &owner_addr, - Uint128::new(10000000000u128), - &owner_addr, - ); - - roll_blockchain(&mut router, 1).unwrap(); - - // INIT LP, STAKING, AMM PAIRS - let admin_contract = init_admin_contract(&mut router, &owner_addr).unwrap(); //store_init_factory_contract(&mut router, &convert_to_contract_link(&admin_contract)).unwrap(); - let amm_contract_info = router.store_code(amm_pair_contract_store_in()); - let lp_token_info = router.store_code(snip20_lp_token_contract_store()); - let staking_info = router.store_code(staking_contract_store_in()); - - // STORE ROUTER CONTRACT - let router_contract_info = router.store_code(router_contract_store()); - roll_blockchain(&mut router, 1).unwrap(); - - // INIT ROUTER CONTRACTs - let init_msg = InitMsg { - prng_seed: to_binary("password").unwrap(), - entropy: to_binary("password").unwrap(), - admin_auth: convert_to_contract_link(&admin_contract), - airdrop_address: None, - }; - - roll_blockchain(&mut router, 1).unwrap(); - let router_contract = router - .instantiate_contract( - router_contract_info, - owner_addr.to_owned(), - &init_msg, - &[], - "router", - Some(OWNER.to_string()), - ) - .unwrap(); - - // CREATE FACTORY - roll_blockchain(&mut router, 1).unwrap(); - let factory_contract = init_factory( - &mut router, - &convert_to_contract_link(&admin_contract), - &OWNER, - false, - create_amm_settings(3, 100, 8, 100, &staker_a_addr), - ContractInstantiationInfo { - code_hash: amm_contract_info.code_hash.clone(), - id: amm_contract_info.code_id, - }, - ContractInstantiationInfo { - code_hash: lp_token_info.code_hash.clone(), - id: lp_token_info.code_id, - }, - "seed", - "api_key", - None, - ) - .unwrap(); - - // CREATE AMM_PAIR SNIP20 vs SNIP20 - create_amm_pairs_to_factory( - &mut router, - &factory_contract, - &create_token_pair( - &convert_to_contract_link(&token_0_contract), - &convert_to_contract_link(&token_1_contract), - false, - ), - "seed", - &create_staking_info_contract( - staking_info.code_id, - &staking_info.code_hash, - Uint128::new(30000u128), - RawContract { - address: reward_contract.address.to_string(), - code_hash: reward_contract.code_hash.clone(), - }, - 30000000000u64, - None, - ), - &router_contract, - 18u8, - &owner_addr, - None, - None, - None, - ) - .unwrap(); - - // LIST AMM PAIR - let amm_pairs = list_amm_pairs_from_factory(&mut router, &factory_contract, 0, 30).unwrap(); - - // ASSERT AMM PAIRS == 1 - assert_eq!(amm_pairs.len(), 1); - - // INCREASE ALLOWANCE FOR AMM PAIR - increase_allowance( - &mut router, - &token_0_contract, - Uint128::new(10000000000000000u128), - &amm_pairs[0].address, - &owner_addr, - ) - .unwrap(); - increase_allowance( - &mut router, - &token_1_contract, - Uint128::new(10000000000000000u128), - &amm_pairs[0].address, - &owner_addr, - ) - .unwrap(); - - // ADD LIQUIDITY TO AMM_PAIR SNIP20 vs SNIP20 - add_liquidity_to_amm_pairs( - &mut router, - &ContractInfo { - address: amm_pairs[0].address.clone(), - code_hash: "".to_string(), - }, - &amm_pairs[0].pair, - Uint128::new(1000000000u128), - Uint128::new(1000000000u128), - Some(Uint128::new(1000000000u128)), - Some(true), - &owner_addr, - &[], - ) - .unwrap(); - - // REGISTER SNIP 20 ROUTER - roll_blockchain(&mut router, 1).unwrap(); - let msg = ExecuteMsg::RegisterSNIP20Token { - token_addr: token_0_contract.address.to_string(), - token_code_hash: token_0_contract.code_hash.to_owned(), - oracle_key: None, - padding: None, - }; - roll_blockchain(&mut router, 1).unwrap(); - let _ = router - .execute_contract(owner_addr.to_owned(), &router_contract, &msg, &[]) - .unwrap(); - - roll_blockchain(&mut router, 1).unwrap(); - let msg = ExecuteMsg::RegisterSNIP20Token { - token_addr: token_1_contract.address.to_string(), - token_code_hash: token_1_contract.code_hash.to_owned(), - oracle_key: None, - padding: None, - }; - roll_blockchain(&mut router, 1).unwrap(); - let _ = router - .execute_contract(owner_addr.to_owned(), &router_contract, &msg, &[]) - .unwrap(); - - roll_blockchain(&mut router, 1).unwrap(); - // SWAPSIMULATION - QUERY - let offer = TokenAmount { - token: TokenType::CustomToken { - contract_addr: token_0_contract.address.to_owned(), - token_code_hash: token_0_contract.code_hash.to_owned(), - }, - amount: Uint128::new(1000u128), - }; - let swap_query = QueryMsg::SwapSimulation { - offer: offer.to_owned(), - path: vec![Hop { - addr: amm_pairs[0].address.to_string(), - code_hash: amm_contract_info.code_hash.clone(), - }], - exclude_fee: None, - }; - - // ASSERT SWAPSIMULATION - let query_response: QueryMsgResponse = router - .query_test(router_contract.to_owned(), to_binary(&swap_query).unwrap()) - .unwrap(); - - match query_response { - QueryMsgResponse::SwapSimulation { - total_fee_amount, - lp_fee_amount, - shade_dao_fee_amount, - result, - price, - } => { - // Verify result not actual amount - assert_ne!(total_fee_amount, Uint128::zero()); - assert_ne!(lp_fee_amount, Uint128::zero()); - assert_ne!(shade_dao_fee_amount, Uint128::zero()); - assert_ne!(result.return_amount, Uint128::zero()); - assert_eq!(price, "1".to_string()); - } - _ => panic!("Query Responsedoes not match"), - } - - // ASSERT SWAPTOKENS - roll_blockchain(&mut router, 1).unwrap(); - let invoke_msg = to_binary(&InvokeMsg::SwapTokens { - expected_return: Some(Uint128::new(100u128)), - to: Some(staker_a_addr.to_string()), - padding: None, - }) - .unwrap(); - - let msg = snip20::ExecuteMsg::Send { - recipient: amm_pairs[0].address.to_owned().to_string(), - recipient_code_hash: Some(amm_contract_info.code_hash.clone()), - amount: Uint128::new(1000u128), - msg: Some(invoke_msg), - memo: None, - padding: None, - }; - - let _response = router - .execute_contract( - owner_addr.to_owned(), - &token_0_contract, - &msg, - &[], // - ) - .unwrap(); - - // ASSERT SWAPTOKENSFOREXACT - roll_blockchain(&mut router, 1).unwrap(); - let execute_swap = ExecuteMsg::SwapTokensForExact { - offer: offer.to_owned(), - expected_return: Some(Uint128::new(1000u128)), - path: vec![Hop { - addr: amm_pairs[0].address.to_string(), - code_hash: amm_contract_info.code_hash.clone(), - }], - recipient: Some(owner_addr.to_string()), - padding: None, - }; - - let _response = - router.execute_contract(owner_addr.to_owned(), &router_contract, &execute_swap, &[]); - - // ASSERT BALANCE TOKEN_1 - let balance = - snip_20_balance_query(&mut router, &owner_addr, "seed", &token_1_contract).unwrap(); - assert_eq!(balance, Uint128::new(1000019000000000u128)); - - // CREATE AMM_PAIR NATIVE - SNIP20 - create_amm_pairs_to_factory( - &mut router, - &factory_contract, - &create_token_pair_with_native(&convert_to_contract_link(&token_1_contract)), - "seed", - &create_staking_info_contract( - staking_info.code_id, - &staking_info.code_hash, - Uint128::new(30000u128), - RawContract { - address: reward_contract.address.to_string(), - code_hash: reward_contract.code_hash.clone(), - }, - 30000000000u64, - None, - ), - &router_contract, - 18u8, - &owner_addr, - None, - None, - None, - ) - .unwrap(); - - // LIST AMM PAIR - let amm_pairs = list_amm_pairs_from_factory(&mut router, &factory_contract, 0, 30).unwrap(); - - // ASSERT AMM PAIRS == 2 - assert_eq!(amm_pairs.len(), 2); - increase_allowance( - &mut router, - &token_1_contract, - Uint128::new(10000000000000000u128), - &amm_pairs[1].address, - &owner_addr, - ) - .unwrap(); - // ADD LIQUIDITY TO AMM_PAIR NATIVE vs SNIP20 - add_liquidity_to_amm_pairs( - &mut router, - &ContractInfo { - address: amm_pairs[1].address.clone(), - code_hash: "".to_string(), - }, - &amm_pairs[1].pair, - Uint128::new(1000000000u128), - Uint128::new(1000000000u128), - Some(Uint128::new(1000000000u128)), - Some(true), - &owner_addr, - &[Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(1000000000u128), - }], - ) - .unwrap(); - roll_blockchain(&mut router, 1).unwrap(); - - // SWAP NATIVE TOKEN -> SNIP20 - let native_offer = TokenAmount { - token: TokenType::NativeToken { - denom: "uscrt".to_string(), - }, - amount: Uint128::new(1000u128), - }; - let _ = router - .send_tokens( - owner_addr.clone(), - staker_a_addr.clone(), - &[Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(1000), - }], - ) - .unwrap(); - let execute_swap = ExecuteMsg::SwapTokensForExact { - offer: native_offer.to_owned(), - expected_return: Some(Uint128::new(100u128)), - path: vec![Hop { - addr: amm_pairs[1].address.to_string(), - code_hash: amm_contract_info.code_hash.clone(), - }], - recipient: None, - padding: None, - }; - - let _response = router.execute_contract( - staker_a_addr.to_owned(), - &router_contract, - &execute_swap, - &[Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(1000u128), - }], - ); - - // ASSERT BALANCE TOKEN_1 889 - let _ = set_viewing_key(&mut router, &token_1_contract, "password", &staker_a_addr).unwrap(); - let balance = - snip_20_balance_query(&mut router, &staker_a_addr, "password", &token_1_contract).unwrap(); - assert_eq!(balance, Uint128::new(970u128)); -} - -#[cfg(not(target_arch = "wasm32"))] -#[test] -#[ignore = "broken"] -pub fn router_integration_tests_stable() { - use cosmwasm_std::{Coin, ContractInfo, Uint128}; - use multi_test::admin::admin_help::init_admin_contract; - use multi_test::amm_pairs::amm_pairs_lib::amm_pairs_lib::{ - add_liquidity_to_amm_pairs, amm_pair_contract_store_in, create_amm_settings, - }; - use multi_test::help_lib::integration_help_lib::{ - configure_block_send_init_funds, convert_to_contract_link, create_token_pair, - create_token_pair_with_native, increase_allowance, mint_deposit_snip20, roll_blockchain, - set_viewing_key, snip20_lp_token_contract_store, snip_20_balance_query, TestingExt, - }; - use multi_test::staking::staking_lib::staking_lib::{ - create_staking_info_contract, staking_contract_store_in, - }; - use multi_test::util_addr::util_addr::{OWNER, STAKER_A}; - use router::contract::{execute, instantiate, query, reply}; - - use shade_protocol::snip20; - use shade_protocol::utils::asset::RawContract; - use shadeswap_shared::core::{ContractInstantiationInfo, TokenAmount}; - use shadeswap_shared::msg::amm_pair::InvokeMsg; - use shadeswap_shared::msg::router::QueryMsgResponse; - use shadeswap_shared::router::Hop; - - use multi_test::help_lib::integration_help_lib::generate_snip20_contract; - use shadeswap_shared::core::TokenType; - - use multi_test::factory::factory_lib::factory_lib::{ - create_amm_pairs_to_factory, init_factory, list_amm_pairs_from_factory, - }; - - pub fn router_contract_store() -> Box> { - let contract = - ContractWrapper::new_with_empty(execute, instantiate, query).with_reply(reply); - Box::new(contract) - } - - let staker_a_addr = Addr::unchecked(STAKER_A.to_owned()); - let owner_addr = Addr::unchecked(OWNER); - let mut router = App::default(); - - configure_block_send_init_funds(&mut router, &owner_addr, Uint128::new(100000000000000u128)); - // GENERATE TOKEN PAIRS & REWARD TOKEN - let token_0_contract = generate_snip20_contract( - &mut router, - "ETH".to_string(), - "ETH".to_string(), - 18, - &owner_addr, - ) - .unwrap(); - let token_1_contract = generate_snip20_contract( - &mut router, - "USDT".to_string(), - "USDT".to_string(), - 18, - &owner_addr, - ) - .unwrap(); - let reward_contract = generate_snip20_contract( - &mut router, - "RWD".to_string(), - "RWD".to_string(), - 18, - &owner_addr, - ) - .unwrap(); - - // MINT AND DEPOSIT FOR LIQUIDITY - mint_deposit_snip20( - &mut router, - &token_0_contract, - &owner_addr, - Uint128::new(10000000000u128), - &owner_addr, - ); - mint_deposit_snip20( - &mut router, - &token_1_contract, - &owner_addr, - Uint128::new(10000000000u128), - &owner_addr, - ); - mint_deposit_snip20( - &mut router, - &reward_contract, - &owner_addr, - Uint128::new(10000000000u128), - &owner_addr, - ); - - roll_blockchain(&mut router, 1).unwrap(); - - // INIT LP, STAKING, AMM PAIRS - let admin_contract = init_admin_contract(&mut router, &owner_addr).unwrap(); //store_init_factory_contract(&mut router, &convert_to_contract_link(&admin_contract)).unwrap(); - let amm_contract_info = router.store_code(amm_pair_contract_store_in()); - let lp_token_info = router.store_code(snip20_lp_token_contract_store()); - let staking_info = router.store_code(staking_contract_store_in()); - - // STORE ROUTER CONTRACT - let router_contract_info = router.store_code(router_contract_store()); - roll_blockchain(&mut router, 1).unwrap(); - - // INIT ROUTER CONTRACTs - let init_msg = InitMsg { - prng_seed: to_binary("password").unwrap(), - entropy: to_binary("password").unwrap(), - admin_auth: convert_to_contract_link(&admin_contract), - airdrop_address: None, - }; - - roll_blockchain(&mut router, 1).unwrap(); - let router_contract = router - .instantiate_contract( - router_contract_info, - owner_addr.to_owned(), - &init_msg, - &[], - "router", - Some(OWNER.to_string()), - ) - .unwrap(); - - // CREATE FACTORY - roll_blockchain(&mut router, 1).unwrap(); - let factory_contract = init_factory( - &mut router, - &convert_to_contract_link(&admin_contract), - &OWNER, - false, - create_amm_settings(3, 100, 8, 100, &staker_a_addr), - ContractInstantiationInfo { - code_hash: amm_contract_info.code_hash.clone(), - id: amm_contract_info.code_id, - }, - ContractInstantiationInfo { - code_hash: lp_token_info.code_hash.clone(), - id: lp_token_info.code_id, - }, - "seed", - "api_key", - None, - ) - .unwrap(); - - // CREATE AMM_PAIR SNIP20 vs SNIP20 - create_amm_pairs_to_factory( - &mut router, - &factory_contract, - &create_token_pair( - &convert_to_contract_link(&token_0_contract), - &convert_to_contract_link(&token_1_contract), - false, - ), - "seed", - &create_staking_info_contract( - staking_info.code_id, - &staking_info.code_hash, - Uint128::new(30000u128), - RawContract { - address: reward_contract.address.to_string(), - code_hash: reward_contract.code_hash.clone(), - }, - 30000000000u64, - None, - ), - &router_contract, - 18u8, - &owner_addr, - None, - None, - None, - ) - .unwrap(); - - // LIST AMM PAIR - let amm_pairs = list_amm_pairs_from_factory(&mut router, &factory_contract, 0, 30).unwrap(); - - // ASSERT AMM PAIRS == 1 - assert_eq!(amm_pairs.len(), 1); - - // INCREASE ALLOWANCE FOR AMM PAIR - increase_allowance( - &mut router, - &token_0_contract, - Uint128::new(10000000000000000u128), - &amm_pairs[0].address, - &owner_addr, - ) - .unwrap(); - increase_allowance( - &mut router, - &token_1_contract, - Uint128::new(10000000000000000u128), - &amm_pairs[0].address, - &owner_addr, - ) - .unwrap(); - - // ADD LIQUIDITY TO AMM_PAIR SNIP20 vs SNIP20 - add_liquidity_to_amm_pairs( - &mut router, - &ContractInfo { - address: amm_pairs[0].address.clone(), - code_hash: "".to_string(), - }, - &amm_pairs[0].pair, - Uint128::new(1000000000u128), - Uint128::new(1000000000u128), - Some(Uint128::new(1000000000u128)), - Some(true), - &owner_addr, - &[], - ) - .unwrap(); - - // REGISTER SNIP 20 ROUTER - roll_blockchain(&mut router, 1).unwrap(); - let msg = ExecuteMsg::RegisterSNIP20Token { - token_addr: token_0_contract.address.to_string(), - token_code_hash: token_0_contract.code_hash.to_owned(), - oracle_key: None, - padding: None, - }; - roll_blockchain(&mut router, 1).unwrap(); - let _ = router - .execute_contract(owner_addr.to_owned(), &router_contract, &msg, &[]) - .unwrap(); - - roll_blockchain(&mut router, 1).unwrap(); - let msg = ExecuteMsg::RegisterSNIP20Token { - token_addr: token_1_contract.address.to_string(), - token_code_hash: token_1_contract.code_hash.to_owned(), - oracle_key: None, - padding: None, - }; - roll_blockchain(&mut router, 1).unwrap(); - let _ = router - .execute_contract(owner_addr.to_owned(), &router_contract, &msg, &[]) - .unwrap(); - - roll_blockchain(&mut router, 1).unwrap(); - // SWAPSIMULATION - QUERY - let offer = TokenAmount { - token: TokenType::CustomToken { - contract_addr: token_0_contract.address.to_owned(), - token_code_hash: token_0_contract.code_hash.to_owned(), - }, - amount: Uint128::new(1000u128), - }; - let swap_query = QueryMsg::SwapSimulation { - offer: offer.to_owned(), - path: vec![Hop { - addr: amm_pairs[0].address.to_string(), - code_hash: amm_contract_info.code_hash.clone(), - }], - exclude_fee: None, - }; - - // ASSERT SWAPSIMULATION - let query_response: QueryMsgResponse = router - .query_test(router_contract.to_owned(), to_binary(&swap_query).unwrap()) - .unwrap(); - - match query_response { - QueryMsgResponse::SwapSimulation { - total_fee_amount, - lp_fee_amount, - shade_dao_fee_amount, - result, - price, - } => { - // Verify result not actual amount - assert_ne!(total_fee_amount, Uint128::zero()); - assert_ne!(lp_fee_amount, Uint128::zero()); - assert_ne!(shade_dao_fee_amount, Uint128::zero()); - assert_ne!(result.return_amount, Uint128::zero()); - assert_eq!(price, "1".to_string()); - } - _ => panic!("Query Responsedoes not match"), - } - - // ASSERT SWAPTOKENS - roll_blockchain(&mut router, 1).unwrap(); - let invoke_msg = to_binary(&InvokeMsg::SwapTokens { - expected_return: Some(Uint128::new(100u128)), - to: Some(staker_a_addr.to_string()), - padding: None, - }) - .unwrap(); - - let msg = snip20::ExecuteMsg::Send { - recipient: amm_pairs[0].address.to_owned().to_string(), - recipient_code_hash: Some(amm_contract_info.code_hash.clone()), - amount: Uint128::new(1000u128), - msg: Some(invoke_msg), - memo: None, - padding: None, - }; - - let _response = router - .execute_contract( - owner_addr.to_owned(), - &token_0_contract, - &msg, - &[], // - ) - .unwrap(); - - // ASSERT SWAPTOKENSFOREXACT - roll_blockchain(&mut router, 1).unwrap(); - let execute_swap = ExecuteMsg::SwapTokensForExact { - offer: offer.to_owned(), - expected_return: Some(Uint128::new(1000u128)), - path: vec![Hop { - addr: amm_pairs[0].address.to_string(), - code_hash: amm_contract_info.code_hash.clone(), - }], - recipient: Some(owner_addr.to_string()), - padding: None, - }; - - let _response = - router.execute_contract(owner_addr.to_owned(), &router_contract, &execute_swap, &[]); - - // ASSERT BALANCE TOKEN_1 - let balance = - snip_20_balance_query(&mut router, &owner_addr, "seed", &token_1_contract).unwrap(); - assert_eq!(balance, Uint128::new(1000019000000000u128)); - - // CREATE AMM_PAIR NATIVE - SNIP20 - create_amm_pairs_to_factory( - &mut router, - &factory_contract, - &create_token_pair_with_native(&convert_to_contract_link(&token_1_contract)), - "seed", - &create_staking_info_contract( - staking_info.code_id, - &staking_info.code_hash, - Uint128::new(30000u128), - RawContract { - address: reward_contract.address.to_string(), - code_hash: reward_contract.code_hash.clone(), - }, - 30000000000u64, - None, - ), - &router_contract, - 18u8, - &owner_addr, - None, - None, - None, - ) - .unwrap(); - - // LIST AMM PAIR - let amm_pairs = list_amm_pairs_from_factory(&mut router, &factory_contract, 0, 30).unwrap(); - - // ASSERT AMM PAIRS == 2 - assert_eq!(amm_pairs.len(), 2); - increase_allowance( - &mut router, - &token_1_contract, - Uint128::new(10000000000000000u128), - &amm_pairs[1].address, - &owner_addr, - ) - .unwrap(); - // ADD LIQUIDITY TO AMM_PAIR NATIVE vs SNIP20 - add_liquidity_to_amm_pairs( - &mut router, - &ContractInfo { - address: amm_pairs[1].address.clone(), - code_hash: "".to_string(), - }, - &amm_pairs[1].pair, - Uint128::new(1000000000u128), - Uint128::new(1000000000u128), - Some(Uint128::new(1000000000u128)), - Some(true), - &owner_addr, - &[Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(1000000000u128), - }], - ) - .unwrap(); - roll_blockchain(&mut router, 1).unwrap(); - - // SWAP NATIVE TOKEN -> SNIP20 - let native_offer = TokenAmount { - token: TokenType::NativeToken { - denom: "uscrt".to_string(), - }, - amount: Uint128::new(1000u128), - }; - let _ = router - .send_tokens( - owner_addr.clone(), - staker_a_addr.clone(), - &[Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(1000), - }], - ) - .unwrap(); - let execute_swap = ExecuteMsg::SwapTokensForExact { - offer: native_offer.to_owned(), - expected_return: Some(Uint128::new(100u128)), - path: vec![Hop { - addr: amm_pairs[1].address.to_string(), - code_hash: amm_contract_info.code_hash.clone(), - }], - recipient: None, - padding: None, - }; - - let _response = router.execute_contract( - staker_a_addr.to_owned(), - &router_contract, - &execute_swap, - &[Coin { - denom: "uscrt".to_string(), - amount: Uint128::new(1000u128), - }], - ); - - // ASSERT BALANCE TOKEN_1 889 - let _ = set_viewing_key(&mut router, &token_1_contract, "password", &staker_a_addr).unwrap(); - let balance = - snip_20_balance_query(&mut router, &staker_a_addr, "password", &token_1_contract).unwrap(); - assert_eq!(balance, Uint128::new(889u128)); -} diff --git a/contracts/liquidity_book/router/tests/registered_tokens.rs b/contracts/liquidity_book/router/tests/registered_tokens.rs deleted file mode 100644 index cd975a93a..000000000 --- a/contracts/liquidity_book/router/tests/registered_tokens.rs +++ /dev/null @@ -1,161 +0,0 @@ -use cosmwasm_std::{to_binary, Addr, Empty}; -use secret_multi_test::{App, Contract, ContractWrapper, Executor}; -use shadeswap_shared::msg::router::{ExecuteMsg, InitMsg, QueryMsg}; - -#[test] -pub fn router_registered_tokens() { - use cosmwasm_std::{Coin, ContractInfo, Uint128}; - use multi_test::admin::admin_help::init_admin_contract; - use multi_test::amm_pairs::amm_pairs_lib::amm_pairs_lib::{ - add_liquidity_to_amm_pairs, amm_pair_contract_store_in, create_amm_settings, - }; - use multi_test::help_lib::integration_help_lib::{ - configure_block_send_init_funds, convert_to_contract_link, create_token_pair, - create_token_pair_with_native, increase_allowance, mint_deposit_snip20, set_viewing_key, - snip20_lp_token_contract_store, snip_20_balance_query, TestingExt, - }; - use multi_test::staking::staking_lib::staking_lib::{ - create_staking_info_contract, staking_contract_store_in, - }; - use multi_test::util_addr::util_addr::{OWNER, STAKER_A}; - use router::contract::{execute, instantiate, query, reply}; - - use shade_protocol::snip20; - use shade_protocol::utils::asset::RawContract; - use shadeswap_shared::core::{ContractInstantiationInfo, TokenAmount}; - use shadeswap_shared::msg::amm_pair::InvokeMsg; - use shadeswap_shared::msg::router::QueryMsgResponse; - use shadeswap_shared::router::Hop; - - use multi_test::help_lib::integration_help_lib::generate_snip20_contract; - use shadeswap_shared::core::TokenType; - - use multi_test::factory::factory_lib::factory_lib::{ - create_amm_pairs_to_factory, init_factory, list_amm_pairs_from_factory, - }; - - pub fn router_store() -> Box> { - let contract = - ContractWrapper::new_with_empty(execute, instantiate, query).with_reply(reply); - Box::new(contract) - } - - let owner = Addr::unchecked(OWNER); - let mut app = App::default(); - - configure_block_send_init_funds(&mut app, &owner, Uint128::new(100000000000000u128)); - // GENERATE TOKEN PAIRS & REWARD TOKEN - let token_0 = - generate_snip20_contract(&mut app, "ETH".to_string(), "ETH".to_string(), 18, &owner) - .unwrap(); - let token_1 = - generate_snip20_contract(&mut app, "USDT".to_string(), "USDT".to_string(), 18, &owner) - .unwrap(); - - // INIT LP, STAKING, AMM PAIRS - let admin_contract = init_admin_contract(&mut app, &owner).unwrap(); - let staking_info = app.store_code(staking_contract_store_in()); - - // STORE ROUTER CONTRACT - let router_info = app.store_code(router_store()); - - // INIT ROUTER CONTRACT - let router = app - .instantiate_contract( - router_info, - owner.to_owned(), - &InitMsg { - prng_seed: to_binary("password").unwrap(), - entropy: to_binary("password").unwrap(), - admin_auth: convert_to_contract_link(&admin_contract), - airdrop_address: None, - }, - &[], - "router", - Some(OWNER.to_string()), - ) - .unwrap(); - - match app - .query_test( - router.to_owned(), - to_binary(&QueryMsg::RegisteredTokens {}).unwrap(), - ) - .unwrap() - { - QueryMsgResponse::RegisteredTokens { tokens } => { - assert_eq!( - tokens, - Vec::::new(), - "Empty registered tokens after init" - ); - } - _ => { - panic!("Query Failed"); - } - } - - app.execute_contract( - owner.to_owned(), - &router, - &ExecuteMsg::RegisterSNIP20Token { - token_addr: token_0.address.to_string(), - token_code_hash: token_0.code_hash.to_owned(), - oracle_key: None, - padding: None, - }, - &[], - ) - .unwrap(); - - match app - .query_test( - router.to_owned(), - to_binary(&QueryMsg::RegisteredTokens {}).unwrap(), - ) - .unwrap() - { - QueryMsgResponse::RegisteredTokens { tokens } => { - assert_eq!( - tokens, - vec![token_0.address.clone()], - "First token registered successfully" - ); - } - _ => { - panic!("Query Failed"); - } - } - - app.execute_contract( - owner.to_owned(), - &router, - &ExecuteMsg::RegisterSNIP20Token { - token_addr: token_1.address.to_string(), - token_code_hash: token_1.code_hash.to_owned(), - oracle_key: None, - padding: None, - }, - &[], - ) - .unwrap(); - - match app - .query_test( - router.to_owned(), - to_binary(&QueryMsg::RegisteredTokens {}).unwrap(), - ) - .unwrap() - { - QueryMsgResponse::RegisteredTokens { tokens } => { - assert_eq!( - tokens, - vec![token_0.address, token_1.address], - "Second token registered successfully" - ); - } - _ => { - panic!("Query Failed"); - } - } -} diff --git a/contracts/liquidity_book/tests/Cargo.toml b/contracts/liquidity_book/tests/Cargo.toml new file mode 100644 index 000000000..612b77bc4 --- /dev/null +++ b/contracts/liquidity_book/tests/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "lb_tests" +version = "0.1.0" +edition = "2021" +exclude = [ + # Those files are contract-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = [] +# for quicker tests, cargo test --lib +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] + +[dependencies] +shade-protocol = { version = "0.1.0", path = "../../../packages/shade_protocol", features = [ + "liquidity_book","storage_plus","lb-libraries", +] } +cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.0.0", features=["iterator"] } +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0", features=["iterator"] } +cosmwasm-schema = "1.1.5" +# schemars = { version = "0.8" } +serde = { version = "1" } +serde-json-wasm = { version = "0.5"} +thiserror = { version = "1" } +secret-storage-plus = { git = "https://github.com/securesecrets/secret-plus-utils", version = "0.13.4", optional = true } +ethnum = { version = "1" } +rand = "0.8" + +[dev-dependencies] +anyhow = "1" +# cw-multi-test = { version = "0.16.0", default-features = false } +shade-multi-test = { path = "../../../packages/multi_test", features = [ + "admin", + "lb_pair", + "lb_token", + "snip20", + "router", + "lb_factory" +] } + +shadeswap-shared = { version = "0.1.0", path = "../../../packages/shadeswap_shared", features = [] } diff --git a/contracts/liquidity_book/tests/src/lib.rs b/contracts/liquidity_book/tests/src/lib.rs new file mode 100644 index 000000000..ce5bf69b0 --- /dev/null +++ b/contracts/liquidity_book/tests/src/lib.rs @@ -0,0 +1,2 @@ +#[cfg(test)] +pub mod multitests; diff --git a/contracts/liquidity_book/tests/src/multitests/lb_factory.rs b/contracts/liquidity_book/tests/src/multitests/lb_factory.rs new file mode 100644 index 000000000..1af01202d --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/lb_factory.rs @@ -0,0 +1,1232 @@ +use crate::multitests::test_helper::{ + extract_contract_info, generate_random, token_type_snip20_generator, DEFAULT_BASE_FACTOR, + DEFAULT_BIN_STEP, DEFAULT_DECAY_PERIOD, DEFAULT_FILTER_PERIOD, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, DEFAULT_OPEN_STATE, DEFAULT_PROTOCOL_SHARE, + DEFAULT_REDUCTION_FACTOR, DEFAULT_VARIABLE_FEE_CONTROL, SBTC, SHADE, SILK, SSCRT, USDC, +}; + +use super::test_helper::{bound, init_addrs, setup, ID_ONE}; +use anyhow::Ok; +use cosmwasm_std::{ContractInfo, StdError}; +use shade_multi_test::{ + interfaces::{lb_factory, lb_pair, snip20}, + multi::{lb_pair::LbPair, lb_token::LbToken}, +}; +use shade_protocol::{ + lb_libraries::{ + constants::BASIS_POINT_MAX, + math::{ + encoded_sample::{MASK_UINT12, MASK_UINT20}, + u24::U24, + }, + tokens::TokenType, + }, + utils::MultiTestable, +}; + +#[test] +pub fn test_setup() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + //query fee recipient + let fee_recipient = lb_factory::query_fee_recipient(&mut app, &lb_factory.clone().into())?; + + assert_eq!(fee_recipient.as_str(), addrs.altaf_bhai().as_str()); + //query flashloanfee + let flash_loan_fee = lb_factory::query_flash_loan_fee(&mut app, &lb_factory.clone().into())?; + + assert_eq!(flash_loan_fee, 0u8); + //query getMinBinStep + let min_bin_step = lb_factory::query_min_bin_step(&mut app, &lb_factory.clone().into())?; + assert_eq!(min_bin_step, 1u8); // fixed in contract + + //query getMaxFlashLoanFee + let max_flash_loan_fee = + lb_factory::query_max_flash_loan_fee(&mut app, &lb_factory.into())?; + assert_eq!(max_flash_loan_fee, 10 ^ 17); + Ok(()) +} + +#[test] +pub fn test_set_lb_pair_implementation() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let lb_pair_stored_code = app.store_code(LbPair::default().contract()); + + lb_factory::set_lb_pair_implementation( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + lb_pair_stored_code.code_id, + lb_pair_stored_code.code_hash, + )?; + let lb_pair_code_info = + lb_factory::query_lb_pair_implementation(&mut app, &lb_factory.into())?; + assert_eq!(lb_pair_stored_code.code_id, lb_pair_code_info.id); + + Ok(()) +} + +#[test] +pub fn test_revert_set_lb_pair_implementation() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let lb_pair_stored_code = app.store_code(LbPair::default().contract()); + + lb_factory::set_lb_pair_implementation( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + lb_pair_stored_code.code_id, + lb_pair_stored_code.code_hash.clone(), + )?; + let err = lb_factory::set_lb_pair_implementation( + &mut app, + addrs.admin().as_str(), + &lb_factory.into(), + lb_pair_stored_code.code_id, + lb_pair_stored_code.code_hash, + ); + + assert_eq!( + err.unwrap_err(), + StdError::generic_err(format!( + "LB implementation is already set to code ID {}!", + lb_pair_stored_code.code_id + )) + ); + + Ok(()) +} + +#[test] +pub fn test_set_lb_token_implementation() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let lb_token_stored_code = app.store_code(LbToken::default().contract()); + lb_factory::set_lb_token_implementation( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + lb_token_stored_code.code_id, + lb_token_stored_code.code_hash, + )?; + let lb_token_code_info = + lb_factory::query_lb_token_implementation(&mut app, &lb_factory.into())?; + assert_eq!(lb_token_stored_code.code_id, lb_token_code_info.id); + Ok(()) +} + +#[test] +pub fn test_create_lb_pair() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + // 3. Create an LBPair. + + let shd = extract_contract_info(&deployed_contracts, SHADE)?; + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_x = token_type_snip20_generator(&shd)?; + let token_y = token_type_snip20_generator(&sscrt)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + + // 4. Check if the number of LBPairs is 1. + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x.clone(), + token_y.clone(), + )?; + + assert_eq!(all_pairs.len(), 1); + // 5. Get the LBPair information using `factory.getLBPairInformation`. + let lb_pair_info = lb_factory::query_lb_pair_information( + &mut app, + &lb_factory.into(), + token_x.clone(), + token_y.clone(), + DEFAULT_BIN_STEP, + )?; + + assert_eq!(lb_pair_info, all_pairs[0]); + assert_eq!(lb_pair_info.bin_step, DEFAULT_BIN_STEP); + assert!(lb_pair_info.created_by_owner); + assert!(!lb_pair_info.ignored_for_routing); + + // 6. Validate that the returned information matches the expected values (binStep, LBPair address, createdByOwner, ignoredForRouting). + //SORTED token + if token_x.unique_key() < token_y.unique_key() { + assert_eq!(lb_pair_info.lb_pair.token_x, token_x); + assert_eq!(lb_pair_info.lb_pair.token_y, token_y); + } else { + assert_eq!(lb_pair_info.lb_pair.token_x, token_y); + assert_eq!(lb_pair_info.lb_pair.token_y, token_x); + } + + let ( + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + ) = lb_pair::query_static_fee_params(&mut app, &lb_pair_info.lb_pair.contract)?; + assert_eq!(base_factor, DEFAULT_BASE_FACTOR); + assert_eq!(filter_period, DEFAULT_FILTER_PERIOD); + assert_eq!(decay_period, DEFAULT_DECAY_PERIOD); + assert_eq!(reduction_factor, DEFAULT_REDUCTION_FACTOR); + assert_eq!(variable_fee_control, DEFAULT_VARIABLE_FEE_CONTROL); + assert_eq!(protocol_share, DEFAULT_PROTOCOL_SHARE); + assert_eq!( + max_volatility_accumulator, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR + ); + + let (volatility_accumulator, volatility_reference, id_reference, time_of_last_update) = + lb_pair::query_variable_fee_params(&mut app, &lb_pair_info.lb_pair.contract)?; + + assert_eq!(volatility_reference, 0); + assert_eq!(volatility_accumulator, 0); + assert_eq!(time_of_last_update, 0); + assert_eq!(id_reference, ID_ONE); + + Ok(()) +} + +#[test] +pub fn test_create_lb_pair_factory_unlocked() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + let shd = extract_contract_info(&deployed_contracts, SHADE)?; + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_x = token_type_snip20_generator(&shd)?; + let token_y = token_type_snip20_generator(&sscrt)?; + // try creating as 'batman' and get an error + let res = lb_factory::create_lb_pair( + &mut app, + addrs.batman().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!( + "Preset {} is locked for users! {} is not the owner!", + DEFAULT_BIN_STEP, + addrs.batman().as_str() + )) + ); + // set open preset for bin_id + lb_factory::set_preset_open_state( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + true, + )?; + // create lb_pair + lb_factory::create_lb_pair( + &mut app, + addrs.batman().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + // query and check if created by owner == false + let lb_pair_info = lb_factory::query_lb_pair_information( + &mut app, + &lb_factory.clone().into(), + token_x.clone(), + token_y.clone(), + DEFAULT_BIN_STEP, + )?; + assert!(!lb_pair_info.created_by_owner); + + // close preset + // set open preset for bin_id + lb_factory::set_preset_open_state( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + false, + )?; + // get an error on creating + let res = lb_factory::create_lb_pair( + &mut app, + addrs.scare_crow().as_str(), + &lb_factory.into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x, + token_y, + "viewing_key".to_string(), + ); + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!( + "Preset {} is locked for users! {} is not the owner!", + DEFAULT_BIN_STEP, + addrs.scare_crow().as_str() + )) + ); + Ok(()) +} + +#[test] +fn test_revert_create_lb_pair() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + let shd = extract_contract_info(&deployed_contracts, SHADE)?; + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_x = token_type_snip20_generator(&shd)?; + let token_y = token_type_snip20_generator(&sscrt)?; + //Batmam tried to create a lb_pair + let res = lb_factory::create_lb_pair( + &mut app, + addrs.batman().as_str(), + &lb_factory.into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + ); + //Check failing error + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!( + "Preset {} is locked for users! {} is not the owner!", + DEFAULT_BIN_STEP, + addrs.batman().as_str() + )) + ); + + let new_lb_factory = lb_factory::init(&mut app, addrs.admin().as_str(), addrs.admin(), 0)?; + + //can't create a pair if the preset is not set + let res = lb_factory::create_lb_pair( + &mut app, + addrs.batman().as_str(), + &new_lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x, + token_y, + "viewing_key".to_string(), + ); + //Check failing error + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!("Bin step {} has no preset!", DEFAULT_BIN_STEP,)) + ); + + lb_factory::set_pair_preset( + &mut app, + addrs.admin().as_str(), + &new_lb_factory.clone().into(), + DEFAULT_BIN_STEP, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE, + )?; + + //can't create a pair if quote asset is not whitelisted + let sbtc = extract_contract_info(&deployed_contracts, SBTC)?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_snip20_generator(&sbtc)?; + let token_y = token_type_snip20_generator(&silk)?; + let res = lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &new_lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + ); + //Check failing error + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!( + "Quote Asset {} is not whitelisted!", + token_y.unique_key(), + )) + ); + + lb_factory::add_quote_asset( + &mut app, + addrs.admin().as_str(), + &new_lb_factory.clone().into(), + TokenType::CustomToken { + contract_addr: silk.address, + token_code_hash: silk.code_hash, + }, + )?; + lb_factory::add_quote_asset( + &mut app, + addrs.admin().as_str(), + &new_lb_factory.clone().into(), + TokenType::CustomToken { + contract_addr: sbtc.address.clone(), + token_code_hash: sbtc.code_hash, + }, + )?; + //can't create a pair if quote asset is the same as the base asset + let res = lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &new_lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_x.clone(), + "viewing_key".to_string(), + ); + //Check failing error + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!( + "Tokens are identical! Both addresses are {}!", + sbtc.address.as_str() + )) + ); + //can't create a pair if the implementation is not set + let res = lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &new_lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + ); + //Check failing error + assert_eq!( + res.unwrap_err(), + StdError::generic_err("The LBPair implementation has not been set yet!".to_string()) + ); + + //can't create a pair the same pair twice + let lb_token_stored_code = app.store_code(LbToken::default().contract()); + let lb_pair_stored_code = app.store_code(LbPair::default().contract()); + + lb_factory::set_lb_pair_implementation( + &mut app, + addrs.admin().as_str(), + &new_lb_factory.clone().into(), + lb_pair_stored_code.code_id, + lb_pair_stored_code.code_hash, + )?; + + lb_factory::set_lb_token_implementation( + &mut app, + addrs.admin().as_str(), + &new_lb_factory.clone().into(), + lb_token_stored_code.code_id, + lb_token_stored_code.code_hash, + )?; + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &new_lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + let res = lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &new_lb_factory.into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + ); + + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!( + "LBPair ({}, {}, bin_step: {}) already exists!", + token_x.unique_key(), + token_y.unique_key(), + DEFAULT_BIN_STEP + )) + ); + + Ok(()) +} + +#[test] +fn test_fuzz_set_preset() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + let mut bin_step: u16 = generate_random(0, u16::MAX); + let base_factor: u16 = generate_random(0, u16::MAX); + let mut filter_period: u16 = generate_random(0, u16::MAX); + let mut decay_period: u16 = generate_random(0, u16::MAX); + let mut reduction_factor: u16 = generate_random(0, u16::MAX); + let mut variable_fee_control: u32 = generate_random(0, U24::MAX); + let mut protocol_share: u16 = generate_random(0, u16::MAX); + let mut max_volatility_accumulator: u32 = generate_random(0, U24::MAX); + let is_open: bool = generate_random(0, 1) == 0; + + let min_bin_step = lb_factory::query_min_bin_step(&mut app, &lb_factory.clone().into())?; + + // Bounds checking for each parameter + bin_step = bound(bin_step, min_bin_step as u16, u16::MAX); + filter_period = bound(filter_period, 0, MASK_UINT12.as_u16() - 1); + decay_period = bound(decay_period, filter_period + 1, MASK_UINT12.as_u16()); + reduction_factor = bound(reduction_factor, 0u16, BASIS_POINT_MAX as u16); + variable_fee_control = bound(variable_fee_control, 032, BASIS_POINT_MAX); + protocol_share = bound(protocol_share, 0, 2_500); + max_volatility_accumulator = bound(max_volatility_accumulator, 0, MASK_UINT20.as_u32()); + + lb_factory::set_pair_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + bin_step, + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + is_open, + )?; + + // Additional assertions and verifications + let all_bin_steps = lb_factory::query_all_bin_steps(&mut app, &lb_factory.clone().into())?; + + if bin_step != DEFAULT_BIN_STEP { + assert_eq!(all_bin_steps.len(), 2); + assert_eq!(all_bin_steps[0], DEFAULT_BIN_STEP); + assert_eq!(all_bin_steps[1], bin_step); + } else { + assert_eq!(all_bin_steps.len(), 1); + assert_eq!(all_bin_steps[0], bin_step); + } + + let ( + base_factor_view, + filter_period_view, + decay_period_view, + reduction_factor_view, + variable_fee_control_view, + protocol_share_view, + max_volatility_accumulator_view, + is_open_view, + ) = lb_factory::query_preset(&mut app, &lb_factory.into(), bin_step)?; + + assert_eq!(base_factor, base_factor_view); + assert_eq!(filter_period, filter_period_view); + assert_eq!(decay_period, decay_period_view); + assert_eq!(reduction_factor, reduction_factor_view); + assert_eq!(variable_fee_control, variable_fee_control_view); + assert_eq!(protocol_share, protocol_share_view); + assert_eq!(max_volatility_accumulator, max_volatility_accumulator_view); + assert_eq!(is_open, is_open_view); + + Ok(()) +} + +#[test] +fn test_remove_preset() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + + // Set presets + lb_factory::set_pair_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP + 1, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE, + )?; + + lb_factory::set_pair_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP - 1, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE, + )?; + + let all_bin_steps = lb_factory::query_all_bin_steps(&mut app, &lb_factory.clone().into())?; + assert_eq!(all_bin_steps.len(), 3, "test_remove_preset::1"); + + // Remove preset + lb_factory::remove_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + )?; + + let all_bin_steps = lb_factory::query_all_bin_steps(&mut app, &lb_factory.clone().into())?; + assert_eq!(all_bin_steps.len(), 2, "test_remove_preset::2"); + + // Expect getPreset to fail for removed bin_step + let res = lb_factory::query_preset(&mut app, &lb_factory.clone().into(), DEFAULT_BIN_STEP); + assert!( + res.is_err(), + "Should revert because bin_step no longer exists" + ); + + // Expect failure if not owner + let res = lb_factory::remove_preset( + &mut app, + addrs.batman().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ); + assert!(res.is_err(), "Should revert because not owner"); + + // Expect failure if bin_step does not exist + let res = lb_factory::remove_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.into(), + DEFAULT_BIN_STEP, + ); + assert!( + res.is_err(), + "Should revert because bin_step no longer exists" + ); + + Ok(()) +} + +#[test] +pub fn test_set_fees_parameters_on_pair() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let shd = extract_contract_info(&deployed_contracts, SHADE)?; + let token_x = token_type_snip20_generator(&sscrt)?; + let token_y = token_type_snip20_generator(&shd)?; + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + // let liquidity_parameters = liquidity_parameters_generator( + // &deployed_contracts, + // ID_ONE, + // Uint128::from(100u128 * 10 ^ 18), + // Uint128::from(100u128 * 10 ^ 18), + // 10, + // 10, + // )?; + + // 4. Check if the number of LBPairs is 1. + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x.clone(), + token_y.clone(), + )?; + + // lb_pair::add_liquidity( + // &mut app, + // &lb_factory.address.as_str(), + // &all_pairs[0].lb_pair.contract, + // liquidity_parameters, + // )?; + + let ( + old_volatility_accumulator, + old_volatility_reference, + old_id_reference, + old_time_of_last_update, + ) = lb_pair::query_variable_fee_params(&mut app, &all_pairs[0].lb_pair.contract)?; + + // Set the fees parameters on pair + lb_factory::set_fees_parameters_on_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + token_x.clone(), + token_y.clone(), + DEFAULT_BIN_STEP, + DEFAULT_BASE_FACTOR * 2, + DEFAULT_FILTER_PERIOD * 2, + DEFAULT_DECAY_PERIOD * 2, + DEFAULT_REDUCTION_FACTOR * 2, + DEFAULT_VARIABLE_FEE_CONTROL * 2, + DEFAULT_PROTOCOL_SHARE * 2, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR * 2, + )?; + + // Validate that the fees parameters were correctly set + let ( + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + ) = lb_pair::query_static_fee_params(&mut app, &all_pairs[0].lb_pair.contract)?; + + assert_eq!(base_factor, DEFAULT_BASE_FACTOR * 2); + assert_eq!(filter_period, DEFAULT_FILTER_PERIOD * 2); + assert_eq!(decay_period, DEFAULT_DECAY_PERIOD * 2); + assert_eq!(reduction_factor, DEFAULT_REDUCTION_FACTOR * 2); + assert_eq!(variable_fee_control, DEFAULT_VARIABLE_FEE_CONTROL * 2); + assert_eq!(protocol_share, DEFAULT_PROTOCOL_SHARE * 2); + assert_eq!( + max_volatility_accumulator, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR * 2 + ); + + let (volatility_accumulator, volatility_reference, id_reference, time_of_last_update) = + lb_pair::query_variable_fee_params(&mut app, &all_pairs[0].lb_pair.contract)?; + + assert_eq!(volatility_accumulator, old_volatility_accumulator); + assert_eq!(volatility_reference, old_volatility_reference); + assert_eq!(id_reference, old_id_reference); + assert_eq!(time_of_last_update, old_time_of_last_update); + + // Simulate invalid operations (not the owner, pair not exist) + + // Set the fees parameters on pair + let res = lb_factory::set_fees_parameters_on_pair( + &mut app, + addrs.batman().as_str(), + &lb_factory.into(), + token_x, + token_y, + DEFAULT_BIN_STEP, + DEFAULT_BASE_FACTOR * 2, + DEFAULT_FILTER_PERIOD * 2, + DEFAULT_DECAY_PERIOD * 2, + DEFAULT_REDUCTION_FACTOR * 2, + DEFAULT_VARIABLE_FEE_CONTROL * 2, + DEFAULT_PROTOCOL_SHARE * 2, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR * 2, + ); + + assert_eq!( + res.unwrap_err(), + StdError::generic_err("Only the Owner can do that!".to_string()) + ); + + Ok(()) +} + +#[test] +pub fn test_set_fee_recipient() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, _deployed_contracts) = setup(None)?; + lb_factory::set_fee_recipient( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + addrs.batman(), + )?; + + // Assert fee recipient is set correctly + let fee_recipient = lb_factory::query_fee_recipient(&mut app, &lb_factory.clone().into())?; + assert_eq!( + fee_recipient, + addrs.batman().as_str(), + "test_set_fee_recipient::1" + ); + + // Try setting fee recipient when not the owner, should revert + let err = lb_factory::set_fee_recipient( + &mut app, + addrs.scare_crow().as_str(), + &lb_factory.clone().into(), + addrs.scare_crow(), + ); + assert_eq!( + err.unwrap_err(), + StdError::generic_err("Only the Owner can do that!") + ); + + // Try setting fee recipient to the same recipient, should revert + let err = lb_factory::set_fee_recipient( + &mut app, + addrs.admin().as_str(), + &lb_factory.into(), + addrs.batman(), + ); + assert_eq!( + err.unwrap_err(), + StdError::generic_err(format!("Fee recipient is already {}!", addrs.batman())) + ); + + Ok(()) +} + +#[test] +pub fn test_fuzz_open_presets() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); // Initialize addresses + let (mut app, lb_factory, _deployed_contracts) = setup(None)?; // Setup + + let min_bin_step = lb_factory::query_min_bin_step(&mut app, &lb_factory.clone().into())?; + let max_bin_step = u16::MAX; + + let bin_step: u16 = generate_random(min_bin_step as u16, max_bin_step); + + // Presets are not open to the public by default + if bin_step == DEFAULT_BIN_STEP { + let (_, _, _, _, _, _, _, is_open) = + lb_factory::query_preset(&mut app, &lb_factory.clone().into(), bin_step)?; + assert!(!is_open, "test_fuzz_open_presets::1"); + } else { + let err = lb_factory::query_preset(&mut app, &lb_factory.clone().into(), bin_step); + assert_eq!( + err.unwrap_err(), + StdError::generic_err( + StdError::GenericErr { + msg: format!( + "Querier contract error: Bin step {} has no preset!", + bin_step + ) + } + .to_string() + ) + ); + } + + lb_factory::set_pair_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + bin_step, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + true, + )?; + let (_, _, _, _, _, _, _, is_open) = + lb_factory::query_preset(&mut app, &lb_factory.clone().into(), bin_step)?; + assert!(is_open, "test_fuzz_open_presets::2"); + + // Can't set to the same state + let err = lb_factory::set_preset_open_state( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + bin_step, + true, + ); + assert_eq!( + err.unwrap_err(), + StdError::generic_err("Preset open state is already in the same state!") + ); + + // Can be closed + lb_factory::set_preset_open_state( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + bin_step, + false, + )?; + + let (_, _, _, _, _, _, _, is_open) = + lb_factory::query_preset(&mut app, &lb_factory.clone().into(), bin_step)?; + assert!(!is_open, "test_fuzz_open_presets::3"); + + // Can't open if not the owner + let err = lb_factory::set_preset_open_state( + &mut app, + addrs.batman().as_str(), + &lb_factory.clone().into(), + bin_step, + true, + ); + assert_eq!( + err.unwrap_err(), + StdError::generic_err("Only the Owner can do that!") + ); + + // Can't set to the same state + let err = lb_factory::set_preset_open_state( + &mut app, + addrs.admin().as_str(), + &lb_factory.into(), + bin_step, + false, + ); + assert_eq!( + err.unwrap_err(), + StdError::generic_err("Preset open state is already in the same state!") + ); + + Ok(()) +} + +#[test] +pub fn test_add_quote_asset() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); // Initialize addresses + let (mut app, lb_factory, mut deployed_contracts) = setup(None)?; // Setup + + let num_quote_assets_before = + lb_factory::query_number_of_quote_assets(&mut app, &lb_factory.clone().into())?; + + snip20::init( + &mut app, + addrs.admin().as_str(), + &mut deployed_contracts, + "sBTC", + "SBTC", + 8, + Some(shade_protocol::snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: Some(true), + }), + ) + .unwrap(); + let sbtc = extract_contract_info(&deployed_contracts, "SBTC")?; + let new_token = token_type_snip20_generator(&sbtc)?; + // Check if the new token is a quote asset + let is_quote_asset = + lb_factory::query_is_quote_asset(&mut app, &lb_factory.clone().into(), new_token.clone())?; + assert!(!is_quote_asset, "test_add_quote_asset::1"); + + // Add the new token as a quote asset + lb_factory::add_quote_asset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + new_token.clone(), + )?; + + // Validate that the new token is now a quote asset + let is_quote_asset = + lb_factory::query_is_quote_asset(&mut app, &lb_factory.clone().into(), new_token.clone())?; + assert!(is_quote_asset, "test_add_quote_asset::2"); + + let num_quote_assets_after = + lb_factory::query_number_of_quote_assets(&mut app, &lb_factory.clone().into())?; + assert_eq!( + num_quote_assets_after, + num_quote_assets_before + 1, + "test_add_quote_asset::3" + ); + + let last_quote_asset = lb_factory::query_quote_asset_at_index( + &mut app, + &lb_factory.clone().into(), + num_quote_assets_before, + )?; + assert_eq!(last_quote_asset, new_token, "test_add_quote_asset::4"); + + // Try to add the same asset when not the owner + let err = lb_factory::add_quote_asset( + &mut app, + addrs.batman().as_str(), + &lb_factory.clone().into(), + new_token.clone(), + ); + assert_eq!( + err.unwrap_err(), + StdError::generic_err("Only the Owner can do that!") + ); + + // Try to add the same asset again, should revert + let err = lb_factory::add_quote_asset( + &mut app, + addrs.admin().as_str(), + &lb_factory.into(), + new_token.clone(), + ); + assert_eq!( + err.unwrap_err(), + StdError::generic_err(format!( + "Quote Asset {} is already whitelisted!", + new_token.unique_key() + )) + ); + + Ok(()) +} + +#[test] +pub fn test_remove_quote_asset() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); // Initialize addresses + let (mut app, lb_factory, mut deployed_contracts) = setup(None)?; // Setup + + //SSCRT and SHD already added as quote asset + let num_quote_assets_before = + lb_factory::query_number_of_quote_assets(&mut app, &lb_factory.clone().into())?; + + snip20::init( + &mut app, + addrs.admin().as_str(), + &mut deployed_contracts, + USDC, + USDC, + 8, + Some(shade_protocol::snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: Some(true), + }), + ) + .unwrap(); + let usdc_info: ContractInfo = extract_contract_info(&deployed_contracts, USDC)?; + let usdc_token_type = token_type_snip20_generator(&usdc_info)?; + let usdc = usdc_token_type; + + // Add the new token as a quote asset + lb_factory::add_quote_asset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + usdc.clone(), + )?; + + // Check if usdc is a quote asset + let is_quote_asset = + lb_factory::query_is_quote_asset(&mut app, &lb_factory.clone().into(), usdc.clone())?; + assert!(is_quote_asset, "test_remove_quote_asset::1"); + + // Remove usdc as a quote asset + lb_factory::remove_quote_asset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + usdc.clone(), + )?; + + // Validate that usdc is no longer a quote asset + let is_quote_asset = + lb_factory::query_is_quote_asset(&mut app, &lb_factory.clone().into(), usdc.clone())?; + assert!(!is_quote_asset, "test_remove_quote_asset::2"); + + let num_quote_assets_after = + lb_factory::query_number_of_quote_assets(&mut app, &lb_factory.clone().into())?; + assert_eq!( + num_quote_assets_after, num_quote_assets_before, + "test_remove_quote_asset::3" + ); + + // Try to remove usdc when not the owner + let err = lb_factory::remove_quote_asset( + &mut app, + addrs.batman().as_str(), + &lb_factory.clone().into(), + usdc.clone(), + ); + assert_eq!( + err.unwrap_err(), + StdError::generic_err("Only the Owner can do that!") + ); + + // Try to remove usdc again, should revert + let err = lb_factory::remove_quote_asset( + &mut app, + addrs.admin().as_str(), + &lb_factory.into(), + usdc.clone(), + ); + assert_eq!( + err.unwrap_err(), + StdError::generic_err(format!( + "Quote Asset {} is not whitelisted!", + usdc.unique_key() + )) + ); + + Ok(()) +} + +#[test] +pub fn test_force_decay() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); // Initialize addresses + let (mut app, lb_factory, deployed_contracts) = setup(None)?; // Setup + + let sscrt_info = extract_contract_info(&deployed_contracts, SSCRT)?; + let sscrt = token_type_snip20_generator(&sscrt_info)?; + + let shd_info = extract_contract_info(&deployed_contracts, SHADE)?; + let shd = token_type_snip20_generator(&shd_info)?; + + // Create a new LBPair with usdt and usdc + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + sscrt.clone(), + shd.clone(), + "viewing_key".to_string(), + )?; + + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + sscrt, + shd, + )?; + + let lb_pair = all_pairs[0].clone().lb_pair; + + // Force decay on the created LBPair + lb_factory::force_decay( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + lb_pair.clone(), + )?; + + // Try to force decay when not the owner + let err = lb_factory::force_decay( + &mut app, + addrs.batman().as_str(), + &lb_factory.into(), + lb_pair, + ); + assert_eq!( + err.unwrap_err(), + StdError::generic_err("Only the Owner can do that!") + ); + + Ok(()) +} + +#[test] +pub fn test_get_all_lb_pair() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + // 3. Create an LBPair. + + let shd = extract_contract_info(&deployed_contracts, SHADE)?; + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + let token_x = token_type_snip20_generator(&shd)?; + let token_y = token_type_snip20_generator(&sscrt)?; + + lb_factory::set_pair_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + 5, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE, + )?; + + lb_factory::set_pair_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + 20, + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE, + )?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + 5, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + 20, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.into(), + token_x, + token_y, + )?; + + assert_eq!(all_pairs.len(), 2); + + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs new file mode 100644 index 000000000..fa1ba01aa --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_fees.rs @@ -0,0 +1,2226 @@ +use crate::multitests::test_helper::*; + +use super::test_helper::{ + increase_allowance_helper, init_addrs, liquidity_parameters_generator, mint_token_helper, + setup, ID_ONE, +}; +use anyhow::Ok; +use cosmwasm_std::{ContractInfo, StdError, Uint128, Uint256}; +use shade_multi_test::interfaces::{ + lb_factory, lb_pair, lb_token, snip20, utils::DeployedContracts, +}; +use shade_protocol::{ + lb_libraries::{ + math::{encoded_sample::MASK_UINT20, u24::U24}, + types::LBPairInformation, + }, + liquidity_book::lb_pair::RemoveLiquidity, + multi_test::App, +}; + +pub const DEPOSIT_AMOUNT: u128 = 1_000_000_000_000_000_000_u128; + +pub const ACTIVE_ID: u32 = ID_ONE; + +pub fn lb_pair_setup() -> Result< + ( + App, + ContractInfo, + DeployedContracts, + LBPairInformation, + ContractInfo, + ), + anyhow::Error, +> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let shade = extract_contract_info(&deployed_contracts, SHADE)?; + let token_x = token_type_snip20_generator(&shade)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x, + token_y, + )?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::lb_token_query(&app, &lb_pair.lb_pair.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(DEPOSIT_AMOUNT); + let amount_y = Uint128::from(DEPOSIT_AMOUNT); + let nb_bins_x = 10; + let nb_bins_y = 10; + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SHADE, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + snip20::set_viewing_key_exec( + &mut app, + addrs.scare_crow().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + snip20::set_viewing_key_exec( + &mut app, + addrs.scare_crow().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + snip20::set_viewing_key_exec( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_string(), + )?; + + snip20::set_viewing_key_exec( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_string(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + Ok(( + app, + lb_factory.into(), + deployed_contracts, + lb_pair, + lb_token, + )) +} + +#[test] +pub fn test_fuzz_swap_in_x() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + let amount_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_in, amount_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_out, true)?; + assert_eq!(amount_out_left, Uint128::MIN); + + let tokens_to_mint = vec![(SHADE, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_in, + )?; + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, Uint128::zero()); + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_out); + + //REMOVE LIQUIDITY + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let total_bins = get_total_bins(10, 10) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, 10); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x), + amount_y_min: Uint128::from(reserves_y), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let (protocol_fee_x, _) = lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!( + balance_x.u128(), + DEPOSIT_AMOUNT + amount_in.u128() - protocol_fee_x + ); + + assert_eq!(balance_y.u128(), reserves_y + amount_out.u128()); + + Ok(()) +} + +#[test] +pub fn test_fuzz_swap_in_y() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + let amount_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_in, amount_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_out, false)?; + assert_eq!(amount_out_left, Uint128::MIN); + + let tokens_to_mint = vec![(SILK, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_in, + )?; + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, Uint128::zero()); + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, amount_out); + + //REMOVE LIQUIDITY + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let total_bins = get_total_bins(10, 10) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, 10); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x), + amount_y_min: Uint128::from(reserves_y), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let (_, protocol_fee_y) = lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_x.u128(), reserves_x + amount_out.u128()); + + assert_eq!( + balance_y.u128(), + DEPOSIT_AMOUNT + amount_in.u128() - protocol_fee_y + ); + + Ok(()) +} + +#[test] +pub fn test_fuzz_swap_out_for_x() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + let amount_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_in, true)?; + + assert!(amount_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SHADE, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_in, + )?; + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, Uint128::zero()); + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_out); + + //REMOVE LIQUIDITY + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let total_bins = get_total_bins(10, 10) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, 10); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x), + amount_y_min: Uint128::from(reserves_y), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let (protocol_fee_x, _) = lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!( + balance_x.u128(), + DEPOSIT_AMOUNT + amount_in.u128() - protocol_fee_x + ); + + assert_eq!(balance_y.u128(), reserves_y + amount_out.u128()); + Ok(()) +} + +#[test] +pub fn test_fuzz_swap_out_for_y() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + let amount_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_in, false)?; + + assert!(amount_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SILK, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_in, + )?; + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, Uint128::zero()); + + let shade_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shade_balance, amount_out); + + //REMOVE LIQUIDITY + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let total_bins = get_total_bins(10, 10) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, 10); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x), + amount_y_min: Uint128::from(reserves_y), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let (_, protocol_fee_y) = lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_x.u128(), reserves_x + amount_out.u128()); + + assert_eq!( + balance_y.u128(), + DEPOSIT_AMOUNT + amount_in.u128() - protocol_fee_y + ); + + Ok(()) +} + +#[test] +pub fn test_fuzz_swap_in_x_and_y() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + //generate random number + let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + // get swap_in for y + let (amount_x_in, amount_y_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_y_out, true)?; + // amount_y_out_left == zero since the amount_x_out must be less than total deposit + assert_eq!(amount_y_out_left, Uint128::zero()); + // mint the tokens + let tokens_to_mint = vec![(SHADE, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + // check the balance of silk if it's equal to the amount_y_out + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_y_out); + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, Uint128::zero()); + + //generate random number + let amount_x_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + // get swap_in for y + let (amount_y_in, amount_x_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_x_out, false)?; + // amount_y_out_left == zero since the amount_x_out must be less than total deposit + assert_eq!(amount_x_out_left, Uint128::zero()); + // mint the tokens + let tokens_to_mint = vec![(SILK, amount_y_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_y_in, + )?; + + // check the balance of silk if it's equal to the amount_y_out + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_y_out); + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, amount_x_out); + + //REMOVE LIQUIDITY + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let total_bins = get_total_bins(10, 10) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, 10); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x), + amount_y_min: Uint128::from(reserves_y), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let lb_pair_balance_x = snip20::balance_query( + &mut app, + lb_pair.lb_pair.contract.address.as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let lb_pair_balance_y = snip20::balance_query( + &mut app, + lb_pair.lb_pair.contract.address.as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(lb_pair_balance_x.u128(), protocol_fee_x); + assert_eq!(lb_pair_balance_y.u128(), protocol_fee_y); + + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_x.u128(), reserves_x + amount_x_out.u128()); + assert_eq!(balance_y.u128(), reserves_y + amount_y_out.u128()); + + assert_eq!( + reserves_x, + DEPOSIT_AMOUNT + amount_x_in.u128() - amount_x_out.u128() - protocol_fee_x + ); + assert_eq!( + reserves_y, + DEPOSIT_AMOUNT + amount_y_in.u128() - amount_y_out.u128() - protocol_fee_y + ); + + Ok(()) +} + +#[test] +pub fn test_fuzz_swap_in_y_and_x() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + //generate random number + let amount_x_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + // get swap_in for y + let (amount_y_in, amount_x_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_x_out, false)?; + // amount_y_out_left == zero since the amount_x_out must be less than total deposit + assert_eq!(amount_x_out_left, Uint128::zero()); + // mint the tokens + let tokens_to_mint = vec![(SILK, amount_y_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_y_in, + )?; + + // check the balance of silk if it's equal to the amount_y_out + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, Uint128::zero()); + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, amount_x_out); + + //generate random number + let amount_y_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + // get swap_in for y + let (amount_x_in, amount_y_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_y_out, true)?; + // amount_y_out_left == zero since the amount_x_out must be less than total deposit + assert_eq!(amount_y_out_left, Uint128::zero()); + // mint the tokens + let tokens_to_mint = vec![(SHADE, amount_x_in)]; + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + // make a swap with amount_x_in + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + // check the balance of silk if it's equal to the amount_y_out + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_y_out); + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, amount_x_out); + + //REMOVE LIQUIDITY + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let total_bins = get_total_bins(10, 10) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, 10); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x), + amount_y_min: Uint128::from(reserves_y), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let lb_pair_balance_x = snip20::balance_query( + &mut app, + lb_pair.lb_pair.contract.address.as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let lb_pair_balance_y = snip20::balance_query( + &mut app, + lb_pair.lb_pair.contract.address.as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(lb_pair_balance_x.u128(), protocol_fee_x); + assert_eq!(lb_pair_balance_y.u128(), protocol_fee_y); + + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_x.u128(), reserves_x + amount_x_out.u128()); + assert_eq!(balance_y.u128(), reserves_y + amount_y_out.u128()); + + assert_eq!( + reserves_x, + DEPOSIT_AMOUNT + amount_x_in.u128() - amount_x_out.u128() - protocol_fee_x + ); + assert_eq!( + reserves_y, + DEPOSIT_AMOUNT + amount_y_in.u128() - amount_y_out.u128() - protocol_fee_y + ); + + Ok(()) +} + +#[test] +pub fn test_fuzz_swap_out_x_and_y() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + let amount_x_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_y_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_x_in, true)?; + + assert!(amount_y_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SHADE, amount_x_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, Uint128::zero()); + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_y_out); + + let amount_y_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_x_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_y_in, false)?; + + assert!(amount_x_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SILK, amount_y_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_y_in, + )?; + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_y_out); + + let shade_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shade_balance, amount_x_out); + + //REMOVE LIQUIDITY + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let total_bins = get_total_bins(10, 10) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, 10); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x), + amount_y_min: Uint128::from(reserves_y), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let lb_pair_balance_x = snip20::balance_query( + &mut app, + lb_pair.lb_pair.contract.address.as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let lb_pair_balance_y = snip20::balance_query( + &mut app, + lb_pair.lb_pair.contract.address.as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(lb_pair_balance_x.u128(), protocol_fee_x); + assert_eq!(lb_pair_balance_y.u128(), protocol_fee_y); + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(balance_x.u128(), reserves_x + amount_x_out.u128()); + assert_eq!(balance_y.u128(), reserves_y + amount_y_out.u128()); + + assert_eq!( + reserves_x, + DEPOSIT_AMOUNT + amount_x_in.u128() - amount_x_out.u128() - protocol_fee_x + ); + assert_eq!( + reserves_y, + DEPOSIT_AMOUNT + amount_y_in.u128() - amount_y_out.u128() - protocol_fee_y + ); + Ok(()) +} + +#[test] +pub fn test_fuzz_swap_out_y_and_x() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + let amount_y_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_x_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_y_in, false)?; + + assert!(amount_x_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SILK, amount_y_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_y_in, + )?; + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, Uint128::zero()); + + let shade_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shade_balance, amount_x_out); + + let amount_x_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_y_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_x_in, true)?; + + assert!(amount_y_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SHADE, amount_x_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, amount_x_out); + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_y_out); + + //REMOVE LIQUIDITY + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let total_bins = get_total_bins(10, 10) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, 10); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x), + amount_y_min: Uint128::from(reserves_y), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let lb_pair_balance_x = snip20::balance_query( + &mut app, + lb_pair.lb_pair.contract.address.as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let lb_pair_balance_y = snip20::balance_query( + &mut app, + lb_pair.lb_pair.contract.address.as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(lb_pair_balance_x.u128(), protocol_fee_x); + assert_eq!(lb_pair_balance_y.u128(), protocol_fee_y); + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(balance_x.u128(), reserves_x + amount_x_out.u128()); + assert_eq!(balance_y.u128(), reserves_y + amount_y_out.u128()); + + assert_eq!( + reserves_x, + DEPOSIT_AMOUNT + amount_x_in.u128() - amount_x_out.u128() - protocol_fee_x + ); + assert_eq!( + reserves_y, + DEPOSIT_AMOUNT + amount_y_in.u128() - amount_y_out.u128() - protocol_fee_y + ); + Ok(()) +} + +#[test] +pub fn test_fee_x_2_lp() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + let amount = Uint128::from(DEPOSIT_AMOUNT); + //add_liquidity second time: + let tokens_to_mint = vec![(SHADE, amount), (SILK, amount)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount, + amount, + 10, + 10, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + let amount_in = Uint128::from(DEPOSIT_AMOUNT); + + let (amount_out, _amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_in, true)?; + + let tokens_to_mint = vec![(SHADE, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.scare_crow().into_string(), + tokens_to_mint.clone(), + )?; + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.scare_crow().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.scare_crow().to_string()), + token_x, + amount_in, + )?; + + //REMOVE LIQUIDITY + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let total_bins = get_total_bins(10, 10) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, 10); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )? + .checked_div(Uint256::from(2u128))?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x / 2 - 1), + amount_y_min: Uint128::from(reserves_y / 2 - 1), + ids: ids.clone(), + amounts: balances.clone(), + deadline: 99999999999, + }, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_approx_eq_abs( + Uint256::from(balance_x.u128()), + Uint256::from(DEPOSIT_AMOUNT + (amount_in.u128() - protocol_fee_x) / 2), + Uint256::from(2u128), + "test_fee_x_2_lp::1", + ); + assert_approx_eq_abs( + Uint256::from(balance_y.u128()), + Uint256::from(DEPOSIT_AMOUNT - (amount_out.u128() + protocol_fee_y) / 2), + Uint256::from(2u128), + "test_fee_x_2_lp::2", + ); + + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x / 2 - 1), + amount_y_min: Uint128::from(reserves_y / 2 - 1), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_approx_eq_abs( + Uint256::from(balance_x.u128()), + Uint256::from(DEPOSIT_AMOUNT * 2 + (amount_in.u128() - protocol_fee_x)), + Uint256::from(2u128), + "test_fee_x_2_lp::3", + ); + assert_approx_eq_abs( + Uint256::from(balance_y.u128()), + Uint256::from(DEPOSIT_AMOUNT * 2 - (amount_out.u128() + protocol_fee_y)), + Uint256::from(2u128), + "test_fee_x_2_lp::4", + ); + + Ok(()) +} + +#[test] +pub fn test_fee_y_2_lp() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + let amount = Uint128::from(DEPOSIT_AMOUNT); + //add_liquidity second time: + let tokens_to_mint = vec![(SHADE, amount), (SILK, amount)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount, + amount, + 10, + 10, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + let amount_in = Uint128::from(DEPOSIT_AMOUNT); + + let (amount_out, _amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_in, false)?; + + let tokens_to_mint = vec![(SILK, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.scare_crow().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.scare_crow().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.scare_crow().into_string()), + token_y, + amount_in, + )?; + + //REMOVE LIQUIDITY + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let total_bins = get_total_bins(10, 10) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, 10); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )? + .checked_div(Uint256::from(2u128))?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x / 2 - 1), + amount_y_min: Uint128::from(reserves_y / 2 - 1), + ids: ids.clone(), + amounts: balances.clone(), + deadline: 99999999999, + }, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_approx_eq_abs( + Uint256::from(balance_x.u128()), + Uint256::from(DEPOSIT_AMOUNT - (amount_out.u128() + protocol_fee_x) / 2), + Uint256::from(2u128), + "test_fee_y_2_lp::1", + ); + assert_approx_eq_abs( + Uint256::from(balance_y.u128()), + Uint256::from(DEPOSIT_AMOUNT + (amount_in.u128() - protocol_fee_y) / 2), + Uint256::from(2u128), + "test_fee_y_2_lp::2", + ); + + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x / 2 - 1), + amount_y_min: Uint128::from(reserves_y / 2 - 1), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let balance_y = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_approx_eq_abs( + Uint256::from(balance_x.u128()), + Uint256::from(DEPOSIT_AMOUNT * 2 - (amount_out.u128() + protocol_fee_x)), + Uint256::from(2u128), + "test_fee_y_2_lp::3", + ); + assert_approx_eq_abs( + Uint256::from(balance_y.u128()), + Uint256::from(DEPOSIT_AMOUNT * 2 + (amount_in.u128() - protocol_fee_y)), + Uint256::from(2u128), + "test_fee_y_2_lp::4", + ); + + Ok(()) +} + +//TODO: Flash loan test +#[test] +pub fn test_fees_2lp_flash_loan() {} + +//TODO: wtf flipped the amount_y_in with amount_x_in +#[test] +pub fn test_collect_protocol_fees_x_tokens() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + let amount_y_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_x_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_y_in, true)?; + + assert!(amount_x_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SHADE, amount_y_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_y_in, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(protocol_fee_y, 0); + + lb_pair::collect_protocol_fees(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_x.u128(), protocol_fee_x - 1); + + let balance_y = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_y.u128(), 0); + + Ok(()) +} + +//TODO: wtf flipped the amount_y_in with amount_x_in + +#[test] +pub fn test_collect_protocol_fees_y_tokens() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + let amount_x_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_y_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_x_in, false)?; + + assert!(amount_y_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SILK, amount_x_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_x_in, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(protocol_fee_x, 0); + + lb_pair::collect_protocol_fees(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_x.u128(), 0); + + let balance_y = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_y.u128(), protocol_fee_y - 1); + + Ok(()) +} + +#[test] +pub fn test_collect_protocol_fees_both_tokens() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + let amount_x_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_y_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_x_in, true)?; + + assert!(amount_y_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SHADE, amount_x_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, Uint128::zero()); + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_y_out); + + let amount_y_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_x_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_y_in, false)?; + + assert!(amount_x_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SILK, amount_y_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_y_in, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + assert!(protocol_fee_x > 0); + assert!(protocol_fee_y > 0); + + lb_pair::collect_protocol_fees(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let balance_x = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_x.u128(), protocol_fee_x - 1); + + let balance_y = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_y.u128(), protocol_fee_y - 1); + + Ok(()) +} + +#[test] +pub fn test_collect_protocol_fees_after_swap() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + let amount_x_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_y_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_x_in, true)?; + + assert!(amount_y_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SHADE, amount_x_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_x_in, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + let prev_protocol_fee_x = protocol_fee_x; + + assert!(protocol_fee_x > 0); + assert_eq!(protocol_fee_y, 0); + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + lb_pair::collect_protocol_fees(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let (reserves_x_after, reserves_y_after) = + lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(reserves_x_after, reserves_x); + assert_eq!(reserves_y_after, reserves_y); + + let balance_x = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_x.u128(), protocol_fee_x - 1); + + let balance_y = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_y.u128(), 0); + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + assert_eq!(protocol_fee_x, 1); + assert_eq!(protocol_fee_y, 0); + + let amount_y_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_x_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_y_in, false)?; + + assert!(amount_x_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SILK, amount_y_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_y_in, + )?; + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + let prev_protocol_fee_y = protocol_fee_y; + assert_eq!(protocol_fee_x, 1); + assert!(protocol_fee_y > 0); + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + lb_pair::collect_protocol_fees(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract)?; + + let (reserves_x_after, reserves_y_after) = + lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(reserves_x_after, reserves_x); + assert_eq!(reserves_y_after, reserves_y); + + let balance_x = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_x.u128(), prev_protocol_fee_x - 1); + + let balance_y = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_y.u128(), protocol_fee_y - 1); + + let (protocol_fee_x, protocol_fee_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + assert_eq!(protocol_fee_x, 1); + assert_eq!(protocol_fee_y, 1); + + let res = + lb_pair::collect_protocol_fees(&mut app, addrs.admin().as_str(), &lb_pair.lb_pair.contract); + + assert_eq!( + res.unwrap_err(), + (StdError::generic_err("Not enough funds".to_string())) + ); + + let balance_x = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_x.u128(), prev_protocol_fee_x - 1); + + let balance_y = snip20::balance_query( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(balance_y.u128(), prev_protocol_fee_y - 1); + + Ok(()) +} + +#[test] + +pub fn test_revert_total_fee_exceeded() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + + let bin_step = Uint128::from(generate_random(1u16, u16::MAX)); + let (mut app, lb_factory, deployed_contracts) = setup(Some(bin_step.u128() as u16))?; + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let shade = extract_contract_info(&deployed_contracts, SHADE)?; + let token_x = token_type_snip20_generator(&shade)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + bin_step.u128() as u16, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x, + token_y, + )?; + let lb_pair = all_pairs[0].clone(); + let base_factor = Uint128::from(generate_random(1u16, u16::MAX)); + let variable_fee_control = Uint128::from(generate_random(1u32, U24::MAX)); + let max_volatility_accumulator = Uint128::from(generate_random(1u32, MASK_UINT20.as_u32())); + + let base_fee = base_factor * bin_step * Uint128::from(10_000_000_000u128); + let var_fee = ((bin_step * max_volatility_accumulator).pow(2) * variable_fee_control + + Uint128::from(99u128)) + / Uint128::from(100u128); + + if base_fee + var_fee > Uint128::from(10u128).pow(17) { + let res = lb_pair::set_static_fee_parameters( + &mut app, + lb_factory.address.as_str(), + &lb_pair.lb_pair.contract, + base_factor.u128() as u16, + 1, + 1, + 1, + variable_fee_control.u128() as u32, + 1, + max_volatility_accumulator.u128() as u32, + ); + + assert_eq!( + res.unwrap_err(), + (StdError::generic_err("Max total fee exceeded!".to_string())) + ); + } else { + test_revert_total_fee_exceeded()?; + } + + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs new file mode 100644 index 000000000..e713ceb01 --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_initial_state.rs @@ -0,0 +1,436 @@ +use std::str::FromStr; + +use crate::multitests::test_helper::{ + extract_contract_info, generate_random, token_type_snip20_generator, DEFAULT_BASE_FACTOR, + DEFAULT_BIN_STEP, DEFAULT_DECAY_PERIOD, DEFAULT_FILTER_PERIOD, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, DEFAULT_PROTOCOL_SHARE, DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, SHADE, SSCRT, +}; + +use super::test_helper::{assert_approx_eq_abs, assert_approx_eq_rel, init_addrs, setup, ID_ONE}; +use anyhow::Ok; +use cosmwasm_std::{ContractInfo, Uint128, Uint256}; +use shade_multi_test::interfaces::{lb_factory, lb_pair, utils::DeployedContracts}; +use shade_protocol::{ + lb_libraries::{math::u24::U24, oracle_helper::MAX_SAMPLE_LIFETIME, types::LBPairInformation}, + multi_test::App, +}; + +pub fn lb_pair_setup( +) -> Result<(App, ContractInfo, DeployedContracts, LBPairInformation), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + let shd = extract_contract_info(&deployed_contracts, SHADE)?; + let sscrt = extract_contract_info(&deployed_contracts, SSCRT)?; + + let token_x = token_type_snip20_generator(&shd)?; + let token_y = token_type_snip20_generator(&sscrt)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x, + token_y, + )?; + let lb_pair = all_pairs[0].clone(); + Ok((app, lb_factory.into(), deployed_contracts, lb_pair)) +} + +#[test] +pub fn test_query_factory() -> Result<(), anyhow::Error> { + let (app, lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let factory_addr = lb_pair::query_factory(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(lb_factory.address, factory_addr); + + Ok(()) +} + +#[test] +pub fn test_query_token_x() -> Result<(), anyhow::Error> { + let (app, _lb_factory, deployed_contracts, lb_pair) = lb_pair_setup()?; + + let shd = token_type_snip20_generator(&extract_contract_info(&deployed_contracts, SHADE)?)?; + + let token_x = lb_pair::query_token_x(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(token_x.unique_key(), shd.unique_key()); + + Ok(()) +} + +#[test] +pub fn test_query_token_y() -> Result<(), anyhow::Error> { + let (app, _lb_factory, deployed_contracts, lb_pair) = lb_pair_setup()?; + + let sscrt = token_type_snip20_generator(&extract_contract_info(&deployed_contracts, SSCRT)?)?; + + let token_y = lb_pair::query_token_y(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(token_y.unique_key(), sscrt.unique_key()); + + Ok(()) +} + +#[test] +pub fn test_query_bin_step() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let bin_step = lb_pair::query_bin_step(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(bin_step, DEFAULT_BIN_STEP); + + Ok(()) +} + +#[test] +pub fn test_query_bin_reserves() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(reserves_x, 0u128); + assert_eq!(reserves_y, 0u128); + + Ok(()) +} + +#[test] +pub fn test_query_active_id() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let active_id = lb_pair::query_active_id(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(active_id, ID_ONE); + + Ok(()) +} + +#[test] +pub fn test_fuzz_query_bin() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let id = generate_random(0, U24::MAX); + + let (reserves_x, reserves_y) = lb_pair::query_bin(&app, &lb_pair.lb_pair.contract, id)?; + + assert_eq!(reserves_x, 0u128); + assert_eq!(reserves_y, 0u128); + Ok(()) +} + +#[test] +pub fn test_query_next_non_empty_bin() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let id = lb_pair::query_next_non_empty_bin(&app, &lb_pair.lb_pair.contract, false, 0)?; + assert_eq!(id, 0u32); + + let id = lb_pair::query_next_non_empty_bin(&app, &lb_pair.lb_pair.contract, true, 0)?; + assert_eq!(id, U24::MAX); + + let id = lb_pair::query_next_non_empty_bin(&app, &lb_pair.lb_pair.contract, false, U24::MAX)?; + assert_eq!(id, 0u32); + + let id = lb_pair::query_next_non_empty_bin(&app, &lb_pair.lb_pair.contract, true, U24::MAX)?; + assert_eq!(id, U24::MAX); + Ok(()) +} + +#[test] +pub fn test_query_protocol_fees() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let (protocol_fees_x, protocol_fees_y) = + lb_pair::query_protocol_fees(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(protocol_fees_x, 0u128); + assert_eq!(protocol_fees_y, 0u128); + Ok(()) +} + +#[test] +pub fn test_query_static_fee_parameters() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let ( + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + ) = lb_pair::query_static_fee_params(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(base_factor, DEFAULT_BASE_FACTOR); + assert_eq!(filter_period, DEFAULT_FILTER_PERIOD); + assert_eq!(decay_period, DEFAULT_DECAY_PERIOD); + assert_eq!(reduction_factor, DEFAULT_REDUCTION_FACTOR); + assert_eq!(variable_fee_control, DEFAULT_VARIABLE_FEE_CONTROL); + assert_eq!(protocol_share, DEFAULT_PROTOCOL_SHARE); + assert_eq!( + max_volatility_accumulator, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR + ); + + Ok(()) +} + +#[test] +pub fn test_query_variable_fee_parameters() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let (volatility_accumulator, volatility_reference, id_reference, time_of_last_update) = + lb_pair::query_variable_fee_params(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(volatility_reference, 0); + assert_eq!(volatility_accumulator, 0); + assert_eq!(time_of_last_update, 0); + assert_eq!(id_reference, ID_ONE); + + Ok(()) +} + +#[test] +pub fn test_query_oracle_parameters() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let (sample_lifetime, size, active_size, last_updated, first_timestamp) = + lb_pair::query_oracle_parameters(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(size, 0); + assert_eq!(active_size, 0); + assert_eq!(last_updated, 0); + assert_eq!(first_timestamp, 0); + assert_eq!(sample_lifetime, MAX_SAMPLE_LIFETIME); + + Ok(()) +} + +#[test] +pub fn test_query_oracle_sample_at() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let (cumulative_id, cumulative_volatility, cumulative_bin_crossed) = + lb_pair::query_oracle_sample_at(&app, &lb_pair.lb_pair.contract, 1)?; + + assert_eq!(cumulative_id, 0); + assert_eq!(cumulative_volatility, 0); + assert_eq!(cumulative_bin_crossed, 0); + + Ok(()) +} + +#[test] +pub fn test_query_price_from_id() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + let delta = Uint256::from(DEFAULT_BIN_STEP).checked_mul(Uint256::from((5 * 10) ^ 13_u128))?; + + assert_approx_eq_rel( + lb_pair::query_price_from_id(&app, &lb_pair.lb_pair.contract, 1_000 + ID_ONE)?, + Uint256::from_str("924521306405372907020063908180274956666")?, + delta, + "test_query_id_from_price::1", + ); + assert_approx_eq_rel( + lb_pair::query_price_from_id(&app, &lb_pair.lb_pair.contract, ID_ONE - 1_000)?, + Uint256::from_str("125245452360126660303600960578690115355")?, + delta, + "test_query_id_from_price::2", + ); + assert_approx_eq_rel( + lb_pair::query_price_from_id(&app, &lb_pair.lb_pair.contract, 10_000 + ID_ONE)?, + Uint256::from_str("7457860201113570250644758522304565438757805")?, + delta, + "test_query_id_from_price::3", + ); + assert_approx_eq_rel( + lb_pair::query_price_from_id(&app, &lb_pair.lb_pair.contract, ID_ONE - 10_000)?, + Uint256::from_str("15526181252368702469753297095319515")?, + delta, + "test_query_id_from_price::4", + ); + + assert!( + lb_pair::query_price_from_id(&app, &lb_pair.lb_pair.contract, ID_ONE + 80_000)? + < Uint256::from_str( + "18133092123953330812316154041959812232388892985347108730495479426840526848" + )?, + "test_query_id_from_price::5", + ); + + assert!( + lb_pair::query_price_from_id(&app, &lb_pair.lb_pair.contract, ID_ONE + 80_000)? + > Uint256::from_str( + "18096880266539986845478224721407196147811144510344442837666495029900738560" + )?, + "test_query_id_from_price::6", + ); + + assert_approx_eq_rel( + lb_pair::query_price_from_id(&app, &lb_pair.lb_pair.contract, ID_ONE - 80_000)?, + Uint256::from_str("6392")?, + Uint256::from(10 ^ 8_u128), + "test_query_id_from_price::7", + ); + + assert_approx_eq_rel( + lb_pair::query_price_from_id(&app, &lb_pair.lb_pair.contract, ID_ONE + 12_345)?, + Uint256::from_str("77718771515321296819382407317364352468140333")?, + delta, + "test_query_id_from_price::8", + ); + + assert_approx_eq_rel( + lb_pair::query_price_from_id(&app, &lb_pair.lb_pair.contract, ID_ONE - 12_345)?, + Uint256::from_str("1489885737765286392982993705955521")?, + delta, + "test_query_id_from_price::9", + ); + + Ok(()) +} + +#[test] +pub fn test_query_id_from_price() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + let delta = Uint256::from(1u128); + + assert_approx_eq_abs( + Uint256::from(lb_pair::query_id_from_price( + &app, + &lb_pair.lb_pair.contract, + Uint256::from_str("924521306405372907020063908180274956666")?, + )?), + Uint256::from(1_000 + ID_ONE), + delta, + "test_query_id_from_price::1", + ); + + assert_approx_eq_abs( + Uint256::from(lb_pair::query_id_from_price( + &app, + &lb_pair.lb_pair.contract, + Uint256::from_str("125245452360126660303600960578690115355")?, + )?), + Uint256::from(ID_ONE - 1_000), + delta, + "test_query_id_from_price::2", + ); + + assert_approx_eq_abs( + Uint256::from(lb_pair::query_id_from_price( + &app, + &lb_pair.lb_pair.contract, + Uint256::from_str("7457860201113570250644758522304565438757805")?, + )?), + Uint256::from(10_000 + ID_ONE), + delta, + "test_query_id_from_price::3", + ); + + assert_approx_eq_abs( + Uint256::from(lb_pair::query_id_from_price( + &app, + &lb_pair.lb_pair.contract, + Uint256::from_str("15526181252368702469753297095319515")?, + )?), + Uint256::from(ID_ONE - 10_000), + delta, + "test_query_id_from_price::4", + ); + + assert_approx_eq_abs( + Uint256::from(lb_pair::query_id_from_price( + &app, + &lb_pair.lb_pair.contract, + Uint256::from_str( + "18114977146806524168130684952726477124021312024291123319263609183005067158", + )?, + )?), + Uint256::from(ID_ONE + 80_000), + delta, + "test_query_id_from_price::5", + ); + + assert_approx_eq_abs( + Uint256::from(lb_pair::query_id_from_price( + &app, + &lb_pair.lb_pair.contract, + Uint256::from_str("6392")?, + )?), + Uint256::from(ID_ONE - 80_000), + delta, + "test_query_id_from_price::6", + ); + + assert_approx_eq_abs( + Uint256::from(lb_pair::query_id_from_price( + &app, + &lb_pair.lb_pair.contract, + Uint256::from_str("77718771515321296819382407317364352468140333")?, + )?), + Uint256::from(ID_ONE + 12_345), + delta, + "test_query_id_from_price::7", + ); + + assert_approx_eq_abs( + Uint256::from(lb_pair::query_id_from_price( + &app, + &lb_pair.lb_pair.contract, + Uint256::from_str("1489885737765286392982993705955521")?, + )?), + Uint256::from(ID_ONE - 12_345), + delta, + "test_query_id_from_price::8", + ); + + Ok(()) +} + +#[test] +fn test_fuzz_query_swap_out() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let amount_out: Uint128 = Uint128::from(generate_random(0, u128::MAX)); + let swap_for_y: bool = generate_random(0, 1) == 1; + + let (amount_in, amount_out_left, fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_out, swap_for_y)?; + + assert_eq!(amount_in.u128(), 0); + assert_eq!(amount_out_left, amount_out); + assert_eq!(fee.u128(), 0); + + Ok(()) +} + +#[test] +fn test_fuzz_query_swap_in() -> Result<(), anyhow::Error> { + let (app, _lb_factory, _deployed_contracts, lb_pair) = lb_pair_setup()?; + + let amount_in = Uint128::from(generate_random(0, u128::MAX)); + let swap_for_y = generate_random(0, 1) == 1; + let (amount_out, amount_in_left, fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_in, swap_for_y)?; + + assert_eq!(amount_out.u128(), 0); + assert_eq!(amount_in_left, amount_in); + assert_eq!(fee.u128(), 0); + + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs new file mode 100644 index 000000000..6a5ee6953 --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_liquidity.rs @@ -0,0 +1,1270 @@ +use std::ops::Add; + +use crate::multitests::test_helper::*; + +use super::test_helper::{ + assert_approx_eq_rel, increase_allowance_helper, init_addrs, liquidity_parameters_generator, + mint_token_helper, setup, ID_ONE, +}; +use anyhow::Ok; +use cosmwasm_std::{ContractInfo, StdError, Uint128, Uint256}; +use shade_multi_test::interfaces::{ + lb_factory, lb_pair, lb_token, snip20, utils::DeployedContracts, +}; +use shade_protocol::{ + lb_libraries::{math::u24::U24, types::LBPairInformation}, + liquidity_book::lb_pair::RemoveLiquidity, + multi_test::App, +}; + +pub const PRECISION: u128 = 1_000_000_000_000_000_000_u128; + +pub const ACTIVE_ID: u32 = ID_ONE - 24647; + +pub fn lb_pair_setup() -> Result< + ( + App, + ContractInfo, + DeployedContracts, + LBPairInformation, + ContractInfo, + ), + anyhow::Error, +> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let shade = extract_contract_info(&deployed_contracts, SHADE)?; + let token_x = token_type_snip20_generator(&silk)?; + let token_y = token_type_snip20_generator(&shade)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x, + token_y, + )?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::lb_token_query(&app, &lb_pair.lb_pair.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + Ok(( + app, + lb_factory.into(), + deployed_contracts, + lb_pair, + lb_token, + )) +} + +#[test] +pub fn test_simple_mint() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + let amount_x = Uint128::from(600 * 100_000_000_u128); //10^8 + let amount_y = Uint128::from(100 * 100_000_000_u128); + let nb_bins_x = 6; + let nb_bins_y = 6; + + let token_x = extract_contract_info(&deployed_contracts, SILK)?; + let token_y = extract_contract_info(&deployed_contracts, SHADE)?; + + let tokens_to_mint = vec![(SILK, amount_x), (SHADE, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + // query balance for token_minted and calculating the residue + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + let expected_batman_balance = amount_x + - ((amount_x * Uint128::from(PRECISION / nb_bins_x as u128)) / Uint128::from(PRECISION)) + * Uint128::from(nb_bins_x as u128); + + assert_eq!(silk_balance, expected_batman_balance, "test_SimpleMint::1"); + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + let expected_batman_balance = amount_y + - ((amount_y * Uint128::from(PRECISION / nb_bins_y as u128)) / Uint128::from(PRECISION)) + * Uint128::from(nb_bins_y as u128); + + assert_eq!(shd_balance, expected_batman_balance, "test_SimpleMint::2"); + + let total_bins = get_total_bins(nb_bins_x, nb_bins_y) as u32; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, nb_bins_y); + let (reserves_x, reserves_y) = lb_pair::query_bin(&app, &lb_pair.lb_pair.contract, id)?; + + if id < ACTIVE_ID { + assert_eq!(reserves_x, 0u128, "test_sample_mint::3"); + assert_eq!( + reserves_y, + ((amount_y * Uint128::from(PRECISION / nb_bins_y as u128)) + / Uint128::from(PRECISION)) + .u128(), + "test_sample_mint::4" + ); + } else if id == ACTIVE_ID { + assert_approx_eq_rel( + Uint256::from(reserves_x), + Uint256::from( + ((amount_x * Uint128::from(PRECISION / nb_bins_x as u128)) + / Uint128::from(PRECISION)) + .u128(), + ), + Uint256::from(1_000_000_000_000_000_u128), + "test_sample_mint::5", + ); + assert_approx_eq_rel( + Uint256::from(reserves_y), + Uint256::from( + ((amount_y * Uint128::from(PRECISION / nb_bins_y as u128)) + / Uint128::from(PRECISION)) + .u128(), + ), + Uint256::from(1_000_000_000_000_000_u128), + "test_sample_mint::6", + ) + } else { + assert_eq!(reserves_y, 0u128, "test_sample_mint::7"); + assert_eq!( + reserves_x, + ((amount_x * Uint128::from(PRECISION / nb_bins_x as u128)) + / Uint128::from(PRECISION)) + .u128(), + "test_sample_mint::8" + ); + } + + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + + assert!(balance > Uint256::MIN, "test_sample_mint::9"); + } + + Ok(()) +} + +#[test] +pub fn test_mint_twice() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + + let amount_x = Uint128::from(600 * 100_000_000_u128); //10^8 + let amount_y = Uint128::from(100 * 100_000_000_u128); + let nb_bins_x = 6; + let nb_bins_y = 6; + + let token_x = extract_contract_info(&deployed_contracts, SILK)?; + let token_y = extract_contract_info(&deployed_contracts, SHADE)?; + + let tokens_to_mint = vec![(SILK, amount_x + amount_x), (SHADE, amount_y + amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters.clone(), + )?; + let total_bins = get_total_bins(nb_bins_x, nb_bins_y) as u32; + + let mut balances = vec![Uint256::zero(); total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, nb_bins_y); + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, nb_bins_y); + let (reserves_x, reserves_y) = lb_pair::query_bin(&app, &lb_pair.lb_pair.contract, id)?; + + if id < ACTIVE_ID { + assert_eq!(reserves_x, 0u128, "test_sample_mint::3"); + assert_eq!( + reserves_y, + 2 * ((amount_y * Uint128::from(PRECISION / nb_bins_y as u128)) + / Uint128::from(PRECISION)) + .u128(), + "test_sample_mint::4" + ); + } else if id == ACTIVE_ID { + assert_approx_eq_rel( + Uint256::from(reserves_x), + Uint256::from( + 2 * ((amount_x * Uint128::from(PRECISION / nb_bins_x as u128)) + / Uint128::from(PRECISION)) + .u128(), + ), + Uint256::from(1_000_000_000_000_000_u128), + "test_sample_mint::5", + ); + assert_approx_eq_rel( + Uint256::from(reserves_y), + Uint256::from( + 2 * ((amount_y * Uint128::from(PRECISION / nb_bins_y as u128)) + / Uint128::from(PRECISION)) + .u128(), + ), + Uint256::from(1_000_000_000_000_000_u128), + "test_sample_mint::6", + ) + } else { + assert_eq!(reserves_y, 0u128, "test_sample_mint::7"); + assert_eq!( + reserves_x, + 2 * ((amount_x * Uint128::from(PRECISION / nb_bins_x as u128)) + / Uint128::from(PRECISION)) + .u128(), + "test_sample_mint::8" + ); + } + + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + + assert_eq!( + balance, + balances[i as usize] + balances[i as usize], + "test_sample_mint::9" + ); + } + + Ok(()) +} + +#[test] +pub fn test_mint_with_different_bins() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + let amount_x = Uint128::from(600 * 100_000_000_u128); //10^8 + let amount_y = Uint128::from(100 * 100_000_000_u128); + let nb_bins_x = 6; + let nb_bins_y = 6; + + let token_x = extract_contract_info(&deployed_contracts, SILK)?; + let token_y = extract_contract_info(&deployed_contracts, SHADE)?; + + let tokens_to_mint = vec![(SILK, amount_x + amount_x), (SHADE, amount_y + amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + // Adding liquidity with nb_bins_x and nb_bins_y + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + let total_bins = get_total_bins(nb_bins_x, nb_bins_y) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, nb_bins_y); + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + // Adding liquidity with nb_bins_x and 0 for nb_bins_y + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + amount_x, + amount_y, + nb_bins_x, + 0, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + 0, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + // Verify + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, nb_bins_y); + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + + if id == ACTIVE_ID { + assert_eq!( + balance, + balances[i as usize] + balances[i as usize], + "test_MintWithDifferentBins::1", + ); + } else { + assert_eq!( + balance, + balances[i as usize] + balances[i as usize], + "test_MintWithDifferentBins::2" + ); + } + } + + Ok(()) +} + +#[test] +pub fn test_simple_burn() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + let amount_x = Uint128::from(600 * 100_000_000_u128); //10^8 + let amount_y = Uint128::from(100 * 100_000_000_u128); + let nb_bins_x = 6; + let nb_bins_y = 6; + + let token_x = extract_contract_info(&deployed_contracts, SILK)?; + let token_y = extract_contract_info(&deployed_contracts, SHADE)?; + + let tokens_to_mint = vec![(SILK, amount_x), (SHADE, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + // Adding liquidity with nb_bins_x and nb_bins_y + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + let total_bins = get_total_bins(nb_bins_x, nb_bins_y) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, nb_bins_y); + ids[i as usize] = id; + balances[i as usize] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(reserves_x), + amount_y_min: Uint128::from(reserves_y), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, amount_y); + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_x); + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(reserves_x, 0u128); + assert_eq!(reserves_y, 0u128); + + let total_bins = get_total_bins(nb_bins_x, nb_bins_y) as u32; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, nb_bins_y); + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + + assert_eq!(balance, Uint256::MIN); + } + + Ok(()) +} + +#[test] +pub fn test_burn_half_twice() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + let amount_x = Uint128::from(600 * 100_000_000_u128); //10^8 + let amount_y = Uint128::from(100 * 100_000_000_u128); + let nb_bins_x = 6; + let nb_bins_y = 6; + + let token_x = extract_contract_info(&deployed_contracts, SILK)?; + let token_y = extract_contract_info(&deployed_contracts, SHADE)?; + + let tokens_to_mint = vec![(SILK, amount_x), (SHADE, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + // Adding liquidity with nb_bins_x and nb_bins_y + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + let residue_silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + let residue_shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let total_bins = get_total_bins(nb_bins_x, nb_bins_y) as u32; + let mut balances = vec![Uint256::zero(); total_bins as usize]; + let mut half_balances = vec![Uint256::zero(); total_bins as usize]; + let mut ids = vec![0u32; total_bins as usize]; + + for i in 0..total_bins { + let id = get_id(ACTIVE_ID, i, nb_bins_y); + ids[i as usize] = id; + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + id.to_string(), + )?; + half_balances[i as usize] = balance / Uint256::from(2u128); + balances[i as usize] = balance - balance / Uint256::from(2u128); + } + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(1u128), + amount_y_min: Uint128::from(1u128), + ids: ids.clone(), + amounts: half_balances, + deadline: 99999999999, + }, + )?; + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_approx_eq_rel( + Uint256::from(silk_balance), + Uint256::from(reserves_x / 2), + Uint256::from(10_000_000_000u128), + "test_burn__half_twice::1", + ); + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_approx_eq_rel( + Uint256::from(shd_balance), + Uint256::from(reserves_y / 2), + Uint256::from(10_000_000_000u128), + "test_burn__half_twice::2", + ); + + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(1u128), + amount_y_min: Uint128::from(1u128), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!( + silk_balance.u128(), + reserves_x + residue_silk_balance.u128() + ); + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance.u128(), reserves_y + residue_shd_balance.u128()); + + let (reserves_x, reserves_y) = lb_pair::query_reserves(&app, &lb_pair.lb_pair.contract)?; + + assert_eq!(reserves_x, 0u128); + assert_eq!(reserves_y, 0u128); + + Ok(()) +} + +#[test] +pub fn test_query_next_non_empty_bin() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + let amount_x = Uint128::from(600 * 100_000_000_u128); //10^8 + let amount_y = Uint128::from(100 * 100_000_000_u128); + let nb_bins_x = 6; + let nb_bins_y = 6; + + let token_x = extract_contract_info(&deployed_contracts, SILK)?; + let token_y = extract_contract_info(&deployed_contracts, SHADE)?; + + let tokens_to_mint = vec![(SILK, amount_x), (SHADE, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + // Adding liquidity with nb_bins_x and nb_bins_y + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + //calculate lower id + let lower_id = ACTIVE_ID - nb_bins_y as u32 + 1u32; + //calculate upper id + let upper_id = ACTIVE_ID + nb_bins_y as u32 - 1u32; + + let mut id = lb_pair::query_next_non_empty_bin(&app, &lb_pair.lb_pair.contract, false, 0)?; + assert_eq!(lower_id, id); + + let total_bins = get_total_bins(nb_bins_x, nb_bins_y) as u32; + + for i in 0..(total_bins - 1u32) { + id = lb_pair::query_next_non_empty_bin(&app, &lb_pair.lb_pair.contract, false, id)?; + assert_eq!(lower_id + i + 1u32, id); + } + + let mut id = + lb_pair::query_next_non_empty_bin(&app, &lb_pair.lb_pair.contract, true, U24::MAX)?; + assert_eq!(upper_id, id); + + let mut balances = vec![Uint256::zero(); 1_usize]; + let mut ids = vec![0u32; 1_usize]; + + ids[0] = ACTIVE_ID; + balances[0] = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + ACTIVE_ID.to_string(), + )?; + + lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(1u128), + amount_y_min: Uint128::from(1u128), + ids, + amounts: balances, + deadline: 99999999999, + }, + )?; + + id = lb_pair::query_next_non_empty_bin( + &app, + &lb_pair.lb_pair.contract, + false, + ACTIVE_ID - 1u32, + )?; + + assert_eq!(id, ACTIVE_ID + 1); + + id = + lb_pair::query_next_non_empty_bin(&app, &lb_pair.lb_pair.contract, true, ACTIVE_ID + 1u32)?; + + assert_eq!(id, ACTIVE_ID - 1); + + Ok(()) +} + +#[test] +pub fn test_revert_mint_zero_shares() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + let amount_x = Uint128::from(0u128); //10^8 + let amount_y = Uint128::from(0u128); + let nb_bins_x = 6; + let nb_bins_y = 6; + + let token_x = extract_contract_info(&deployed_contracts, SILK)?; + let token_y = extract_contract_info(&deployed_contracts, SHADE)?; + + let tokens_to_mint = vec![(SILK, amount_x), (SHADE, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + // Adding liquidity with nb_bins_x and nb_bins_y + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + let res = lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + ); + + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!( + "Zero amount for bin id: {:?}", + ACTIVE_ID - nb_bins_y as u32 + 1 + )) + ); + + Ok(()) +} + +#[test] +pub fn test_revert_burn_empty_array() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + let amount_x = Uint128::from(600 * 100_000_000_u128); //10^8 + let amount_y = Uint128::from(100 * 100_000_000_u128); + let nb_bins_x = 6; + let nb_bins_y = 6; + + let token_x = extract_contract_info(&deployed_contracts, SILK)?; + let token_y = extract_contract_info(&deployed_contracts, SHADE)?; + + let tokens_to_mint = vec![(SILK, amount_x), (SHADE, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + // Adding liquidity with nb_bins_x and nb_bins_y + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + //uneven + let mut ids = vec![]; + let mut balances = vec![Uint256::zero()]; + + let res = lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(0u128), + amount_y_min: Uint128::from(0u128), + ids, + amounts: balances, + deadline: 99999999999, + }, + ); + + assert_eq!( + res.unwrap_err(), + StdError::generic_err("Invalid input!".to_string()) + ); + + //uneven input + ids = vec![0u32]; + balances = vec![]; + + let res = lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(0u128), + amount_y_min: Uint128::from(0u128), + ids, + amounts: balances, + deadline: 99999999999, + }, + ); + + assert_eq!( + res.unwrap_err(), + StdError::generic_err("Invalid input!".to_string()) + ); + + //both zero + ids = vec![]; + balances = vec![]; + let res = lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(0u128), + amount_y_min: Uint128::from(0u128), + ids, + amounts: balances, + deadline: 99999999999, + }, + ); + + assert_eq!( + res.unwrap_err(), + StdError::generic_err("Invalid input!".to_string()) + ); + + // non-zero values + ids = vec![ACTIVE_ID]; + let balances = vec![Uint256::one(), Uint256::one()]; + + let res = lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(0u128), + amount_y_min: Uint128::from(0u128), + ids, + amounts: balances, + deadline: 99999999999, + }, + ); + + assert_eq!( + res.unwrap_err(), + StdError::generic_err("Invalid input!".to_string()) + ); + + Ok(()) +} + +#[test] +pub fn test_revert_burn_more_than_balance() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, lb_token) = lb_pair_setup()?; + let amount_x = Uint128::from(600 * 100_000_000_u128); //10^8 + let amount_y = Uint128::from(100 * 100_000_000_u128); + let nb_bins_x = 1; + let nb_bins_y = 0; + + let token_x = extract_contract_info(&deployed_contracts, SILK)?; + let token_y = extract_contract_info(&deployed_contracts, SHADE)?; + + let tokens_to_mint = vec![(SILK, amount_x), (SHADE, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + // Adding liquidity with nb_bins_x and nb_bins_y + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + //uneven + + let balance = lb_token::query_balance( + &app, + &lb_token, + addrs.batman(), + addrs.batman(), + String::from("viewing_key"), + ACTIVE_ID.to_string(), + )?; + + let ids = vec![ACTIVE_ID]; + let balances = vec![balance.add(Uint256::one())]; + + let res = lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(0u128), + amount_y_min: Uint128::from(0u128), + ids, + amounts: balances, + deadline: 99999999999, + }, + ); + + assert_eq!( + res.unwrap_err(), + StdError::generic_err("Generic error: insufficient funds".to_string()) + ); + + Ok(()) +} + +#[test] +pub fn test_revert_burn_zero() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + let amount_x = Uint128::from(600 * 100_000_000_u128); //10^8 + let amount_y = Uint128::from(100 * 100_000_000_u128); + let nb_bins_x = 1; + let nb_bins_y = 0; + + let token_x = extract_contract_info(&deployed_contracts, SILK)?; + let token_y = extract_contract_info(&deployed_contracts, SHADE)?; + + let tokens_to_mint = vec![(SILK, amount_x), (SHADE, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + // Adding liquidity with nb_bins_x and nb_bins_y + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + //uneven + + let ids = vec![ACTIVE_ID]; + let balances = vec![Uint256::zero()]; + + let res = lb_pair::remove_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + RemoveLiquidity { + token_x: token_type_snip20_generator(&token_x)?, + token_y: token_type_snip20_generator(&token_y)?, + bin_step: lb_pair.bin_step, + amount_x_min: Uint128::from(0u128), + amount_y_min: Uint128::from(0u128), + ids, + amounts: balances, + deadline: 99999999999, + }, + ); + + assert_eq!( + res.unwrap_err(), + StdError::generic_err(format!("Zero Shares for bin id: {:?}", ACTIVE_ID)) + ); + + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_pair_swap.rs b/contracts/liquidity_book/tests/src/multitests/lb_pair_swap.rs new file mode 100644 index 000000000..6e9884e0d --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/lb_pair_swap.rs @@ -0,0 +1,520 @@ +use crate::multitests::test_helper::*; + +use super::test_helper::{ + increase_allowance_helper, init_addrs, liquidity_parameters_generator, mint_token_helper, + setup, ID_ONE, +}; +use anyhow::Ok; +use cosmwasm_std::{ContractInfo, StdError, Uint128}; +use shade_multi_test::interfaces::{ + lb_factory, lb_pair, lb_token, snip20, utils::DeployedContracts, +}; +use shade_protocol::{ + lb_libraries::{types::LBPairInformation}, + multi_test::App, +}; + +pub const DEPOSIT_AMOUNT: u128 = 1_000_000_000_000_000_000_u128; + +pub const ACTIVE_ID: u32 = ID_ONE; + +pub fn lb_pair_setup() -> Result< + ( + App, + ContractInfo, + DeployedContracts, + LBPairInformation, + ContractInfo, + ), + anyhow::Error, +> { + let addrs = init_addrs(); + let (mut app, lb_factory, deployed_contracts) = setup(None)?; + + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let shade = extract_contract_info(&deployed_contracts, SHADE)?; + let token_x = token_type_snip20_generator(&shade)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ACTIVE_ID, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x, + token_y, + )?; + let lb_pair = all_pairs[0].clone(); + + let lb_token = lb_pair::lb_token_query(&app, &lb_pair.lb_pair.contract)?; + + lb_token::set_viewing_key( + &mut app, + addrs.batman().as_str(), + &lb_token, + "viewing_key".to_owned(), + )?; + + let amount_x = Uint128::from(DEPOSIT_AMOUNT); + let amount_y = Uint128::from(DEPOSIT_AMOUNT); + let nb_bins_x = 50; + let nb_bins_y = 50; + + let token_x = extract_contract_info(&deployed_contracts, SHADE)?; + let token_y = extract_contract_info(&deployed_contracts, SILK)?; + + let tokens_to_mint = vec![(SHADE, amount_x), (SILK, amount_y)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + //Adding liquidity + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ACTIVE_ID, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + Ok(( + app, + lb_factory.into(), + deployed_contracts, + lb_pair, + lb_token, + )) +} + +#[test] +pub fn test_fuzz_swap_in_x() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + let amount_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_in, amount_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_out, true)?; + assert_eq!(amount_out_left, Uint128::zero()); + + let tokens_to_mint = vec![(SHADE, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, amount_in); + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_in, + )?; + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, Uint128::zero()); + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_out); + + Ok(()) +} + +#[test] +pub fn test_fuzz_swap_in_y() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + let amount_out = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_in, amount_out_left, _fee) = + lb_pair::query_swap_in(&app, &lb_pair.lb_pair.contract, amount_out, false)?; + assert_eq!(amount_out_left, Uint128::MIN); + + let tokens_to_mint = vec![(SILK, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_in, + )?; + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, amount_out); + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, Uint128::zero()); + + Ok(()) +} + +#[test] +pub fn test_fuzz_swap_out_for_y() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + let amount_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_in, true)?; + + assert!(amount_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SHADE, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + amount_in, + )?; + + let shd_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shd_balance, Uint128::zero()); + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, amount_out); + + Ok(()) +} + +#[test] +pub fn test_fuzz_swap_out_for_x() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + let amount_in = Uint128::from(generate_random(1u128, DEPOSIT_AMOUNT - 1)); + + let (amount_out, amount_in_left, _fee) = + lb_pair::query_swap_out(&app, &lb_pair.lb_pair.contract, amount_in, false)?; + + assert!(amount_out > Uint128::zero()); + assert_eq!(amount_in_left, Uint128::zero()); + + let tokens_to_mint = vec![(SILK, amount_in)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + amount_in, + )?; + + let silk_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(silk_balance, Uint128::zero()); + + let shade_balance = snip20::balance_query( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + assert_eq!(shade_balance, amount_out); + + Ok(()) +} + +#[test] + +pub fn test_revert_swap_insufficient_amount_in() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + + let result = lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + Uint128::zero(), + ); + + assert_eq!( + result, + Err(StdError::GenericErr { + msg: "Insufficient amount in!".to_string() + }) + ); + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + let result = lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + Uint128::zero(), + ); + + assert_eq!( + result, + Err(StdError::GenericErr { + msg: "Insufficient amount in!".to_string() + }) + ); + + Ok(()) +} + +#[test] +pub fn test_revert_swap_insufficient_amount_out() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + // Simulate transferring 1 token to the LB pair contract + let token_amount = Uint128::from(1u128); + + let tokens_to_mint = vec![(SHADE, token_amount)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + + let result = lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + token_amount, + ); + + // Check for the expected error + assert_eq!( + result, + Err(StdError::GenericErr { + msg: "Insufficient amount out!".to_string() + }) + ); + let tokens_to_mint = vec![(SILK, token_amount)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + let result = lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + token_amount, + ); + + // Check for the expected error + assert_eq!( + result, + Err(StdError::GenericErr { + msg: "Insufficient amount out!".to_string() + }) + ); + Ok(()) +} + +#[test] +pub fn test_revert_swap_out_of_liquidity() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, deployed_contracts, lb_pair, _lb_token) = lb_pair_setup()?; + + // Simulate transferring 2e18 tokens to the LB pair contract + let token_amount = Uint128::from(2 * DEPOSIT_AMOUNT); + let tokens_to_mint = vec![(SHADE, token_amount)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + let token_x = &extract_contract_info(&deployed_contracts, SHADE)?; + + let result = lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_x, + token_amount, + ); + // Check for the expected error + assert_eq!( + result, + Err(StdError::GenericErr { + msg: "Not enough liquidity!".to_string() + }) + ); + + let tokens_to_mint = vec![(SILK, token_amount)]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + let token_y = &extract_contract_info(&deployed_contracts, SILK)?; + + let result = lb_pair::swap_snip_20( + &mut app, + addrs.batman().as_str(), + &lb_pair.lb_pair.contract, + Some(addrs.batman().to_string()), + token_y, + token_amount, + ); + + // Check for the expected error + assert_eq!( + result, + Err(StdError::GenericErr { + msg: "Not enough liquidity!".to_string() + }) + ); + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_router_integration.rs b/contracts/liquidity_book/tests/src/multitests/lb_router_integration.rs new file mode 100644 index 000000000..b2c44e8d2 --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/lb_router_integration.rs @@ -0,0 +1,531 @@ +use std::ops::Add; + +use anyhow::Ok; +use cosmwasm_std::{to_binary, BalanceResponse, BankQuery, Coin, QueryRequest, StdError}; +use shade_multi_test::interfaces::{lb_factory, lb_pair, snip20}; +use shade_protocol::lb_libraries::tokens::TokenType; + +use shadeswap_shared::{ + core::TokenAmount, + router::{Hop, InvokeMsg}, +}; + +use crate::multitests::test_helper::{ + liquidity_parameters_generator_with_native, + token_type_native_generator, + SHADE, + SILK, +}; + +use super::{ + lb_pair_fees::DEPOSIT_AMOUNT, + test_helper::{ + extract_contract_info, + increase_allowance_helper, + init_addrs, + liquidity_parameters_generator, + mint_token_helper, + setup, + token_type_snip20_generator, + DEFAULT_BIN_STEP, + ID_ONE, + }, +}; +use cosmwasm_std::Uint128; +use shade_multi_test::interfaces::{ + router::{self}, + utils::SupportedContracts, +}; +const SWAP_AMOUNT: u128 = 1000; +#[test] +pub fn router_integration() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, lb_factory, mut deployed_contracts) = setup(None)?; + + //test the registered tokens + + // 1. IMPORT necessary modules and components from various libraries. + // 2. DEFINE function router_contract_store that returns a new contract box. + // 3. INITIALIZE: + // a. Addresses for `staker_a` and `owner`. + // b. A default router. + // 4. CONFIGURE the blockchain and send initial funds to the owner address. + router::init(&mut app, addrs.admin().as_str(), &mut deployed_contracts)?; + + let router = match deployed_contracts.clone().get(&SupportedContracts::Router) { + Some(router) => router, + None => panic!("Router init failed"), + } + .clone() + .into(); + + // 5. GENERATE three token contracts: + // a. ETH token + // b. USDT token + // c. RWD token (reward token) + let shd = match deployed_contracts.get(&SupportedContracts::Snip20(SHADE.to_string())) { + Some(shd) => shd, + None => panic!("Shade not registered"), + }; + + let silk = match deployed_contracts.get(&SupportedContracts::Snip20(SILK.to_string())) { + Some(silk) => silk, + None => panic!("Silk not registered"), + }; + + router::register_snip20_token( + &mut app, + addrs.admin().as_str(), + &router, + &shd.clone().into(), + )?; + + router::register_snip20_token( + &mut app, + addrs.admin().as_str(), + &router, + &silk.clone().into(), + )?; + // 6. MINT and DEPOSIT funds for each of the three generated tokens using the owner address. + let tokens_to_mint = vec![ + (SHADE, Uint128::from(DEPOSIT_AMOUNT + SWAP_AMOUNT)), + (SILK, Uint128::from(DEPOSIT_AMOUNT + SWAP_AMOUNT)), + ]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.batman().into_string(), + tokens_to_mint.clone(), + )?; + + // 7. ROLL the blockchain forward by 1 block. + + // 8. INITIALIZE contracts and store their info: + // a. Admin contract -> already initialized in router + // b. AMM pair contract + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let shade = extract_contract_info(&deployed_contracts, SHADE)?; + let token_x = token_type_snip20_generator(&shade)?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + // c. LP token contract -> initializated with lb_pair + // TODO: d. Staking contract + + // 13. LIST the AMM pairs and ASSERT that there's only 1 AMM pair. + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.clone().into(), + token_x, + token_y, + )?; + assert_eq!(all_pairs.len(), 1); + let shd_silk_lb_pair = all_pairs[0].clone(); + + // 14. INCREASE the allowance for both tokens for the created AMM pair. + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.batman().into_string(), + shd_silk_lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + // 15. ADD liquidity to the AMM pair. + let amount_x = Uint128::from(DEPOSIT_AMOUNT); + let amount_y = Uint128::from(DEPOSIT_AMOUNT); + let nb_bins_x = 10; + let nb_bins_y = 10; + let liquidity_parameters = liquidity_parameters_generator( + &deployed_contracts, + ID_ONE, + shade.clone(), + silk, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + lb_pair::add_liquidity( + &mut app, + addrs.batman().as_str(), + &shd_silk_lb_pair.lb_pair.contract, + liquidity_parameters, + )?; + + // 17. QUERY the router for a swap simulation and ASSERT the expected results. + let offer = TokenAmount { + token: TokenType::CustomToken { + contract_addr: shade.address.clone(), + token_code_hash: shade.code_hash, + }, + amount: Uint128::new(1000u128), + }; + + // ASSERT SWAPSIMULATION + let (total_fee_amount, lp_fee_amount, shade_dao_fee_amount, result, price) = + router::query_swap_simulation( + &app, + &router, + offer.to_owned(), + vec![Hop { + addr: all_pairs[0].lb_pair.contract.address.to_string(), + code_hash: all_pairs[0].lb_pair.contract.code_hash.clone(), + }], + None, + )?; + + // Verify result not actual amount + assert_ne!(total_fee_amount, Uint128::zero()); + assert_ne!(lp_fee_amount, Uint128::zero()); + assert_ne!(shade_dao_fee_amount, Uint128::zero()); + assert_ne!(result.return_amount, Uint128::zero()); + assert_eq!(price, "0".to_string()); + + // 18. EXECUTE a token swap operation. + + let router_invoke_msg = to_binary(&InvokeMsg::SwapTokensForExact { + expected_return: Some(Uint128::new(999u128)), + path: vec![Hop { + addr: shd_silk_lb_pair.lb_pair.contract.address.to_string(), + code_hash: shd_silk_lb_pair.lb_pair.contract.code_hash.clone(), + }], + recipient: Some(addrs.scare_crow().to_string()), + }) + .unwrap(); + + snip20::send_exec( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + router.address.to_string(), + Uint128::new(SWAP_AMOUNT), + Some(router_invoke_msg), + )?; + + snip20::set_viewing_key_exec( + &mut app, + addrs.scare_crow().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + let scare_crow_balance = snip20::balance_query( + &app, + addrs.scare_crow().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(scare_crow_balance, Uint128::from(999u128)); + + // 19. EXECUTE a swap for exact tokens operation and check the resulting balance of a token. + let res = router::swap_tokens_for_exact_tokens( + &mut app, + addrs.batman().as_str(), + &router, + offer, + Some(Uint128::from(999u128)), + vec![Hop { + addr: all_pairs[0].lb_pair.contract.address.to_string(), + code_hash: all_pairs[0].lb_pair.contract.code_hash.clone(), + }], + Some(addrs.scare_crow().to_string()), + ); + assert_eq!( + res, + Err(StdError::GenericErr { + msg: "Generic error: Sent a non-native token. Should use the receive interface in SNIP20.".to_string() + }) + ); + + snip20::set_viewing_key_exec( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + let batman_balance = snip20::balance_query( + &app, + addrs.batman().as_str(), + &deployed_contracts, + SHADE, + "viewing_key".to_owned(), + )?; + + assert_eq!(batman_balance, Uint128::zero()); + + snip20::set_viewing_key_exec( + &mut app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + let batman_balance = snip20::balance_query( + &app, + addrs.batman().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + assert_eq!(batman_balance, Uint128::from(SWAP_AMOUNT)); + + // 20. CREATE another AMM pair between a native token(SSCRT) and a SNIP20 token(SILK) + let silk = extract_contract_info(&deployed_contracts, SILK)?; + let token_x = token_type_native_generator("uscrt".to_string())?; + let token_y = token_type_snip20_generator(&silk)?; + + lb_factory::create_lb_pair( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + DEFAULT_BIN_STEP, + ID_ONE, + token_x.clone(), + token_y.clone(), + "viewing_key".to_string(), + )?; + + // 21. LIST the AMM pairs and ASSERT there are now 2 AMM pairs. + let number_of_pairs = + lb_factory::query_number_of_lb_pairs(&mut app, &lb_factory.clone().into())?; + + assert_eq!(number_of_pairs, 2); + + let all_pairs = lb_factory::query_all_lb_pairs( + &mut app, + &lb_factory.into(), + token_x.clone(), + token_y.clone(), + )?; + assert_eq!(all_pairs.len(), 1); + let scrt_silk_lb_pair = all_pairs[0].clone(); + + // 22. INCREASE the allowance for the SNIP20 token for the new AMM pair. + let tokens_to_mint = vec![(SILK, Uint128::from(DEPOSIT_AMOUNT + SWAP_AMOUNT))]; + + mint_token_helper( + &mut app, + &deployed_contracts, + &addrs, + addrs.altaf_bhai().into_string(), + tokens_to_mint.clone(), + )?; + + increase_allowance_helper( + &mut app, + &deployed_contracts, + addrs.altaf_bhai().into_string(), + scrt_silk_lb_pair.lb_pair.contract.address.to_string(), + tokens_to_mint, + )?; + + // 23. ADD liquidity to the new AMM pair. + let amount_x = Uint128::from(DEPOSIT_AMOUNT); + let amount_y = Uint128::from(DEPOSIT_AMOUNT); + let nb_bins_x = 10; + let nb_bins_y = 10; + let liquidity_parameters = liquidity_parameters_generator_with_native( + &deployed_contracts, + ID_ONE, + token_x, + token_y, + amount_x, + amount_y, + nb_bins_x, + nb_bins_y, + )?; + + app.init_modules(|router, _, storage| { + router + .bank + .init_balance(storage, &addrs.altaf_bhai(), vec![Coin { + denom: "uscrt".into(), + amount: amount_x.add(Uint128::from(SWAP_AMOUNT)), + }]) + .unwrap(); + }); + + lb_pair::add_native_liquidity( + &mut app, + addrs.altaf_bhai().as_str(), + &scrt_silk_lb_pair.lb_pair.contract, + liquidity_parameters, + vec![Coin { + denom: String::from("uscrt"), + amount: amount_x, + }], + )?; + + let res: BalanceResponse = app + .wrap() + .query::(&QueryRequest::Bank(BankQuery::Balance { + address: addrs.altaf_bhai().to_string(), + denom: "uscrt".to_string(), + })) + .unwrap(); + + assert_eq!(res, BalanceResponse { + amount: Coin { + amount: Uint128::new(SWAP_AMOUNT), + denom: "uscrt".to_string(), + }, + }); + + // 25. SWAP a native token for a SNIP20 token and ASSERT the resulting balance of the SNIP20 token. + let offer = TokenAmount { + token: TokenType::CustomToken { + contract_addr: silk.address.clone(), + token_code_hash: silk.code_hash, + }, + amount: Uint128::new(SWAP_AMOUNT), + }; + let (total_fee_amount, lp_fee_amount, shade_dao_fee_amount, result, price) = + router::query_swap_simulation( + &app, + &router, + offer, + vec![Hop { + addr: scrt_silk_lb_pair.lb_pair.contract.address.to_string(), + code_hash: scrt_silk_lb_pair.lb_pair.contract.code_hash.clone(), + }], + None, + )?; + + // Verify result not actual amount + assert_ne!(total_fee_amount, Uint128::zero()); + assert_ne!(lp_fee_amount, Uint128::zero()); + assert_ne!(shade_dao_fee_amount, Uint128::zero()); + assert_ne!(result.return_amount, Uint128::zero()); + assert_eq!(price, "0".to_string()); + + //Swapping SILK -> USCRT + let router_invoke_msg = to_binary(&InvokeMsg::SwapTokensForExact { + expected_return: Some(Uint128::new(999u128)), + path: vec![Hop { + addr: scrt_silk_lb_pair.lb_pair.contract.address.to_string(), + code_hash: scrt_silk_lb_pair.lb_pair.contract.code_hash.clone(), + }], + recipient: None, + }) + .unwrap(); + + snip20::send_exec( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + router.address.to_string(), + Uint128::new(SWAP_AMOUNT), + Some(router_invoke_msg), + )?; + + //Query SILK and uscrt balance + snip20::set_viewing_key_exec( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + let altaf_bhai_balance = snip20::balance_query( + &app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(altaf_bhai_balance, Uint128::zero()); + + let res: BalanceResponse = app + .wrap() + .query::(&QueryRequest::Bank(BankQuery::Balance { + address: addrs.altaf_bhai().to_string(), + denom: "uscrt".to_string(), + })) + .unwrap(); + + assert_eq!(res, BalanceResponse { + amount: Coin { + amount: Uint128::new(SWAP_AMOUNT + 999), + denom: "uscrt".to_string(), + }, + }); + + //Swapping USCRT -> SILK + let offer = TokenAmount { + token: TokenType::NativeToken { + denom: "uscrt".to_string(), + }, + amount: Uint128::new(SWAP_AMOUNT), + }; + + router::swap_tokens_for_exact_tokens( + &mut app, + addrs.altaf_bhai().as_str(), + &router, + offer, + Some(Uint128::new(999u128)), + vec![Hop { + addr: scrt_silk_lb_pair.lb_pair.contract.address.to_string(), + code_hash: scrt_silk_lb_pair.lb_pair.contract.code_hash.clone(), + }], + None, + )?; + + //Query SILK and uscrt balance + snip20::set_viewing_key_exec( + &mut app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + + let altaf_bhai_balance = snip20::balance_query( + &app, + addrs.altaf_bhai().as_str(), + &deployed_contracts, + SILK, + "viewing_key".to_owned(), + )?; + assert_eq!(altaf_bhai_balance, Uint128::from(999u128)); + + let res: BalanceResponse = app + .wrap() + .query::(&QueryRequest::Bank(BankQuery::Balance { + address: addrs.altaf_bhai().to_string(), + denom: "uscrt".to_string(), + })) + .unwrap(); + + assert_eq!(res, BalanceResponse { + amount: Coin { + amount: Uint128::from(999u128), + denom: "uscrt".to_string(), + }, + }); + + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_router_register_tokens.rs b/contracts/liquidity_book/tests/src/multitests/lb_router_register_tokens.rs new file mode 100644 index 000000000..299713319 --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/lb_router_register_tokens.rs @@ -0,0 +1,78 @@ +use anyhow::Ok; +use cosmwasm_std::Addr; + +use crate::multitests::test_helper::{SHADE, SILK}; + +use super::test_helper::{init_addrs, setup}; +use shade_multi_test::interfaces::{ + router::{self, query_router_registered_tokens}, + utils::SupportedContracts, +}; + +#[test] +pub fn router_registered_tokens() -> Result<(), anyhow::Error> { + let addrs = init_addrs(); + let (mut app, _lb_factory, mut deployed_contracts) = setup(None)?; + + //intro app + router::init(&mut app, addrs.admin().as_str(), &mut deployed_contracts)?; + + let router = match deployed_contracts.clone().get(&SupportedContracts::Router) { + Some(router) => router, + None => panic!("Router init failed"), + } + .clone() + .into(); + + //generate the snip-20's and rewards token shd,silk + + // init admin_contract + + //init the staking contract + + // query registered tokens + + let reg_tokens = query_router_registered_tokens(&app, &router)?; + assert_eq!( + reg_tokens, + Vec::::new(), + "Empty registered tokens after init" + ); + + // register the tokens + + let shd = match deployed_contracts.get(&SupportedContracts::Snip20(SHADE.to_string())) { + Some(shd) => shd, + None => panic!("Shade not registered"), + }; + + let silk = match deployed_contracts.get(&SupportedContracts::Snip20(SILK.to_string())) { + Some(silk) => silk, + None => panic!("Silk not registered"), + }; + + router::register_snip20_token( + &mut app, + addrs.admin().as_str(), + &router, + &shd.clone().into(), + )?; + + router::register_snip20_token( + &mut app, + addrs.admin().as_str(), + &router, + &silk.clone().into(), + )?; + //test the registered tokens + + let reg_tokens = query_router_registered_tokens(&app, &router)?; + assert_eq!(reg_tokens.len(), 2, "2 tokens not registered"); + assert_eq!(reg_tokens[0], shd.address, "Shade tokens not registered "); + assert_eq!( + reg_tokens[1], silk.address, + "Silk tokens not registered tokens" + ); + + Ok(()) +} diff --git a/contracts/liquidity_book/tests/src/multitests/lb_token.rs b/contracts/liquidity_book/tests/src/multitests/lb_token.rs new file mode 100644 index 000000000..e69de29bb diff --git a/contracts/liquidity_book/tests/src/multitests/mod.rs b/contracts/liquidity_book/tests/src/multitests/mod.rs new file mode 100644 index 000000000..789cc0731 --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/mod.rs @@ -0,0 +1,22 @@ +#[cfg(test)] +pub mod lb_factory; + +#[cfg(test)] +pub mod lb_pair_initial_state; + +#[cfg(test)] +mod lb_pair_liquidity; + +#[cfg(test)] +mod lb_pair_fees; + +#[cfg(test)] +mod lb_pair_swap; + +#[cfg(test)] +mod lb_router_register_tokens; + +#[cfg(test)] +mod lb_router_integration; + +pub mod test_helper; diff --git a/contracts/liquidity_book/tests/src/multitests/test_helper.rs b/contracts/liquidity_book/tests/src/multitests/test_helper.rs new file mode 100644 index 000000000..6a9a7335b --- /dev/null +++ b/contracts/liquidity_book/tests/src/multitests/test_helper.rs @@ -0,0 +1,740 @@ +extern crate rand; +use cosmwasm_std::{Addr, BlockInfo, ContractInfo, StdResult, Timestamp, Uint128, Uint256}; +use rand::Rng; +use shade_multi_test::{ + interfaces::{ + lb_factory, snip20, + utils::{DeployedContracts, SupportedContracts}, + }, + multi::{lb_pair::LbPair, lb_token::LbToken}, +}; +use shade_protocol::{ + lb_libraries::{constants::PRECISION, math::u24::U24, tokens::TokenType}, + liquidity_book::lb_pair::LiquidityParameters, + multi_test::App, + utils::{asset::Contract, cycle::parse_utc_datetime, MultiTestable}, +}; + +pub const ID_ONE: u32 = 1 << 23; +pub const BASIS_POINT_MAX: u128 = 10_000; + +// Avalanche market config for 10bps +pub const DEFAULT_BIN_STEP: u16 = 10; +pub const DEFAULT_BASE_FACTOR: u16 = 5_000; +pub const DEFAULT_FILTER_PERIOD: u16 = 30; +pub const DEFAULT_DECAY_PERIOD: u16 = 600; +pub const DEFAULT_REDUCTION_FACTOR: u16 = 5_000; +pub const DEFAULT_VARIABLE_FEE_CONTROL: u32 = 40_000; +pub const DEFAULT_PROTOCOL_SHARE: u16 = 1_000; +pub const DEFAULT_MAX_VOLATILITY_ACCUMULATOR: u32 = 350_000; +pub const DEFAULT_OPEN_STATE: bool = false; +pub const DEFAULT_FLASHLOAN_FEE: u128 = 800_000_000_000_000; + +pub const SHADE: &str = "SHD"; +pub const SSCRT: &str = "SSCRT"; +pub const SILK: &str = "SILK"; +pub const USDC: &str = "USDC"; +pub const SBTC: &str = "SBTC"; +pub struct Addrs { + addrs: Vec, + hashes: Vec, +} + +impl Addrs { + pub fn admin(&self) -> Addr { + self.addrs[0].clone() + } + pub fn user1(&self) -> Addr { + self.addrs[1].clone() + } + pub fn user2(&self) -> Addr { + self.addrs[2].clone() + } + pub fn batman(&self) -> Addr { + self.addrs[3].clone() + } + pub fn scare_crow(&self) -> Addr { + self.addrs[4].clone() + } + pub fn altaf_bhai(&self) -> Addr { + self.addrs[5].clone() + } + pub fn all(&self) -> Vec { + self.addrs.clone() + } + pub fn a_hash(&self) -> String { + self.hashes[0].clone() + } + pub fn b_hash(&self) -> String { + self.hashes[1].clone() + } + pub fn c_hash(&self) -> String { + self.hashes[2].clone() + } + pub fn _d_hash(&self) -> String { + self.hashes[3].clone() + } +} + +/// inits 3 addresses +pub fn init_addrs() -> Addrs { + let addr_strs = vec!["addr0", "addr1", "addr2", "addr3", "addr4", "addr5"]; + let hashes = vec![ + "addr0_hash".to_string(), + "addr1_hash".to_string(), + "addr2_hash".to_string(), + "addr3_hash".to_string(), + "addr4_hash".to_string(), + "addr5_hash".to_string(), + ]; + let mut addrs: Vec = vec![]; + for addr in addr_strs { + addrs.push(Addr::unchecked(addr.to_string())); + } + Addrs { addrs, hashes } +} + +pub fn assert_approx_eq_rel(a: Uint256, b: Uint256, delta: Uint256, error_message: &str) { + let abs_delta = (a).abs_diff(b); + let percent_delta = abs_delta.multiply_ratio(Uint256::from(10_u128.pow(18)), b); + + if percent_delta > delta { + panic!( + "{}: expected delta {:?}, got {:?}", + error_message, delta, percent_delta + ); + } +} + +pub fn assert_approx_eq_abs(a: Uint256, b: Uint256, delta: Uint256, error_message: &str) { + let abs_delta = (a).abs_diff(b); + if abs_delta > delta { + panic!( + "{}: expected delta {:?}, got {:?}", + error_message, delta, abs_delta + ); + } +} + +pub fn setup(bin_step: Option) -> Result<(App, Contract, DeployedContracts), anyhow::Error> { + // init snip-20's + let mut app = App::default(); + let addrs = init_addrs(); + let mut deployed_contracts = DeployedContracts::new(); + app.set_block(BlockInfo { + height: 1, + time: Timestamp::from_seconds( + parse_utc_datetime(&"1995-11-13T00:00:00.00Z".to_string()) + .unwrap() + .timestamp() as u64, + ), + chain_id: "chain_id".to_string(), + random: None, + }); + //1. Initialize the tokens + snip20::init( + &mut app, + addrs.admin().as_str(), + &mut deployed_contracts, + SSCRT, + SSCRT, + 6, + Some(shade_protocol::snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: Some(true), + }), + ) + .unwrap(); + snip20::init( + &mut app, + addrs.admin().as_str(), + &mut deployed_contracts, + SHADE, + SHADE, + 8, + Some(shade_protocol::snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: Some(true), + }), + ) + .unwrap(); + snip20::init( + &mut app, + addrs.admin().as_str(), + &mut deployed_contracts, + SILK, + SILK, + 8, + Some(shade_protocol::snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: Some(true), + }), + ) + .unwrap(); + + snip20::init( + &mut app, + addrs.admin().as_str(), + &mut deployed_contracts, + USDC, + USDC, + 6, + Some(shade_protocol::snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: Some(true), + }), + ) + .unwrap(); + snip20::init( + &mut app, + addrs.admin().as_str(), + &mut deployed_contracts, + SBTC, + SBTC, + 8, + Some(shade_protocol::snip20::InitConfig { + public_total_supply: Some(true), + enable_deposit: None, + enable_redeem: None, + enable_mint: Some(true), + enable_burn: None, + enable_transfer: Some(true), + }), + ) + .unwrap(); + + //2. init factory + let lb_factory = lb_factory::init(&mut app, addrs.admin().as_str(), addrs.altaf_bhai(), 0)?; + let lb_token_stored_code = app.store_code(LbToken::default().contract()); + let lb_pair_stored_code = app.store_code(LbPair::default().contract()); + + lb_factory::set_lb_pair_implementation( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + lb_pair_stored_code.code_id, + lb_pair_stored_code.code_hash, + )?; + + lb_factory::set_lb_token_implementation( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + lb_token_stored_code.code_id, + lb_token_stored_code.code_hash, + )?; + + lb_factory::set_pair_preset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + bin_step.unwrap_or(DEFAULT_BIN_STEP), + DEFAULT_BASE_FACTOR, + DEFAULT_FILTER_PERIOD, + DEFAULT_DECAY_PERIOD, + DEFAULT_REDUCTION_FACTOR, + DEFAULT_VARIABLE_FEE_CONTROL, + DEFAULT_PROTOCOL_SHARE, + DEFAULT_MAX_VOLATILITY_ACCUMULATOR, + DEFAULT_OPEN_STATE, + )?; + + // add quote asset + let shd: ContractInfo = deployed_contracts + .get(&SupportedContracts::Snip20(SHADE.to_string())) + .unwrap() + .clone() + .into(); + + lb_factory::add_quote_asset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + TokenType::CustomToken { + contract_addr: shd.address, + token_code_hash: shd.code_hash, + }, + )?; + let sscrt: ContractInfo = deployed_contracts + .get(&SupportedContracts::Snip20(SSCRT.to_string())) + .unwrap() + .clone() + .into(); + lb_factory::add_quote_asset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + TokenType::CustomToken { + contract_addr: sscrt.address, + token_code_hash: sscrt.code_hash, + }, + )?; + + let silk: ContractInfo = deployed_contracts + .get(&SupportedContracts::Snip20(SILK.to_string())) + .unwrap() + .clone() + .into(); + lb_factory::add_quote_asset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + TokenType::CustomToken { + contract_addr: silk.address, + token_code_hash: silk.code_hash, + }, + )?; + + let usdc: ContractInfo = deployed_contracts + .get(&SupportedContracts::Snip20(USDC.to_string())) + .unwrap() + .clone() + .into(); + lb_factory::add_quote_asset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + TokenType::CustomToken { + contract_addr: usdc.address, + token_code_hash: usdc.code_hash, + }, + )?; + + let sbtc: ContractInfo = deployed_contracts + .get(&SupportedContracts::Snip20(SBTC.to_string())) + .unwrap() + .clone() + .into(); + lb_factory::add_quote_asset( + &mut app, + addrs.admin().as_str(), + &lb_factory.clone().into(), + TokenType::CustomToken { + contract_addr: sbtc.address, + token_code_hash: sbtc.code_hash, + }, + )?; + + Ok((app, lb_factory, deployed_contracts)) +} + +pub fn extract_contract_info( + deployed_contracts: &DeployedContracts, + symbol: &str, +) -> StdResult { + Ok(deployed_contracts + .get(&SupportedContracts::Snip20(symbol.to_string())) + .unwrap() + .clone() + .into()) +} + +pub fn token_type_snip20_generator(contract: &ContractInfo) -> StdResult { + Ok(TokenType::CustomToken { + contract_addr: contract.address.clone(), + token_code_hash: contract.code_hash.clone(), + }) +} +pub fn token_type_native_generator(denom: String) -> StdResult { + Ok(TokenType::NativeToken { denom }) +} + +fn safe64_divide(numerator: u128, denominator: u64) -> u64 { + (numerator / denominator as u128) as u64 +} + +pub fn get_id(active_id: u32, i: u32, nb_bin_y: u8) -> u32 { + let mut id: u32 = active_id + i; + + if nb_bin_y > 0 { + id = id - nb_bin_y as u32 + 1; + }; + + safe24(id) +} + +pub fn get_total_bins(nb_bin_x: u8, nb_bin_y: u8) -> u8 { + if nb_bin_x > 0 && nb_bin_y > 0 { + return nb_bin_x + nb_bin_y - 1; // Convert to u256 + } + nb_bin_x + nb_bin_y +} + +// Placeholder function for safe24 +// Ensure the value fits into 24 bits. +fn safe24(value: u32) -> u32 { + if value >= (1 << 24) { + panic!("Value too large for 24 bits"); + } + value +} + +// Utility function to bound a value within a range [min, max] +pub fn bound(value: T, min: T, max: T) -> T { + if value < min { + return min; + } + if value > max { + return max; + } + value +} + +pub fn generate_random(min: T, max: T) -> T +where + T: rand::distributions::uniform::SampleUniform + PartialOrd, +{ + let mut rng = rand::thread_rng(); + rng.gen_range(min..=max) +} + +pub fn liquidity_parameters_generator( + // Assuming lbPair has methods to get tokenX and tokenY + // lbPair: &LBPair, + _deployed_contracts: &DeployedContracts, + active_id: u32, + token_x: ContractInfo, + token_y: ContractInfo, + amount_x: Uint128, + amount_y: Uint128, + nb_bins_x: u8, + nb_bins_y: u8, +) -> StdResult { + let total = get_total_bins(nb_bins_x, nb_bins_y); + + if active_id > U24::MAX { + panic!("active_id too big"); + } + + let mut distribution_x: Vec = Vec::new(); + let mut distribution_y: Vec = Vec::new(); + + let mut delta_ids = Vec::new(); + + for i in 0..total { + if nb_bins_y > 0 { + delta_ids.push(i as i64 - nb_bins_y as i64 + 1_i64); + } else { + delta_ids.push(i as i64); + } + let id = get_id(active_id, i.into(), nb_bins_y); + let distrib_x = if id >= active_id && nb_bins_x > 0 { + safe64_divide(PRECISION, nb_bins_x as u64) + } else { + 0 + }; + + distribution_x.push(distrib_x); + + let distrib_y = if id <= active_id && nb_bins_y > 0 { + safe64_divide(PRECISION, nb_bins_y as u64) + } else { + 0 + }; + distribution_y.push(distrib_y); + } + + let liquidity_parameters = LiquidityParameters { + token_x: TokenType::CustomToken { + contract_addr: token_x.address, + token_code_hash: token_x.code_hash, + }, + token_y: TokenType::CustomToken { + contract_addr: token_y.address, + token_code_hash: token_y.code_hash, + }, + bin_step: DEFAULT_BIN_STEP, + amount_x, + amount_y, + amount_x_min: amount_x.multiply_ratio(90u128, 100u128), + amount_y_min: amount_y.multiply_ratio(90u128, 100u128), + active_id_desired: active_id, + id_slippage: 15, + delta_ids, + distribution_x, + distribution_y, + deadline: 99999999999, + }; + + Ok(liquidity_parameters) +} + +pub fn liquidity_parameters_generator_with_native( + // Assuming lbPair has methods to get tokenX and tokenY + // lbPair: &LBPair, + _deployed_contracts: &DeployedContracts, + active_id: u32, + token_x: TokenType, + token_y: TokenType, + amount_x: Uint128, + amount_y: Uint128, + nb_bins_x: u8, + nb_bins_y: u8, +) -> StdResult { + let total = get_total_bins(nb_bins_x, nb_bins_y); + + if active_id > U24::MAX { + panic!("active_id too big"); + } + + let mut distribution_x: Vec = Vec::new(); + let mut distribution_y: Vec = Vec::new(); + + let mut delta_ids = Vec::new(); + + for i in 0..total { + if nb_bins_y > 0 { + delta_ids.push(i as i64 - nb_bins_y as i64 + 1_i64); + } else { + delta_ids.push(i as i64); + } + let id = get_id(active_id, i.into(), nb_bins_y); + let distrib_x = if id >= active_id && nb_bins_x > 0 { + safe64_divide(PRECISION, nb_bins_x as u64) + } else { + 0 + }; + + distribution_x.push(distrib_x); + + let distrib_y = if id <= active_id && nb_bins_y > 0 { + safe64_divide(PRECISION, nb_bins_y as u64) + } else { + 0 + }; + distribution_y.push(distrib_y); + } + + let token_x_temp; + let token_y_temp; + + if token_x.is_native_token() { + token_x_temp = TokenType::NativeToken { + denom: token_x.unique_key(), + } + } else { + token_x_temp = TokenType::CustomToken { + contract_addr: token_x.address(), + token_code_hash: token_x.code_hash(), + } + } + + if token_y.is_native_token() { + token_y_temp = TokenType::NativeToken { + denom: token_y.unique_key(), + } + } else { + token_y_temp = TokenType::CustomToken { + contract_addr: token_y.address(), + token_code_hash: token_y.code_hash(), + } + } + + let liquidity_parameters = LiquidityParameters { + token_x: token_x_temp, + token_y: token_y_temp, + bin_step: DEFAULT_BIN_STEP, + amount_x, + amount_y, + amount_x_min: amount_x.multiply_ratio(90u128, 100u128), + amount_y_min: amount_y.multiply_ratio(90u128, 100u128), + active_id_desired: active_id, + id_slippage: 15, + delta_ids, + distribution_x, + distribution_y, + deadline: 99999999999, + }; + + Ok(liquidity_parameters) +} + +// pub fn mint_increase_allowance_helper( +// mut app: &mut App, +// deployed_contracts: &DeployedContracts, +// addrs: &Addrs, +// lb_pair_contract_info: &Contract, +// ) -> StdResult<()> { +// //adding minters and minting + +// snip20::add_minters_exec( +// &mut app, +// addrs.admin().as_str(), +// &deployed_contracts, +// SSCRT, +// vec![addrs.admin().to_string()], +// )?; + +// snip20::mint_exec( +// &mut app, +// addrs.admin().as_str(), +// &deployed_contracts, +// SSCRT, +// &vec![], +// addrs.user1().into_string(), +// Uint128::from(1_000_000_000u128), +// )?; + +// snip20::add_minters_exec( +// &mut app, +// addrs.admin().as_str(), +// &deployed_contracts, +// SHADE, +// vec![addrs.admin().to_string()], +// )?; + +// // mint token for user1 +// snip20::mint_exec( +// &mut app, +// addrs.admin().as_str(), +// &deployed_contracts, +// SHADE, +// &vec![], +// addrs.user1().into_string(), +// Uint128::from(1_000_000_000u128), +// )?; + +// snip20::set_viewing_key_exec( +// &mut app, +// addrs.user1().as_str(), +// &deployed_contracts, +// SHADE, +// "viewing_key".to_owned(), +// )?; + +// // query balance for token_minted +// let balance = snip20::balance_query( +// &mut app, +// addrs.user1().as_str(), +// &deployed_contracts, +// SHADE, +// "viewing_key".to_owned(), +// )?; + +// assert_eq!(balance, Uint128::from(1_000_000_000u128)); + +// // setting allowance to snip20's +// snip20::set_allowance_exec( +// &mut app, +// addrs.user1().as_str(), +// &deployed_contracts, +// SSCRT, +// lb_pair_contract_info.address.to_string(), +// Uint128::MAX, +// None, +// )?; +// snip20::set_allowance_exec( +// &mut app, +// addrs.user1().as_str(), +// &deployed_contracts, +// SHADE, +// lb_pair_contract_info.address.to_string(), +// Uint128::MAX, +// None, +// )?; +// snip20::set_allowance_exec( +// &mut app, +// addrs.user1().as_str(), +// &deployed_contracts, +// SILK, +// lb_pair_contract_info.address.to_string(), +// Uint128::MAX, +// None, +// )?; +// snip20::set_allowance_exec( +// &mut app, +// addrs.user1().as_str(), +// &deployed_contracts, +// USDC, +// lb_pair_contract_info.address.to_string(), +// Uint128::MAX, +// None, +// )?; +// snip20::set_allowance_exec( +// &mut app, +// addrs.user1().as_str(), +// &deployed_contracts, +// SBTC, +// lb_pair_contract_info.address.to_string(), +// Uint128::MAX, +// None, +// )?; +// Ok(()) +// } + +pub fn mint_token_helper( + app: &mut App, + deployed_contracts: &DeployedContracts, + addrs: &Addrs, + user: String, + tokens_to_mint: Vec<(&str, Uint128)>, +) -> StdResult<()> { + let admin = &addrs.admin().to_string(); + + // Adding minters and minting for SSCRT and SHADE + for (token, amount) in tokens_to_mint { + snip20::add_minters_exec( + app, + admin, + deployed_contracts, + token, + vec![admin.to_string()], + )?; + snip20::mint_exec( + app, + admin, + deployed_contracts, + token, + &vec![], + user.clone(), + amount, + )?; + snip20::set_viewing_key_exec( + app, + &user.clone(), + deployed_contracts, + token, + "viewing_key".to_owned(), + )?; + } + + Ok(()) +} + +pub fn increase_allowance_helper( + app: &mut App, + deployed_contracts: &DeployedContracts, + sender: String, + spender: String, + tokens_to_mint: Vec<(&str, Uint128)>, +) -> StdResult<()> { + for (token, _) in tokens_to_mint { + snip20::set_allowance_exec( + app, + &sender.clone(), + deployed_contracts, + token, + spender.clone(), + Uint128::MAX, + None, + )?; + } + + Ok(()) +} diff --git a/contracts/snip20/src/handle/transfers.rs b/contracts/snip20/src/handle/transfers.rs index 5f9e0b1d3..74edfc22a 100644 --- a/contracts/snip20/src/handle/transfers.rs +++ b/contracts/snip20/src/handle/transfers.rs @@ -1,15 +1,6 @@ use shade_protocol::{ c_std::{ - to_binary, - Addr, - Binary, - DepsMut, - Env, - MessageInfo, - Response, - StdResult, - Storage, - SubMsg, + to_binary, Addr, Binary, DepsMut, Env, MessageInfo, Response, StdResult, Storage, SubMsg, Uint128, }, contract_interfaces::snip20::{ @@ -17,8 +8,7 @@ use shade_protocol::{ errors::transfer_disabled, manager::{Allowance, Balance, CoinInfo, Config, ReceiverHash}, transaction_history::store_transfer, - ExecuteAnswer, - ReceiverHandleMsg, + ExecuteAnswer, ReceiverHandleMsg, }, utils::{ generic_response::ResponseStatus::Success, @@ -102,7 +92,11 @@ pub fn try_batch_transfer( &block, )?; } - Ok(Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransfer { status: Success })?)) + Ok( + Response::new().set_data(to_binary(&ExecuteAnswer::BatchTransfer { + status: Success, + })?), + ) } #[allow(clippy::too_many_arguments)] diff --git a/packages/multi_test/Cargo.toml b/packages/multi_test/Cargo.toml index b523bf971..2798df989 100644 --- a/packages/multi_test/Cargo.toml +++ b/packages/multi_test/Cargo.toml @@ -15,7 +15,9 @@ airdrop = ["dep:airdrop"] admin = ["dep:admin", "shade-protocol/admin"] snip20 = ["dep:snip20"] lb_pair = ["dep:lb_pair"] +router = ["dep:router"] lb_token = ["dep:lb_token"] +lb_factory = ["dep:lb_factory"] #liability_mint = ["dep:liability_mint"] #mint = ["dep:mint"] #oracle = ["dep:oracle"] @@ -41,7 +43,9 @@ snip20_migration = ["dep:snip20_migration"] airdrop = { path = "../../contracts/airdrop", optional = true } snip20 = { version = "0.1.0", path = "../../contracts/snip20", optional = true } lb_pair = { version = "0.1.0", path = "../../contracts/liquidity_book/lb_pair", optional = true } +router = { version = "0.1.0", path = "../../contracts/liquidity_book/router", optional = true } lb_token = { version = "0.1.0", path = "../../contracts/liquidity_book/lb_token", optional = true } +lb_factory = { version = "0.1.0", path = "../../contracts/liquidity_book/lb_factory", optional = true} #liability_mint = { version = "0.1.0", path = "../../contracts/liability_mint", optional = true } #mint = { version = "0.1.0", path = "../../contracts/mint", optional = true } #oracle = { version = "0.1.0", path = "../../contracts/oracle", optional = true } @@ -62,6 +66,8 @@ mock_stkd = { version = "0.1.0", package = "mock_stkd_derivative", path = "../.. mock_sienna = { version = "0.1.0", package = "mock_sienna_pair", path = "../../contracts/mock/mock_sienna_pair", optional = true } snip20_migration = { version = "0.1.0", path = "../../contracts/snip20_migration", optional = true } shade-protocol = { path = "../shade_protocol", features = ["multi-test"] } +shadeswap-shared = { version = "0.1.0", path = "../../packages/shadeswap_shared", features = [] } +anyhow = "1" [target.'cfg(not(target_arch="wasm32"))'.dependencies] shade-protocol = { path = "../shade_protocol", features = ["multi-test"] } diff --git a/packages/multi_test/src/interfaces/lb_factory.rs b/packages/multi_test/src/interfaces/lb_factory.rs new file mode 100644 index 000000000..373a9604b --- /dev/null +++ b/packages/multi_test/src/interfaces/lb_factory.rs @@ -0,0 +1,463 @@ +use crate::multi::lb_factory::LbFactory; +use shade_protocol::{ + c_std::{Addr, ContractInfo, StdError, StdResult}, + contract_interfaces::liquidity_book::lb_factory, + lb_libraries::{ + tokens::TokenType, + types::{ContractInstantiationInfo, LBPair, LBPairInformation}, + }, + multi_test::App, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +pub fn init( + app: &mut App, + sender: &str, + fee_recipient: Addr, + flash_loan_fee: u8, +) -> StdResult { + let lb_factory = Contract::from( + match (lb_factory::InstantiateMsg { + owner: Some(Addr::unchecked(sender)), + fee_recipient, + flash_loan_fee, + } + .test_init( + LbFactory::default(), + app, + Addr::unchecked(sender), + "lb_factory", + &[], + )) { + Ok(contract_info) => contract_info, + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + }, + ); + Ok(lb_factory) +} + +pub fn set_lb_pair_implementation( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + id: u64, + code_hash: String, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::SetLBPairImplementation { + lb_pair_implementation: ContractInstantiationInfo { id, code_hash }, + } + .test_exec(lb_factory, app, Addr::unchecked(sender), &[])) + { + Ok(_) => Ok(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + } +} + +pub fn set_lb_token_implementation( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + id: u64, + code_hash: String, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::SetLBTokenImplementation { + lb_token_implementation: ContractInstantiationInfo { id, code_hash }, + } + .test_exec(lb_factory, app, Addr::unchecked(sender), &[])) + { + Ok(_) => Ok(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + } +} + +pub fn set_pair_preset( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + bin_step: u16, + base_factor: u16, + filter_period: u16, + decay_period: u16, + reduction_factor: u16, + variable_fee_control: u32, + protocol_share: u16, + max_volatility_accumulator: u32, + is_open: bool, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::SetPairPreset { + bin_step, + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + is_open, + } + .test_exec(lb_factory, app, Addr::unchecked(sender), &[])) + { + Ok(_) => Ok(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + } +} + +pub fn remove_preset( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + bin_step: u16, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::RemovePreset { bin_step }.test_exec( + lb_factory, + app, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + } +} + +pub fn add_quote_asset( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + asset: TokenType, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::AddQuoteAsset { asset }.test_exec( + lb_factory, + app, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => { + return Err(StdError::generic_err(e.root_cause().to_string())); + } + } +} + +pub fn remove_quote_asset( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + asset: TokenType, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::RemoveQuoteAsset { asset }.test_exec( + lb_factory, + app, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => { + return Err(StdError::generic_err(e.root_cause().to_string())); + } + } +} + +pub fn create_lb_pair( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + bin_step: u16, + active_id: u32, + token_x: TokenType, + token_y: TokenType, + viewing_key: String, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::CreateLBPair { + token_x, + token_y, + active_id, + bin_step, + viewing_key, + } + .test_exec(lb_factory, app, Addr::unchecked(sender), &[])) + { + Ok(_) => Ok(()), + Err(e) => { + return Err(StdError::generic_err(e.root_cause().to_string())); + } + } +} + +pub fn set_fees_parameters_on_pair( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + token_x: TokenType, + token_y: TokenType, + bin_step: u16, + base_factor: u16, + filter_period: u16, + decay_period: u16, + reduction_factor: u16, + variable_fee_control: u32, + protocol_share: u16, + max_volatility_accumulator: u32, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::SetFeeParametersOnPair { + token_x, + token_y, + bin_step, + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + } + .test_exec(lb_factory, app, Addr::unchecked(sender), &[])) + { + Ok(_) => Ok(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + } +} + +pub fn set_preset_open_state( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + bin_step: u16, + is_open: bool, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::SetPresetOpenState { bin_step, is_open }.test_exec( + lb_factory, + app, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => { + return Err(StdError::generic_err(e.root_cause().to_string())); + } + } +} + +pub fn set_fee_recipient( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + fee_recipient: Addr, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::SetFeeRecipient { fee_recipient }.test_exec( + lb_factory, + app, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => { + return Err(StdError::generic_err(e.root_cause().to_string())); + } + } +} + +pub fn force_decay( + app: &mut App, + sender: &str, + lb_factory: &ContractInfo, + pair: LBPair, +) -> StdResult<()> { + match (lb_factory::ExecuteMsg::ForceDecay { pair }.test_exec( + lb_factory, + app, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => { + return Err(StdError::generic_err(e.root_cause().to_string())); + } + } +} + +pub fn query_flash_loan_fee(app: &mut App, lb_factory: &ContractInfo) -> StdResult { + let flash_loan_fee = + match (lb_factory::QueryMsg::GetFlashLoanFee {}.test_query(lb_factory, app)) { + Ok(lb_factory::FlashLoanFeeResponse { flash_loan_fee }) => Ok(flash_loan_fee), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + flash_loan_fee +} + +pub fn query_lb_pair_implementation( + app: &mut App, + lb_factory: &ContractInfo, +) -> StdResult { + let lb_pair_implementation = + match (lb_factory::QueryMsg::GetLBPairImplementation {}.test_query(lb_factory, app)) { + Ok(lb_factory::LBPairImplementationResponse { + lb_pair_implementation, + }) => Ok(lb_pair_implementation), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + lb_pair_implementation +} + +pub fn query_lb_token_implementation( + app: &mut App, + lb_factory: &ContractInfo, +) -> StdResult { + let lb_token_implementation = + match (lb_factory::QueryMsg::GetLBTokenImplementation {}.test_query(lb_factory, app)) { + Ok(lb_factory::LBTokenImplementationResponse { + lb_token_implementation, + }) => Ok(lb_token_implementation), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + lb_token_implementation +} + +pub fn query_min_bin_step(app: &mut App, lb_factory: &ContractInfo) -> StdResult { + let min_bin_step = match (lb_factory::QueryMsg::GetMinBinStep {}.test_query(lb_factory, app)) { + Ok(lb_factory::MinBinStepResponse { min_bin_step }) => Ok(min_bin_step), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + min_bin_step +} + +pub fn query_fee_recipient(app: &mut App, lb_factory: &ContractInfo) -> StdResult { + let fee_recpient = match (lb_factory::QueryMsg::GetFeeRecipient {}.test_query(lb_factory, app)) + { + Ok(lb_factory::FeeRecipientResponse { fee_recipient }) => Ok(fee_recipient), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + fee_recpient +} + +pub fn query_max_flash_loan_fee(app: &mut App, lb_factory: &ContractInfo) -> StdResult { + let max_fee = match (lb_factory::QueryMsg::GetMaxFlashLoanFee {}.test_query(lb_factory, app)) { + Ok(lb_factory::MaxFlashLoanFeeResponse { max_fee }) => Ok(max_fee), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + max_fee +} + +pub fn query_number_of_lb_pairs(app: &mut App, lb_factory: &ContractInfo) -> StdResult { + let lb_pair_number = + match (lb_factory::QueryMsg::GetNumberOfLBPairs {}.test_query(lb_factory, app)) { + Ok(lb_factory::NumberOfLBPairsResponse { lb_pair_number }) => Ok(lb_pair_number), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + lb_pair_number +} + +pub fn query_all_lb_pairs( + app: &mut App, + lb_factory: &ContractInfo, + token_x: TokenType, + token_y: TokenType, +) -> StdResult> { + let lb_pairs_available = match (lb_factory::QueryMsg::GetAllLBPairs { token_x, token_y } + .test_query(lb_factory, app)) + { + Ok(lb_factory::AllLBPairsResponse { lb_pairs_available }) => Ok(lb_pairs_available), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + lb_pairs_available +} + +pub fn query_lb_pair_information( + app: &mut App, + lb_factory: &ContractInfo, + token_x: TokenType, + token_y: TokenType, + bin_step: u16, +) -> StdResult { + let lb_pair_information = match (lb_factory::QueryMsg::GetLBPairInformation { + token_x, + token_y, + bin_step, + } + .test_query(lb_factory, app)) + { + Ok(lb_factory::LBPairInformationResponse { + lb_pair_information, + }) => Ok(lb_pair_information), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + lb_pair_information +} + +pub fn query_all_bin_steps(app: &mut App, lb_factory: &ContractInfo) -> StdResult> { + let bin_step_with_preset = + match (lb_factory::QueryMsg::GetAllBinSteps {}.test_query(lb_factory, app)) { + Ok(lb_factory::AllBinStepsResponse { + bin_step_with_preset, + }) => Ok(bin_step_with_preset), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + bin_step_with_preset +} + +pub fn query_preset( + app: &mut App, + lb_factory: &ContractInfo, + bin_step: u16, +) -> StdResult<(u16, u16, u16, u16, u32, u16, u32, bool)> { + match (lb_factory::QueryMsg::GetPreset { bin_step }.test_query(lb_factory, app)) { + Ok(lb_factory::PresetResponse { + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + is_open, + }) => { + return Ok(( + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + is_open, + )) + } + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; +} + +pub fn query_number_of_quote_assets(app: &mut App, lb_factory: &ContractInfo) -> StdResult { + let number_of_quote_assets = + match (lb_factory::QueryMsg::GetNumberOfQuoteAssets {}.test_query(lb_factory, app)) { + Ok(lb_factory::NumberOfQuoteAssetsResponse { + number_of_quote_assets, + }) => Ok(number_of_quote_assets), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + number_of_quote_assets +} + +pub fn query_is_quote_asset( + app: &mut App, + lb_factory: &ContractInfo, + token: TokenType, +) -> StdResult { + let is_quote = match (lb_factory::QueryMsg::IsQuoteAsset { token }.test_query(lb_factory, app)) + { + Ok(lb_factory::IsQuoteAssetResponse { is_quote }) => Ok(is_quote), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + is_quote +} + +pub fn query_quote_asset_at_index( + app: &mut App, + lb_factory: &ContractInfo, + index: u32, +) -> StdResult { + let asset = + match (lb_factory::QueryMsg::GetQuoteAssetAtIndex { index }.test_query(lb_factory, app)) { + Ok(lb_factory::QuoteAssetAtIndexResponse { asset }) => Ok(asset), + Err(e) => return Err(StdError::generic_err(e.to_string())), + }; + asset +} diff --git a/packages/multi_test/src/interfaces/lb_pair.rs b/packages/multi_test/src/interfaces/lb_pair.rs index 3cbf7e8c5..028cea6db 100644 --- a/packages/multi_test/src/interfaces/lb_pair.rs +++ b/packages/multi_test/src/interfaces/lb_pair.rs @@ -1,7 +1,8 @@ use crate::multi::lb_pair::LbPair; use shade_protocol::{ - c_std::{Addr, ContractInfo, StdError, StdResult, Uint128}, + c_std::{to_binary, Addr, Coin, ContractInfo, StdError, StdResult, Uint128, Uint256}, contract_interfaces::liquidity_book::lb_pair, + contract_interfaces::snip20, lb_libraries::{ tokens::TokenType, types::{ContractInstantiationInfo, StaticFeeParameters}, @@ -24,6 +25,7 @@ pub fn init( viewing_key: String, pair_name: String, entropy: String, + protocol_fee_recipient: Addr, ) -> StdResult { let lb_pair = Contract::from( match (lb_pair::InstantiateMsg { @@ -37,12 +39,13 @@ pub fn init( viewing_key, pair_name, entropy, + protocol_fee_recipient, } .test_init( LbPair::default(), app, Addr::unchecked(sender), - "snip20", + "lb_pair", &[], )) { Ok(contract_info) => contract_info, @@ -64,7 +67,24 @@ pub fn add_liquidity( .test_exec(lb_pair, app, Addr::unchecked(sender), &[])) { Ok(_) => Ok(()), - Err(e) => return Err(StdError::generic_err(e.to_string())), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + } +} + +pub fn add_native_liquidity( + app: &mut App, + sender: &str, + lb_pair: &ContractInfo, + liquidity_parameters: LiquidityParameters, + native_funds: Vec, +) -> StdResult<()> { + match (lb_pair::ExecuteMsg::AddLiquidity { + liquidity_parameters, + } + .test_exec(lb_pair, app, Addr::unchecked(sender), &native_funds)) + { + Ok(_) => Ok(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), } } @@ -80,39 +100,87 @@ pub fn remove_liquidity( .test_exec(lb_pair, app, Addr::unchecked(sender), &[])) { Ok(_) => Ok(()), - Err(e) => return Err(StdError::generic_err(e.to_string())), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), } } -pub fn swap( +pub fn swap_snip_20( app: &mut App, sender: &str, lb_pair: &ContractInfo, - liquidity_parameters: LiquidityParameters, - swap_for_y: bool, - to: Addr, - amount_received: Uint128, + to: Option, + lb_token: &ContractInfo, + + amount: Uint128, ) -> StdResult<()> { - match (lb_pair::ExecuteMsg::Swap { - swap_for_y, + let msg = to_binary(&lb_pair::InvokeMsg::SwapTokens { + expected_return: None, to, - amount_received, + padding: None, + })?; + match (snip20::ExecuteMsg::Send { + amount, + msg: Some(msg), + memo: None, + padding: None, + recipient_code_hash: Some(lb_pair.code_hash.clone()), + recipient: lb_pair.address.to_string(), + } + .test_exec(&lb_token, app, Addr::unchecked(sender), &[])) + { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.root_cause().to_string())), + } +} + +pub fn collect_protocol_fees(app: &mut App, sender: &str, lb_pair: &ContractInfo) -> StdResult<()> { + match (lb_pair::ExecuteMsg::CollectProtocolFees {}.test_exec( + lb_pair, + app, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + } +} + +pub fn set_static_fee_parameters( + app: &mut App, + sender: &str, + lb_pair: &ContractInfo, + base_factor: u16, + filter_period: u16, + decay_period: u16, + reduction_factor: u16, + variable_fee_control: u32, + protocol_share: u16, + max_volatility_accumulator: u32, +) -> StdResult<()> { + match (lb_pair::ExecuteMsg::SetStaticFeeParameters { + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, } .test_exec(lb_pair, app, Addr::unchecked(sender), &[])) { Ok(_) => Ok(()), - Err(e) => return Err(StdError::generic_err(e.to_string())), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), } } -pub fn lb_token_query(chain: &App, lb_pair: &ContractInfo) -> StdResult { - let res = lb_pair::QueryMsg::GetLbToken {}.test_query(lb_pair, chain)?; +pub fn lb_token_query(app: &App, lb_pair: &ContractInfo) -> StdResult { + let res = lb_pair::QueryMsg::GetLbToken {}.test_query(lb_pair, app)?; let lb_pair::LbTokenResponse { lb_token } = res; Ok(lb_token) } -pub fn bin_query(chain: &App, lb_pair: &ContractInfo, id: u32) -> StdResult<(u128, u128)> { - let res = lb_pair::QueryMsg::GetBin { id }.test_query(lb_pair, chain)?; +pub fn bin_query(app: &App, lb_pair: &ContractInfo, id: u32) -> StdResult<(u128, u128)> { + let res = lb_pair::QueryMsg::GetBin { id }.test_query(lb_pair, app)?; let lb_pair::BinResponse { bin_reserve_x, bin_reserve_y, @@ -121,7 +189,7 @@ pub fn bin_query(chain: &App, lb_pair: &ContractInfo, id: u32) -> StdResult<(u12 } pub fn swap_in_query( - chain: &App, + app: &App, lb_pair: &ContractInfo, amount_out: Uint128, swap_for_y: bool, @@ -130,7 +198,7 @@ pub fn swap_in_query( amount_out, swap_for_y, } - .test_query(lb_pair, chain)?; + .test_query(lb_pair, app)?; let lb_pair::SwapInResponse { amount_in, amount_out_left, @@ -140,7 +208,7 @@ pub fn swap_in_query( } pub fn swap_out_query( - chain: &App, + app: &App, lb_pair: &ContractInfo, amount_in: Uint128, swap_for_y: bool, @@ -149,7 +217,186 @@ pub fn swap_out_query( swap_for_y, amount_in, } - .test_query(lb_pair, chain)?; + .test_query(lb_pair, app)?; + let lb_pair::SwapOutResponse { + amount_out, + amount_in_left, + fee, + } = res; + Ok((amount_out, amount_in_left, fee)) +} + +pub fn query_static_fee_params( + app: &App, + lb_pair: &ContractInfo, +) -> StdResult<(u16, u16, u16, u16, u32, u16, u32)> { + let res = lb_pair::QueryMsg::GetStaticFeeParameters {}.test_query(lb_pair, app)?; + let lb_pair::StaticFeeParametersResponse { + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + } = res; + Ok(( + base_factor, + filter_period, + decay_period, + reduction_factor, + variable_fee_control, + protocol_share, + max_volatility_accumulator, + )) +} + +pub fn query_variable_fee_params( + app: &App, + lb_pair: &ContractInfo, +) -> StdResult<(u32, u32, u32, u64)> { + let res = lb_pair::QueryMsg::GetVariableFeeParameters {}.test_query(lb_pair, app)?; + let lb_pair::VariableFeeParametersResponse { + volatility_accumulator, + volatility_reference, + id_reference, + time_of_last_update, + } = res; + Ok(( + volatility_accumulator, + volatility_reference, + id_reference, + time_of_last_update, + )) +} + +pub fn query_factory(app: &App, lb_pair: &ContractInfo) -> StdResult { + let res = lb_pair::QueryMsg::GetFactory {}.test_query(lb_pair, app)?; + let lb_pair::FactoryResponse { factory } = res; + Ok(factory) +} + +pub fn query_token_x(app: &App, lb_pair: &ContractInfo) -> StdResult { + let res = lb_pair::QueryMsg::GetTokenX {}.test_query(lb_pair, app)?; + let lb_pair::TokenXResponse { token_x } = res; + Ok(token_x) +} + +pub fn query_token_y(app: &App, lb_pair: &ContractInfo) -> StdResult { + let res = lb_pair::QueryMsg::GetTokenY {}.test_query(lb_pair, app)?; + let lb_pair::TokenYResponse { token_y } = res; + Ok(token_y) +} + +pub fn query_bin_step(app: &App, lb_pair: &ContractInfo) -> StdResult { + let res = lb_pair::QueryMsg::GetBinStep {}.test_query(lb_pair, app)?; + let lb_pair::BinStepResponse { bin_step } = res; + Ok(bin_step) +} + +pub fn query_reserves(app: &App, lb_pair: &ContractInfo) -> StdResult<(u128, u128)> { + let res = lb_pair::QueryMsg::GetReserves {}.test_query(lb_pair, app)?; + let lb_pair::ReservesResponse { + reserve_x, + reserve_y, + } = res; + Ok((reserve_x, reserve_y)) +} + +pub fn query_active_id(app: &App, lb_pair: &ContractInfo) -> StdResult { + let res = lb_pair::QueryMsg::GetActiveId {}.test_query(lb_pair, app)?; + let lb_pair::ActiveIdResponse { active_id } = res; + Ok(active_id) +} + +pub fn query_bin(app: &App, lb_pair: &ContractInfo, id: u32) -> StdResult<(u128, u128)> { + let res = lb_pair::QueryMsg::GetBin { id }.test_query(lb_pair, app)?; + let lb_pair::BinResponse { + bin_reserve_x, + bin_reserve_y, + } = res; + Ok((bin_reserve_x, bin_reserve_y)) +} + +pub fn query_next_non_empty_bin( + app: &App, + lb_pair: &ContractInfo, + swap_for_y: bool, + id: u32, +) -> StdResult { + let res = lb_pair::QueryMsg::GetNextNonEmptyBin { swap_for_y, id }.test_query(lb_pair, app)?; + let lb_pair::NextNonEmptyBinResponse { next_id } = res; + Ok(next_id) +} + +pub fn query_protocol_fees(app: &App, lb_pair: &ContractInfo) -> StdResult<(u128, u128)> { + let res = lb_pair::QueryMsg::GetProtocolFees {}.test_query(lb_pair, app)?; + let lb_pair::ProtocolFeesResponse { + protocol_fee_x, + protocol_fee_y, + } = res; + Ok((protocol_fee_x, protocol_fee_y)) +} + +pub fn query_oracle_parameters( + app: &App, + lb_pair: &ContractInfo, +) -> StdResult<(u8, u16, u16, u64, u64)> { + let res = lb_pair::QueryMsg::GetOracleParameters {}.test_query(lb_pair, app)?; + let lb_pair::OracleParametersResponse { + sample_lifetime, + size, + active_size, + last_updated, + first_timestamp, + } = res; + Ok(( + sample_lifetime, + size, + active_size, + last_updated, + first_timestamp, + )) +} + +pub fn query_oracle_sample_at( + app: &App, + lb_pair: &ContractInfo, + look_up_timestamp: u64, +) -> StdResult<(u64, u64, u64)> { + let res = + lb_pair::QueryMsg::GetOracleSampleAt { look_up_timestamp }.test_query(lb_pair, app)?; + let lb_pair::OracleSampleAtResponse { + cumulative_id, + cumulative_volatility, + cumulative_bin_crossed, + } = res; + Ok((cumulative_id, cumulative_volatility, cumulative_bin_crossed)) +} + +pub fn query_price_from_id(app: &App, lb_pair: &ContractInfo, id: u32) -> StdResult { + let res = lb_pair::QueryMsg::GetPriceFromId { id }.test_query(lb_pair, app)?; + let lb_pair::PriceFromIdResponse { price } = res; + Ok(price) +} + +pub fn query_id_from_price(app: &App, lb_pair: &ContractInfo, price: Uint256) -> StdResult { + let res = lb_pair::QueryMsg::GetIdFromPrice { price }.test_query(lb_pair, app)?; + let lb_pair::IdFromPriceResponse { id } = res; + Ok(id) +} + +pub fn query_swap_out( + app: &App, + lb_pair: &ContractInfo, + amount_in: Uint128, + swap_for_y: bool, +) -> StdResult<(Uint128, Uint128, Uint128)> { + let res = lb_pair::QueryMsg::GetSwapOut { + amount_in, + swap_for_y, + } + .test_query(lb_pair, app)?; let lb_pair::SwapOutResponse { amount_out, amount_in_left, @@ -157,3 +404,22 @@ pub fn swap_out_query( } = res; Ok((amount_out, amount_in_left, fee)) } + +pub fn query_swap_in( + app: &App, + lb_pair: &ContractInfo, + amount_out: Uint128, + swap_for_y: bool, +) -> StdResult<(Uint128, Uint128, Uint128)> { + let res = lb_pair::QueryMsg::GetSwapIn { + amount_out, + swap_for_y, + } + .test_query(lb_pair, app)?; + let lb_pair::SwapInResponse { + amount_in, + amount_out_left, + fee, + } = res; + Ok((amount_in, amount_out_left, fee)) +} diff --git a/packages/multi_test/src/interfaces/lb_token.rs b/packages/multi_test/src/interfaces/lb_token.rs index d76617ecc..6a6222413 100644 --- a/packages/multi_test/src/interfaces/lb_token.rs +++ b/packages/multi_test/src/interfaces/lb_token.rs @@ -1,22 +1,61 @@ use shade_protocol::{ - c_std::{ContractInfo, StdError, StdResult}, + c_std::{Addr, ContractInfo, StdError, StdResult, Uint256}, + contract_interfaces::liquidity_book::lb_token, contract_interfaces::liquidity_book::lb_token::*, multi_test::App, - utils::Query, + utils::{ExecuteCallback, Query}, }; -pub fn contract_info_query(chain: &App, info: &ContractInfo) -> StdResult { - let res: QueryAnswer = QueryMsg::TokenContractInfo {}.test_query(&info, chain)?; +pub fn set_viewing_key( + app: &mut App, + sender: &str, + lb_token: &ContractInfo, + key: String, +) -> StdResult<()> { + match (lb_token::ExecuteMsg::SetViewingKey { key, padding: None }.test_exec( + lb_token, + app, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + } +} + +pub fn query_contract_info(app: &App, info: &ContractInfo) -> StdResult { + let res: QueryAnswer = QueryMsg::TokenContractInfo {}.test_query(&info, app)?; match res { QueryAnswer::TokenContractInfo { .. } => Ok(res), _ => Err(StdError::generic_err("Query failed")), } } -pub fn id_balance_query(chain: &App, info: &ContractInfo, id: String) -> StdResult { - let res: QueryAnswer = QueryMsg::IdTotalBalance { id }.test_query(&info, chain)?; +pub fn query_id_balance(app: &App, info: &ContractInfo, id: String) -> StdResult { + let res: QueryAnswer = QueryMsg::IdTotalBalance { id }.test_query(&info, app)?; match res { QueryAnswer::IdTotalBalance { .. } => Ok(res), _ => Err(StdError::generic_err("Query failed")), } } + +pub fn query_balance( + app: &App, + info: &ContractInfo, + owner: Addr, + viewer: Addr, + key: String, + token_id: String, +) -> StdResult { + let res: QueryAnswer = QueryMsg::Balance { + owner, + viewer, + key, + token_id, + } + .test_query(&info, app)?; + match res { + QueryAnswer::Balance { amount } => Ok(amount), + _ => Err(StdError::generic_err("Query failed")), + } +} diff --git a/packages/multi_test/src/interfaces/mod.rs b/packages/multi_test/src/interfaces/mod.rs index 471081479..87496a997 100644 --- a/packages/multi_test/src/interfaces/mod.rs +++ b/packages/multi_test/src/interfaces/mod.rs @@ -6,6 +6,8 @@ pub mod manager; #[cfg(feature = "dao")] pub mod adapter; */ +#[cfg(feature = "lb_factory")] +pub mod lb_factory; #[cfg(feature = "lb_pair")] pub mod lb_pair; #[cfg(feature = "lb_token")] @@ -20,4 +22,7 @@ pub mod treasury_manager; #[cfg(feature = "scrt_staking")] pub mod scrt_staking; +#[cfg(feature = "router")] +pub mod router; + pub mod utils; diff --git a/packages/multi_test/src/interfaces/router.rs b/packages/multi_test/src/interfaces/router.rs new file mode 100644 index 000000000..c3f5e8612 --- /dev/null +++ b/packages/multi_test/src/interfaces/router.rs @@ -0,0 +1,145 @@ +use crate::{ + interfaces::utils::{DeployedContracts, SupportedContracts}, + multi::{admin::init_admin_auth, router::Router}, +}; +use anyhow::Error; +use shade_protocol::{ + c_std::{to_binary, Addr, Coin, ContractInfo, StdError, StdResult, Uint128}, + liquidity_book::lb_pair::SwapResult, + multi_test::App, + utils::{asset::Contract, ExecuteCallback, InstantiateCallback, MultiTestable, Query}, +}; + +use shadeswap_shared::{ + core::TokenAmount, + router::{self, Hop}, +}; + +pub fn init(chain: &mut App, sender: &str, contracts: &mut DeployedContracts) -> StdResult<()> { + let admin_auth = match contracts.get(&SupportedContracts::AdminAuth) { + Some(admin) => admin.clone(), + None => { + let contract = Contract::from(init_admin_auth(chain, &Addr::unchecked(sender))); + contracts.insert(SupportedContracts::AdminAuth, contract.clone()); + contract + } + }; + + let router = Contract::from( + match (router::InitMsg { + prng_seed: to_binary("password").unwrap(), + entropy: to_binary("password").unwrap(), + admin_auth, + airdrop_address: None, + } + .test_init( + Router::default(), + chain, + Addr::unchecked(sender), + "router", + &[], + )) { + Ok(contract_info) => contract_info, + Err(e) => return Err(StdError::generic_err(e.to_string())), + }, + ); + contracts.insert(SupportedContracts::Router, router); + + Ok(()) +} + +pub fn swap_tokens_for_exact_tokens( + chain: &mut App, + sender: &str, + router: &ContractInfo, + offer: TokenAmount, + expected_return: Option, + path: Vec, + recipient: Option, +) -> StdResult<()> { + let mut funds = Vec::new(); + if offer.token.is_native_token() { + funds = [Coin { + denom: offer.token.unique_key(), + amount: offer.amount, + }] + .to_vec(); + } + + match (router::ExecuteMsg::SwapTokensForExact { + offer, + expected_return, + path, + recipient, + padding: None, + } + .test_exec(&router, chain, Addr::unchecked(sender), &funds)) + { + Ok(_) => Ok::<(), Error>(()), + Err(e) => return Err(StdError::generic_err(e.root_cause().to_string())), + }; + + Ok(()) +} + +pub fn register_snip20_token( + chain: &mut App, + sender: &str, + router: &ContractInfo, + snip20_token: &ContractInfo, +) -> Result<(), anyhow::Error> { + let res = router::ExecuteMsg::RegisterSNIP20Token { + token_addr: snip20_token.address.to_string(), + token_code_hash: snip20_token.code_hash.to_string(), + oracle_key: None, + padding: None, + } + .test_exec(&router, chain, Addr::unchecked(sender), &[]); + match res { + Ok(_) => Ok::<(), Error>(()), + Err(e) => return Err(e), + }; + + Ok(()) +} + +pub fn query_router_registered_tokens(app: &App, router: &ContractInfo) -> StdResult> { + let res = router::QueryMsg::RegisteredTokens {}.test_query(router, app)?; + let tokens = match res { + router::QueryMsgResponse::RegisteredTokens { tokens } => tokens, + _ => panic!("Query failed"), + }; + Ok(tokens) +} + +pub fn query_swap_simulation( + app: &App, + router: &ContractInfo, + offer: TokenAmount, + path: Vec, + exclude_fee: Option, +) -> StdResult<(Uint128, Uint128, Uint128, SwapResult, String)> { + let res = router::QueryMsg::SwapSimulation { + offer, + path, + exclude_fee, + } + .test_query(router, app)?; + let tokens = match res { + router::QueryMsgResponse::SwapSimulation { + total_fee_amount, + lp_fee_amount, + shade_dao_fee_amount, + result, + price, + } => ( + total_fee_amount, + lp_fee_amount, + shade_dao_fee_amount, + result, + price, + ), + _ => panic!("Query failed"), + }; + Ok(tokens) +} diff --git a/packages/multi_test/src/interfaces/snip20.rs b/packages/multi_test/src/interfaces/snip20.rs index 35dcfea9d..af95c1a49 100644 --- a/packages/multi_test/src/interfaces/snip20.rs +++ b/packages/multi_test/src/interfaces/snip20.rs @@ -180,6 +180,35 @@ pub fn set_viewing_key_exec( } } +pub fn transfer_exec( + chain: &mut App, + sender: &str, + contracts: &DeployedContracts, + snip20_symbol: &str, + recipient: String, + amount: Uint128, +) -> StdResult<()> { + match (snip20::ExecuteMsg::Transfer { + recipient, + amount, + memo: None, + padding: None, + } + .test_exec( + &contracts + .get(&SupportedContracts::Snip20(snip20_symbol.to_string())) + .unwrap() + .clone() + .into(), + chain, + Addr::unchecked(sender), + &[], + )) { + Ok(_) => Ok(()), + Err(e) => Err(StdError::generic_err(e.root_cause().to_string())), + } +} + pub fn send_exec( chain: &mut App, sender: &str, @@ -208,7 +237,7 @@ pub fn send_exec( &[], )) { Ok(_) => Ok(()), - Err(_) => Err(StdError::generic_err("snip20 send failed")), + Err(e) => Err(StdError::generic_err(e.root_cause().to_string())), } } diff --git a/packages/multi_test/src/interfaces/utils.rs b/packages/multi_test/src/interfaces/utils.rs index e2808c7e7..7785ef078 100644 --- a/packages/multi_test/src/interfaces/utils.rs +++ b/packages/multi_test/src/interfaces/utils.rs @@ -5,6 +5,7 @@ use std::collections::HashMap; pub enum SupportedContracts { AdminAuth, Snip20(String), + Router, Treasury, TreasuryManager(usize), MockAdapter(usize), diff --git a/packages/multi_test/src/multi.rs b/packages/multi_test/src/multi.rs index d91b2a7c3..6ce4bef9f 100644 --- a/packages/multi_test/src/multi.rs +++ b/packages/multi_test/src/multi.rs @@ -20,6 +20,18 @@ pub mod snip20 { multi_derive::implement_multi!(Snip20, snip20); } +#[cfg(feature = "router")] +pub mod router { + use router; + multi_derive::implement_multi_with_reply!(Router, router); +} + +#[cfg(feature = "lb_factory")] +pub mod lb_factory { + use lb_factory; + multi_derive::implement_multi_with_reply!(LbFactory, lb_factory); +} + #[cfg(feature = "lb_pair")] pub mod lb_pair { use lb_pair; diff --git a/packages/shade_protocol/Cargo.toml b/packages/shade_protocol/Cargo.toml index cd6b80db4..233fafc02 100644 --- a/packages/shade_protocol/Cargo.toml +++ b/packages/shade_protocol/Cargo.toml @@ -112,8 +112,8 @@ backtraces = ["cosmwasm-std/backtraces"] debug-print = [] # TODO: remove this from all cargo configs [dependencies] -cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" } -cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.0.0" } +cosmwasm-std = { package = "secret-cosmwasm-std", version = "1.0.0" , features=["iterator"]} +cosmwasm-storage = { package = "secret-cosmwasm-storage", version = "1.0.0" , features=["iterator"]} cosmwasm-schema = "1.1.5" contract-derive = { version = "0.1.0", path = "../contract_derive" } ethnum = { version = "1" } diff --git a/packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs b/packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs index feba1f527..fdaed6800 100644 --- a/packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs +++ b/packages/shade_protocol/src/contract_interfaces/dex/shadeswap.rs @@ -1,11 +1,11 @@ +use crate::liquidity_book::lb_pair::TokenPair; use crate::{ c_std::{Addr, Binary, Uint128}, - utils::{ - asset::Contract, - Query, - }, + lb_libraries::tokens::TokenType, + utils::{asset::Contract, Query}, }; use cosmwasm_schema::cw_serde; +use cosmwasm_std::{Decimal256, Uint256}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; @@ -49,22 +49,16 @@ impl Query for PairQuery { const BLOCK_SIZE: usize = 256; } -#[cw_serde] -pub enum TokenType { - CustomToken { - contract_addr: Addr, - token_code_hash: String, - }, - NativeToken { - denom: String, - }, -} - -#[cw_serde] -pub struct TokenPair { - pub token_0: TokenType, - pub token_1: TokenType, -} +// #[cw_serde] +// pub enum TokenType { +// CustomToken { +// contract_addr: Addr, +// token_code_hash: String, +// }, +// NativeToken { +// denom: String, +// }, +// } /* #[cw_serde] @@ -149,6 +143,73 @@ pub struct TradeHistory { pub shade_dao_fee_amount: Uint128, } +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct FeeInfo { + pub shade_dao_address: Addr, + pub lp_fee: Fee, + pub shade_dao_fee: Fee, + pub stable_lp_fee: Fee, + pub stable_shade_dao_fee: Fee, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct StablePairInfoResponse { + pub stable_params: StableParams, + pub stable_token0_data: StableTokenData, + pub stable_token1_data: StableTokenData, + //p is optional so that the PairInfo query can still return even when the calculation of p fails + pub p: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct CustomIterationControls { + pub epsilon: Uint256, // assumed to have same decimals as SignedDecimal + pub max_iter_newton: u16, + pub max_iter_bisect: u16, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct StableParams { + pub a: Decimal256, + pub gamma1: Uint256, + pub gamma2: Uint256, + pub oracle: Contract, + pub min_trade_size_x_for_y: Decimal256, + pub min_trade_size_y_for_x: Decimal256, + pub max_price_impact_allowed: Decimal256, + pub custom_iteration_controls: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct StableTokenData { + pub oracle_key: String, + pub decimals: u8, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct Fee { + pub nom: u64, + pub denom: u64, +} + +impl Fee { + pub fn new(nom: u64, denom: u64) -> Self { + Self { nom, denom } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub struct CustomFee { + pub shade_dao_fee: Fee, + pub lp_fee: Fee, +} /* #[cw_serde] pub struct PoolResponse { diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs index 950477144..54e2cf7e5 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_factory.rs @@ -1,10 +1,32 @@ -use crate::utils::liquidity_book::{ - tokens::TokenType, transfer::space_pad, types::LBPairInformation, -}; +use super::lb_pair; +use crate::utils::liquidity_book::types::{LBPair, LBPairInformation}; +use crate::utils::liquidity_book::{tokens::TokenType, types::ContractInstantiationInfo}; + +use crate::utils::{ExecuteCallback, InstantiateCallback, Query}; use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{to_binary, Coin, CosmosMsg, StdResult, Uint128, WasmMsg}; +use cosmwasm_std::Addr; +pub use lb_pair::InstantiateMsg as LBPairInstantiateMsg; + +#[cw_serde] +pub struct InstantiateMsg { + pub owner: Option, + pub fee_recipient: Addr, + pub flash_loan_fee: u8, +} +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = 256; +} + #[cw_serde] pub enum ExecuteMsg { + #[serde(rename = "set_lb_pair_implementation")] + SetLBPairImplementation { + lb_pair_implementation: ContractInstantiationInfo, + }, + #[serde(rename = "set_lb_token_implementation")] + SetLBTokenImplementation { + lb_token_implementation: ContractInstantiationInfo, + }, #[serde(rename = "create_lb_pair")] CreateLBPair { token_x: TokenType, @@ -12,47 +34,99 @@ pub enum ExecuteMsg { // u24 active_id: u32, bin_step: u16, + viewing_key: String, + }, + // #[serde(rename = "set_lb_pair_ignored")] + // SetLBPairIgnored { + // token_x: TokenType, + // token_y: TokenType, + // bin_step: u16, + // ignored: bool, + // }, + SetPairPreset { + bin_step: u16, + base_factor: u16, + filter_period: u16, + decay_period: u16, + reduction_factor: u16, + // u24 + variable_fee_control: u32, + protocol_share: u16, + // u24 + max_volatility_accumulator: u32, + is_open: bool, + }, + SetPresetOpenState { + bin_step: u16, + is_open: bool, + }, + RemovePreset { + bin_step: u16, + }, + SetFeeParametersOnPair { + token_x: TokenType, + token_y: TokenType, + bin_step: u16, + base_factor: u16, + filter_period: u16, + decay_period: u16, + reduction_factor: u16, + // u24 + variable_fee_control: u32, + protocol_share: u16, + // u24 + max_volatility_accumulator: u32, + }, + SetFeeRecipient { + fee_recipient: Addr, + }, + SetFlashLoanFee { + flash_loan_fee: u8, + }, + AddQuoteAsset { + asset: TokenType, + }, + RemoveQuoteAsset { + asset: TokenType, + }, + ForceDecay { + pair: LBPair, }, } -impl ExecuteMsg { - /// Returns a StdResult used to execute a SNIP20 contract function - /// - /// # Arguments - /// - /// * `block_size` - pad the message to blocks of this size - /// * `callback_code_hash` - String holding the code hash of the contract being called - /// * `contract_addr` - address of the contract being called - /// * `send_amount` - Optional Uint128 amount of native coin to send with the callback message - /// NOTE: Only a Deposit message should have an amount sent with it - pub fn to_cosmos_msg( - &self, - code_hash: String, - contract_addr: String, - send_amount: Option, - ) -> StdResult { - let mut msg = to_binary(self)?; - space_pad(&mut msg.0, 256); - let mut funds = Vec::new(); - if let Some(amount) = send_amount { - funds.push(Coin { - amount, - denom: String::from("uscrt"), - }); - } - let execute = WasmMsg::Execute { - contract_addr, - code_hash, - msg, - funds, - }; - Ok(execute.into()) - } +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { + #[returns(MinBinStepResponse)] + GetMinBinStep {}, + #[returns(FeeRecipientResponse)] + GetFeeRecipient {}, + #[returns(MaxFlashLoanFeeResponse)] + GetMaxFlashLoanFee {}, + #[returns(FlashLoanFeeResponse)] + GetFlashLoanFee {}, + #[returns(LBPairImplementationResponse)] + #[serde(rename = "get_lb_pair_implementation")] + GetLBPairImplementation {}, + #[returns(LBTokenImplementationResponse)] + #[serde(rename = "get_lb_token_implementation")] + GetLBTokenImplementation {}, + #[returns(NumberOfLBPairsResponse)] + #[serde(rename = "get_number_of_lb_pairs")] + GetNumberOfLBPairs {}, + #[returns(LBPairAtIndexResponse)] + #[serde(rename = "get_lb_pair_at_index")] + GetLBPairAtIndex { index: u32 }, + #[returns(NumberOfQuoteAssetsResponse)] + GetNumberOfQuoteAssets {}, + #[returns(QuoteAssetAtIndexResponse)] + GetQuoteAssetAtIndex { index: u32 }, + #[returns(IsQuoteAssetResponse)] + IsQuoteAsset { token: TokenType }, #[returns(LBPairInformationResponse)] #[serde(rename = "get_lb_pair_information")] GetLBPairInformation { @@ -60,9 +134,110 @@ pub enum QueryMsg { token_y: TokenType, bin_step: u16, }, + #[returns(PresetResponse)] + GetPreset { bin_step: u16 }, + #[returns(AllBinStepsResponse)] + GetAllBinSteps {}, + #[returns(OpenBinStepsResponse)] + GetOpenBinSteps {}, + #[returns(AllLBPairsResponse)] + #[serde(rename = "get_all_lb_pairs")] + GetAllLBPairs { + token_x: TokenType, + token_y: TokenType, + }, +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = 256; +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct MinBinStepResponse { + pub min_bin_step: u8, +} + +#[cw_serde] +pub struct FeeRecipientResponse { + pub fee_recipient: Addr, +} + +#[cw_serde] +pub struct MaxFlashLoanFeeResponse { + pub max_fee: u8, +} + +#[cw_serde] +pub struct FlashLoanFeeResponse { + pub flash_loan_fee: u8, +} + +#[cw_serde] +pub struct LBPairImplementationResponse { + pub lb_pair_implementation: ContractInstantiationInfo, +} + +#[cw_serde] +pub struct LBTokenImplementationResponse { + pub lb_token_implementation: ContractInstantiationInfo, +} + +#[cw_serde] +pub struct NumberOfLBPairsResponse { + pub lb_pair_number: u32, +} + +#[cw_serde] +pub struct LBPairAtIndexResponse { + pub lb_pair: LBPair, +} + +#[cw_serde] +pub struct NumberOfQuoteAssetsResponse { + pub number_of_quote_assets: u32, +} + +#[cw_serde] +pub struct QuoteAssetAtIndexResponse { + pub asset: TokenType, +} + +#[cw_serde] +pub struct IsQuoteAssetResponse { + pub is_quote: bool, } #[cw_serde] pub struct LBPairInformationResponse { pub lb_pair_information: LBPairInformation, } + +#[cw_serde] +pub struct PresetResponse { + pub base_factor: u16, + pub filter_period: u16, + pub decay_period: u16, + pub reduction_factor: u16, + // u24 + pub variable_fee_control: u32, + pub protocol_share: u16, + // u24 + pub max_volatility_accumulator: u32, + pub is_open: bool, +} + +#[cw_serde] +pub struct AllBinStepsResponse { + pub bin_step_with_preset: Vec, +} + +#[cw_serde] +pub struct OpenBinStepsResponse { + pub open_bin_steps: Vec, +} + +#[cw_serde] +pub struct AllLBPairsResponse { + pub lb_pairs_available: Vec, +} diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs index 50964a6b5..1e942f481 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_pair.rs @@ -1,12 +1,30 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, ContractInfo, Uint128, Uint256}; - -use crate::utils::{ - liquidity_book::{ - tokens::TokenType, - types::{Bytes32, ContractInstantiationInfo, StaticFeeParameters}, +use crate::{ + snip20::Snip20ReceiveMsg, + utils::{ + liquidity_book::{ + tokens::{SwapTokenAmount, TokenAmount, TokenType}, + types::{Bytes32, ContractInstantiationInfo, StaticFeeParameters}, + }, + space_pad, + ExecuteCallback, + InstantiateCallback, + Query, }, - ExecuteCallback, InstantiateCallback, Query, + Contract, +}; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{ + to_binary, + Addr, + Coin, + ContractInfo, + CosmosMsg, + Decimal256, + StdResult, + Uint128, + Uint256, + WasmMsg, }; #[cw_serde] @@ -21,6 +39,7 @@ pub struct InstantiateMsg { pub viewing_key: String, pub pair_name: String, pub entropy: String, + pub protocol_fee_recipient: Addr, } impl InstantiateCallback for InstantiateMsg { @@ -37,11 +56,13 @@ impl InstantiateCallback for InstantiateMsg { #[cw_serde] pub enum ExecuteMsg { - Swap { - swap_for_y: bool, - to: Addr, - amount_received: Uint128, + SwapTokens { + offer: SwapTokenAmount, + expected_return: Option, + to: Option, + padding: Option, }, + Receive(Snip20ReceiveMsg), AddLiquidity { liquidity_parameters: LiquidityParameters, }, @@ -63,7 +84,6 @@ pub enum ExecuteMsg { new_length: u16, }, SetStaticFeeParameters { - active_id: u32, base_factor: u16, filter_period: u16, decay_period: u16, @@ -75,10 +95,49 @@ pub enum ExecuteMsg { ForceDecay {}, } +impl ExecuteMsg { + pub fn to_cosmos_msg( + &self, + code_hash: String, + contract_addr: String, + send_amount: Option, + ) -> StdResult { + let mut msg = to_binary(self)?; + space_pad(&mut msg.0, 256); + let mut funds = Vec::new(); + if let Some(amount) = send_amount { + funds.push(Coin { + amount, + denom: String::from("uscrt"), + }); + } + let execute = WasmMsg::Execute { + contract_addr, + code_hash, + msg, + funds, + }; + Ok(execute.into()) + } +} + impl ExecuteCallback for ExecuteMsg { const BLOCK_SIZE: usize = 256; } +#[cw_serde] +pub enum InvokeMsg { + SwapTokens { + expected_return: Option, + to: Option, + padding: Option, + }, +} + +impl ExecuteCallback for InvokeMsg { + const BLOCK_SIZE: usize = 256; +} + #[cw_serde] pub struct MintResponse { pub amounts_received: Bytes32, @@ -91,6 +150,13 @@ pub struct MintResponse { pub enum QueryMsg { #[returns(LbTokenResponse)] GetLbToken {}, + #[returns(GetPairInfoResponse)] + GetPairInfo {}, + #[returns(SwapSimulationResponse)] + SwapSimulation { + offer: TokenAmount, + exclude_fee: Option, + }, #[returns(FactoryResponse)] GetFactory {}, #[returns(TokensResponse)] @@ -144,6 +210,26 @@ impl Query for QueryMsg { pub struct LbTokenResponse { pub lb_token: ContractInfo, } +#[cw_serde] +pub struct GetPairInfoResponse { + pub liquidity_token: Contract, + pub factory: Option, + pub pair: TokenPair, + pub amount_0: Uint128, + pub amount_1: Uint128, + pub total_liquidity: Uint128, + pub contract_version: u32, + pub fee_info: FeeInfo, + pub stable_info: Option, +} +#[cw_serde] +pub struct SwapSimulationResponse { + total_fee_amount: Uint128, + lp_fee_amount: Uint128, + shade_dao_fee_amount: Uint128, + result: SwapResult, + price: String, +} // We define a custom struct for each query response #[cw_serde] pub struct FactoryResponse { @@ -298,3 +384,82 @@ pub struct RemoveLiquidity { pub amounts: Vec, pub deadline: u64, } + +#[cw_serde] + +pub struct FeeInfo { + pub shade_dao_address: Addr, + pub lp_fee: Fee, + pub shade_dao_fee: Fee, + pub stable_lp_fee: Fee, + pub stable_shade_dao_fee: Fee, +} + +#[cw_serde] + +pub struct StablePairInfoResponse { + pub stable_params: StableParams, + pub stable_token0_data: StableTokenData, + pub stable_token1_data: StableTokenData, + //p is optional so that the PairInfo query can still return even when the calculation of p fails + pub p: Option, +} + +#[cw_serde] + +pub struct CustomIterationControls { + pub epsilon: Uint256, // assumed to have same decimals as SignedDecimal + pub max_iter_newton: u16, + pub max_iter_bisect: u16, +} + +#[cw_serde] + +pub struct StableParams { + pub a: Decimal256, + pub gamma1: Uint256, + pub gamma2: Uint256, + pub oracle: Contract, + pub min_trade_size_x_for_y: Decimal256, + pub min_trade_size_y_for_x: Decimal256, + pub max_price_impact_allowed: Decimal256, + pub custom_iteration_controls: Option, +} + +#[cw_serde] + +pub struct StableTokenData { + pub oracle_key: String, + pub decimals: u8, +} + +#[cw_serde] + +pub struct Fee { + pub nom: u64, + pub denom: u64, +} + +impl Fee { + pub fn new(nom: u64, denom: u64) -> Self { + Self { nom, denom } + } +} + +#[cw_serde] + +pub struct CustomFee { + pub shade_dao_fee: Fee, + pub lp_fee: Fee, +} + +#[cw_serde] +pub struct TokenPair { + pub token_0: TokenType, + pub token_1: TokenType, +} + +#[cw_serde] +pub struct SwapResult { + pub return_amount: Uint128, +} diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_token.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_token.rs index 011d5f3e6..541146f97 100644 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_token.rs +++ b/packages/shade_protocol/src/contract_interfaces/liquidity_book/lb_token.rs @@ -12,7 +12,7 @@ use crate::utils::{ state_structs::{CurateTokenId, LbPair, OwnerBalance, StoredTokenInfo, TokenAmount}, txhistory::Tx, }, - InstantiateCallback, Query, + ExecuteCallback, InstantiateCallback, Query, }; use secret_toolkit::permit::Permit; @@ -215,6 +215,10 @@ pub enum ExecuteMsg { }, } +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = 256; +} + impl ExecuteMsg { pub fn to_cosmos_msg( &self, diff --git a/packages/shade_protocol/src/contract_interfaces/liquidity_book/snip20.rs b/packages/shade_protocol/src/contract_interfaces/liquidity_book/snip20.rs deleted file mode 100644 index 224fc0a37..000000000 --- a/packages/shade_protocol/src/contract_interfaces/liquidity_book/snip20.rs +++ /dev/null @@ -1,350 +0,0 @@ -use super::{batch, manager::AllowanceResponse, ExecuteMsg, QueryAnswer, QueryMsg}; -use crate::{ - c_std::{Addr, Binary, CosmosMsg, QuerierWrapper, StdError, StdResult, Uint128}, - utils::{asset::Contract, ExecuteCallback, Query}, -}; -use cosmwasm_schema::cw_serde; -use cosmwasm_std::Coin; - -#[cw_serde] -pub struct Snip20Asset { - pub contract: Contract, - pub token_info: TokenInfo, - pub token_config: Option, -} - -pub fn fetch_snip20(contract: &Contract, querier: &QuerierWrapper) -> StdResult { - Ok(Snip20Asset { - contract: contract.clone(), - token_info: token_info(querier, contract)?, - token_config: Some(token_config(querier, contract)?), - }) -} - -/// Returns a StdResult used to execute Send -#[allow(clippy::too_many_arguments)] -pub fn send_msg( - recipient: Addr, - amount: Uint128, - msg: Option, - memo: Option, - padding: Option, - contract: &Contract, -) -> StdResult { - Ok(ExecuteMsg::Send { - recipient: recipient.to_string(), - recipient_code_hash: None, - amount, - msg, - memo, - padding, - } - .to_cosmos_msg(contract, vec![])?) -} - -/// Returns a StdResult used to execute Redeem -pub fn redeem_msg( - amount: Uint128, - denom: Option, - padding: Option, - contract: &Contract, -) -> StdResult { - ExecuteMsg::Redeem { - amount, - denom, - padding, - } - .to_cosmos_msg(contract, vec![]) -} - -/// Returns a StdResult used to execute Deposit -pub fn deposit_msg( - amount: Uint128, - padding: Option, - contract: &Contract, -) -> StdResult { - ExecuteMsg::Deposit { padding }.to_cosmos_msg( - contract, - vec![Coin { - denom: "uscrt".to_string(), - amount, - }], - ) -} - -/// Returns a StdResult used to execute Mint -pub fn mint_msg( - recipient: Addr, - amount: Uint128, - memo: Option, - padding: Option, - contract: &Contract, -) -> StdResult { - ExecuteMsg::Mint { - recipient: recipient.to_string(), - amount, - memo, - padding, - } - .to_cosmos_msg(contract, vec![]) -} - -/// Returns a StdResult used to execute Burn -pub fn burn_msg( - amount: Uint128, - memo: Option, - padding: Option, - contract: &Contract, -) -> StdResult { - ExecuteMsg::Burn { - amount, - memo, - padding, - } - .to_cosmos_msg(contract, vec![]) -} - -/// Returns a StdResult used to execute RegisterReceive -pub fn register_receive( - register_hash: String, - padding: Option, - contract: &Contract, -) -> StdResult { - ExecuteMsg::RegisterReceive { - code_hash: register_hash, - padding, - } - .to_cosmos_msg(contract, vec![]) -} - -pub fn set_viewing_key_msg( - viewing_key: String, - padding: Option, - contract: &Contract, -) -> StdResult { - ExecuteMsg::SetViewingKey { - key: viewing_key, - padding, - } - .to_cosmos_msg(contract, vec![]) -} - -pub fn batch_send_msg( - actions: Vec, - padding: Option, - contract: &Contract, -) -> StdResult { - ExecuteMsg::BatchSend { actions, padding }.to_cosmos_msg(contract, vec![]) -} - -pub fn batch_send_from_msg( - actions: Vec, - padding: Option, - contract: &Contract, -) -> StdResult { - ExecuteMsg::BatchSendFrom { actions, padding }.to_cosmos_msg(contract, vec![]) -} - -/// TokenInfo response -#[cw_serde] -pub struct TokenInfo { - pub name: String, - pub symbol: String, - pub decimals: u8, - #[serde(skip_serializing_if = "Option::is_none")] - pub total_supply: Option, -} -/// Returns a StdResult from performing TokenInfo query -pub fn token_info(querier: &QuerierWrapper, contract: &Contract) -> StdResult { - let answer: QueryAnswer = QueryMsg::TokenInfo {}.query(querier, contract)?; - match answer { - QueryAnswer::TokenInfo { - name, - symbol, - decimals, - total_supply, - } => Ok(TokenInfo { - name, - symbol, - decimals, - total_supply, - }), - _ => Err(StdError::generic_err( - "Query answer does not match possible enum values.", - )), //TODO: better error - } -} - -/// Returns a StdResult from performing a Balance query -pub fn balance_query( - querier: &QuerierWrapper, - address: Addr, - key: String, - contract: &Contract, -) -> StdResult { - let answer: QueryAnswer = QueryMsg::Balance { - address: address.to_string(), - key, - } - .query(querier, contract)?; - - match answer { - QueryAnswer::Balance { amount, .. } => Ok(amount), - _ => Err(StdError::generic_err("Invalid Balance Response")), //TODO: better error - } -} - -/// TokenConfig response -#[cw_serde] -pub struct TokenConfig { - pub public_total_supply: bool, - pub deposit_enabled: bool, - pub redeem_enabled: bool, - pub mint_enabled: bool, - pub burn_enabled: bool, - // Optionals only relevant to some snip20a - #[serde(skip_serializing_if = "Option::is_none")] - pub transfer_enabled: Option, -} -/// Returns a StdResult from performing TokenConfig query -pub fn token_config(querier: &QuerierWrapper, contract: &Contract) -> StdResult { - let answer: QueryAnswer = QueryMsg::TokenConfig {}.query(querier, contract)?; - - match answer { - QueryAnswer::TokenConfig { - public_total_supply, - deposit_enabled, - redeem_enabled, - mint_enabled, - burn_enabled, - .. - } => Ok(TokenConfig { - public_total_supply, - deposit_enabled, - redeem_enabled, - mint_enabled, - burn_enabled, - transfer_enabled: None, - }), - _ => Err(StdError::generic_err("Wrong answer")), //TODO: better error - } -} - -/// Returns a StdResult used to execute IncreaseAllowance -/// -/// # Arguments -/// -/// * `spender` - the address of the allowed spender -/// * `amount` - Uint128 additional amount the spender is allowed to send/burn -/// * `expiration` - Optional u64 denoting the epoch time in seconds that the allowance will expire -/// * `padding` - Optional String used as padding if you don't want to use block padding -/// * `block_size` - pad the message to blocks of this size -/// * `callback_code_hash` - String holding the code hash of the contract being called -/// * `contract_addr` - address of the contract being called -pub fn increase_allowance_msg( - spender: Addr, - amount: Uint128, - expiration: Option, - padding: Option, - _block_size: usize, - contract: &Contract, - funds: Vec, -) -> StdResult { - ExecuteMsg::IncreaseAllowance { - spender: spender.to_string(), - amount, - expiration, - padding, - } - .to_cosmos_msg(contract, funds) -} - -/// Returns a StdResult used to execute DecreaseAllowance -/// -/// # Arguments -/// -/// * `spender` - the address of the allowed spender -/// * `amount` - Uint128 amount the spender is no longer allowed to send/burn -/// * `expiration` - Optional u64 denoting the epoch time in seconds that the allowance will expire -/// * `padding` - Optional String used as padding if you don't want to use block padding -/// * `block_size` - pad the message to blocks of this size -/// * `callback_code_hash` - String holding the code hash of the contract being called -/// * `contract_addr` - address of the contract being called -pub fn decrease_allowance_msg( - spender: Addr, - amount: Uint128, - expiration: Option, - padding: Option, - _block_size: usize, - contract: &Contract, - funds: Vec, -) -> StdResult { - ExecuteMsg::DecreaseAllowance { - spender: spender.to_string(), - amount, - expiration, - padding, - } - .to_cosmos_msg(contract, funds) -} - -/// Returns a StdResult from performing Allowance query -/// -/// # Arguments -/// -/// * `querier` - a reference to the Querier dependency of the querying contract -/// * `owner` - the address that owns the tokens -/// * `spender` - the address allowed to send/burn tokens -/// * `key` - String holding the authentication key needed to view the allowance -/// * `block_size` - pad the message to blocks of this size -/// * `callback_code_hash` - String holding the code hash of the contract being queried -/// * `contract_addr` - address of the contract being queried -#[allow(clippy::too_many_arguments)] -pub fn allowance_query( - querier: &QuerierWrapper, - owner: Addr, - spender: Addr, - key: String, - _block_size: usize, - contract: &Contract, -) -> StdResult { - let answer: QueryAnswer = QueryMsg::Allowance { - owner: owner.to_string(), - spender: spender.to_string(), - key, - } - .query(querier, contract)?; - match answer { - QueryAnswer::Allowance { - spender, - owner, - allowance, - expiration, - } => Ok(AllowanceResponse { - spender, - owner, - expiration, - amount: allowance, - }), - QueryAnswer::ViewingKeyError { .. } => Err(StdError::generic_err("Unauthorized")), - _ => Err(StdError::generic_err("Invalid Allowance query response")), - } -} - -pub fn transfer_from_msg( - owner: String, - recipient: String, - amount: Uint128, - memo: Option, - padding: Option, - contract: &Contract, -) -> StdResult { - ExecuteMsg::TransferFrom { - owner, - recipient, - amount, - memo, - padding, - } - .to_cosmos_msg(contract, vec![]) -} diff --git a/packages/shade_protocol/src/contract_interfaces/sky/cycles.rs b/packages/shade_protocol/src/contract_interfaces/sky/cycles.rs index ea23f0a5a..e93490e5e 100644 --- a/packages/shade_protocol/src/contract_interfaces/sky/cycles.rs +++ b/packages/shade_protocol/src/contract_interfaces/sky/cycles.rs @@ -4,17 +4,11 @@ use crate::{ mint::mint, snip20::helpers::send_msg, }, + lb_libraries::tokens::TokenType, utils::{asset::Contract, Query}, }; use cosmwasm_schema::cw_serde; -use cosmwasm_std::{ - to_binary, - CosmosMsg, - Deps, - StdError, - StdResult, - Uint128, -}; +use cosmwasm_std::{to_binary, CosmosMsg, Deps, StdError, StdResult, Uint128}; #[cw_serde] pub struct ArbPair { @@ -62,7 +56,7 @@ impl ArbPair { amount_1, .. } => match pair.token_0 { - shadeswap::TokenType::CustomToken { contract_addr, .. } => { + TokenType::CustomToken { contract_addr, .. } => { if contract_addr == self.token0.address.clone() { self.token0_amount = Some(amount_0); self.token1_amount = Some(amount_1); @@ -144,7 +138,7 @@ impl ArbPair { Dex::ShadeSwap => { let res = shadeswap::PairQuery::GetEstimatedPrice { offer: shadeswap::TokenAmount { - token: shadeswap::TokenType::CustomToken { + token: TokenType::CustomToken { token_code_hash: offer.asset.code_hash.clone(), contract_addr: offer.asset.address.clone(), }, diff --git a/packages/shade_protocol/src/utils/callback.rs b/packages/shade_protocol/src/utils/callback.rs index f77450960..ebe7fb8b7 100644 --- a/packages/shade_protocol/src/utils/callback.rs +++ b/packages/shade_protocol/src/utils/callback.rs @@ -7,17 +7,8 @@ use crate::multi_test::{App, AppResponse, Contract as MultiContract, Executor}; use crate::AnyResult; use crate::{ c_std::{ - to_binary, - Addr, - Coin, - ContractInfo, - CosmosMsg, - Empty, - QuerierWrapper, - QueryRequest, - StdResult, - WasmMsg, - WasmQuery, + to_binary, Addr, Coin, ContractInfo, CosmosMsg, Empty, QuerierWrapper, QueryRequest, + StdResult, WasmMsg, WasmQuery, }, serde::{de::DeserializeOwned, Serialize}, Contract, diff --git a/packages/shade_protocol/src/utils/liquidity_book/bin_helper.rs b/packages/shade_protocol/src/utils/liquidity_book/bin_helper.rs index 3364dd743..a31b8781c 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/bin_helper.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/bin_helper.rs @@ -287,9 +287,10 @@ impl BinHelper { /// * `is_x` - Whether the reserve to check is the X reserve (true) or the Y reserve (false) pub fn is_empty(bin_reserves: Bytes32, is_x: bool) -> bool { if is_x { - return bin_reserves.decode_alt(is_x) == 0; + return bin_reserves.decode_x() == 0; + } else { + return bin_reserves.decode_y() == 0; } - bin_reserves.decode_alt(!is_x) == 0 } /// Returns the amounts of tokens that will be added and removed from the bin during a swap @@ -334,15 +335,21 @@ impl BinHelper { let max_amount_in = max_amount_in + max_fee; - let amount_in128 = amounts_in_left.decode_alt(swap_for_y); - let (fee128, amount_out128) = if amount_in128 >= max_amount_in { - (max_fee, bin_reserve_out) + let mut amount_in128 = amounts_in_left.decode_alt(swap_for_y); + + let mut fee128; + let mut amount_out128; + + if amount_in128 >= max_amount_in { + fee128 = max_fee; + amount_in128 = max_amount_in.as_u128(); + amount_out128 = bin_reserve_out; } else { - let fee128 = FeeHelper::get_fee_amount_from(amount_in128, total_fee)?; + fee128 = FeeHelper::get_fee_amount_from(amount_in128, total_fee)?; let amount_in = amount_in128 - fee128; - let amount_out128 = if swap_for_y { + amount_out128 = if swap_for_y { U256x256Math::mul_shift_round_down(U256::from(amount_in), price, SCALE_OFFSET)? .min(U256::from(u128::MAX)) .as_u128() @@ -353,9 +360,7 @@ impl BinHelper { }; if amount_out128 > bin_reserve_out { - (fee128, bin_reserve_out) - } else { - (fee128, amount_out128) + amount_out128 = bin_reserve_out; } }; diff --git a/packages/shade_protocol/src/utils/liquidity_book/lb_token/state_structs.rs b/packages/shade_protocol/src/utils/liquidity_book/lb_token/state_structs.rs index 6cb94f557..17e5e38e6 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/lb_token/state_structs.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/lb_token/state_structs.rs @@ -7,9 +7,6 @@ use cosmwasm_std::{Addr, Uint256}; use self::metadata::Metadata; -#[cfg(test)] -use crate::utils::liquidity_book::lb_token::metadata::Extension; - ///////////////////////////////////////////////////////////////////////////////// // Contract and Token Id configs ///////////////////////////////////////////////////////////////////////////////// diff --git a/packages/shade_protocol/src/utils/liquidity_book/math/packed_u128_math.rs b/packages/shade_protocol/src/utils/liquidity_book/math/packed_u128_math.rs index 63e43ef07..ef88ed0c5 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/math/packed_u128_math.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/math/packed_u128_math.rs @@ -821,6 +821,7 @@ mod tests { } #[test] + #[should_panic] fn test_sub() { // Basic Test Case let bytes1 = Bytes32::encode(25, 45); @@ -831,7 +832,7 @@ mod tests { // Underflow Test Case let bytes1 = Bytes32::encode(0, 0); let bytes2 = Bytes32::encode(10, 20); - // This should panic + Bytes32::sub(&bytes1, bytes2); // let result = Bytes32::sub(&bytes1, bytes2); // Zero Test Case @@ -860,7 +861,7 @@ mod tests { // Underflow Test Case let bytes1 = Bytes32::encode(0, 0); // This should panic - let result = Bytes32::sub_alt(&bytes1, 10, 20); + let _result = Bytes32::sub_alt(&bytes1, 10, 20); } #[test] diff --git a/packages/shade_protocol/src/utils/liquidity_book/math/tree_math.rs b/packages/shade_protocol/src/utils/liquidity_book/math/tree_math.rs index 57811deaf..a733fc443 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/math/tree_math.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/math/tree_math.rs @@ -477,4 +477,28 @@ mod tests { } } } + + #[test] + fn test_test() { + let mut tree = TreeUint24::new(); + let id = 8363961; + + tree.add(id + 1); + tree.add(id); + tree.add(id - 1); + + tree.remove(id); + + assert_eq!( + tree.find_first_left(id - 1), + id + 1, + "test_remove_logic_and_search_right::1" + ); + + assert_eq!( + tree.find_first_right(id + 1), + id - 1, + "test_remove_logic_and_search_right::2" + ); + } } diff --git a/packages/shade_protocol/src/utils/liquidity_book/math/u128x128_math.rs b/packages/shade_protocol/src/utils/liquidity_book/math/u128x128_math.rs index c2216e776..3d9847851 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/math/u128x128_math.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/math/u128x128_math.rs @@ -21,8 +21,8 @@ pub enum U128x128MathError { const LOG_SCALE_OFFSET: U256 = U256::new(127u128); const LOG_SCALE: U256 = U256::new(1u128 << 127u128); -// TODO: verify this works out to 2^256 -const LOG_SCALE_SQUARED: U256 = U256::from_words(1u128 << 127u128, 0); +// TODO: verify this works out to 2^256, 2^127 * 2^127 +const LOG_SCALE_SQUARED: U256 = U256::from_words(1u128 << 127u128 - 1, 0); pub struct U128x128Math; @@ -76,6 +76,7 @@ impl U128x128Math { } // Calculate the integer part of the logarithm and add it to the result and finally calculate y = x * 2^(-n). + let n = BitMath::most_significant_bit(x >> LOG_SCALE_OFFSET); // The integer part of the logarithm as a signed 129.127-binary fixed-point number. The operation can't overflow diff --git a/packages/shade_protocol/src/utils/liquidity_book/math/u256x256_math.rs b/packages/shade_protocol/src/utils/liquidity_book/math/u256x256_math.rs index aa4cc3d38..29ef77998 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/math/u256x256_math.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/math/u256x256_math.rs @@ -407,9 +407,9 @@ mod tests { use ethnum::U256; - use crate::{ - utils::liquidity_book::constants::{PRECISION, SCALE_OFFSET}, - utils::liquidity_book::math::u256x256_math::U256x256Math, + use crate::utils::liquidity_book::{ + constants::{PRECISION, SCALE_OFFSET}, + math::u256x256_math::U256x256Math, }; #[test] @@ -418,7 +418,7 @@ mod tests { U256::from_str_prefixed("42008768997448919173843294709597899956404323600000").unwrap(); let y = U256::from_str_prefixed("42008768997448919173843294709597899956404323600000").unwrap(); - let z = U256x256Math::_get_mul_prods(x, y); + let _z = U256x256Math::_get_mul_prods(x, y); } #[test] diff --git a/packages/shade_protocol/src/utils/liquidity_book/oracle_helper.rs b/packages/shade_protocol/src/utils/liquidity_book/oracle_helper.rs index eae790be7..aea263dbe 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/oracle_helper.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/oracle_helper.rs @@ -20,7 +20,8 @@ use serde::{Deserialize, Serialize}; use super::pair_parameter_helper::PairParameters; use crate::utils::liquidity_book::math::{ - encoded_sample::EncodedSample, sample_math::OracleSample, + encoded_sample::EncodedSample, + sample_math::OracleSample, }; // TODO: consider creating a different type of storage for this. @@ -315,7 +316,7 @@ impl Oracle { // It's confusing looking because we don't have methods for pow or bitOR for bytes32, // so we have to convert to U256 and back. let new_sample = - (U256::from_le_bytes(sample.0 .0) ^ U256::from(length)) | U256::from(new_length); + (U256::from_le_bytes(sample.0.0) ^ U256::from(length)) | U256::from(new_length); self.set_sample( oracle_id, @@ -782,7 +783,7 @@ mod tests { }; // Populate inputs struct (you may want to fuzz these values) - let mut inputs = UpdateInputs { + let inputs = UpdateInputs { oracle_length: 3, oracle_id: 2, previous_active_id: 100, diff --git a/packages/shade_protocol/src/utils/liquidity_book/pair_parameter_helper.rs b/packages/shade_protocol/src/utils/liquidity_book/pair_parameter_helper.rs index f1bf9f032..2826c938d 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/pair_parameter_helper.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/pair_parameter_helper.rs @@ -45,6 +45,8 @@ const MASK_STATIC_PARAMETER: u128 = 0xffffffffffffffffffffffffffffu128; pub enum PairParametersError { #[error("Pair Parameters Error: Invalid Parameter")] InvalidParameter, + #[error("Max total fee exceeded!")] + MaxTotalFeeExceeded, } #[cw_serde] @@ -234,6 +236,11 @@ impl PairParameters { base_factor * (bin_step as u128) * 10_000_000_000 } + pub fn get_base_fee_u64(&self, bin_step: u16) -> u64 { + let base_factor = Self::get_base_factor(&self) as u64; + base_factor * (bin_step as u64) * 10_000_000_000 + } + /// Calculates the variable fee. /// /// # Arguments diff --git a/packages/shade_protocol/src/utils/liquidity_book/price_helper.rs b/packages/shade_protocol/src/utils/liquidity_book/price_helper.rs index 78abbc3fc..b439ffd7d 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/price_helper.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/price_helper.rs @@ -5,9 +5,13 @@ use ethnum::{I256, U256}; -use super::constants::*; -use super::math::u128x128_math::{U128x128Math, U128x128MathError}; -use super::math::u256x256_math::{U256x256Math, U256x256MathError}; +use super::{ + constants::*, + math::{ + u128x128_math::{U128x128Math, U128x128MathError}, + u256x256_math::{U256x256Math, U256x256MathError}, + }, +}; // represents a 23 bit number (uint24, which we're not using yet) const REAL_ID_SHIFT: I256 = I256::new(1 << 23); @@ -105,8 +109,8 @@ mod tests { let integer_part = fixed_point >> 128; let shifted: U256 = U256::from(1u128) << 128; let fractional_part = fixed_point & U256::from(shifted.checked_sub(U256::ONE).unwrap()); - let fractional_part_decimal = fractional_part / U256::from(shifted); - let real_value = integer_part; + let _fractional_part_decimal = fractional_part / U256::from(shifted); + let _real_value = integer_part; } #[test] diff --git a/packages/shade_protocol/src/utils/liquidity_book/tokens.rs b/packages/shade_protocol/src/utils/liquidity_book/tokens.rs index c1a52d1ca..96c688ae2 100644 --- a/packages/shade_protocol/src/utils/liquidity_book/tokens.rs +++ b/packages/shade_protocol/src/utils/liquidity_book/tokens.rs @@ -3,18 +3,39 @@ //! use crate::utils::liquidity_book::transfer::{self, HandleMsg, QueryAnswer, QueryMsg}; + +use std::fmt::{Display, Formatter, Result}; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - to_binary, Addr, BankMsg, Coin, ContractInfo, CosmosMsg, Deps, MessageInfo, QuerierWrapper, - QueryRequest, StdError, StdResult, Uint128, WasmMsg, WasmQuery, + to_binary, + Addr, + BankMsg, + Coin, + ContractInfo, + CosmosMsg, + Deps, + MessageInfo, + QuerierWrapper, + QueryRequest, + StdError, + StdResult, + Uint128, + WasmMsg, + WasmQuery, }; +#[cw_serde] +pub struct SwapTokenAmount { + pub token: TokenType, + pub amount: Uint128, +} + #[cw_serde] pub enum TokenType { CustomToken { contract_addr: Addr, token_code_hash: String, - //viewing_key: String, }, NativeToken { denom: String, @@ -22,12 +43,33 @@ pub enum TokenType { } impl TokenType { + // pub fn query_decimals(&self, deps: &Deps) -> StdResult { + // match self { + // TokenType::CustomToken { + // contract_addr, + // token_code_hash, + // .. + // } => Ok(token_info(&deps.querier, &Contract { + // address: contract_addr.clone(), + // code_hash: token_code_hash.clone(), + // })? + // .decimals), + // TokenType::NativeToken { denom } => match denom.as_str() { + // "uscrt" => Ok(6), + // _ => Err(StdError::generic_err( + // "Cannot retrieve decimals for native token", + // )), + // }, + // } + // } + pub fn is_native_token(&self) -> bool { match self { TokenType::NativeToken { .. } => true, TokenType::CustomToken { .. } => false, } } + pub fn unique_key(&self) -> String { match self { TokenType::NativeToken { denom } => denom.to_string(), @@ -37,12 +79,34 @@ impl TokenType { } => contract_addr.to_string(), } } + + pub fn address(&self) -> Addr { + match self { + TokenType::NativeToken { .. } => panic!("Doesn't work for native tokens"), + TokenType::CustomToken { + contract_addr, + token_code_hash: _, + } => contract_addr.clone(), + } + } + + pub fn code_hash(&self) -> String { + match self { + TokenType::NativeToken { .. } => panic!("Doesn't work for native tokens"), + TokenType::CustomToken { + contract_addr: _, + token_code_hash, + } => token_code_hash.to_string(), + } + } + pub fn is_custom_token(&self) -> bool { match self { TokenType::NativeToken { .. } => false, TokenType::CustomToken { .. } => true, } } + pub fn assert_sent_native_token_balance( &self, info: &MessageInfo, @@ -54,14 +118,18 @@ impl TokenType { if amount == coin.amount { Ok(()) } else { - Err(StdError::generic_err("Native token balance mismatch between the argument and the transferred")) + Err(StdError::generic_err( + "Native token balance mismatch between the argument and the transferred", + )) } } None => { if amount.is_zero() { Ok(()) } else { - Err(StdError::generic_err("Native token balance mismatch between the argument and the transferred")) + Err(StdError::generic_err( + "Native token balance mismatch between the argument and the transferred", + )) } } }; @@ -175,12 +243,7 @@ impl TokenType { } } - pub fn create_send_msg( - &self, - _sender: String, - recipient: String, - amount: Uint128, - ) -> StdResult { + pub fn create_send_msg(&self, recipient: String, amount: Uint128) -> StdResult { let msg = match self { TokenType::CustomToken { contract_addr, @@ -232,3 +295,25 @@ pub fn balance_query( let QueryAnswer::Balance { amount, .. } = result; Ok(amount) } + +impl Display for TokenType { + fn fmt(&self, f: &mut Formatter) -> Result { + match self { + TokenType::NativeToken { denom, .. } => write!(f, "{}", denom), + TokenType::CustomToken { contract_addr, .. } => write!(f, "{}", contract_addr), + } + } +} + +#[cw_serde] +pub struct TokenAmount { + pub token: TokenType, + pub amount: Uint128, +} + +impl TokenAmount { + pub fn assert_sent_native_token_balance(&self, info: &MessageInfo) -> StdResult<()> { + self.token + .assert_sent_native_token_balance(info, self.amount) + } +} diff --git a/packages/shadeswap_shared/Cargo.toml b/packages/shadeswap_shared/Cargo.toml new file mode 100644 index 000000000..e84848f8f --- /dev/null +++ b/packages/shadeswap_shared/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "shadeswap-shared" +version = "0.1.0" +authors = ["Tony "] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[features] +default = [] +utils = [] +staking = [] + +[dependencies] +schemars = "0.8.11" +serde = { version = "1.0.114", default-features = false, features = ["derive"] } +cosmwasm-std = { version = "1.0.0", package = "secret-cosmwasm-std" } +cosmwasm-storage = { version = "1.0.0", package = "secret-cosmwasm-storage" } +cosmwasm-schema = { version = "1.1.8" } +subtle = { version = "2.2.3", default-features = false } +query-authentication = { git = "https://github.com/securesecrets/query-authentication", branch = "cosmwasm_v1_upgrade" } +shade-protocol = { version = "0.1.0", path = "../shade_protocol", features = ["snip20", "admin", "query_auth", "airdrop","liquidity_book"] } + +better-secret-math = { git = "https://github.com/securesecrets/better-secret-math" } +secret-storage-plus = { git = "https://github.com/securesecrets/secret-plus-utils" } diff --git a/packages/shadeswap_shared/src/amm_pair.rs b/packages/shadeswap_shared/src/amm_pair.rs new file mode 100644 index 000000000..31600c9ad --- /dev/null +++ b/packages/shadeswap_shared/src/amm_pair.rs @@ -0,0 +1,53 @@ +use cosmwasm_std::{ + Addr, Decimal256, Uint256 +}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use crate::core::{ContractLink, TokenPair, Fee, TokenType}; +use shade_protocol::Contract; + +/// Represents the address of an exchange and the pair that it manages +#[derive(Serialize, Deserialize, JsonSchema, Clone, PartialEq, Debug)] +pub struct AMMPair { + /// The pair that the contract manages. + pub pair: TokenPair, + /// Address of the contract that manages the exchange. + pub address: Addr, + /// Used to enable or disable the AMMPair + pub enabled: bool +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct StableParams { + pub a: Decimal256, + pub gamma1: Uint256, + pub gamma2: Uint256, + pub oracle: Contract, +} + +#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Debug,Clone)] +pub struct AMMSettings { + pub lp_fee: Fee, + pub shade_dao_fee: Fee, + pub stable_lp_fee: Fee, + pub stable_shade_dao_fee: Fee, + pub shade_dao_address: ContractLink +} + +pub fn generate_pair_key(pair: &TokenPair) -> Vec { + let mut bytes: Vec<&[u8]> = Vec::new(); + + match &pair.0 { + TokenType::NativeToken { denom, ..} => bytes.push(denom.as_bytes()), + TokenType::CustomToken { contract_addr, .. } => bytes.push(contract_addr.as_bytes()) + } + + match &pair.1 { + TokenType::NativeToken { denom, .. } => bytes.push(denom.as_bytes()), + TokenType::CustomToken { contract_addr, .. } => bytes.push(contract_addr.as_bytes()) + } + + bytes.sort(); + + bytes.concat() +} \ No newline at end of file diff --git a/packages/shadeswap_shared/src/core/admin.rs b/packages/shadeswap_shared/src/core/admin.rs new file mode 100644 index 000000000..a26ae8e56 --- /dev/null +++ b/packages/shadeswap_shared/src/core/admin.rs @@ -0,0 +1,37 @@ +// use cosmwasm_std::{ +// StdError, +// StdResult, +// Storage, Response, MessageInfo, Addr +// }; +// use cosmwasm_storage::{singleton, Singleton, singleton_read, ReadonlySingleton}; + +// pub static ADMIN: &[u8] =b"contract_pair_admin"; + +// pub fn admin_w(storage: &mut dyn Storage) -> Singleton { +// singleton(storage, ADMIN) +// } + +// pub fn admin_r(storage: & dyn Storage) -> ReadonlySingleton { +// singleton_read(storage, ADMIN) +// } + +// // pub fn apply_admin_guard( +// // caller: &Addr, +// // storage: &mut dyn Storage, +// // ) -> StdResult { +// // let admin_address = admin_r(storage).load()?; +// // if caller != &admin_address { +// // return Err(StdError::generic_err("Caller is not admin")) +// // } +// // return Ok(true) +// // } + +// pub fn set_admin_guard( +// storage: &mut dyn Storage, +// info: MessageInfo, +// admin: Addr +// ) -> StdResult{ +// apply_admin_guard(&info.sender, storage)?; +// admin_w(storage).save(&admin)?; +// Ok(Response::default()) +// } diff --git a/packages/shadeswap_shared/src/core/callback.rs b/packages/shadeswap_shared/src/core/callback.rs new file mode 100644 index 000000000..57c74952f --- /dev/null +++ b/packages/shadeswap_shared/src/core/callback.rs @@ -0,0 +1,25 @@ +use cosmwasm_std::{Binary, CosmosMsg, WasmMsg}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +use shade_protocol::Contract; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +/// Info needed to have the other contract respond. +pub struct Callback { + /// The message to call. + pub msg: Binary, + /// Info about the contract requesting the callback. + pub contract: Contract, +} + +impl Into for Callback { + fn into(self) -> CosmosMsg { + CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.contract.address.into_string(), + code_hash: self.contract.code_hash, + msg: self.msg, + funds: vec![], + }) + } +} diff --git a/packages/shadeswap_shared/src/core/custom_fee.rs b/packages/shadeswap_shared/src/core/custom_fee.rs new file mode 100644 index 000000000..e62b51740 --- /dev/null +++ b/packages/shadeswap_shared/src/core/custom_fee.rs @@ -0,0 +1,20 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Clone, Copy, Debug)] +pub struct Fee { + pub nom: u64, + pub denom: u64, +} + +impl Fee { + pub fn new(nom: u64, denom: u64) -> Self { + Self { nom, denom } + } +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct CustomFee { + pub shade_dao_fee: Fee, + pub lp_fee: Fee, +} diff --git a/packages/shadeswap_shared/src/core/display.rs b/packages/shadeswap_shared/src/core/display.rs new file mode 100644 index 000000000..21a147d65 --- /dev/null +++ b/packages/shadeswap_shared/src/core/display.rs @@ -0,0 +1,8 @@ +use super::TokenPair; +use std::fmt::{Display, Formatter, Result}; + +impl Display for TokenPair { + fn fmt(&self, f: &mut Formatter) -> Result { + write!(f, "Token 1: {} \n Token 2: {}", self.0, self.1) + } +} diff --git a/packages/shadeswap_shared/src/core/events.rs b/packages/shadeswap_shared/src/core/events.rs new file mode 100644 index 000000000..124d326fa --- /dev/null +++ b/packages/shadeswap_shared/src/core/events.rs @@ -0,0 +1,29 @@ +use cosmwasm_std::{Event, StdError, StdResult}; + +pub struct EventsParser; + +impl EventsParser { + /// Searches through custom events for a specific attribute key and returns its value if it exists. Custom events have type 'wasm'. + pub fn may_find_custom_value(events: &[Event], attribute_key: &str) -> Option { + for event in events { + if event.ty == "wasm" { + for attribute in &event.attributes { + if attribute.key == attribute_key { + return Some(attribute.value.clone()); + } + } + } + } + None + } + + pub fn try_find_custom_value(events: &[Event], attribute_key: &str) -> StdResult { + if let Some(value) = Self::may_find_custom_value(events, attribute_key) { + Ok(value) + } else { + Err(StdError::generic_err(format!( + "Could not find custom attribute with {attribute_key}" + ))) + } + } +} diff --git a/packages/shadeswap_shared/src/core/link.rs b/packages/shadeswap_shared/src/core/link.rs new file mode 100644 index 000000000..716db5c6b --- /dev/null +++ b/packages/shadeswap_shared/src/core/link.rs @@ -0,0 +1,68 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +pub type CodeId = u64; +pub type CodeHash = String; + +/// Info needed to instantiate a contract. +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +pub struct ContractInstantiationInfo { + pub code_hash: CodeHash, + pub id: CodeId, +} + +// Disregard code hash because it is case insensitive. +// Converting to the same case first and the comparing is unnecessary +// as providing the wrong code hash when calling a contract will result +// in an error regardless and we have no way of checking that here. +impl PartialEq for ContractInstantiationInfo { + fn eq(&self, other: &Self) -> bool { + self.id == other.id + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_eq() { + assert_eq!( + ContractInstantiationInfo { + id: 1, + code_hash: "c1dc8261059fee1de9f1873cd1359ccd7a6bc5623772661fa3d55332eb652084" + .into() + }, + ContractInstantiationInfo { + id: 1, + code_hash: "c1dc8261059fee1de9f1873cd1359ccd7a6bc5623772661fa3d55332eb652084" + .into() + } + ); + + assert_eq!( + ContractInstantiationInfo { + id: 1, + code_hash: "c1dc8261059fee1de9f1873cd1359ccd7a6bc5623772661fa3d55332eb652084" + .into() + }, + ContractInstantiationInfo { + id: 1, + code_hash: "C1dc8261059fee1de9f1873cd1359ccd7a6bc5623772661fa3d55332eb652084" + .into() + } + ); + + assert_ne!( + ContractInstantiationInfo { + id: 1, + code_hash: "c1dc8261059fee1de9f1873cd1359ccd7a6bc5623772661fa3d55332eb652084" + .into() + }, + ContractInstantiationInfo { + id: 2, + code_hash: "C1dc8261059fee1de9f1873cd1359ccd7a6bc5623772661fa3d55332eb652084" + .into() + } + ); + } +} diff --git a/packages/shadeswap_shared/src/core/mod.rs b/packages/shadeswap_shared/src/core/mod.rs new file mode 100644 index 000000000..bfd3b4554 --- /dev/null +++ b/packages/shadeswap_shared/src/core/mod.rs @@ -0,0 +1,21 @@ +mod admin; +mod callback; +mod custom_fee; +mod display; +mod link; +mod token_amount; +mod token_pair; +mod token_pair_amount; +mod token_type; +mod viewing_keys; +pub use admin::*; +pub use callback::*; +pub use custom_fee::*; +pub use display::*; +pub use link::*; +pub use token_amount::*; +pub use token_pair::*; +pub use token_pair_amount::*; +pub use token_type::*; +pub use viewing_keys::*; +pub mod events; diff --git a/packages/shadeswap_shared/src/core/token_amount.rs b/packages/shadeswap_shared/src/core/token_amount.rs new file mode 100644 index 000000000..15a5f96d4 --- /dev/null +++ b/packages/shadeswap_shared/src/core/token_amount.rs @@ -0,0 +1,18 @@ +use cosmwasm_std::StdResult; +use cosmwasm_std::{MessageInfo, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use shade_protocol::utils::liquidity_book::tokens::TokenType; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct TokenAmount { + pub token: TokenType, + pub amount: Uint128, +} + +impl TokenAmount { + pub fn assert_sent_native_token_balance(&self, info: &MessageInfo) -> StdResult<()> { + self.token + .assert_sent_native_token_balance(info, self.amount) + } +} diff --git a/packages/shadeswap_shared/src/core/token_pair.rs b/packages/shadeswap_shared/src/core/token_pair.rs new file mode 100644 index 000000000..857195882 --- /dev/null +++ b/packages/shadeswap_shared/src/core/token_pair.rs @@ -0,0 +1,196 @@ +use cosmwasm_std::{Deps, StdResult, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use shade_protocol::{utils::liquidity_book::tokens::TokenType, Contract}; + +use super::TokenPairAmount; + +#[derive(Clone, Debug, JsonSchema)] +pub struct TokenPair(pub TokenType, pub TokenType, pub bool); + +pub struct TokenPairIterator<'a> { + pair: &'a TokenPair, + index: u8, +} + +impl TokenPair { + /// Returns `true` if one of the token types in the pair is the same as the argument. + pub fn contains(&self, token: &TokenType) -> bool { + self.0 == *token || self.1 == *token + } + + /// Returns the index of the stored token type (0 or 1) that matches the argument. + /// Returns `None` if there are no matches. + pub fn get_token_index(&self, token: &TokenType) -> Option { + if self.0 == *token { + return Some(0); + } else if self.1 == *token { + return Some(1); + } + + None + } + + pub fn get_token(&self, index: usize) -> Option<&TokenType> { + match index { + 0 => Some(&self.0), + 1 => Some(&self.1), + _ => None, + } + } + + pub fn new_from_custom(token_a: Contract, token_b: Contract, is_stable: bool) -> Self { + Self( + TokenType::CustomToken { + contract_addr: token_a.address.to_owned(), + token_code_hash: token_a.code_hash, + }, + TokenType::CustomToken { + contract_addr: token_b.address.to_owned(), + token_code_hash: token_b.code_hash, + }, + is_stable, + ) + } + + pub fn new_amount( + &self, + amount_a: impl Into + Copy, + amount_b: impl Into + Copy, + ) -> TokenPairAmount { + TokenPairAmount { + pair: self.clone(), + amount_0: amount_a.into(), + amount_1: amount_b.into(), + } + } +} + +impl TokenPair { + /// Returns the balance for each token in the pair. The order of the balances in returned array + /// correspond to the token order in the pair i.e `[ self.0 balance, self.1 balance ]`. + pub fn query_balances( + &self, + deps: Deps, + exchange_addr: String, + viewing_key: String, + ) -> StdResult<[Uint128; 2]> { + let amount_0 = self + .0 + .query_balance(deps, exchange_addr.clone(), viewing_key.clone())?; + let amount_1 = self.1.query_balance(deps, exchange_addr, viewing_key)?; + + // order is important + Ok([amount_0, amount_1]) + } + + // pub fn query_decimals(&self, deps: &Deps) -> StdResult<[u8; 2]> { + // let decimal_0 = self.0.query_decimals(deps)?; + // let decimal_1 = self.1.query_decimals(deps)?; + // Ok([decimal_0, decimal_1]) + // } +} + +impl PartialEq for TokenPair { + fn eq(&self, other: &TokenPair) -> bool { + (self.0 == other.0 && self.1 == other.1 && self.2 == other.2) + || (self.0 == other.1 && self.1 == other.0 && self.2 == other.2) + } +} + +impl<'a> IntoIterator for &'a TokenPair { + type IntoIter = TokenPairIterator<'a>; + type Item = &'a TokenType; + + fn into_iter(self) -> Self::IntoIter { + TokenPairIterator { + pair: self, + index: 0, + } + } +} + +impl<'a> Iterator for TokenPairIterator<'a> { + type Item = &'a TokenType; + + fn next(&mut self) -> Option { + let result = match self.index { + 0 => Some(&self.pair.0), + 1 => Some(&self.pair.1), + _ => None, + }; + + self.index += 1; + + result + } +} + +impl Serialize for TokenPair { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + (self.0.clone(), self.1.clone(), self.2.clone()).serialize(serializer) + } +} + +impl<'de> Deserialize<'de> for TokenPair { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Deserialize::deserialize(deserializer) + .map(|(token_0, token_1, is_stable)| TokenPair(token_0, token_1, is_stable)) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::Addr; + + use super::*; + + #[test] + fn token_pair_equality() { + let pair: TokenPair = TokenPair( + TokenType::CustomToken { + contract_addr: Addr::unchecked("address"), + token_code_hash: "hash".into(), + }, + TokenType::NativeToken { + denom: "denom".into(), + }, + false, + ); + + let pair2 = TokenPair(pair.1.clone(), pair.0.clone(), false); + + assert_eq!(pair, pair); + assert_eq!(pair2, pair2); + assert_eq!(pair, pair2); + + let pair2 = TokenPair(pair.1.clone(), pair.1.clone(), false); + + assert_eq!(pair2, pair2); + assert_ne!(pair, pair2); + + let pair2 = TokenPair( + pair.1.clone(), + TokenType::CustomToken { + contract_addr: Addr::unchecked("address2"), + token_code_hash: "hash2".into(), + }, + false, + ); + + assert_eq!(pair, pair); + assert_eq!(pair2, pair2); + assert_ne!(pair, pair2); + + let pair2_reversed = TokenPair(pair2.1.clone(), pair2.0.clone(), false); + + assert_eq!(pair2_reversed, pair2); + assert_ne!(pair, pair2); + } +} diff --git a/packages/shadeswap_shared/src/core/token_pair_amount.rs b/packages/shadeswap_shared/src/core/token_pair_amount.rs new file mode 100644 index 000000000..db2da6145 --- /dev/null +++ b/packages/shadeswap_shared/src/core/token_pair_amount.rs @@ -0,0 +1,157 @@ +#[warn(unused_imports)] +use cosmwasm_std::{MessageInfo, StdError, StdResult, Uint128}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use shade_protocol::utils::liquidity_book::tokens::TokenType; + +use super::TokenPair; +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct TokenPairAmount { + pub pair: TokenPair, + pub amount_0: Uint128, + pub amount_1: Uint128, +} + +impl TokenPairAmount { + pub fn assert_sent_native_token_balance(&self, info: &MessageInfo) -> StdResult<()> { + self.pair + .0 + .assert_sent_native_token_balance(info, self.amount_0)?; + self.pair + .1 + .assert_sent_native_token_balance(info, self.amount_1)?; + + Ok(()) + } + + /// reorders the TokenPairAmount so that it's tokens and amounts are in the same order as the given 'pair_to_match' + pub fn create_new_pair_amount_to_match_order_of( + self, + pair_to_match: &TokenPair, + ) -> StdResult { + if !self.pair.contains(&pair_to_match.0) || !self.pair.contains(&pair_to_match.1) { + return Err(StdError::generic_err( + "Pair to match does not contain same tokens as current TokenPairAmount", + )); + } + + //at this point we know that the 'pair to match' contains the same tokens as the current pair + + if self.pair.0 == pair_to_match.0 { + //order is already correct + Ok(self) + } else { + //order is wrong + + Ok(TokenPairAmount { + pair: TokenPair(self.pair.1, self.pair.0, self.pair.2), + amount_0: self.amount_1, + amount_1: self.amount_0, + }) + } + } +} + +impl<'a> IntoIterator for &'a TokenPairAmount { + type IntoIter = TokenPairAmountIterator<'a>; + type Item = (Uint128, &'a TokenType); + + fn into_iter(self) -> Self::IntoIter { + TokenPairAmountIterator { + pair: self, + index: 0, + } + } +} + +pub struct TokenPairAmountIterator<'a> { + pair: &'a TokenPairAmount, + index: u8, +} + +impl<'a> Iterator for TokenPairAmountIterator<'a> { + type Item = (Uint128, &'a TokenType); + + fn next(&mut self) -> Option { + let result = match self.index { + 0 => Some((self.pair.amount_0, &self.pair.pair.0)), + 1 => Some((self.pair.amount_1, &self.pair.pair.1)), + _ => None, + }; + self.index += 1; + result + } +} + +pub mod tests { + use cosmwasm_std::{Addr, Uint128}; + use shade_protocol::utils::liquidity_book::tokens::TokenType; + + use crate::core::TokenPair; + + use super::TokenPairAmount; + + #[test] + pub fn test_rearrange() { + let token0 = TokenType::CustomToken { + contract_addr: Addr::unchecked("token0".to_string()), + token_code_hash: "token0".to_string(), + }; + let token1 = TokenType::CustomToken { + contract_addr: Addr::unchecked("token1".to_string()), + token_code_hash: "token1".to_string(), + }; + + let pair_correct_order = TokenPair(token0.clone(), token1.clone(), false); + + // test reversing a pair amount + let pair_param_2 = true; + let reverse_amount = TokenPairAmount { + pair: TokenPair(token1.clone(), token0.clone(), pair_param_2.clone()), + amount_0: Uint128::one(), + amount_1: Uint128::zero(), + }; + + let fixed = reverse_amount + .clone() + .create_new_pair_amount_to_match_order_of(&pair_correct_order) + .unwrap(); + assert_eq!(fixed.pair.0, pair_correct_order.0); + assert_eq!(fixed.pair.1, pair_correct_order.1); + assert_eq!(fixed.pair.2, pair_param_2); + assert_eq!(fixed.amount_0, Uint128::zero()); + assert_eq!(fixed.amount_1, Uint128::one()); + + //test leaving a pair amount how it is + let pair_param_2 = false; + let amount = TokenPairAmount { + pair: TokenPair(token0.clone(), token1.clone(), pair_param_2.clone()), + amount_0: Uint128::zero(), + amount_1: Uint128::one(), + }; + + let fixed = amount + .create_new_pair_amount_to_match_order_of(&pair_correct_order) + .unwrap(); + assert_eq!(fixed.pair.0, pair_correct_order.0); + assert_eq!(fixed.pair.1, pair_correct_order.1); + assert_eq!(fixed.pair.2, pair_param_2); + assert_eq!(fixed.amount_0, Uint128::zero()); + assert_eq!(fixed.amount_1, Uint128::one()); + + //test a non-matching pair + let broken_pair = TokenPair( + token0.clone(), + TokenType::CustomToken { + contract_addr: Addr::unchecked("token3".to_string()), + token_code_hash: "a".to_string(), + }, + false, + ); + assert!( + reverse_amount + .create_new_pair_amount_to_match_order_of(&broken_pair) + .is_err() + ); + } +} diff --git a/packages/shadeswap_shared/src/core/token_type.rs b/packages/shadeswap_shared/src/core/token_type.rs new file mode 100644 index 000000000..03bd16ad8 --- /dev/null +++ b/packages/shadeswap_shared/src/core/token_type.rs @@ -0,0 +1,15 @@ +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use shade_protocol::utils::liquidity_book::tokens::TokenType; + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct StableTokenData { + pub oracle_key: String, + pub decimals: u8, +} + +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] +pub struct StableTokenType { + pub token: TokenType, + pub stable_token_data: StableTokenData, +} diff --git a/packages/shadeswap_shared/src/core/viewing_keys.rs b/packages/shadeswap_shared/src/core/viewing_keys.rs new file mode 100644 index 000000000..ac408fceb --- /dev/null +++ b/packages/shadeswap_shared/src/core/viewing_keys.rs @@ -0,0 +1,72 @@ +use cosmwasm_std::{Binary, Env, MessageInfo}; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use std::fmt; +use subtle::ConstantTimeEq; + +use shade_protocol::utils::crypto::{sha_256, Prng}; + +pub const VIEWING_KEY_SIZE: usize = 32; +const VIEWING_KEY_PREFIX: &str = "api_key_"; + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug, PartialEq)] +pub struct ViewingKey(pub String); + +pub fn create_hashed_password(s1: &str) -> [u8; VIEWING_KEY_SIZE] { + sha_256(s1.as_bytes()) +} + +impl ViewingKey { + pub fn new(env: &Env, info: &MessageInfo, seed: &[u8], entropy: &[u8]) -> Self { + // 16 here represents the lengths in bytes of the block height and time. + let entropy_len = 16 + info.sender.to_string().len() + entropy.len(); + let mut rng_entropy = Vec::with_capacity(entropy_len); + rng_entropy.extend_from_slice(&env.block.height.to_be_bytes()); + rng_entropy.extend_from_slice(&env.block.time.nanos().to_be_bytes()); + rng_entropy.extend_from_slice(&info.sender.as_bytes()); + rng_entropy.extend_from_slice(entropy); + + let mut rng = Prng::new(seed, &rng_entropy); + + let rand_slice = rng.rand_bytes(); + + let key = sha_256(&rand_slice); + + Self(VIEWING_KEY_PREFIX.to_string() + &Binary::from(&key).to_base64()) + } + + pub fn to_hashed(&self) -> [u8; VIEWING_KEY_SIZE] { + create_hashed_password(&self.0) + } + + pub fn as_bytes(&self) -> &[u8] { + self.0.as_bytes() + } + + pub fn check_viewing_key(&self, hashed_pw: &[u8]) -> bool { + let mine_hashed = create_hashed_password(&self.0); + + bool::from(mine_hashed.ct_eq(hashed_pw)) + } +} + +impl fmt::Display for ViewingKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From<&str> for ViewingKey { + fn from(vk: &str) -> Self { + ViewingKey(vk.into()) + } +} + +pub fn create_viewing_key( + env: &Env, + info: &MessageInfo, + seed: Binary, + entroy: Binary, +) -> ViewingKey { + ViewingKey::new(&env, info, seed.as_slice(), entroy.as_slice()) +} diff --git a/packages/shadeswap_shared/src/helpers.rs b/packages/shadeswap_shared/src/helpers.rs new file mode 100644 index 000000000..eb50b215e --- /dev/null +++ b/packages/shadeswap_shared/src/helpers.rs @@ -0,0 +1,47 @@ +use cosmwasm_std::{from_binary, Addr, Deps, QuerierWrapper, StdError, StdResult}; +use serde::de::DeserializeOwned; +use shade_protocol::{ + query_auth::{self, helpers::PermitAuthentication, QueryPermit}, + utils::Query, + Contract, +}; + +pub fn authenticate_permit( + deps: Deps, + permit: QueryPermit, + querier: &QuerierWrapper, + authenticator: Option, +) -> StdResult> { + let sender: Addr; + let revoked: bool; + match authenticator { + Some(a) => { + let res: query_auth::QueryAnswer = query_auth::QueryMsg::ValidatePermit { + permit: permit.clone(), + } + .query(querier, &a)?; + + match res { + query_auth::QueryAnswer::ValidatePermit { user, is_revoked } => { + sender = user; + revoked = is_revoked; + } + _ => return Err(StdError::generic_err("Wrong query response")), + } + + Ok(PermitAuthentication { + sender, + revoked, + data: from_binary(&permit.params.data)?, + }) + } + None => { + sender = permit.validate(deps.api, None)?.as_addr(None)?; + Ok(PermitAuthentication { + sender, + revoked: false, + data: from_binary(&permit.params.data)?, + }) + } + } +} diff --git a/packages/shadeswap_shared/src/lib.rs b/packages/shadeswap_shared/src/lib.rs new file mode 100644 index 000000000..0dde33a7d --- /dev/null +++ b/packages/shadeswap_shared/src/lib.rs @@ -0,0 +1,20 @@ +mod helpers; +pub mod msg; +pub use msg::*; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +pub mod core; +// Forward important libs to avoid constantly importing them in the cargo crates, could help reduce compile times +pub mod c_std { + pub use cosmwasm_std::*; +} +pub use shade_protocol::{admin, airdrop, query_auth, snip20, utils}; +pub const BLOCK_SIZE: usize = 256; +pub use helpers::*; +pub use serde; + +#[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] +pub struct Pagination { + pub start: u64, + pub limit: u8, +} diff --git a/packages/shadeswap_shared/src/msg/mod.rs b/packages/shadeswap_shared/src/msg/mod.rs new file mode 100644 index 000000000..53a3851f6 --- /dev/null +++ b/packages/shadeswap_shared/src/msg/mod.rs @@ -0,0 +1,585 @@ +use crate::core::ContractInstantiationInfo; +use cosmwasm_std::Addr; +use cosmwasm_std::Binary; +use cosmwasm_std::Uint128; +use schemars::JsonSchema; +use serde::{Deserialize, Serialize}; +use shade_protocol::{ + utils::{ExecuteCallback, InstantiateCallback, Query}, + BLOCK_SIZE, +}; + +pub mod staking; + +pub mod router { + + use super::*; + use crate::core::TokenAmount; + use shade_protocol::{ + liquidity_book::lb_pair::SwapResult, snip20::Snip20ReceiveMsg, + utils::liquidity_book::tokens::TokenType, Contract, + }; + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + #[serde(rename_all = "snake_case")] + pub enum ExecuteMsgResponse { + SwapResult { + amount_in: Uint128, + amount_out: Uint128, + }, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + #[serde(rename_all = "snake_case")] + pub enum InvokeMsg { + SwapTokensForExact { + path: Vec, + expected_return: Option, + recipient: Option, + }, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + pub struct InitMsg { + pub prng_seed: Binary, + pub entropy: Binary, + pub admin_auth: Contract, + pub airdrop_address: Option, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + pub struct Hop { + pub addr: String, + pub code_hash: String, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + #[serde(rename_all = "snake_case")] + pub enum ExecuteMsg { + // SNIP20 receiver interface + Receive(Snip20ReceiveMsg), + SwapTokensForExact { + /// The token type to swap from. + offer: TokenAmount, + expected_return: Option, + path: Vec, + recipient: Option, + padding: Option, + }, + RegisterSNIP20Token { + token_addr: String, + token_code_hash: String, + oracle_key: Option, + padding: Option, + }, + RecoverFunds { + token: TokenType, + amount: Uint128, + to: String, + msg: Option, + padding: Option, + }, + SetConfig { + admin_auth: Option, + padding: Option, + }, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + #[serde(rename_all = "snake_case")] + pub enum QueryMsg { + SwapSimulation { + offer: TokenAmount, + path: Vec, + exclude_fee: Option, + }, + GetConfig {}, + RegisteredTokens {}, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + #[serde(rename_all = "snake_case")] + pub enum QueryMsgResponse { + SwapSimulation { + total_fee_amount: Uint128, + lp_fee_amount: Uint128, + shade_dao_fee_amount: Uint128, + result: SwapResult, + price: String, + }, + GetConfig { + admin_auth: Contract, + airdrop_address: Option, + }, + RegisteredTokens { + tokens: Vec, + }, + } + + impl InstantiateCallback for InitMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; + } + + impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; + } + + impl Query for QueryMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; + } +} + +pub mod amm_pair { + use std::fmt::{Debug, Display}; + + use super::*; + use crate::{ + core::{ + ContractInstantiationInfo, CustomFee, Fee, StableTokenData, TokenAmount, TokenPair, + TokenPairAmount, + }, + staking::StakingContractInstantiateInfo, + }; + use cosmwasm_std::{Addr, Decimal256, Uint256}; + use schemars::JsonSchema; + use serde::{Deserialize, Serialize}; + use shade_protocol::{ + liquidity_book::lb_pair::SwapResult, snip20::Snip20ReceiveMsg, utils::asset::RawContract, + utils::liquidity_book::tokens::TokenType, Contract, + }; + + /// Represents the address of an exchange and the pair that it manages + #[derive(Serialize, Deserialize, JsonSchema, Clone, PartialEq, Debug)] + pub struct AMMPair { + /// The pair that the contract manages. + pub pair: TokenPair, + /// Address of the contract that manages the exchange. + pub address: Addr, + // Code hash of the AMM Pair + pub code_hash: String, + /// Used to enable or disable the AMMPair + pub enabled: bool, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug, Clone, JsonSchema)] + #[serde(rename_all = "snake_case")] + pub enum ContractStatus { + Active, // allows all operations + FreezeAll, // blocks everything except admin-protected config changes + LpWithdrawOnly, // blocks everything except LP withdraws and admin-protected config changes + } + + impl Display for ContractStatus { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + Debug::fmt(self, f) + } + } + + #[derive(Clone, Debug, PartialEq, Deserialize, Serialize, JsonSchema)] + pub struct CustomIterationControls { + pub epsilon: Uint256, // assumed to have same decimals as SignedDecimal + pub max_iter_newton: u16, + pub max_iter_bisect: u16, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + pub struct StableParams { + pub a: Decimal256, + pub gamma1: Uint256, + pub gamma2: Uint256, + pub oracle: Contract, + pub min_trade_size_x_for_y: Decimal256, + pub min_trade_size_y_for_x: Decimal256, + pub max_price_impact_allowed: Decimal256, + pub custom_iteration_controls: Option, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + pub struct StablePairInfoResponse { + pub stable_params: StableParams, + pub stable_token0_data: StableTokenData, + pub stable_token1_data: StableTokenData, + //p is optional so that the PairInfo query can still return even when the calculation of p fails + pub p: Option, + } + + #[derive(Serialize, Deserialize, JsonSchema, PartialEq, Debug, Clone)] + pub struct AMMSettings { + pub lp_fee: Fee, + pub shade_dao_fee: Fee, + pub stable_lp_fee: Fee, + pub stable_shade_dao_fee: Fee, + pub shade_dao_address: Contract, + } + + pub fn generate_pair_key(pair: &TokenPair) -> Vec { + let mut bytes: Vec<&[u8]> = Vec::new(); + let mut values: Vec = Vec::new(); + + values.push(pair.0.unique_key()); + values.push(pair.1.unique_key()); + values.push(pair.2.to_string()); + values.sort(); + bytes.push(values[0].as_bytes()); + bytes.push(values[1].as_bytes()); + bytes.push(values[2].as_bytes()); + bytes.concat() + } + + #[derive(Serialize, Deserialize, PartialEq, Debug, JsonSchema, Clone)] + pub struct SwapInfo { + pub total_fee_amount: Uint128, + pub lp_fee_amount: Uint128, + pub shade_dao_fee_amount: Uint128, + pub result: SwapResult, + pub price: String, + pub new_input_pool: Uint128, + pub new_output_pool: Uint128, + pub index_of_input_token: u8, + pub index_of_output_token: u8, + } + + pub struct VirtualSwapResponse { + pub output: TokenPairAmount, + pub swap_info: Option, + } + + #[derive(Serialize, Deserialize, PartialEq, Clone, Debug, JsonSchema)] + pub struct FeeInfo { + pub shade_dao_address: Addr, + pub lp_fee: Fee, + pub shade_dao_fee: Fee, + pub stable_lp_fee: Fee, + pub stable_shade_dao_fee: Fee, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + pub struct InitMsg { + pub pair: TokenPair, + pub token0_oracle_key: Option, + pub token1_oracle_key: Option, + pub lp_token_contract: ContractInstantiationInfo, + // Leave none if initializing without factory + pub factory_info: Option, + pub prng_seed: Binary, + pub entropy: Binary, + pub admin_auth: Contract, + pub staking_contract: Option, + pub custom_fee: Option, + pub stable_params: Option, + pub lp_token_decimals: u8, + pub lp_token_custom_label: Option, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + #[serde(rename_all = "snake_case")] + pub enum ExecuteMsg { + AddLiquidityToAMMContract { + deposit: TokenPairAmount, + expected_return: Option, + staking: Option, + execute_sslp_virtual_swap: Option, + padding: Option, + }, + SwapTokens { + /// The token type to swap from. + offer: TokenAmount, + expected_return: Option, + to: Option, + padding: Option, + }, + // SNIP20 receiver interface + Receive(Snip20ReceiveMsg), + AddWhiteListAddress { + address: String, + padding: Option, + }, + RemoveWhitelistAddresses { + addresses: Vec, + padding: Option, + }, + SetConfig { + admin_auth: Option, + padding: Option, + }, + SetStableParams { + stable_params: Option, + padding: Option, + }, + SetCustomPairFee { + custom_fee: Option, + padding: Option, + }, + SetViewingKey { + viewing_key: String, + padding: Option, + }, + SetOracleKeyAndDecimals { + token: TokenType, + oracle_key: String, + padding: Option, + }, + SetStakingContract { + staking: RawContract, + padding: Option, + }, + SetContractStatus { + contract_status: ContractStatus, + padding: Option, + }, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + #[serde(rename_all = "snake_case")] + pub enum ExecuteMsgResponse { + SwapResult { + price: String, + amount_in: Uint128, + amount_out: Uint128, + total_fee_amount: Uint128, + lp_fee_amount: Uint128, + shade_dao_fee_amount: Uint128, + }, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + #[serde(rename_all = "snake_case")] + pub enum InvokeMsg { + SwapTokens { + expected_return: Option, + to: Option, + padding: Option, + }, + RemoveLiquidity { + /// If sender is removing LP for someone else, from should contain who that someone is + from: Option, + single_sided_withdraw_type: Option, //None means 50/50 balanced withdraw, and a value here tells which token to send the withdraw in + single_sided_expected_return: Option, //this field will be ignored on balanced withdraws + padding: Option, + }, + } + #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] + #[serde(rename_all = "snake_case")] + pub enum QueryMsg { + GetConfig {}, + GetPairInfo {}, + GetWhiteListAddress {}, + GetTradeCount {}, + SwapSimulation { + offer: TokenAmount, + exclude_fee: Option, + }, + GetShadeDaoInfo {}, + GetEstimatedLiquidity { + deposit: TokenPairAmount, + sender: Addr, + execute_sslp_virtual_swap: Option, + }, + GetContractStatus {}, + } + + #[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq)] + #[serde(rename_all = "snake_case")] + pub enum QueryMsgResponse { + GetPairInfo { + liquidity_token: Contract, + factory: Option, + pair: TokenPair, + amount_0: Uint128, + amount_1: Uint128, + total_liquidity: Uint128, + contract_version: u32, + fee_info: FeeInfo, + stable_info: Option, + }, + GetWhiteListAddress { + addresses: Vec, + }, + GetTradeCount { + count: u64, + }, + GetClaimReward { + amount: Uint128, + }, + GetEstimatedPrice { + estimated_price: String, + }, + SwapSimulation { + total_fee_amount: Uint128, + lp_fee_amount: Uint128, + shade_dao_fee_amount: Uint128, + result: SwapResult, + price: String, + }, + GetShadeDaoInfo { + shade_dao_address: String, + shade_dao_fee: Fee, + lp_fee: Fee, + admin_auth: Contract, + }, + GetEstimatedLiquidity { + lp_token: Uint128, + tokens_returned: Option, + total_lp_token: Uint128, + }, + GetConfig { + factory_contract: Option, + lp_token: Contract, + staking_contract: Option, + pair: TokenPair, + custom_fee: Option, + }, + GetContractStatus { + contract_status: ContractStatus, + }, + } + + impl InstantiateCallback for InitMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; + } + + impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; + } + + impl Query for QueryMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; + } +} + +pub mod factory { + use super::*; + use crate::amm_pair::{AMMPair, StableParams}; + use crate::core::TokenPair; + use crate::staking::StakingContractInstantiateInfo; + use crate::{amm_pair::AMMSettings, Pagination}; + use schemars::JsonSchema; + use serde::{Deserialize, Serialize}; + use shade_protocol::Contract; + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + pub struct InitMsg { + pub pair_contract: ContractInstantiationInfo, + pub amm_settings: AMMSettings, + pub lp_token_contract: ContractInstantiationInfo, + pub prng_seed: Binary, + pub api_key: String, + //Set the default authenticator for all permits on the contracts + pub authenticator: Option, + pub admin_auth: Contract, + } + + #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] + #[serde(rename_all = "snake_case")] + pub enum ExecuteMsg { + SetConfig { + pair_contract: Option, + lp_token_contract: Option, + amm_settings: Option, + api_key: Option, + admin_auth: Option, + padding: Option, + }, + CreateAMMPair { + pair: TokenPair, + entropy: Binary, + staking_contract: Option, + stable_params: Option, + token0_oracle_key: Option, + token1_oracle_key: Option, + lp_token_decimals: u8, + amm_pair_custom_label: Option, + lp_token_custom_label: Option, + padding: Option, + }, + AddAMMPairs { + amm_pairs: Vec, + padding: Option, + }, + } + + #[derive(Serialize, Deserialize, Debug, JsonSchema, PartialEq)] + #[serde(rename_all = "snake_case")] + pub enum QueryResponse { + ListAMMPairs { + amm_pairs: Vec, + }, + GetConfig { + pair_contract: ContractInstantiationInfo, + amm_settings: AMMSettings, + lp_token_contract: ContractInstantiationInfo, + authenticator: Option, + admin_auth: Contract, + }, + GetAMMPairAddress { + address: String, + }, + AuthorizeApiKey { + authorized: bool, + }, + } + + #[derive(Serialize, Deserialize, JsonSchema, Clone, Debug)] + #[serde(rename_all = "snake_case")] + pub enum QueryMsg { + // GetCount returns the current count as a json-encoded number + ListAMMPairs { pagination: Pagination }, + GetAMMPairAddress { pair: TokenPair }, + GetConfig {}, + AuthorizeApiKey { api_key: String }, + } + + impl InstantiateCallback for InitMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; + } + + impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; + } + + impl Query for QueryMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; + } +} + +pub mod lp_token { + + use shade_protocol::contract_interfaces::snip20::InitialBalance; + + use super::*; + + #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] + pub struct InitConfig { + /// Indicates whether the total supply is public or should be kept secret. + /// default: False + pub public_total_supply: Option, + /// Indicates whether deposit functionality should be enabled + /// default: False + pub enable_deposit: Option, + /// Indicates whether redeem functionality should be enabled + /// default: False + pub enable_redeem: Option, + /// Indicates whether mint functionality should be enabled + /// default: False + pub enable_mint: Option, + /// Indicates whether burn functionality should be enabled + /// default: False + pub enable_burn: Option, + } + + #[derive(Serialize, Deserialize, Clone, Debug, JsonSchema)] + pub struct InstantiateMsg { + pub name: String, + pub admin: Option, + pub symbol: String, + pub decimals: u8, + pub initial_balances: Option>, + pub prng_seed: Binary, + pub config: Option, + pub supported_denoms: Option>, + } + + impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; + } +} diff --git a/packages/shadeswap_shared/src/msg/staking.rs b/packages/shadeswap_shared/src/msg/staking.rs new file mode 100644 index 000000000..9536d9e26 --- /dev/null +++ b/packages/shadeswap_shared/src/msg/staking.rs @@ -0,0 +1,668 @@ +use crate::{core::ContractInstantiationInfo, BLOCK_SIZE}; +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::{Addr, Binary, Uint128, Uint256}; +use shade_protocol::{ + query_auth::QueryPermit, + snip20::Snip20ReceiveMsg, + utils::liquidity_book::tokens::TokenType, + utils::{asset::RawContract, ExecuteCallback, InstantiateCallback, Query}, + Contract, +}; +#[cfg(feature = "staking")] +pub use state::*; + +impl InstantiateCallback for InstantiateMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; +} + +impl ExecuteCallback for ExecuteMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; +} + +impl Query for QueryMsg { + const BLOCK_SIZE: usize = BLOCK_SIZE; +} + +#[cw_serde] +pub struct StakingContractInstantiateInfo { + pub staking_contract_info: ContractInstantiationInfo, + pub custom_label: Option, + pub first_reward_token: Option, + pub query_auth: Option, +} + +#[cw_serde] +pub struct InstantiateMsg { + pub amm_pair: String, + pub lp_token: RawContract, + pub admin_auth: RawContract, + pub query_auth: Option, + pub first_reward_token: Option, +} + +#[cw_serde] +pub enum ExecuteMsg { + ClaimRewards { + padding: Option, + }, + Unstake { + amount: Uint128, + remove_liquidity: Option, + padding: Option, + }, + Receive(Snip20ReceiveMsg), + UpdateRewardTokens(Vec), + CreateRewardTokens(Vec), + UpdateConfig { + admin_auth: Option, + query_auth: Option, + padding: Option, + }, + RecoverFunds { + token: TokenType, + amount: Uint128, + to: String, + msg: Option, + padding: Option, + }, +} + +#[cw_serde] +pub enum InvokeMsg { + /// From is used to determine the staker since this can be called by the AMMPair when auto staking. + Stake { + from: Option, + padding: Option, + }, +} + +#[cw_serde] +pub struct RewardTokenUpdate { + pub reward_token: RawContract, + pub index: u64, + pub valid_to: u64, +} + +#[cw_serde] +pub struct RewardTokenCreate { + pub reward_token: RawContract, + pub daily_reward_amount: Uint128, + pub valid_to: u64, +} + +#[allow(clippy::large_enum_variant)] +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(ConfigResponse)] + GetConfig {}, + #[returns(PermitQueryResponse)] + WithPermit { + permit: QueryPermit, + query: AuthQuery, + }, +} + +#[cw_serde] +pub struct QueryPermitData {} + +#[cw_serde] +pub enum AuthQuery { + GetStakerInfo {}, +} + +#[derive(PartialEq, Debug, Clone)] +pub struct ClaimRewardResponse { + pub token: Contract, + pub amount: Uint128, +} + +// RESPONSE TYPES + +#[cw_serde] +pub struct ConfigResponse { + pub lp_token: Contract, + pub amm_pair: Addr, + pub admin_auth: Contract, + pub query_auth: Option, + pub total_amount_staked: Uint128, + pub reward_tokens: Vec, +} + +#[cw_serde] +pub enum PermitQueryResponse { + StakerInfo { + /// Amount normally staked. + staked: Uint128, + /// Staked + total_staked: Uint128, + claimable_rewards: Vec, + }, +} + +#[cw_serde] +pub struct ClaimableRewardsResponse { + pub token: Contract, + pub amount: Uint128, +} + +#[cw_serde] +pub struct RewardTokenInfo { + pub token: Contract, + pub decimals: u8, + pub reward_per_second: Uint256, + pub reward_per_staked_token: Uint256, + pub valid_to: u64, + pub last_updated: u64, +} + +impl RewardTokenUpdate { + pub fn new(reward_token: impl Into, index: u64, valid_to: u64) -> Self { + Self { + reward_token: reward_token.into(), + index, + valid_to, + } + } +} + +impl RewardTokenCreate { + pub fn new( + reward_token: impl Into, + daily_reward_amount: Uint128, + valid_to: u64, + ) -> Self { + Self { + reward_token: reward_token.into(), + daily_reward_amount, + valid_to, + } + } +} + +#[cfg(feature = "staking")] +pub mod state { + use better_secret_math::{ + common::{bankers_round, exp10, muldiv}, + ud60x18::mul, + U256, + }; + use core::time; + use cosmwasm_schema::cw_serde; + use cosmwasm_std::{ + Addr, CosmosMsg, Decimal256, OverflowError, QuerierWrapper, StdError, StdResult, Storage, + Uint128, Uint256, + }; + use secret_storage_plus::{Bincode2, Item, ItemStorage, Map}; + use shade_protocol::{ + contract_interfaces::snip20::ExecuteMsg as Snip20ExecuteMsg, utils::ExecuteCallback, + Contract, + }; + use std::{cmp::min, collections::HashMap}; + + use super::{ + ClaimRewardResponse, ClaimableRewardsResponse, ConfigResponse, PermitQueryResponse, + RewardTokenInfo, + }; + + /// Manages the global state of the staking contract. + #[cw_serde] + pub struct Custodian { + pub lp_token: Contract, + pub amm_pair: Addr, + pub admin_auth: Contract, + pub query_auth: Option, + pub total_amount_staked: Uint128, + } + + impl ItemStorage for Custodian { + const ITEM: Item<'static, Self> = Item::new("custodian"); + } + + #[cw_serde] + pub struct RewardTokenSet(Vec); + + impl RewardTokenSet { + pub fn insert(&mut self, addr: &Addr) { + if !self.0.contains(addr) { + self.0.push(addr.clone()); + } + } + pub fn get(&self) -> &[Addr] { + self.0.as_slice() + } + } + + impl<'a> Custodian { + pub const STAKERS: Map<'static, &'a Addr, u128, Bincode2> = Map::new("stakers"); + pub const REWARD_TOKEN_INFO: Map<'static, &'a Addr, Vec> = + Map::new("reward_token_info"); + pub const REWARD_TOKENS: Item<'static, RewardTokenSet> = Item::new("reward_tokens"); + } + + impl Custodian { + pub fn require_lp_token(&self, addr: &Addr) -> StdResult<()> { + if self.lp_token.address.eq(addr) { + return Ok(()); + } + Err(StdError::generic_err(format!( + "Must stake the LP token {}. Attempted to stake {addr}.", + self.lp_token.address + ))) + } + pub fn save_staker(storage: &mut dyn Storage, staker: &Staker) -> StdResult<()> { + staker.save_rewards(storage)?; + Self::STAKERS.save(storage, &staker.addr, &staker.staked.u128()) + } + } + + impl Custodian { + pub fn store_empty_reward_set(&self, storage: &mut dyn Storage) -> StdResult<()> { + match Self::REWARD_TOKENS.may_load(storage)? { + Some(_) => Err(StdError::generic_err("Reward token storage already exists")), + None => Self::REWARD_TOKENS.save(storage, &RewardTokenSet(vec![])), + } + } + + pub fn update_reward_token( + &self, + now: u64, + storage: &mut dyn Storage, + token: &Contract, + index: u64, + valid_to: u64, + ) -> StdResult> { + if valid_to < now { + return Err(StdError::generic_err("valid_to cannot be in the past")); + } + let mut reward_configs = Self::REWARD_TOKEN_INFO.load(storage, &token.address)?; + match reward_configs.get_mut(index as usize) { + Some(info) => { + info.valid_to = valid_to; + } + None => return Err(StdError::generic_err("Invalid index")), + }; + Self::REWARD_TOKEN_INFO.save(storage, &token.address, &reward_configs)?; + Ok(reward_configs) + } + + pub fn create_reward_token( + &self, + storage: &mut dyn Storage, + now: u64, + token: &Contract, + daily_emission_amount: Uint128, + valid_to: u64, + decimals: u8, + ) -> StdResult> { + let mut reward_configs = + match Self::REWARD_TOKEN_INFO.may_load(storage, &token.address)? { + Some(rewards) => rewards, + None => vec![], + }; + let info = RewardTokenInfo::init_from_daily_rewards( + now, + token, + decimals, + daily_emission_amount, + valid_to, + )?; + match Self::REWARD_TOKENS.may_load(storage)? { + Some(mut tokens) => { + tokens.insert(&info.token.address); + Self::REWARD_TOKENS.save(storage, &tokens)?; + } + None => Self::REWARD_TOKENS + .save(storage, &RewardTokenSet(vec![info.token.address.clone()]))?, + }; + reward_configs.push(info); + Self::REWARD_TOKEN_INFO.save(storage, &token.address, &reward_configs)?; + Ok(reward_configs) + } + + pub fn to_config_response(self, storage: &dyn Storage) -> StdResult { + let tokens = Self::REWARD_TOKENS.load(storage)?; + let mut infos = Vec::with_capacity(tokens.0.len()); + for token in tokens.0 { + if let Some(reward_configs) = Self::REWARD_TOKEN_INFO.may_load(storage, &token)? { + for info in reward_configs { + let info = info.to_response()?; + infos.push(info); + } + } + } + Ok(ConfigResponse { + lp_token: self.lp_token, + amm_pair: self.amm_pair, + admin_auth: self.admin_auth, + query_auth: self.query_auth, + reward_tokens: infos, + total_amount_staked: self.total_amount_staked, + }) + } + + pub fn update_reward_per_token( + &self, + now: u64, + info: &mut RewardTokenInfo, + ) -> StdResult { + info.update_reward_per_token(now, self.total_amount_staked) + } + + pub fn may_load_staker(storage: &dyn Storage, user: &Addr) -> StdResult> { + if let Some(staked) = Self::STAKERS.may_load(storage, user)? { + Ok(Some(Staker { + addr: user.clone(), + staked: Uint128::new(staked), + claimable_rewards: HashMap::default(), + })) + } else { + Ok(None) + } + } + } + + impl RewardTokenInfo { + pub const SECONDS_IN_DAY: U256 = U256::new(24u128 * 3600u128); + pub const MAX_DECIMALS: U256 = exp10(18); + + pub fn normalize_amount(amount: impl Into) -> StdResult { + let amount: U256 = amount.into(); + amount + .checked_mul(Self::MAX_DECIMALS) + .ok_or_else(|| StdError::generic_err("Overflow")) + } + + pub fn denormalize_amount(amount: impl Into) -> StdResult { + let amount: U256 = amount.into(); + amount + .checked_div(Self::MAX_DECIMALS) + .ok_or_else(|| StdError::generic_err("Overflow")) + } + + pub fn init_from_daily_rewards( + now: u64, + token: &Contract, + decimals: u8, + daily_emission_amount: Uint128, + valid_to: u64, + ) -> StdResult { + let daily_emission_amount = Self::normalize_amount(daily_emission_amount)?; + Ok(Self { + token: token.clone(), + decimals, + reward_per_second: (daily_emission_amount / Self::SECONDS_IN_DAY).into(), + valid_to, + reward_per_staked_token: Uint256::zero(), + last_updated: now, + }) + } + + pub fn update_reward_per_second( + &mut self, + daily_emission_amount: Uint128, + ) -> StdResult<()> { + let daily_emission_amount = Self::normalize_amount(daily_emission_amount)?; + self.reward_per_second = (daily_emission_amount / Self::SECONDS_IN_DAY).into(); + Ok(()) + } + + /// Denormalizes the reward per second and reward per token stored. + pub fn to_response(&self) -> StdResult { + Ok(RewardTokenInfo { + token: self.token.clone(), + decimals: self.decimals, + reward_per_second: Self::denormalize_amount(self.reward_per_second)?.into(), + valid_to: self.valid_to, + reward_per_staked_token: Self::denormalize_amount(self.reward_per_staked_token)? + .into(), + last_updated: self.last_updated, + }) + } + + /// recalculates reward per staked token + pub fn update_reward_per_token( + &mut self, + now: u64, + total_staked: Uint128, + ) -> StdResult { + let min_time_rewards_applicable = min(now, self.valid_to); + if !total_staked.is_zero() && min_time_rewards_applicable > self.last_updated { + let time_since_updated = + U256::new((min_time_rewards_applicable - self.last_updated).into()); + let total_staked = U256::new(total_staked.u128()); + let rewards_since_updated = muldiv( + time_since_updated, + self.reward_per_second.into(), + total_staked, + )?; + self.reward_per_staked_token = self + .reward_per_staked_token + .checked_add(rewards_since_updated.into())?; + } + self.last_updated = min_time_rewards_applicable; + Ok(self.reward_per_staked_token) + } + } + + #[cw_serde] + pub struct Staker { + pub addr: Addr, + pub staked: Uint128, + pub claimable_rewards: HashMap>, + } + + impl Staker { + pub fn new(addr: &Addr) -> Self { + Self { + addr: addr.clone(), + staked: Uint128::zero(), + claimable_rewards: HashMap::default(), + } + } + } + + #[cw_serde] + pub struct ClaimableRewardsInfo { + pub info: RewardTokenInfo, + pub amount: Uint128, + pub last_reward_per_staked_token_paid: Uint256, + } + + impl ClaimableRewardsInfo { + pub fn new(info: &RewardTokenInfo) -> Self { + Self { + info: info.clone(), + amount: Uint128::zero(), + last_reward_per_staked_token_paid: Uint256::zero(), + } + } + } + + impl<'a> Staker { + pub const REWARDS: Map<'static, (&'a Addr, &'a Addr), Vec> = + Map::new("staker_rewards"); + } + + impl Staker { + pub fn get_rewards_key<'a>(&'a self, reward_token: &'a Addr) -> (&'a Addr, &'a Addr) { + (reward_token, &self.addr) + } + + pub fn total_staked(&self) -> Uint128 { + self.staked + } + + pub fn stake( + &mut self, + storage: &mut dyn Storage, + amount: impl Into + Copy, + ) -> StdResult { + let amount = amount.into(); + self.staked = self.staked.checked_add(amount)?; + Ok(amount) + } + + pub fn unstake( + &mut self, + storage: &mut dyn Storage, + amount: impl Into + Copy, + ) -> StdResult { + let amount = amount.into(); + self.staked = self.staked.checked_sub(amount)?; + Ok(amount) + } + + //called once per reward token type, loads data from storage into staker object, updating claimable amounts + pub fn update_claimable_rewards( + &mut self, + storage: &dyn Storage, + reward_infos: &Vec, + reward_token_address: Addr, + ) -> StdResult<()> { + let rewards_key = self.get_rewards_key(&reward_token_address); + let mut claimable_rewards = match Self::REWARDS.may_load(storage, rewards_key)? { + Some(data) => data, + None => vec![], + }; + + for i in claimable_rewards.len()..reward_infos.len() { + claimable_rewards.push(ClaimableRewardsInfo::new(&reward_infos[i])) + } + if claimable_rewards.len() != reward_infos.len() { + return Err(StdError::generic_err( + "Off by one error in reward list padding", + )); + } + for (reward_info, mut claimable_reward) in + reward_infos.into_iter().zip(claimable_rewards.into_iter()) + { + if reward_info.token.address != reward_token_address { + return Err(StdError::generic_err( + "Update claimable rewards bad reward token address", + )); + } + if reward_info.reward_per_staked_token + > claimable_reward.last_reward_per_staked_token_paid + { + let reward_per_staked_token_earned = reward_info.reward_per_staked_token + - claimable_reward.last_reward_per_staked_token_paid; + let normalized_amount_earned = + reward_per_staked_token_earned.checked_mul(self.total_staked().into())?; + let reward_amount_earned = + RewardTokenInfo::denormalize_amount(normalized_amount_earned)?; + claimable_reward.amount = claimable_reward + .amount + .checked_add(reward_amount_earned.into())?; + claimable_reward.last_reward_per_staked_token_paid = + reward_info.reward_per_staked_token; + } + + //load update claimable reward into object + match self.claimable_rewards.get_mut(&reward_info.token.address) { + Some(list) => list.push(claimable_reward.clone()), + None => { + let list = vec![claimable_reward.clone()]; + self.claimable_rewards + .insert(reward_info.token.address.clone(), list); + } + } + } + Ok(()) + } + + /// write data from staker object to contract storage + pub fn save_rewards(&self, storage: &mut dyn Storage) -> StdResult<()> { + for reward in &self.claimable_rewards { + let rewards_key = self.get_rewards_key(&reward.0); + Self::REWARDS.save(storage, rewards_key, &reward.1)?; + } + Ok(()) + } + + /// send all claimable rewards to staker and save 0 into claimable rewards storage + pub fn claim_and_save_rewards( + &self, + storage: &mut dyn Storage, + ) -> StdResult<(Vec, Vec)> { + let mut response_data = vec![]; + let mut msgs = vec![]; + for rewards in self.claimable_rewards.iter() { + let mut claimable_rewards = vec![]; + for reward in rewards.1 { + if !reward.amount.is_zero() { + response_data.push(ClaimRewardResponse { + token: reward.info.token.clone(), + amount: reward.amount, + }); + msgs.push( + Snip20ExecuteMsg::Send { + recipient: self.addr.to_string(), + recipient_code_hash: None, + amount: reward.amount, + msg: None, + memo: None, + padding: None, + } + .to_cosmos_msg(&reward.info.token, vec![])?, + ); + } + claimable_rewards.push(ClaimableRewardsInfo { + info: reward.info.clone(), + amount: Uint128::zero(), + last_reward_per_staked_token_paid: reward.last_reward_per_staked_token_paid, + }); + } + + let rewards_key = self.get_rewards_key(rewards.0); + Self::REWARDS.save(storage, rewards_key, &claimable_rewards)?; + } + Ok((msgs, response_data)) + } + + pub fn to_staker_info_response(self) -> StdResult { + let mut all_rewards = vec![]; + for (_, rewards) in &self.claimable_rewards { + all_rewards.append( + &mut rewards + .into_iter() + .map(|r| ClaimableRewardsResponse { + token: r.info.token.clone(), + amount: r.amount, + }) + .collect(), + ); + } + Ok(PermitQueryResponse::StakerInfo { + staked: self.staked, + total_staked: self.total_staked(), + claimable_rewards: all_rewards, + }) + } + } + + #[cfg(test)] + mod test { + use super::*; + + #[cfg(feature = "staking")] + #[test] + fn test_max_emission_rate_no_panic() { + let high_emissions = Uint128::new(exp10(77).as_u128()); + let info = RewardTokenInfo::init_from_daily_rewards( + 1u64, + &Contract::default(), + 18u8, + high_emissions, + 1u64, + ) + .unwrap(); + let info = info.to_response().unwrap(); + let rps: Uint128 = info.reward_per_second.try_into().unwrap(); + assert_eq!( + rps, + high_emissions / Uint128::new(RewardTokenInfo::SECONDS_IN_DAY.as_u128()) + ); + } + } +}