From 6826def7568d7e961db628da1b4f7fc845181aec Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 5 Oct 2023 14:55:18 +0200 Subject: [PATCH 1/9] Dex: Use custom queries CoreumQueries from coreum-wasm-sdk to allow querying smart token balances --- Cargo.lock | 14 ++++++++++++++ Cargo.toml | 1 + contracts/pair/Cargo.toml | 1 + contracts/pair/src/contract.rs | 9 ++++++--- packages/dex/Cargo.toml | 1 + packages/dex/src/asset.rs | 23 ++++++++++++----------- packages/dex/src/pair.rs | 5 +++-- packages/dex/src/querier.rs | 19 ++++++++++++++----- 8 files changed, 52 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 85e7808..b975352 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -133,6 +133,18 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28c122c3980598d243d63d9a704629a2d748d101f278052ff068be5a4423ab6f" +[[package]] +name = "coreum-wasm-sdk" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77718ec05e1521bf2fd3f7bceb60c77dfbc882408cd99c106a36ba408b0344f9" +dependencies = [ + "cosmwasm-schema", + "cosmwasm-std", + "schemars", + "serde", +] + [[package]] name = "cosmwasm-crypto" version = "1.4.0" @@ -386,6 +398,7 @@ dependencies = [ name = "dex" version = "0.1.0" dependencies = [ + "coreum-wasm-sdk", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus", @@ -401,6 +414,7 @@ dependencies = [ name = "dex-pair" version = "0.1.0" dependencies = [ + "coreum-wasm-sdk", "cosmwasm-schema", "cosmwasm-std", "cw-multi-test", diff --git a/Cargo.toml b/Cargo.toml index 3dd6032..fb323a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ repository = "https://github.com/WHELP-project/whelp-contracts" [workspace.dependencies] anyhow = "1" cw20-base = { version = "1.1", package = "cw20-base", features = ["library"] } +coreum-wasm-sdk = "0.1.1" cosmwasm-schema = "1.4" cosmwasm-std = "1.4" cw2 = "1.1" diff --git a/contracts/pair/Cargo.toml b/contracts/pair/Cargo.toml index 63800b0..4463833 100644 --- a/contracts/pair/Cargo.toml +++ b/contracts/pair/Cargo.toml @@ -15,6 +15,7 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] +coreum-wasm-sdk = { workspace = true } cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } cw2 = { workspace = true } diff --git a/contracts/pair/src/contract.rs b/contracts/pair/src/contract.rs index 448a21d..cac4899 100644 --- a/contracts/pair/src/contract.rs +++ b/contracts/pair/src/contract.rs @@ -1,5 +1,7 @@ -use crate::state::{Config, CIRCUIT_BREAKER, CONFIG, FROZEN}; +use std::str::FromStr; +use std::vec; +use coreum_wasm_sdk::{assetft, core::CoreumQueries}; use cosmwasm_std::{ attr, ensure, entry_point, from_binary, to_binary, Addr, Binary, CosmosMsg, Decimal, Decimal256, Deps, DepsMut, Env, Isqrt, MessageInfo, QuerierWrapper, Reply, Response, StdError, @@ -8,6 +10,7 @@ use cosmwasm_std::{ use cw2::set_contract_version; use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; + use dex::asset::{ addr_opt_validate, check_swap_parameters, Asset, AssetInfoValidated, AssetValidated, MINIMUM_LIQUIDITY_AMOUNT, @@ -26,8 +29,8 @@ use dex::pair::{ ReverseSimulationResponse, SimulationResponse, TWAP_PRECISION, }; use dex::querier::{query_factory_config, query_supply}; -use std::str::FromStr; -use std::vec; + +use crate::state::{Config, CIRCUIT_BREAKER, CONFIG, FROZEN}; /// Contract name that is used for migration. const CONTRACT_NAME: &str = "dex-pair"; diff --git a/packages/dex/Cargo.toml b/packages/dex/Cargo.toml index b111513..1087dd0 100644 --- a/packages/dex/Cargo.toml +++ b/packages/dex/Cargo.toml @@ -14,6 +14,7 @@ backtraces = ["cosmwasm-std/backtraces"] [dependencies] thiserror = { workspace = true } +coreum-wasm-sdk = { workspace = true } cw20 = { workspace = true } cw20-base = { workspace = true } cw-utils = { workspace = true } diff --git a/packages/dex/src/asset.rs b/packages/dex/src/asset.rs index 35c18e7..2aa07eb 100644 --- a/packages/dex/src/asset.rs +++ b/packages/dex/src/asset.rs @@ -1,19 +1,20 @@ +use itertools::Itertools; +use std::fmt; + +use coreum_wasm_sdk::core::CoreumQueries; use cosmwasm_schema::cw_serde; -use cosmwasm_std::Env; +use cosmwasm_std::{ + to_binary, Addr, Api, BankMsg, Coin, ConversionOverflowError, CosmosMsg, Decimal256, Env, + Fraction, MessageInfo, QuerierWrapper, StdError, StdResult, Uint128, Uint256, WasmMsg, +}; +use cw20::{Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse, TokenInfoResponse}; use cw_storage_plus::{Key, KeyDeserialize, Prefixer, PrimaryKey}; -use std::fmt; use crate::pair::PairInfo; use crate::pair::QueryMsg as PairQueryMsg; use crate::querier::{ query_balance, query_token_balance, query_token_symbol, NATIVE_TOKEN_PRECISION, }; -use cosmwasm_std::{ - to_binary, Addr, Api, BankMsg, Coin, ConversionOverflowError, CosmosMsg, Decimal256, Fraction, - MessageInfo, QuerierWrapper, StdError, StdResult, Uint128, Uint256, WasmMsg, -}; -use cw20::{Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse, TokenInfoResponse}; -use itertools::Itertools; /// Minimum initial LP share pub const MINIMUM_LIQUIDITY_AMOUNT: Uint128 = Uint128::new(1_000); @@ -207,7 +208,7 @@ impl AssetInfo { } pub fn query_pool( &self, - querier: &QuerierWrapper, + querier: &QuerierWrapper, pool_addr: impl Into, ) -> StdResult { match self { @@ -292,7 +293,7 @@ impl AssetInfoValidated { /// * **account_addr** is the address whose token balance we check. pub fn query_balance( &self, - querier: &QuerierWrapper, + querier: &QuerierWrapper, account_addr: impl Into, ) -> StdResult { match self { @@ -304,7 +305,7 @@ impl AssetInfoValidated { } /// Returns the number of decimals that a token has. - pub fn decimals(&self, querier: &QuerierWrapper) -> StdResult { + pub fn decimals(&self, querier: &QuerierWrapper) -> StdResult { let decimals = match &self { AssetInfoValidated::Native { .. } => NATIVE_TOKEN_PRECISION, AssetInfoValidated::Token(contract_addr) => { diff --git a/packages/dex/src/pair.rs b/packages/dex/src/pair.rs index 4f69e58..69b5d59 100644 --- a/packages/dex/src/pair.rs +++ b/packages/dex/src/pair.rs @@ -7,6 +7,7 @@ use crate::{ oracle::{SamplePeriod, TwapResponse}, }; +use coreum_wasm_sdk::core::CoreumQueries; use cosmwasm_std::{ to_binary, Addr, Binary, Decimal, Decimal256, QuerierWrapper, StdError, StdResult, Uint128, WasmMsg, @@ -53,7 +54,7 @@ impl PairInfo { /// * **contract_addr** is pair's pool address. pub fn query_pools( &self, - querier: &QuerierWrapper, + querier: &QuerierWrapper, contract_addr: impl Into, ) -> StdResult> { let contract_addr = contract_addr.into(); @@ -73,7 +74,7 @@ impl PairInfo { /// * **contract_addr** is pair's pool address. pub fn query_pools_decimal( &self, - querier: &QuerierWrapper, + querier: &QuerierWrapper, contract_addr: impl Into, ) -> StdResult> { let contract_addr = contract_addr.into(); diff --git a/packages/dex/src/querier.rs b/packages/dex/src/querier.rs index fae5f30..6dc776e 100644 --- a/packages/dex/src/querier.rs +++ b/packages/dex/src/querier.rs @@ -7,6 +7,7 @@ use crate::pair::{ PairInfo, QueryMsg as PairQueryMsg, ReverseSimulationResponse, SimulationResponse, }; +use coreum_wasm_sdk::{assetft, core::CoreumQueries}; use cosmwasm_std::{ Addr, AllBalanceResponse, BankQuery, Coin, Decimal, QuerierWrapper, QueryRequest, StdResult, Uint128, @@ -21,13 +22,21 @@ pub const NATIVE_TOKEN_PRECISION: u8 = 6; /// /// * **denom** specifies the denomination used to return the balance (e.g uluna). pub fn query_balance( - querier: &QuerierWrapper, + querier: &QuerierWrapper, account_addr: impl Into, denom: impl Into, ) -> StdResult { - querier - .query_balance(account_addr, denom) - .map(|coin| coin.amount) + let request: QueryRequest = CoreumQueries::AssetFT(assetft::Query::Balance { + account: account_addr.into(), + denom: denom.into(), + }) + .into(); + let balance_response: assetft::BalanceResponse = querier.query(&request)?; + Ok(balance_response + .balance + .parse::() + .expect("Failed to parse the string") + .into()) } /// Returns the total balances for all coins at a specified account address. @@ -47,7 +56,7 @@ pub fn query_all_balances(querier: &QuerierWrapper, account_addr: Addr) -> StdRe /// /// * **account_addr** account address for which we return a balance. pub fn query_token_balance( - querier: &QuerierWrapper, + querier: &QuerierWrapper, contract_addr: impl Into, account_addr: impl Into, ) -> StdResult { From 1f07b9935a7b48af583b13bd9833286e16a82505 Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 5 Oct 2023 15:01:20 +0200 Subject: [PATCH 2/9] Pair: Work in progress adding CoreumQueries type --- contracts/pair/src/contract.rs | 12 ++++++------ packages/dex/src/pair/referral.rs | 3 ++- packages/dex/src/querier.rs | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/contracts/pair/src/contract.rs b/contracts/pair/src/contract.rs index cac4899..0683bb4 100644 --- a/contracts/pair/src/contract.rs +++ b/contracts/pair/src/contract.rs @@ -286,7 +286,7 @@ pub fn update_fees( /// /// NOTE - the address that wants to provide liquidity should approve the pair contract to pull its relevant tokens. pub fn provide_liquidity( - mut deps: DepsMut, + mut deps: DepsMut, env: Env, info: MessageInfo, assets: Vec, @@ -660,7 +660,7 @@ pub fn swap( ])) } -fn check_if_frozen(deps: &DepsMut) -> Result<(), ContractError> { +fn check_if_frozen(deps: &DepsMut) -> Result<(), ContractError> { let is_frozen: bool = FROZEN.load(deps.storage)?; ensure!(!is_frozen, ContractError::ContractFrozen {}); Ok(()) @@ -681,7 +681,7 @@ struct SwapResult { /// Important: When providing the pool balances for this method, make sure that those do *not* include the offer asset. #[allow(clippy::too_many_arguments)] fn do_swap( - deps: DepsMut, + deps: DepsMut, env: &Env, config: &mut Config, factory_config: &FactoryConfig, @@ -946,7 +946,7 @@ pub fn query_share(deps: Deps, amount: Uint128) -> StdResult /// /// * **offer_asset** is the asset to swap as well as an amount of the said asset. pub fn query_simulation( - deps: Deps, + deps: Deps, offer_asset: Asset, referral: bool, referral_commission: Option, @@ -1054,7 +1054,7 @@ pub fn query_reverse_simulation( } /// Returns information about cumulative prices for the assets in the pool using a [`CumulativePricesResponse`] object. -pub fn query_cumulative_prices(deps: Deps, env: Env) -> StdResult { +pub fn query_cumulative_prices(deps: Deps, env: Env) -> StdResult { let config = CONFIG.load(deps.storage)?; let (assets, total_share) = pool_info(deps.querier, &config)?; @@ -1224,7 +1224,7 @@ fn assert_slippage_tolerance( /// Returns the total amount of assets in the pool as well as the total amount of LP tokens currently minted. pub fn pool_info( - querier: QuerierWrapper, + querier: QuerierWrapper, config: &Config, ) -> StdResult<(Vec, Uint128)> { let pools = config diff --git a/packages/dex/src/pair/referral.rs b/packages/dex/src/pair/referral.rs index 260894d..1a9bc93 100644 --- a/packages/dex/src/pair/referral.rs +++ b/packages/dex/src/pair/referral.rs @@ -4,6 +4,7 @@ use crate::{ querier::query_factory_config, }; +use coreum_wasm_sdk::core::CoreumQueries; use cosmwasm_std::{Addr, CosmosMsg, Decimal, Decimal256, QuerierWrapper, Uint128, Uint256}; use super::ContractError; @@ -66,7 +67,7 @@ pub fn take_referral( /// such that applying [`take_referral`] to the result will return the original offer asset. /// It also returns the commission amount as a second return value. pub fn add_referral( - querier: &QuerierWrapper, + querier: &QuerierWrapper, factory_addr: &Addr, referral: bool, referral_commission: Option, diff --git a/packages/dex/src/querier.rs b/packages/dex/src/querier.rs index 6dc776e..a911748 100644 --- a/packages/dex/src/querier.rs +++ b/packages/dex/src/querier.rs @@ -92,7 +92,7 @@ pub fn query_token_symbol( /// /// * **contract_addr** token contract address. pub fn query_supply( - querier: &QuerierWrapper, + querier: &QuerierWrapper, contract_addr: impl Into, ) -> StdResult { let res: TokenInfoResponse = @@ -123,7 +123,7 @@ pub fn query_token_precision( /// Returns the configuration for the factory contract. pub fn query_factory_config( - querier: &QuerierWrapper, + querier: &QuerierWrapper, factory_contract: impl Into, ) -> StdResult { querier.query_wasm_smart(factory_contract, &FactoryQueryMsg::Config {}) From ae16efcc26b9599231f11ee80c34fa8dffe955d0 Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 5 Oct 2023 15:11:28 +0200 Subject: [PATCH 3/9] Pair: Implement CoreumQueries in all necessary types that performs queries --- contracts/pair/src/contract.rs | 53 +++++++++++++++------------- packages/dex/src/asset.rs | 2 +- packages/dex/src/pair.rs | 2 +- packages/dex/src/pair/instantiate.rs | 9 ++--- packages/dex/src/pair/utils.rs | 20 +---------- packages/dex/src/querier.rs | 2 +- 6 files changed, 38 insertions(+), 50 deletions(-) diff --git a/contracts/pair/src/contract.rs b/contracts/pair/src/contract.rs index 0683bb4..63f0ea6 100644 --- a/contracts/pair/src/contract.rs +++ b/contracts/pair/src/contract.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use std::vec; -use coreum_wasm_sdk::{assetft, core::CoreumQueries}; +use coreum_wasm_sdk::core::CoreumQueries; use cosmwasm_std::{ attr, ensure, entry_point, from_binary, to_binary, Addr, Binary, CosmosMsg, Decimal, Decimal256, Deps, DepsMut, Env, Isqrt, MessageInfo, QuerierWrapper, Reply, Response, StdError, @@ -20,9 +20,9 @@ use dex::factory::{ConfigResponse as FactoryConfig, PairType}; use dex::fee_config::FeeConfig; use dex::pair::{ add_referral, assert_max_spread, check_asset_infos, check_assets, check_cw20_in_pool, - create_lp_token, get_share_in_assets, handle_referral, handle_reply, migration_check, - mint_token_message, save_tmp_staking_config, take_referral, ConfigResponse, ContractError, - Cw20HookMsg, MigrateMsg, DEFAULT_SLIPPAGE, MAX_ALLOWED_SLIPPAGE, + create_lp_token, get_share_in_assets, handle_referral, handle_reply, mint_token_message, + save_tmp_staking_config, take_referral, ConfigResponse, ContractError, Cw20HookMsg, MigrateMsg, + DEFAULT_SLIPPAGE, MAX_ALLOWED_SLIPPAGE, }; use dex::pair::{ CumulativePricesResponse, ExecuteMsg, InstantiateMsg, PairInfo, PoolResponse, QueryMsg, @@ -40,7 +40,7 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); /// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, + deps: DepsMut, env: Env, _info: MessageInfo, msg: InstantiateMsg, @@ -90,7 +90,11 @@ pub fn instantiate( /// The entry point to the contract for processing replies from submessages. #[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { +pub fn reply( + deps: DepsMut, + _env: Env, + msg: Reply, +) -> Result { let mut config = CONFIG.load(deps.storage)?; let res = handle_reply(&deps, msg, &config.factory_addr, &mut config.pair_info)?; CONFIG.save(deps.storage, &config)?; @@ -100,7 +104,11 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Result { +pub fn migrate( + deps: DepsMut, + _env: Env, + msg: MigrateMsg, +) -> Result { match msg { MigrateMsg::UpdateFreeze { frozen, @@ -138,17 +146,11 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result, env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> Result { - let cfg = CONFIG.load(deps.storage)?; - - if migration_check(deps.querier, &cfg.factory_addr, &env.contract.address)? { - return Err(ContractError::PairIsNotMigrated {}); - } - match msg { ExecuteMsg::Receive(msg) => receive_cw20(deps, env, info, msg), ExecuteMsg::ProvideLiquidity { @@ -206,7 +208,7 @@ pub fn execute( /// /// * **cw20_msg** is the CW20 receive message to process. pub fn receive_cw20( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, cw20_msg: Cw20ReceiveMsg, @@ -254,7 +256,7 @@ pub fn receive_cw20( } pub fn update_fees( - deps: DepsMut, + deps: DepsMut, info: MessageInfo, fee_config: FeeConfig, ) -> Result { @@ -497,7 +499,7 @@ pub fn provide_liquidity( /// /// * **amount** is the amount of LP tokens to burn. pub fn withdraw_liquidity( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, sender: Addr, @@ -572,7 +574,7 @@ pub fn withdraw_liquidity( /// NOTE - the address that wants to swap should approve the pair contract to pull the offer token. #[allow(clippy::too_many_arguments)] pub fn swap( - deps: DepsMut, + deps: DepsMut, env: Env, info: MessageInfo, sender: Addr, @@ -871,7 +873,7 @@ pub fn calculate_protocol_fee( /// /// * **QueryMsg::Config {}** Returns the configuration for the pair contract using a [`ConfigResponse`] object. #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { +pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Pair {} => to_binary(&CONFIG.load(deps.storage)?.pair_info), QueryMsg::Pool {} => to_binary(&query_pool(deps)?), @@ -918,7 +920,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { /// Returns the amounts of assets in the pair contract as well as the amount of LP /// tokens currently minted in an object of type [`PoolResponse`]. -pub fn query_pool(deps: Deps) -> StdResult { +pub fn query_pool(deps: Deps) -> StdResult { let config = CONFIG.load(deps.storage)?; let (assets, total_share) = pool_info(deps.querier, &config)?; @@ -934,7 +936,7 @@ pub fn query_pool(deps: Deps) -> StdResult { /// The result is returned in a vector that contains objects of type [`Asset`]. /// /// * **amount** is the amount of LP tokens for which we calculate associated amounts of assets. -pub fn query_share(deps: Deps, amount: Uint128) -> StdResult> { +pub fn query_share(deps: Deps, amount: Uint128) -> StdResult> { let config = CONFIG.load(deps.storage)?; let (pools, total_share) = pool_info(deps.querier, &config)?; let refund_assets = get_share_in_assets(&pools, amount, total_share); @@ -999,7 +1001,7 @@ pub fn query_simulation( /// * **ask_asset** is the asset to swap to as well as the desired amount of ask /// assets to receive from the swap. pub fn query_reverse_simulation( - deps: Deps, + deps: Deps, ask_asset: Asset, referral: bool, referral_commission: Option, @@ -1054,7 +1056,10 @@ pub fn query_reverse_simulation( } /// Returns information about cumulative prices for the assets in the pool using a [`CumulativePricesResponse`] object. -pub fn query_cumulative_prices(deps: Deps, env: Env) -> StdResult { +pub fn query_cumulative_prices( + deps: Deps, + env: Env, +) -> StdResult { let config = CONFIG.load(deps.storage)?; let (assets, total_share) = pool_info(deps.querier, &config)?; @@ -1091,7 +1096,7 @@ pub fn query_cumulative_prices(deps: Deps, env: Env) -> StdResult } /// Returns the pair contract configuration in a [`ConfigResponse`] object. -pub fn query_config(deps: Deps) -> StdResult { +pub fn query_config(deps: Deps) -> StdResult { let config: Config = CONFIG.load(deps.storage)?; Ok(ConfigResponse { block_time_last: config.block_time_last, diff --git a/packages/dex/src/asset.rs b/packages/dex/src/asset.rs index 2aa07eb..983c8fc 100644 --- a/packages/dex/src/asset.rs +++ b/packages/dex/src/asset.rs @@ -396,7 +396,7 @@ const TOKEN_SYMBOL_MAX_LENGTH: usize = 4; /// Returns a formatted LP token name pub fn format_lp_token_name( asset_infos: &[AssetInfoValidated], - querier: &QuerierWrapper, + querier: &QuerierWrapper, ) -> StdResult { let mut short_symbols: Vec = vec![]; for asset_info in asset_infos { diff --git a/packages/dex/src/pair.rs b/packages/dex/src/pair.rs index 69b5d59..a824d97 100644 --- a/packages/dex/src/pair.rs +++ b/packages/dex/src/pair.rs @@ -138,7 +138,7 @@ impl StakeConfig { /// Call this after instantiating the lp token to get a message to instantiate the staking contract pub fn into_init_msg( self, - querier: &QuerierWrapper, + querier: &QuerierWrapper, lp_token_address: String, factory_addr: String, ) -> StdResult { diff --git a/packages/dex/src/pair/instantiate.rs b/packages/dex/src/pair/instantiate.rs index 8cf39a7..a70e2c0 100644 --- a/packages/dex/src/pair/instantiate.rs +++ b/packages/dex/src/pair/instantiate.rs @@ -1,3 +1,4 @@ +use coreum_wasm_sdk::core::CoreumQueries; use cosmwasm_std::{ to_binary, Addr, DepsMut, Env, QuerierWrapper, Reply, Response, StdError, StdResult, Storage, SubMsg, WasmMsg, @@ -25,7 +26,7 @@ const INSTANTIATE_STAKE_REPLY_ID: u64 = 2; /// Returns a sub-message to instantiate a new LP token. /// It uses [`INSTANTIATE_TOKEN_REPLY_ID`] as id. pub fn create_lp_token( - querier: &QuerierWrapper, + querier: &QuerierWrapper, env: &Env, token_code_id: u64, asset_infos: &[AssetInfoValidated], @@ -69,7 +70,7 @@ pub fn save_tmp_staking_config( /// Handles the replies from the lp token and staking contract instantiation sub-messages. pub fn handle_reply( - deps: &DepsMut, + deps: &DepsMut, msg: Reply, factory: &Addr, pair_info: &mut PairInfo, @@ -90,7 +91,7 @@ pub fn handle_reply( /// lp token contract, reads the temporary staking config and sends a sub-message to instantiate /// the staking contract. pub fn instantiate_lp_token_reply( - deps: &DepsMut, + deps: &DepsMut, res: MsgInstantiateContractResponse, factory: &Addr, pair_info: &mut PairInfo, @@ -115,7 +116,7 @@ pub fn instantiate_lp_token_reply( /// Sets the `pair_info`'s `staking_addr` field to the address of the newly instantiated /// staking contract, and returns a response. pub fn instantiate_staking_reply( - deps: &DepsMut, + deps: &DepsMut, res: MsgInstantiateContractResponse, pair_info: &mut PairInfo, ) -> Result { diff --git a/packages/dex/src/pair/utils.rs b/packages/dex/src/pair/utils.rs index 93af120..0eabec6 100644 --- a/packages/dex/src/pair/utils.rs +++ b/packages/dex/src/pair/utils.rs @@ -4,10 +4,7 @@ use super::error::ContractError; use crate::asset::{Asset, AssetInfo, AssetInfoValidated, AssetValidated}; -use cosmwasm_std::{ - from_slice, wasm_execute, Addr, Api, CosmosMsg, Decimal, Fraction, QuerierWrapper, StdError, - StdResult, Uint128, -}; +use cosmwasm_std::{wasm_execute, Addr, Api, CosmosMsg, Decimal, Fraction, StdError, Uint128}; use cw20::Cw20ExecuteMsg; use itertools::Itertools; @@ -16,21 +13,6 @@ pub const DEFAULT_SLIPPAGE: &str = "0.005"; /// The maximum allowed swap slippage pub const MAX_ALLOWED_SLIPPAGE: &str = "0.5"; -/// This function makes raw query to the factory contract and -/// checks whether the pair needs to update an owner or not. -pub fn migration_check( - querier: QuerierWrapper, - factory: &Addr, - pair_addr: &Addr, -) -> StdResult { - if let Some(res) = querier.query_wasm_raw(factory, b"pairs_to_migrate".as_slice())? { - let res: Vec = from_slice(&res)?; - Ok(res.contains(pair_addr)) - } else { - Ok(false) - } -} - /// Helper function to check if the given asset infos are valid. pub fn check_asset_infos( api: &dyn Api, diff --git a/packages/dex/src/querier.rs b/packages/dex/src/querier.rs index a911748..3f21068 100644 --- a/packages/dex/src/querier.rs +++ b/packages/dex/src/querier.rs @@ -79,7 +79,7 @@ pub fn query_token_balance( /// /// * **contract_addr** token contract address. pub fn query_token_symbol( - querier: &QuerierWrapper, + querier: &QuerierWrapper, contract_addr: impl Into, ) -> StdResult { let res: TokenInfoResponse = From 9b84a7c22cee97fd92929290a539a9d071290c4a Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 5 Oct 2023 15:20:47 +0200 Subject: [PATCH 4/9] Pair: Initial implementation that instantiates LP Share tokens as smart tokens --- packages/dex/src/pair/instantiate.rs | 33 +++++++++++----------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/packages/dex/src/pair/instantiate.rs b/packages/dex/src/pair/instantiate.rs index a70e2c0..74dc896 100644 --- a/packages/dex/src/pair/instantiate.rs +++ b/packages/dex/src/pair/instantiate.rs @@ -1,7 +1,7 @@ -use coreum_wasm_sdk::core::CoreumQueries; +use coreum_wasm_sdk::{assetft, core::{CoreumQueries, CoreumMsg}}; use cosmwasm_std::{ to_binary, Addr, DepsMut, Env, QuerierWrapper, Reply, Response, StdError, StdResult, Storage, - SubMsg, WasmMsg, + SubMsg, WasmMsg, Uint128, CosmosMsg }; use cw20::MinterResponse; use cw20_base::msg::InstantiateMsg as TokenInstantiateMsg; @@ -17,7 +17,7 @@ use super::{ContractError, PairInfo, StakeConfig}; /// lp token instantiation and staking contract instantiation. const TMP_STAKING_CONFIG: Item = Item::new("tmp_staking_config"); -pub const LP_TOKEN_PRECISION: u8 = 6; +pub const LP_TOKEN_PRECISION: u32 = 6; /// A `reply` call code ID used for token instantiation sub-message. const INSTANTIATE_TOKEN_REPLY_ID: u64 = 1; /// A `reply` call code ID used for staking contract instantiation sub-message. @@ -38,23 +38,16 @@ pub fn create_lp_token( querier.query_wasm_smart(factory_addr, &FactoryQueryMsg::Config {})?; Ok(SubMsg::reply_on_success( - WasmMsg::Instantiate { - admin: Some(factory_config.owner.to_string()), - code_id: token_code_id, - msg: to_binary(&TokenInstantiateMsg { - name: token_name, - symbol: "uLP".to_string(), - decimals: LP_TOKEN_PRECISION, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: env.contract.address.to_string(), - cap: None, - }), - marketing: None, - })?, - funds: vec![], - label: "Dex LP token".to_owned(), - }, + CoreumMsg::AssetFT(assetft::Msg::Issue { + symbol: "LP".to_string(), + subunit: "uLP".to_string(), + precision: LP_TOKEN_PRECISION, + initial_amount: Uint128::zero(), + description: Some("Dex LP Share token".to_string()), + features: Some(vec![0, 1, 2]), // 0 - minting, 1 - burning, 2 - freezing + burn_rate: Some("0".into()), + send_commission_rate: None, + }).into::(), INSTANTIATE_TOKEN_REPLY_ID, )) } From 9eff92c8a630d917237fc9e7b9f9149f92adb3e0 Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 5 Oct 2023 15:46:52 +0200 Subject: [PATCH 5/9] Pair: LP Share token is issued through CoreumMsg --- contracts/pair/src/contract.rs | 12 ++------ packages/dex/src/pair/instantiate.rs | 42 +++++++++++----------------- 2 files changed, 19 insertions(+), 35 deletions(-) diff --git a/contracts/pair/src/contract.rs b/contracts/pair/src/contract.rs index 63f0ea6..d61404f 100644 --- a/contracts/pair/src/contract.rs +++ b/contracts/pair/src/contract.rs @@ -1,7 +1,7 @@ use std::str::FromStr; use std::vec; -use coreum_wasm_sdk::core::CoreumQueries; +use coreum_wasm_sdk::core::{CoreumMsg, CoreumQueries}; use cosmwasm_std::{ attr, ensure, entry_point, from_binary, to_binary, Addr, Binary, CosmosMsg, Decimal, Decimal256, Deps, DepsMut, Env, Isqrt, MessageInfo, QuerierWrapper, Reply, Response, StdError, @@ -44,7 +44,7 @@ pub fn instantiate( env: Env, _info: MessageInfo, msg: InstantiateMsg, -) -> Result { +) -> Result, ContractError> { let asset_infos = check_asset_infos(deps.api, &msg.asset_infos)?; if asset_infos.len() != 2 { @@ -57,13 +57,7 @@ pub fn instantiate( let factory_addr = deps.api.addr_validate(msg.factory_addr.as_str())?; - let create_lp_token_msg = create_lp_token( - &deps.querier, - &env, - msg.token_code_id, - &asset_infos, - &factory_addr, - )?; + let create_lp_token_msg = create_lp_token(&deps.querier, &asset_infos)?; let config = Config { pair_info: PairInfo { diff --git a/packages/dex/src/pair/instantiate.rs b/packages/dex/src/pair/instantiate.rs index 74dc896..4d7d71e 100644 --- a/packages/dex/src/pair/instantiate.rs +++ b/packages/dex/src/pair/instantiate.rs @@ -1,15 +1,14 @@ -use coreum_wasm_sdk::{assetft, core::{CoreumQueries, CoreumMsg}}; +use coreum_wasm_sdk::{ + assetft, + core::{CoreumMsg, CoreumQueries}, +}; use cosmwasm_std::{ - to_binary, Addr, DepsMut, Env, QuerierWrapper, Reply, Response, StdError, StdResult, Storage, - SubMsg, WasmMsg, Uint128, CosmosMsg + Addr, DepsMut, QuerierWrapper, Reply, Response, StdError, StdResult, Storage, SubMsg, Uint128, }; -use cw20::MinterResponse; -use cw20_base::msg::InstantiateMsg as TokenInstantiateMsg; use cw_storage_plus::Item; use cw_utils::MsgInstantiateContractResponse; use crate::asset::{format_lp_token_name, AssetInfoValidated}; -use crate::factory::{ConfigResponse as FactoryConfigResponse, QueryMsg as FactoryQueryMsg}; use super::{ContractError, PairInfo, StakeConfig}; @@ -27,29 +26,20 @@ const INSTANTIATE_STAKE_REPLY_ID: u64 = 2; /// It uses [`INSTANTIATE_TOKEN_REPLY_ID`] as id. pub fn create_lp_token( querier: &QuerierWrapper, - env: &Env, - token_code_id: u64, asset_infos: &[AssetInfoValidated], - factory_addr: &Addr, -) -> StdResult { +) -> StdResult> { let token_name = format_lp_token_name(asset_infos, querier)?; - let factory_config: FactoryConfigResponse = - querier.query_wasm_smart(factory_addr, &FactoryQueryMsg::Config {})?; - - Ok(SubMsg::reply_on_success( - CoreumMsg::AssetFT(assetft::Msg::Issue { - symbol: "LP".to_string(), - subunit: "uLP".to_string(), - precision: LP_TOKEN_PRECISION, - initial_amount: Uint128::zero(), - description: Some("Dex LP Share token".to_string()), - features: Some(vec![0, 1, 2]), // 0 - minting, 1 - burning, 2 - freezing - burn_rate: Some("0".into()), - send_commission_rate: None, - }).into::(), - INSTANTIATE_TOKEN_REPLY_ID, - )) + Ok(SubMsg::new(CoreumMsg::AssetFT(assetft::Msg::Issue { + symbol: token_name, + subunit: "uLP".to_string(), + precision: LP_TOKEN_PRECISION, + initial_amount: Uint128::zero(), + description: Some("Dex LP Share token".to_string()), + features: Some(vec![0, 1, 2]), // 0 - minting, 1 - burning, 2 - freezing + burn_rate: Some("0".into()), + send_commission_rate: None, + }))) } /// Saves this `stake_config` to the storage temporarily From f7d47a3003eb87c358b7c8f72b2c97625f0e3b8e Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 12 Oct 2023 10:19:20 +0200 Subject: [PATCH 6/9] Dex: Remove doubled mock_querier module --- packages/dex/src/pair.rs | 3 - packages/dex/src/pair/mock_querier.rs | 189 -------------------------- 2 files changed, 192 deletions(-) delete mode 100644 packages/dex/src/pair/mock_querier.rs diff --git a/packages/dex/src/pair.rs b/packages/dex/src/pair.rs index a824d97..2b748c1 100644 --- a/packages/dex/src/pair.rs +++ b/packages/dex/src/pair.rs @@ -14,9 +14,6 @@ use cosmwasm_std::{ }; use cw20::Cw20ReceiveMsg; -#[cfg(test)] -pub mod mock_querier; - mod error; mod instantiate; mod referral; diff --git a/packages/dex/src/pair/mock_querier.rs b/packages/dex/src/pair/mock_querier.rs deleted file mode 100644 index e446337..0000000 --- a/packages/dex/src/pair/mock_querier.rs +++ /dev/null @@ -1,189 +0,0 @@ -use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; -use cosmwasm_std::{ - from_binary, from_slice, to_binary, Addr, Coin, Decimal, Empty, OwnedDeps, Querier, - QuerierResult, QueryRequest, SystemError, SystemResult, Uint128, WasmQuery, -}; -use std::collections::HashMap; - -use crate::factory::{ - ConfigResponse, FeeInfoResponse, - QueryMsg::{Config, FeeInfo}, -}; -use cw20::{BalanceResponse, Cw20QueryMsg, TokenInfoResponse}; - -/// mock_dependencies is a drop-in replacement for cosmwasm_std::testing::mock_dependencies. -/// This uses the Dex CustomQuerier. -pub fn mock_dependencies( - contract_balance: &[Coin], -) -> OwnedDeps { - let custom_querier: WasmMockQuerier = - WasmMockQuerier::new(MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)])); - - OwnedDeps { - storage: MockStorage::default(), - api: MockApi::default(), - querier: custom_querier, - custom_query_type: Default::default(), - } -} - -pub struct WasmMockQuerier { - base: MockQuerier, - token_querier: TokenQuerier, -} - -#[derive(Clone, Default)] -pub struct TokenQuerier { - // This lets us iterate over all pairs that match the first string - balances: HashMap>, -} - -impl TokenQuerier { - pub fn new(balances: &[(&String, &[(&String, &Uint128)])]) -> Self { - TokenQuerier { - balances: balances_to_map(balances), - } - } -} - -pub(crate) fn balances_to_map( - balances: &[(&String, &[(&String, &Uint128)])], -) -> HashMap> { - let mut balances_map: HashMap> = HashMap::new(); - for (contract_addr, balances) in balances.iter() { - let mut contract_balances_map: HashMap = HashMap::new(); - for (addr, balance) in balances.iter() { - contract_balances_map.insert(addr.to_string(), **balance); - } - - balances_map.insert(contract_addr.to_string(), contract_balances_map); - } - balances_map -} - -impl Querier for WasmMockQuerier { - fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { - // MockQuerier doesn't support Custom, so we ignore it completely - let request: QueryRequest = match from_slice(bin_request) { - Ok(v) => v, - Err(e) => { - return SystemResult::Err(SystemError::InvalidRequest { - error: format!("Parsing query request: {}", e), - request: bin_request.into(), - }) - } - }; - self.handle_query(&request) - } -} - -impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { - match &request { - QueryRequest::Wasm(WasmQuery::Smart { contract_addr, msg }) => { - if contract_addr == "factory" { - match from_binary(msg).unwrap() { - FeeInfo { .. } => SystemResult::Ok( - to_binary(&FeeInfoResponse { - fee_address: Some(Addr::unchecked("fee_address")), - total_fee_bps: 30, - protocol_fee_bps: 1660, - }) - .into(), - ), - Config {} => SystemResult::Ok( - to_binary(&ConfigResponse { - owner: Addr::unchecked("owner"), - pair_configs: vec![], - token_code_id: 0, - fee_address: Some(Addr::unchecked("fee_address")), - max_referral_commission: Decimal::one(), - only_owner_can_create_pairs: true, - trading_starts: None, - }) - .into(), - ), - _ => panic!("DO NOT ENTER HERE"), - } - } else { - match from_binary(msg).unwrap() { - Cw20QueryMsg::TokenInfo {} => { - let balances: &HashMap = - match self.token_querier.balances.get(contract_addr) { - Some(balances) => balances, - None => { - return SystemResult::Err(SystemError::Unknown {}); - } - }; - - let mut total_supply = Uint128::zero(); - - for balance in balances { - total_supply += *balance.1; - } - - SystemResult::Ok( - to_binary(&TokenInfoResponse { - name: "mAPPL".to_string(), - symbol: "mAPPL".to_string(), - decimals: 6, - total_supply, - }) - .into(), - ) - } - Cw20QueryMsg::Balance { address } => { - let balances: &HashMap = - match self.token_querier.balances.get(contract_addr) { - Some(balances) => balances, - None => { - return SystemResult::Err(SystemError::Unknown {}); - } - }; - - let balance = match balances.get(&address) { - Some(v) => v, - None => { - return SystemResult::Err(SystemError::Unknown {}); - } - }; - - SystemResult::Ok( - to_binary(&BalanceResponse { balance: *balance }).into(), - ) - } - _ => panic!("DO NOT ENTER HERE"), - } - } - } - QueryRequest::Wasm(WasmQuery::Raw { contract_addr, .. }) => { - if contract_addr == "factory" { - SystemResult::Ok(to_binary(&Vec::::new()).into()) - } else { - panic!("DO NOT ENTER HERE"); - } - } - _ => self.base.handle_query(request), - } - } -} - -impl WasmMockQuerier { - pub fn new(base: MockQuerier) -> Self { - WasmMockQuerier { - base, - token_querier: TokenQuerier::default(), - } - } - - // Configure the mint whitelist mock querier - pub fn with_token_balances(&mut self, balances: &[(&String, &[(&String, &Uint128)])]) { - self.token_querier = TokenQuerier::new(balances); - } - - pub fn with_balance(&mut self, balances: &[(&String, &[Coin])]) { - for (addr, balance) in balances { - self.base.update_balance(addr.to_string(), balance.to_vec()); - } - } -} From de2ee91e769654922b760ef8ef4cdd476f7b5996 Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 12 Oct 2023 10:41:36 +0200 Subject: [PATCH 7/9] Pair: Add CoreumQueries type to MockQuerier --- contracts/pair/src/mock_querier.rs | 9 +- contracts/pair/src/testing.rs | 3757 ++++++++++++++-------------- 2 files changed, 1884 insertions(+), 1882 deletions(-) diff --git a/contracts/pair/src/mock_querier.rs b/contracts/pair/src/mock_querier.rs index 124e309..09199a9 100644 --- a/contracts/pair/src/mock_querier.rs +++ b/contracts/pair/src/mock_querier.rs @@ -1,3 +1,4 @@ +use coreum_wasm_sdk::core::{CoreumMsg, CoreumQueries}; use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ from_binary, from_slice, to_binary, Addr, Coin, Decimal, Empty, OwnedDeps, Querier, @@ -28,7 +29,7 @@ pub fn mock_dependencies( } pub struct WasmMockQuerier { - base: MockQuerier, + base: MockQuerier, token_querier: TokenQuerier, } @@ -64,7 +65,7 @@ pub(crate) fn balances_to_map( impl Querier for WasmMockQuerier { fn raw_query(&self, bin_request: &[u8]) -> QuerierResult { // MockQuerier doesn't support Custom, so we ignore it completely - let request: QueryRequest = match from_slice(bin_request) { + let request: QueryRequest = match from_slice(bin_request) { Ok(v) => v, Err(e) => { return SystemResult::Err(SystemError::InvalidRequest { @@ -78,7 +79,7 @@ impl Querier for WasmMockQuerier { } impl WasmMockQuerier { - pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { + pub fn handle_query(&self, request: &QueryRequest) -> QuerierResult { match &request { QueryRequest::Wasm(WasmQuery::Smart { contract_addr, msg }) => { if contract_addr == "factory" { @@ -169,7 +170,7 @@ impl WasmMockQuerier { } impl WasmMockQuerier { - pub fn new(base: MockQuerier) -> Self { + pub fn new(base: MockQuerier) -> Self { WasmMockQuerier { base, token_querier: TokenQuerier::default(), diff --git a/contracts/pair/src/testing.rs b/contracts/pair/src/testing.rs index 0ea96c2..be77a39 100644 --- a/contracts/pair/src/testing.rs +++ b/contracts/pair/src/testing.rs @@ -1,3 +1,4 @@ +use coreum_wasm_sdk::core::{CoreumMsg, CoreumQueries}; use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ assert_approx_eq, attr, coins, from_binary, to_binary, Addr, BankMsg, BlockInfo, Coin, @@ -28,7 +29,7 @@ use crate::state::{Config, CONFIG}; // TODO: Copied here just as a temporary measure use crate::mock_querier::mock_dependencies; -fn store_liquidity_token(deps: DepsMut, contract_addr: String) { +fn store_liquidity_token(deps: DepsMut, contract_addr: String) { let res = MsgInstantiateContractResponse { contract_address: contract_addr, data: None, @@ -138,1880 +139,1880 @@ fn proper_initialization() { // We then try to unfreeze with addr0001, which should fail // We then try to unfreeze with addr0000, which should succeed and to prove this we try to // provide liquidity again and swap, which should both succeed -#[test] -fn test_freezing_a_pool_blocking_actions_then_unfreeze() { - let mut deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(200_000000000000000000u128), - }]); - let offer_amount = Uint128::new(1500000000u128); - - deps.querier.with_token_balances(&[ - ( - &String::from("asset0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(0))], - ), - ( - &String::from("liquidity0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(0))], - ), - ]); - - let msg = InstantiateMsg { - asset_infos: vec![ - AssetInfo::Native("uusd".to_string()), - AssetInfo::Token("asset0000".to_string()), - ], - token_code_id: 10u64, - factory_addr: String::from("factory"), - init_params: None, - staking_config: default_stake_config(), - trading_starts: 0, - fee_config: FeeConfig { - total_fee_bps: 0, - protocol_fee_bps: 0, - }, - circuit_breaker: None, - }; - - let env = mock_env(); - let info = mock_info("addr0000", &[]); - // We can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); - - // Store liquidity token - store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); - - // Successfully provide liquidity for the existing pool - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - ], - slippage_tolerance: None, - receiver: None, - }; - - let env = mock_env(); - let info = mock_info( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(100_000000000000000000u128), - }], - ); - // Do one successful action before freezing just for sanity - execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - // Manually set the correct balances for the pool - deps.querier.with_balance(&[( - &String::from(MOCK_CONTRACT_ADDR), - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(100_000000000000000000), - }], - )]); - deps.querier.with_token_balances(&[ - ( - &String::from("liquidity0000"), - &[ - (&String::from(MOCK_CONTRACT_ADDR), &MINIMUM_LIQUIDITY_AMOUNT), - ( - &String::from("addr0000"), - &(Uint128::new(100_000000000000000000) - MINIMUM_LIQUIDITY_AMOUNT), - ), - ], - ), - ( - &String::from("asset0000"), - &[( - &String::from(MOCK_CONTRACT_ADDR), - &Uint128::new(100_000000000000000000), - )], - ), - ]); - - // Migrate with the freeze migrate message - migrate( - deps.as_mut(), - env.clone(), - MigrateMsg::UpdateFreeze { - frozen: true, - circuit_breaker: Some("addr0000".to_string()), - }, - ) - .unwrap(); - - // Failing Execute Actions due to frozen - - // This should now fail, its a good TX with all the normal setup done but because of freezing it should fail - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(200_000000000000000000u128), - }, - ], - slippage_tolerance: Some(Decimal::percent(50)), - receiver: None, - }; - - let env = mock_env_with_block_time(env.block.time.seconds() + 1000); - let info = mock_info( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(200_000000000000000000u128), - }], - ); - - // Assert an error and that its frozen - let res: ContractError = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); - assert_eq!(res, ContractError::ContractFrozen {}); - // Also do a swap, which should also fail - let msg = ExecuteMsg::Swap { - offer_asset: Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: 1_000u128.into(), - }, - to: None, - max_spread: None, - belief_price: None, - ask_asset_info: None, - referral_address: None, - referral_commission: None, - }; - - let info = mock_info( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(1000u128), - }], - ); - // Assert an error and that its frozen - let res: ContractError = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); - assert_eq!(res, ContractError::ContractFrozen {}); - - let msg = ExecuteMsg::UpdateFees { - fee_config: FeeConfig { - total_fee_bps: 5, - protocol_fee_bps: 5, - }, - }; - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); - assert_eq!(res, ContractError::ContractFrozen {}); - - // Normal sell but with CW20 - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("addr0000"), - amount: offer_amount, - msg: to_binary(&Cw20HookMsg::Swap { - ask_asset_info: None, - belief_price: None, - max_spread: Some(Decimal::percent(50)), - to: None, - referral_address: None, - referral_commission: None, - }) - .unwrap(), - }); - let info = mock_info("asset0000", &[]); - - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); - assert_eq!(res, ContractError::ContractFrozen {}); - - // But we can withdraw liquidity - - // Withdraw liquidity - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("addr0000"), - msg: to_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), - amount: Uint128::new(100u128), - }); - - let info = mock_info("liquidity0000", &[]); - // We just want to ensure it doesn't fail with a ContractFrozen error - execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - // Unfreeze the pool again using the Freeze message rather than another migrate - let msg = ExecuteMsg::Freeze { frozen: false }; - // First try a failing case with addr0001 - let info = mock_info("addr0001", &[]); - // Rather than being unfrozen it returns unauthorized as addr0000 is the only addr that can currently call Freeze unless another migration changes that - let err = execute(deps.as_mut(), env.clone(), info, msg.clone()).unwrap_err(); - assert_eq!(err, ContractError::Unauthorized {}); - // But the assigned circuit_breaker address can do an unfreeze with the ExecuteMsg variant - let info = mock_info("addr0000", &[]); - // And it works - execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - // Testing actions working again after unfreeze - - // Initialize token balance to 1:1 - deps.querier.with_balance(&[( - &String::from(MOCK_CONTRACT_ADDR), - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(100_000000000000000000 + 99_000000000000000000 /* user deposit must be pre-applied */), - }], - )]); - - deps.querier.with_token_balances(&[ - ( - &String::from("liquidity0000"), - &[( - &String::from(MOCK_CONTRACT_ADDR), - &Uint128::new(100_000000000000000000), - )], - ), - ( - &String::from("asset0000"), - &[( - &String::from(MOCK_CONTRACT_ADDR), - &Uint128::new(100_000000000000000000), - )], - ), - ]); - - // Successfully provides liquidity - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(99_000000000000000000u128), - }, - ], - slippage_tolerance: Some(Decimal::percent(1)), - receiver: None, - }; - - let info = mock_info( - "addr0001", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(99_000000000000000000u128), - }], - ); - execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - // Normal sell but with CW20 - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("addr0000"), - amount: offer_amount, - msg: to_binary(&Cw20HookMsg::Swap { - ask_asset_info: None, - belief_price: None, - max_spread: Some(Decimal::percent(50)), - to: None, - referral_address: None, - referral_commission: None, - }) - .unwrap(), - }); - let info = mock_info("asset0000", &[]); - - execute(deps.as_mut(), env, info, msg).unwrap(); -} - -#[test] -fn provide_liquidity() { - let mut deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(200_000000000000000000u128), - }]); - - deps.querier.with_token_balances(&[ - ( - &String::from("asset0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(0))], - ), - ( - &String::from("liquidity0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(0))], - ), - ]); - - let msg = InstantiateMsg { - asset_infos: vec![ - AssetInfo::Native("uusd".to_string()), - AssetInfo::Token("asset0000".to_string()), - ], - token_code_id: 10u64, - factory_addr: String::from("factory"), - init_params: None, - staking_config: default_stake_config(), - trading_starts: 0, - fee_config: FeeConfig { - total_fee_bps: 0, - protocol_fee_bps: 0, - }, - circuit_breaker: None, - }; - - let env = mock_env(); - let info = mock_info("addr0000", &[]); - // We can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); - - // Store liquidity token - store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); - - // Successfully provide liquidity for the existing pool - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - ], - slippage_tolerance: None, - receiver: None, - }; - - let env = mock_env(); - let info = mock_info( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(100_000000000000000000u128), - }], - ); - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - let transfer_from_msg = res.messages.get(0).expect("no message"); - let mint_min_liquidity_msg = res.messages.get(1).expect("no message"); - let mint_receiver_msg = res.messages.get(2).expect("no message"); - assert_eq!( - transfer_from_msg, - &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("asset0000"), - msg: to_binary(&Cw20ExecuteMsg::TransferFrom { - owner: String::from("addr0000"), - recipient: String::from(MOCK_CONTRACT_ADDR), - amount: Uint128::from(100_000000000000000000u128), - }) - .unwrap(), - funds: vec![], - } - .into(), - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never - } - ); - assert_eq!( - mint_min_liquidity_msg, - &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from(MOCK_CONTRACT_ADDR), - amount: Uint128::from(1000_u128), - }) - .unwrap(), - funds: vec![], - } - .into(), - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - assert_eq!( - mint_receiver_msg, - &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from("addr0000"), - amount: Uint128::from(99_999999999999999000u128), - }) - .unwrap(), - funds: vec![], - } - .into(), - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - - // Provide more liquidity 1:2, which is not propotional to 1:1, - // It must accept 1:1 and treat the leftover amount as a donation - deps.querier.with_balance(&[( - &String::from(MOCK_CONTRACT_ADDR), - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(200_000000000000000000 + 200_000000000000000000 /* user deposit must be pre-applied */), - }], - )]); - - deps.querier.with_token_balances(&[ - ( - &String::from("liquidity0000"), - &[( - &String::from(MOCK_CONTRACT_ADDR), - &Uint128::new(100_000000000000000000), - )], - ), - ( - &String::from("asset0000"), - &[( - &String::from(MOCK_CONTRACT_ADDR), - &Uint128::new(200_000000000000000000), - )], - ), - ]); - - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(200_000000000000000000u128), - }, - ], - slippage_tolerance: Some(Decimal::percent(50)), - receiver: None, - }; - - let env = mock_env_with_block_time(env.block.time.seconds() + 1000); - let info = mock_info( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(200_000000000000000000u128), - }], - ); - - // Only accept 100, then 50 share will be generated with 100 * (100 / 200) - let res: Response = execute(deps.as_mut(), env, info, msg).unwrap(); - let transfer_from_msg = res.messages.get(0).expect("no message"); - let mint_msg = res.messages.get(1).expect("no message"); - assert_eq!( - transfer_from_msg, - &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("asset0000"), - msg: to_binary(&Cw20ExecuteMsg::TransferFrom { - owner: String::from("addr0000"), - recipient: String::from(MOCK_CONTRACT_ADDR), - amount: Uint128::from(100_000000000000000000u128), - }) - .unwrap(), - funds: vec![], - } - .into(), - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - assert_eq!( - mint_msg, - &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from("addr0000"), - amount: Uint128::from(50_000000000000000000u128), - }) - .unwrap(), - funds: vec![], - } - .into(), - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - - // Check wrong argument - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(50_000000000000000000u128), - }, - ], - slippage_tolerance: None, - receiver: None, - }; - - let env = mock_env(); - let info = mock_info( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(100_000000000000000000u128), - }], - ); - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); - match res { - ContractError::Std(StdError::GenericErr { msg, .. }) => assert_eq!( - msg, - "Native token balance mismatch between the argument and the transferred".to_string() - ), - _ => panic!("Must return generic error"), - } - - // Initialize token amount to the 1:1 ratio - deps.querier.with_balance(&[( - &String::from(MOCK_CONTRACT_ADDR), - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(100_000000000000000000 + 100_000000000000000000 /* user deposit must be pre-applied */), - }], - )]); - - deps.querier.with_token_balances(&[ - ( - &String::from("liquidity0000"), - &[( - &String::from(MOCK_CONTRACT_ADDR), - &Uint128::new(100_000000000000000000), - )], - ), - ( - &String::from("asset0000"), - &[( - &String::from(MOCK_CONTRACT_ADDR), - &Uint128::new(100_000000000000000000), - )], - ), - ]); - - // Failed because the price is under slippage_tolerance - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(98_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - ], - slippage_tolerance: Some(Decimal::percent(1)), - receiver: None, - }; - - let env = mock_env_with_block_time(env.block.time.seconds() + 1000); - let info = mock_info( - "addr0001", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(100_000000000000000000u128), - }], - ); - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); - assert_eq!(res, ContractError::MaxSlippageAssertion {}); - - // Initialize token balance to 1:1 - deps.querier.with_balance(&[( - &String::from(MOCK_CONTRACT_ADDR), - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(100_000000000000000000 + 98_000000000000000000 /* user deposit must be pre-applied */), - }], - )]); - - // Failed because the price is under slippage_tolerance - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(98_000000000000000000u128), - }, - ], - slippage_tolerance: Some(Decimal::percent(1)), - receiver: None, - }; - - let env = mock_env_with_block_time(env.block.time.seconds() + 1000); - let info = mock_info( - "addr0001", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(98_000000000000000000u128), - }], - ); - let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); - assert_eq!(res, ContractError::MaxSlippageAssertion {}); - - // Initialize token amount with a 1:1 ratio - deps.querier.with_balance(&[( - &String::from(MOCK_CONTRACT_ADDR), - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(100_000000000000000000 + 100_000000000000000000 /* user deposit must be pre-applied */), - }], - )]); - - // Successfully provides liquidity - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(99_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - ], - slippage_tolerance: Some(Decimal::percent(1)), - receiver: None, - }; - - let env = mock_env_with_block_time(env.block.time.seconds() + 1000); - let info = mock_info( - "addr0001", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(100_000000000000000000u128), - }], - ); - let _res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); - - // Initialize token balance to 1:1 - deps.querier.with_balance(&[( - &String::from(MOCK_CONTRACT_ADDR), - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(100_000000000000000000 + 99_000000000000000000 /* user deposit must be pre-applied */), - }], - )]); - - // Successfully provides liquidity - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(99_000000000000000000u128), - }, - ], - slippage_tolerance: Some(Decimal::percent(1)), - receiver: None, - }; - - let env = mock_env_with_block_time(env.block.time.seconds() + 1000); - let info = mock_info( - "addr0001", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(99_000000000000000000u128), - }], - ); - execute(deps.as_mut(), env, info, msg).unwrap(); - - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::zero(), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(99_000000000000000000u128), - }, - ], - slippage_tolerance: Some(Decimal::percent(1)), - receiver: None, - }; - let info = mock_info( - "addr0001", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(99_000000000000000000u128), - }], - ); - let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); - assert_eq!(err, ContractError::InvalidZeroAmount {}); - - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: Uint128::from(100_000000000000000000u128), - }, - ], - slippage_tolerance: Some(Decimal::percent(51)), - receiver: None, - }; - let info = mock_info( - "addr0001", - &[Coin { - denom: "uusd".to_string(), - amount: Uint128::from(100_000000000000000000u128), - }], - ); - let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); - assert_eq!(err, ContractError::AllowedSpreadAssertion {}); -} - -#[test] -fn withdraw_liquidity() { - let mut deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: Uint128::new(100u128), - }]); - - deps.querier.with_token_balances(&[ - ( - &String::from("liquidity0000"), - &[ - (&String::from("addr0000"), &Uint128::new(100u128)), - (&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(1000u128)), // MIN_LIQUIDITY_AMOUNT - ], - ), - ( - &String::from("asset0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(100u128))], - ), - ]); - - let msg = InstantiateMsg { - asset_infos: vec![ - AssetInfo::Native("uusd".to_string()), - AssetInfo::Token("asset0000".to_string()), - ], - token_code_id: 10u64, - - factory_addr: String::from("factory"), - init_params: None, - staking_config: default_stake_config(), - trading_starts: 0, - fee_config: FeeConfig { - total_fee_bps: 0, - protocol_fee_bps: 0, - }, - circuit_breaker: None, - }; - - let env = mock_env(); - let info = mock_info("addr0000", &[]); - // We can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); - - // Store liquidity token - store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); - - // need to initialize oracle, because we don't call `provide_liquidity` in this test - dex::oracle::initialize_oracle( - &mut deps.storage, - &mock_env_with_block_time(0), - Decimal::one(), - ) - .unwrap(); - - // Withdraw liquidity - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("addr0000"), - msg: to_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), - amount: Uint128::new(100u128), - }); - - let env = mock_env(); - let info = mock_info("liquidity0000", &[]); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - let log_withdrawn_share = res.attributes.get(2).expect("no log"); - let log_refund_assets = res.attributes.get(3).expect("no log"); - let msg_refund_0 = res.messages.get(0).expect("no message"); - let msg_refund_1 = res.messages.get(1).expect("no message"); - let msg_burn_liquidity = res.messages.get(2).expect("no message"); - assert_eq!( - msg_refund_0, - &SubMsg { - msg: CosmosMsg::Bank(BankMsg::Send { - to_address: String::from("addr0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: Uint128::from(9u128), - }], - }), - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - assert_eq!( - msg_refund_1, - &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("asset0000"), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: String::from("addr0000"), - amount: Uint128::from(9u128), - }) - .unwrap(), - funds: vec![], - } - .into(), - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - assert_eq!( - msg_burn_liquidity, - &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_binary(&Cw20ExecuteMsg::Burn { - amount: Uint128::from(100u128), - }) - .unwrap(), - funds: vec![], - } - .into(), - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - } - ); - - assert_eq!( - log_withdrawn_share, - &attr("withdrawn_share", 100u128.to_string()) - ); - assert_eq!( - log_refund_assets, - &attr("refund_assets", "9uusd, 9asset0000") - ); -} - -#[test] -fn query_twap() { - let mut deps = mock_dependencies(&[]); - let mut env = mock_env(); - - let user = "user"; - - // setup some cw20 tokens, so the queries don't fail - deps.querier.with_token_balances(&[ - ( - &"asset0000".into(), - &[(&MOCK_CONTRACT_ADDR.into(), &0u128.into())], - ), - ( - &"liquidity0000".into(), - &[(&MOCK_CONTRACT_ADDR.into(), &0u128.into())], - ), - ]); - - let uusd = AssetInfoValidated::Native("uusd".to_string()); - let token = AssetInfoValidated::Token(Addr::unchecked("asset0000")); - - // instantiate the contract - let msg = InstantiateMsg { - asset_infos: vec![uusd.clone().into(), token.clone().into()], - token_code_id: 10u64, - factory_addr: String::from("factory"), - init_params: None, - staking_config: default_stake_config(), - trading_starts: 0, - fee_config: FeeConfig { - total_fee_bps: 0, - protocol_fee_bps: 0, - }, - circuit_breaker: None, - }; - instantiate(deps.as_mut(), env.clone(), mock_info("owner", &[]), msg).unwrap(); - - // Store the liquidity token - store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); - - // provide liquidity to get a first price - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: uusd.clone().into(), - amount: 1_000_000u128.into(), - }, - Asset { - info: token.into(), - amount: 1_000_000u128.into(), - }, - ], - slippage_tolerance: None, - receiver: None, - }; - // need to set balance manually to simulate funds being sent - deps.querier - .with_balance(&[(&MOCK_CONTRACT_ADDR.into(), &coins(1_000_000u128, "uusd"))]); - execute( - deps.as_mut(), - env.clone(), - mock_info(user, &coins(1_000_000u128, "uusd")), - msg, - ) - .unwrap(); - - // set cw20 balance manually - deps.querier.with_token_balances(&[ - ( - &"asset0000".into(), - &[(&MOCK_CONTRACT_ADDR.into(), &1_000_000u128.into())], - ), - ( - &"liquidity0000".into(), - &[(&MOCK_CONTRACT_ADDR.into(), &0u128.into())], - ), - ]); - - // querying TWAP after first price change should fail, because only one price is recorded - let err = query( - deps.as_ref(), - env.clone(), - QueryMsg::Twap { - duration: SamplePeriod::HalfHour, - start_age: 1, - end_age: Some(0), - }, - ) - .unwrap_err(); - - assert_eq!( - StdError::generic_err("start index is earlier than earliest recorded price data"), - err - ); - - // forward time half an hour - const HALF_HOUR: u64 = 30 * 60; - env.block.time = env.block.time.plus_seconds(HALF_HOUR); - - // swap to get a second price - let msg = ExecuteMsg::Swap { - offer_asset: Asset { - info: uusd.into(), - amount: 1_000u128.into(), - }, - to: None, - max_spread: None, - belief_price: None, - ask_asset_info: None, - referral_address: None, - referral_commission: None, - }; - // need to set balance manually to simulate funds being sent - deps.querier - .with_balance(&[(&MOCK_CONTRACT_ADDR.into(), &coins(1_001_000u128, "uusd"))]); - execute( - deps.as_mut(), - env.clone(), - mock_info(user, &coins(1_000u128, "uusd")), - msg, - ) - .unwrap(); - - // forward time half an hour again for the last change to accumulate - env.block.time = env.block.time.plus_seconds(HALF_HOUR); - - // query twap after swap price change - let twap: TwapResponse = from_binary( - &query( - deps.as_ref(), - env, - QueryMsg::Twap { - duration: SamplePeriod::HalfHour, - start_age: 1, - end_age: Some(0), - }, - ) - .unwrap(), - ) - .unwrap(); - - assert!(twap.a_per_b > Decimal::one()); - assert!(twap.b_per_a < Decimal::one()); - assert_approx_eq!( - twap.a_per_b.numerator(), - Decimal::from_ratio(1_001_000u128, 999_000u128).numerator(), - "0.000002", - "twap should be slightly below 1" - ); - assert_approx_eq!( - twap.b_per_a.numerator(), - Decimal::from_ratio(999_000u128, 1_001_000u128).numerator(), - "0.000002", - "twap should be slightly above 1" - ); -} - -#[test] -fn try_native_to_token() { - let total_share = Uint128::new(30000000000u128); - let asset_pool_amount = Uint128::new(20000000000u128); - let collateral_pool_amount = Uint128::new(30000000000u128); - let offer_amount = Uint128::new(1500000000u128); - - let mut deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: collateral_pool_amount + offer_amount, /* user deposit must be pre-applied */ - }]); - - deps.querier.with_token_balances(&[ - ( - &String::from("liquidity0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &total_share)], - ), - ( - &String::from("asset0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &asset_pool_amount)], - ), - ]); - - let msg = InstantiateMsg { - asset_infos: vec![ - AssetInfo::Native("uusd".to_string()), - AssetInfo::Token("asset0000".to_string()), - ], - token_code_id: 10u64, - factory_addr: String::from("factory"), - init_params: None, - staking_config: default_stake_config(), - trading_starts: 0, - fee_config: FeeConfig { - total_fee_bps: 30, - protocol_fee_bps: 1660, - }, - circuit_breaker: None, - }; - - let env = mock_env(); - let info = mock_info("addr0000", &[]); - // we can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); - - // Store liquidity token - store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); - - // need to initialize oracle, because we don't call `provide_liquidity` in this test - dex::oracle::initialize_oracle( - &mut deps.storage, - &mock_env_with_block_time(0), - Decimal::one(), - ) - .unwrap(); - - // Normal swap - let msg = ExecuteMsg::Swap { - offer_asset: Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: offer_amount, - }, - ask_asset_info: None, - belief_price: None, - max_spread: Some(Decimal::percent(50)), - to: None, - referral_address: None, - referral_commission: None, - }; - let env = mock_env_with_block_time(1000); - let info = mock_info( - "addr0000", - &[Coin { - denom: "uusd".to_string(), - amount: offer_amount, - }], - ); - - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - let msg_transfer = res.messages.get(0).expect("no message"); - - // Current price is 1.5, so expected return without spread is 1000 - // 952380952 = 20000000000 - (30000000000 * 20000000000) / (30000000000 + 1500000000) - let expected_ret_amount = Uint128::new(952_380_952u128); - - // 47619047 = 1500000000 * (20000000000 / 30000000000) - 952380952 - let expected_spread_amount = Uint128::new(47619047u128); - - let expected_commission_amount = expected_ret_amount.multiply_ratio(3u128, 1000u128); // 0.3% - let expected_protocol_fee_amount = expected_commission_amount.multiply_ratio(166u128, 1000u128); // 0.166 - - let expected_return_amount = expected_ret_amount - .checked_sub(expected_commission_amount) - .unwrap(); - - // Check simulation result - deps.querier.with_balance(&[( - &String::from(MOCK_CONTRACT_ADDR), - &[Coin { - denom: "uusd".to_string(), - amount: collateral_pool_amount, /* user deposit must be pre-applied */ - }], - )]); - - let err = query_simulation( - deps.as_ref(), - Asset { - info: AssetInfo::Native("cny".to_string()), - amount: offer_amount, - }, - false, - None, - ) - .unwrap_err(); - assert_eq!( - err.to_string(), - "Generic error: Given offer asset does not belong in the pair" - ); - - let simulation_res: SimulationResponse = query_simulation( - deps.as_ref(), - Asset { - info: AssetInfo::Native("uusd".to_string()), - amount: offer_amount, - }, - false, - None, - ) - .unwrap(); - assert_eq!(expected_return_amount, simulation_res.return_amount); - assert_eq!(expected_commission_amount, simulation_res.commission_amount); - assert_eq!(expected_spread_amount, simulation_res.spread_amount); - - // Check reverse simulation result - let err = query_reverse_simulation( - deps.as_ref(), - Asset { - info: AssetInfo::Native("cny".to_string()), - amount: expected_return_amount, - }, - false, - None, - ) - .unwrap_err(); - assert_eq!( - err.to_string(), - "Generic error: Given ask asset doesn't belong to pairs" - ); - - let reverse_simulation_res: ReverseSimulationResponse = query_reverse_simulation( - deps.as_ref(), - Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: expected_return_amount, - }, - false, - None, - ) - .unwrap(); - assert!( - (offer_amount.u128() as i128 - reverse_simulation_res.offer_amount.u128() as i128).abs() - < 5i128 - ); - assert!( - (expected_commission_amount.u128() as i128 - - reverse_simulation_res.commission_amount.u128() as i128) - .abs() - < 5i128 - ); - assert!( - (expected_spread_amount.u128() as i128 - - reverse_simulation_res.spread_amount.u128() as i128) - .abs() - < 5i128 - ); - - assert_eq!( - res.attributes, - vec![ - attr("action", "swap"), - attr("sender", "addr0000"), - attr("receiver", "addr0000"), - attr("offer_asset", "uusd"), - attr("ask_asset", "asset0000"), - attr("offer_amount", offer_amount.to_string()), - attr("return_amount", expected_return_amount.to_string()), - attr("spread_amount", expected_spread_amount.to_string()), - attr("commission_amount", expected_commission_amount.to_string()), - attr( - "protocol_fee_amount", - expected_protocol_fee_amount.to_string() - ), - ] - ); - - assert_eq!( - &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("asset0000"), - msg: to_binary(&Cw20ExecuteMsg::Transfer { - recipient: String::from("addr0000"), - amount: expected_return_amount, - }) - .unwrap(), - funds: vec![], - } - .into(), - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - }, - msg_transfer, - ); -} - -#[test] -fn try_token_to_native() { - let total_share = Uint128::new(20000000000u128); - let asset_pool_amount = Uint128::new(30000000000u128); - let collateral_pool_amount = Uint128::new(20000000000u128); - let offer_amount = Uint128::new(1500000000u128); - - let mut deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: collateral_pool_amount, - }]); - - deps.querier.with_token_balances(&[ - ( - &String::from("liquidity0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &total_share)], - ), - ( - &String::from("asset0000"), - &[( - &String::from(MOCK_CONTRACT_ADDR), - &(asset_pool_amount + offer_amount), - )], - ), - ]); - - let msg = InstantiateMsg { - asset_infos: vec![ - AssetInfo::Native("uusd".to_string()), - AssetInfo::Token("asset0000".to_string()), - ], - token_code_id: 10u64, - factory_addr: String::from("factory"), - init_params: None, - staking_config: default_stake_config(), - trading_starts: 0, - fee_config: FeeConfig { - total_fee_bps: 30, - protocol_fee_bps: 1660, - }, - circuit_breaker: None, - }; - - let env = mock_env(); - let info = mock_info("addr0000", &[]); - // We can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); - - // Store liquidity token - store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); - - // need to initialize oracle, because we don't call `provide_liquidity` in this test - dex::oracle::initialize_oracle( - &mut deps.storage, - &mock_env_with_block_time(0), - Decimal::one(), - ) - .unwrap(); - - // Unauthorized access; can not execute swap directy for token swap - let msg = ExecuteMsg::Swap { - offer_asset: Asset { - info: AssetInfo::Token("asset0000".to_string()), - amount: offer_amount, - }, - ask_asset_info: None, - belief_price: None, - max_spread: None, - to: None, - referral_address: None, - referral_commission: None, - }; - let env = mock_env_with_block_time(1000); - let info = mock_info("addr0000", &[]); - let res = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(res, ContractError::Unauthorized {}); - - // Normal sell - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("addr0000"), - amount: offer_amount, - msg: to_binary(&Cw20HookMsg::Swap { - ask_asset_info: None, - belief_price: None, - max_spread: Some(Decimal::percent(50)), - to: None, - referral_address: None, - referral_commission: None, - }) - .unwrap(), - }); - let env = mock_env_with_block_time(1000); - let info = mock_info("asset0000", &[]); - - let res = execute(deps.as_mut(), env, info, msg).unwrap(); - let msg_transfer = res.messages.get(0).expect("no message"); - - // Current price is 1.5, so expected return without spread is 1000 - // 952380952,3809524 = 20000000000 - (30000000000 * 20000000000) / (30000000000 + 1500000000) - let expected_ret_amount = Uint128::new(952_380_952u128); - - // 47619047 = 1500000000 * (20000000000 / 30000000000) - 952380952,3809524 - let expected_spread_amount = Uint128::new(47619047u128); - - let expected_commission_amount = expected_ret_amount.multiply_ratio(3u128, 1000u128); // 0.3% - let expected_protocol_fee_amount = expected_commission_amount.multiply_ratio(166u128, 1000u128); - let expected_return_amount = expected_ret_amount - .checked_sub(expected_commission_amount) - .unwrap(); - - // Check simulation res - // Return asset token balance as normal - deps.querier.with_token_balances(&[ - ( - &String::from("liquidity0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &total_share)], - ), - ( - &String::from("asset0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &(asset_pool_amount))], - ), - ]); - - let simulation_res: SimulationResponse = query_simulation( - deps.as_ref(), - Asset { - amount: offer_amount, - info: AssetInfo::Token("asset0000".to_string()), - }, - false, - None, - ) - .unwrap(); - assert_eq!(expected_return_amount, simulation_res.return_amount); - assert_eq!(expected_commission_amount, simulation_res.commission_amount); - assert_eq!(expected_spread_amount, simulation_res.spread_amount); - - // Check reverse simulation result - let reverse_simulation_res: ReverseSimulationResponse = query_reverse_simulation( - deps.as_ref(), - Asset { - amount: expected_return_amount, - info: AssetInfo::Native("uusd".to_string()), - }, - false, - None, - ) - .unwrap(); - assert!( - (offer_amount.u128() as i128 - reverse_simulation_res.offer_amount.u128() as i128).abs() - < 5i128 - ); - assert!( - (expected_commission_amount.u128() as i128 - - reverse_simulation_res.commission_amount.u128() as i128) - .abs() - < 5i128 - ); - assert!( - (expected_spread_amount.u128() as i128 - - reverse_simulation_res.spread_amount.u128() as i128) - .abs() - < 5i128 - ); - - assert_eq!( - res.attributes, - vec![ - attr("action", "swap"), - attr("sender", "addr0000"), - attr("receiver", "addr0000"), - attr("offer_asset", "asset0000"), - attr("ask_asset", "uusd"), - attr("offer_amount", offer_amount.to_string()), - attr("return_amount", expected_return_amount.to_string()), - attr("spread_amount", expected_spread_amount.to_string()), - attr("commission_amount", expected_commission_amount.to_string()), - attr( - "protocol_fee_amount", - expected_protocol_fee_amount.to_string() - ), - ] - ); - - assert_eq!( - &SubMsg { - msg: CosmosMsg::Bank(BankMsg::Send { - to_address: String::from("addr0000"), - amount: vec![Coin { - denom: "uusd".to_string(), - amount: expected_return_amount - }], - }), - id: 0, - gas_limit: None, - reply_on: ReplyOn::Never, - }, - msg_transfer, - ); - - // Failed due to trying to swap a non token (specifying an address of a non token contract) - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("addr0000"), - amount: offer_amount, - msg: to_binary(&Cw20HookMsg::Swap { - ask_asset_info: None, - belief_price: None, - max_spread: None, - to: None, - referral_address: None, - referral_commission: None, - }) - .unwrap(), - }); - let env = mock_env_with_block_time(1000); - let info = mock_info("liquidtity0000", &[]); - let res = execute(deps.as_mut(), env, info, msg).unwrap_err(); - assert_eq!(res, ContractError::Unauthorized {}); -} - -#[test] -fn test_max_spread() { - assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), - Some(Decimal::percent(1)), - Uint128::from(1200000000u128), - Uint128::from(989999u128), - Uint128::zero(), - ) - .unwrap_err(); - - assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), - Some(Decimal::percent(1)), - Uint128::from(1200000000u128), - Uint128::from(990000u128), - Uint128::zero(), - ) - .unwrap(); - - assert_max_spread( - None, - Some(Decimal::percent(1)), - Uint128::zero(), - Uint128::from(989999u128), - Uint128::from(10001u128), - ) - .unwrap_err(); - - assert_max_spread( - None, - Some(Decimal::percent(1)), - Uint128::zero(), - Uint128::from(990000u128), - Uint128::from(10000u128), - ) - .unwrap(); - - assert_max_spread( - Some(Decimal::from_ratio(1200u128, 1u128)), - Some(Decimal::percent(51)), - Uint128::from(1200000000u128), - Uint128::from(989999u128), - Uint128::zero(), - ) - .unwrap_err(); -} - -#[test] -fn test_query_pool() { - let total_share_amount = Uint128::from(111u128); - let asset_0_amount = Uint128::from(222u128); - let asset_1_amount = Uint128::from(333u128); - let mut deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: asset_0_amount, - }]); - - deps.querier.with_token_balances(&[ - ( - &String::from("asset0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &asset_1_amount)], - ), - ( - &String::from("liquidity0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &total_share_amount)], - ), - ]); - - let msg = InstantiateMsg { - asset_infos: vec![ - AssetInfo::Native("uusd".to_string()), - AssetInfo::Token("asset0000".to_string()), - ], - token_code_id: 10u64, - factory_addr: String::from("factory"), - init_params: None, - staking_config: default_stake_config(), - trading_starts: 0, - fee_config: FeeConfig { - total_fee_bps: 0, - protocol_fee_bps: 0, - }, - circuit_breaker: None, - }; - - let env = mock_env(); - let info = mock_info("addr0000", &[]); - // We can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); - - // Store liquidity token - store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); - - let res: PoolResponse = query_pool(deps.as_ref()).unwrap(); - - assert_eq!( - res.assets, - [ - AssetValidated { - info: AssetInfoValidated::Native("uusd".to_string()), - amount: asset_0_amount - }, - AssetValidated { - info: AssetInfoValidated::Token(Addr::unchecked("asset0000")), - amount: asset_1_amount - } - ] - ); - assert_eq!(res.total_share, total_share_amount); -} - -#[test] -fn test_query_share() { - let total_share_amount = Uint128::from(500u128); - let asset_0_amount = Uint128::from(250u128); - let asset_1_amount = Uint128::from(1000u128); - let mut deps = mock_dependencies(&[Coin { - denom: "uusd".to_string(), - amount: asset_0_amount, - }]); - - deps.querier.with_token_balances(&[ - ( - &String::from("asset0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &asset_1_amount)], - ), - ( - &String::from("liquidity0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &total_share_amount)], - ), - ]); - - let msg = InstantiateMsg { - asset_infos: vec![ - AssetInfo::Native("uusd".to_string()), - AssetInfo::Token("asset0000".to_string()), - ], - token_code_id: 10u64, - factory_addr: String::from("factory"), - init_params: None, - staking_config: default_stake_config(), - trading_starts: 0, - fee_config: FeeConfig { - total_fee_bps: 0, - protocol_fee_bps: 0, - }, - circuit_breaker: None, - }; - - let env = mock_env(); - let info = mock_info("addr0000", &[]); - // We can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); - - // Store liquidity token - store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); - - let res = query_share(deps.as_ref(), Uint128::new(250)).unwrap(); - - assert_eq!(res[0].amount, Uint128::new(125)); - assert_eq!(res[1].amount, Uint128::new(500)); -} - -#[test] -fn test_accumulate_prices() { - struct Case { - block_time: u64, - block_time_last: u64, - last0: u128, - last1: u128, - x_amount: u128, - y_amount: u128, - } - - struct Result { - block_time_last: u64, - price_x: u128, - price_y: u128, - is_some: bool, - } - - let price_precision = 10u128.pow(TWAP_PRECISION.into()); - - let test_cases: Vec<(Case, Result)> = vec![ - ( - Case { - block_time: 1000, - block_time_last: 0, - last0: 0, - last1: 0, - x_amount: 250, - y_amount: 500, - }, - Result { - block_time_last: 1000, - price_x: 2000, // 500/250*1000 - price_y: 500, // 250/500*1000 - is_some: true, - }, - ), - // Same block height, no changes - ( - Case { - block_time: 1000, - block_time_last: 1000, - last0: price_precision, - last1: 2 * price_precision, - x_amount: 250, - y_amount: 500, - }, - Result { - block_time_last: 1000, - price_x: 1, - price_y: 2, - is_some: false, - }, - ), - ( - Case { - block_time: 1500, - block_time_last: 1000, - last0: 500 * price_precision, - last1: 2000 * price_precision, - x_amount: 250, - y_amount: 500, - }, - Result { - block_time_last: 1500, - price_x: 1500, // 500 + (500/250*500) - price_y: 2250, // 2000 + (250/500*500) - is_some: true, - }, - ), - ]; - - for test_case in test_cases { - let (case, result) = test_case; - - let env = mock_env_with_block_time(case.block_time); - let config = accumulate_prices( - &env, - &Config { - pair_info: PairInfo { - asset_infos: vec![ - AssetInfoValidated::Native("uusd".to_string()), - AssetInfoValidated::Token(Addr::unchecked("asset0000")), - ], - contract_addr: Addr::unchecked("pair"), - staking_addr: Addr::unchecked("stake"), - liquidity_token: Addr::unchecked("lp_token"), - pair_type: PairType::Xyk {}, // Implemented in mock querier - fee_config: FeeConfig { - total_fee_bps: 0, - protocol_fee_bps: 0, - }, - }, - factory_addr: Addr::unchecked("factory"), - block_time_last: case.block_time_last, - price0_cumulative_last: Uint128::new(case.last0), - price1_cumulative_last: Uint128::new(case.last1), - trading_starts: 0, - }, - Uint128::new(case.x_amount), - Uint128::new(case.y_amount), - ) - .unwrap(); - - assert_eq!(result.is_some, config.is_some()); - - if let Some(config) = config { - assert_eq!(config.2, result.block_time_last); - assert_eq!( - config.0 / Uint128::from(price_precision), - Uint128::new(result.price_x) - ); - assert_eq!( - config.1 / Uint128::from(price_precision), - Uint128::new(result.price_y) - ); - } - } -} - -fn mock_env_with_block_time(time: u64) -> Env { - let mut env = mock_env(); - env.block = BlockInfo { - height: 1, - time: Timestamp::from_seconds(time), - chain_id: "columbus".to_string(), - }; - env -} - -#[test] -fn compute_swap_rounding() { - let offer_pool = Uint128::from(5_000_000_000_000_u128); - let ask_pool = Uint128::from(1_000_000_000_u128); - let return_amount = Uint128::from(0_u128); - let spread_amount = Uint128::from(0_u128); - let commission_amount = Uint128::from(0_u128); - let offer_amount = Uint128::from(1_u128); - - assert_eq!( - compute_swap(offer_pool, ask_pool, offer_amount, Decimal::zero()), - Ok((return_amount, spread_amount, commission_amount)) - ); -} - -proptest! { - #[test] - fn compute_swap_overflow_test( - offer_pool in 1_000_000..9_000_000_000_000_000_000u128, - ask_pool in 1_000_000..9_000_000_000_000_000_000u128, - offer_amount in 1..100_000_000_000u128, - ) { - - let offer_pool = Uint128::from(offer_pool); - let ask_pool = Uint128::from(ask_pool); - let offer_amount = Uint128::from(offer_amount); - let commission_amount = Decimal::zero(); - - // Make sure there are no overflows - compute_swap( - offer_pool, - ask_pool, - offer_amount, - commission_amount, - ).unwrap(); - } -} - -#[test] -fn ensure_useful_error_messages_are_given_on_swaps() { - const OFFER: Uint128 = Uint128::new(1_000_000_000_000); - const ASK: Uint128 = Uint128::new(1_000_000_000_000); - const AMOUNT: Uint128 = Uint128::new(1_000_000); - const ZERO: Uint128 = Uint128::zero(); - const DZERO: Decimal = Decimal::zero(); - - // Computing ask - assert_eq!( - compute_swap(ZERO, ZERO, ZERO, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_swap(ZERO, ZERO, AMOUNT, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_swap(ZERO, ASK, ZERO, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_swap(ZERO, ASK, AMOUNT, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_swap(OFFER, ZERO, ZERO, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_swap(OFFER, ZERO, AMOUNT, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_swap(OFFER, ASK, ZERO, DZERO).unwrap_err(), - StdError::generic_err("Swap amount must not be zero") - ); - compute_swap(OFFER, ASK, AMOUNT, DZERO).unwrap(); - - // Computing offer - assert_eq!( - compute_offer_amount(ZERO, ZERO, ZERO, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_offer_amount(ZERO, ZERO, AMOUNT, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_offer_amount(ZERO, ASK, ZERO, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_offer_amount(ZERO, ASK, AMOUNT, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_offer_amount(OFFER, ZERO, ZERO, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_offer_amount(OFFER, ZERO, AMOUNT, DZERO).unwrap_err(), - StdError::generic_err("One of the pools is empty") - ); - assert_eq!( - compute_offer_amount(OFFER, ASK, ZERO, DZERO).unwrap_err(), - StdError::generic_err("Swap amount must not be zero") - ); - compute_offer_amount(OFFER, ASK, AMOUNT, DZERO).unwrap(); -} +// #[test] +// fn test_freezing_a_pool_blocking_actions_then_unfreeze() { +// let mut deps = mock_dependencies(&[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::new(200_000000000000000000u128), +// }]); +// let offer_amount = Uint128::new(1500000000u128); +// +// deps.querier.with_token_balances(&[ +// ( +// &String::from("asset0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(0))], +// ), +// ( +// &String::from("liquidity0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(0))], +// ), +// ]); +// +// let msg = InstantiateMsg { +// asset_infos: vec![ +// AssetInfo::Native("uusd".to_string()), +// AssetInfo::Token("asset0000".to_string()), +// ], +// token_code_id: 10u64, +// factory_addr: String::from("factory"), +// init_params: None, +// staking_config: default_stake_config(), +// trading_starts: 0, +// fee_config: FeeConfig { +// total_fee_bps: 0, +// protocol_fee_bps: 0, +// }, +// circuit_breaker: None, +// }; +// +// let env = mock_env(); +// let info = mock_info("addr0000", &[]); +// // We can just call .unwrap() to assert this was a success +// let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); +// +// // Store liquidity token +// store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); +// +// // Successfully provide liquidity for the existing pool +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// ], +// slippage_tolerance: None, +// receiver: None, +// }; +// +// let env = mock_env(); +// let info = mock_info( +// "addr0000", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(100_000000000000000000u128), +// }], +// ); +// // Do one successful action before freezing just for sanity +// execute(deps.as_mut(), env.clone(), info, msg).unwrap(); +// +// // Manually set the correct balances for the pool +// deps.querier.with_balance(&[( +// &String::from(MOCK_CONTRACT_ADDR), +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::new(100_000000000000000000), +// }], +// )]); +// deps.querier.with_token_balances(&[ +// ( +// &String::from("liquidity0000"), +// &[ +// (&String::from(MOCK_CONTRACT_ADDR), &MINIMUM_LIQUIDITY_AMOUNT), +// ( +// &String::from("addr0000"), +// &(Uint128::new(100_000000000000000000) - MINIMUM_LIQUIDITY_AMOUNT), +// ), +// ], +// ), +// ( +// &String::from("asset0000"), +// &[( +// &String::from(MOCK_CONTRACT_ADDR), +// &Uint128::new(100_000000000000000000), +// )], +// ), +// ]); +// +// // Migrate with the freeze migrate message +// migrate( +// deps.as_mut(), +// env.clone(), +// MigrateMsg::UpdateFreeze { +// frozen: true, +// circuit_breaker: Some("addr0000".to_string()), +// }, +// ) +// .unwrap(); +// +// // Failing Execute Actions due to frozen +// +// // This should now fail, its a good TX with all the normal setup done but because of freezing it should fail +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(200_000000000000000000u128), +// }, +// ], +// slippage_tolerance: Some(Decimal::percent(50)), +// receiver: None, +// }; +// +// let env = mock_env_with_block_time(env.block.time.seconds() + 1000); +// let info = mock_info( +// "addr0000", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(200_000000000000000000u128), +// }], +// ); +// +// // Assert an error and that its frozen +// let res: ContractError = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); +// assert_eq!(res, ContractError::ContractFrozen {}); +// // Also do a swap, which should also fail +// let msg = ExecuteMsg::Swap { +// offer_asset: Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: 1_000u128.into(), +// }, +// to: None, +// max_spread: None, +// belief_price: None, +// ask_asset_info: None, +// referral_address: None, +// referral_commission: None, +// }; +// +// let info = mock_info( +// "addr0000", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(1000u128), +// }], +// ); +// // Assert an error and that its frozen +// let res: ContractError = execute(deps.as_mut(), env.clone(), info.clone(), msg).unwrap_err(); +// assert_eq!(res, ContractError::ContractFrozen {}); +// +// let msg = ExecuteMsg::UpdateFees { +// fee_config: FeeConfig { +// total_fee_bps: 5, +// protocol_fee_bps: 5, +// }, +// }; +// let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); +// assert_eq!(res, ContractError::ContractFrozen {}); +// +// // Normal sell but with CW20 +// let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { +// sender: String::from("addr0000"), +// amount: offer_amount, +// msg: to_binary(&Cw20HookMsg::Swap { +// ask_asset_info: None, +// belief_price: None, +// max_spread: Some(Decimal::percent(50)), +// to: None, +// referral_address: None, +// referral_commission: None, +// }) +// .unwrap(), +// }); +// let info = mock_info("asset0000", &[]); +// +// let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); +// assert_eq!(res, ContractError::ContractFrozen {}); +// +// // But we can withdraw liquidity +// +// // Withdraw liquidity +// let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { +// sender: String::from("addr0000"), +// msg: to_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), +// amount: Uint128::new(100u128), +// }); +// +// let info = mock_info("liquidity0000", &[]); +// // We just want to ensure it doesn't fail with a ContractFrozen error +// execute(deps.as_mut(), env.clone(), info, msg).unwrap(); +// +// // Unfreeze the pool again using the Freeze message rather than another migrate +// let msg = ExecuteMsg::Freeze { frozen: false }; +// // First try a failing case with addr0001 +// let info = mock_info("addr0001", &[]); +// // Rather than being unfrozen it returns unauthorized as addr0000 is the only addr that can currently call Freeze unless another migration changes that +// let err = execute(deps.as_mut(), env.clone(), info, msg.clone()).unwrap_err(); +// assert_eq!(err, ContractError::Unauthorized {}); +// // But the assigned circuit_breaker address can do an unfreeze with the ExecuteMsg variant +// let info = mock_info("addr0000", &[]); +// // And it works +// execute(deps.as_mut(), env.clone(), info, msg).unwrap(); +// +// // Testing actions working again after unfreeze +// +// // Initialize token balance to 1:1 +// deps.querier.with_balance(&[( +// &String::from(MOCK_CONTRACT_ADDR), +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::new(100_000000000000000000 + 99_000000000000000000 /* user deposit must be pre-applied */), +// }], +// )]); +// +// deps.querier.with_token_balances(&[ +// ( +// &String::from("liquidity0000"), +// &[( +// &String::from(MOCK_CONTRACT_ADDR), +// &Uint128::new(100_000000000000000000), +// )], +// ), +// ( +// &String::from("asset0000"), +// &[( +// &String::from(MOCK_CONTRACT_ADDR), +// &Uint128::new(100_000000000000000000), +// )], +// ), +// ]); +// +// // Successfully provides liquidity +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(99_000000000000000000u128), +// }, +// ], +// slippage_tolerance: Some(Decimal::percent(1)), +// receiver: None, +// }; +// +// let info = mock_info( +// "addr0001", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(99_000000000000000000u128), +// }], +// ); +// execute(deps.as_mut(), env.clone(), info, msg).unwrap(); +// +// // Normal sell but with CW20 +// let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { +// sender: String::from("addr0000"), +// amount: offer_amount, +// msg: to_binary(&Cw20HookMsg::Swap { +// ask_asset_info: None, +// belief_price: None, +// max_spread: Some(Decimal::percent(50)), +// to: None, +// referral_address: None, +// referral_commission: None, +// }) +// .unwrap(), +// }); +// let info = mock_info("asset0000", &[]); +// +// execute(deps.as_mut(), env, info, msg).unwrap(); +// } +// +// #[test] +// fn provide_liquidity() { +// let mut deps = mock_dependencies(&[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::new(200_000000000000000000u128), +// }]); +// +// deps.querier.with_token_balances(&[ +// ( +// &String::from("asset0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(0))], +// ), +// ( +// &String::from("liquidity0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(0))], +// ), +// ]); +// +// let msg = InstantiateMsg { +// asset_infos: vec![ +// AssetInfo::Native("uusd".to_string()), +// AssetInfo::Token("asset0000".to_string()), +// ], +// token_code_id: 10u64, +// factory_addr: String::from("factory"), +// init_params: None, +// staking_config: default_stake_config(), +// trading_starts: 0, +// fee_config: FeeConfig { +// total_fee_bps: 0, +// protocol_fee_bps: 0, +// }, +// circuit_breaker: None, +// }; +// +// let env = mock_env(); +// let info = mock_info("addr0000", &[]); +// // We can just call .unwrap() to assert this was a success +// let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); +// +// // Store liquidity token +// store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); +// +// // Successfully provide liquidity for the existing pool +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// ], +// slippage_tolerance: None, +// receiver: None, +// }; +// +// let env = mock_env(); +// let info = mock_info( +// "addr0000", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(100_000000000000000000u128), +// }], +// ); +// let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); +// let transfer_from_msg = res.messages.get(0).expect("no message"); +// let mint_min_liquidity_msg = res.messages.get(1).expect("no message"); +// let mint_receiver_msg = res.messages.get(2).expect("no message"); +// assert_eq!( +// transfer_from_msg, +// &SubMsg { +// msg: WasmMsg::Execute { +// contract_addr: String::from("asset0000"), +// msg: to_binary(&Cw20ExecuteMsg::TransferFrom { +// owner: String::from("addr0000"), +// recipient: String::from(MOCK_CONTRACT_ADDR), +// amount: Uint128::from(100_000000000000000000u128), +// }) +// .unwrap(), +// funds: vec![], +// } +// .into(), +// id: 0, +// gas_limit: None, +// reply_on: ReplyOn::Never +// } +// ); +// assert_eq!( +// mint_min_liquidity_msg, +// &SubMsg { +// msg: WasmMsg::Execute { +// contract_addr: String::from("liquidity0000"), +// msg: to_binary(&Cw20ExecuteMsg::Mint { +// recipient: String::from(MOCK_CONTRACT_ADDR), +// amount: Uint128::from(1000_u128), +// }) +// .unwrap(), +// funds: vec![], +// } +// .into(), +// id: 0, +// gas_limit: None, +// reply_on: ReplyOn::Never, +// } +// ); +// assert_eq!( +// mint_receiver_msg, +// &SubMsg { +// msg: WasmMsg::Execute { +// contract_addr: String::from("liquidity0000"), +// msg: to_binary(&Cw20ExecuteMsg::Mint { +// recipient: String::from("addr0000"), +// amount: Uint128::from(99_999999999999999000u128), +// }) +// .unwrap(), +// funds: vec![], +// } +// .into(), +// id: 0, +// gas_limit: None, +// reply_on: ReplyOn::Never, +// } +// ); +// +// // Provide more liquidity 1:2, which is not propotional to 1:1, +// // It must accept 1:1 and treat the leftover amount as a donation +// deps.querier.with_balance(&[( +// &String::from(MOCK_CONTRACT_ADDR), +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::new(200_000000000000000000 + 200_000000000000000000 /* user deposit must be pre-applied */), +// }], +// )]); +// +// deps.querier.with_token_balances(&[ +// ( +// &String::from("liquidity0000"), +// &[( +// &String::from(MOCK_CONTRACT_ADDR), +// &Uint128::new(100_000000000000000000), +// )], +// ), +// ( +// &String::from("asset0000"), +// &[( +// &String::from(MOCK_CONTRACT_ADDR), +// &Uint128::new(200_000000000000000000), +// )], +// ), +// ]); +// +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(200_000000000000000000u128), +// }, +// ], +// slippage_tolerance: Some(Decimal::percent(50)), +// receiver: None, +// }; +// +// let env = mock_env_with_block_time(env.block.time.seconds() + 1000); +// let info = mock_info( +// "addr0000", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(200_000000000000000000u128), +// }], +// ); +// +// // Only accept 100, then 50 share will be generated with 100 * (100 / 200) +// let res: Response = execute(deps.as_mut(), env, info, msg).unwrap(); +// let transfer_from_msg = res.messages.get(0).expect("no message"); +// let mint_msg = res.messages.get(1).expect("no message"); +// assert_eq!( +// transfer_from_msg, +// &SubMsg { +// msg: WasmMsg::Execute { +// contract_addr: String::from("asset0000"), +// msg: to_binary(&Cw20ExecuteMsg::TransferFrom { +// owner: String::from("addr0000"), +// recipient: String::from(MOCK_CONTRACT_ADDR), +// amount: Uint128::from(100_000000000000000000u128), +// }) +// .unwrap(), +// funds: vec![], +// } +// .into(), +// id: 0, +// gas_limit: None, +// reply_on: ReplyOn::Never, +// } +// ); +// assert_eq!( +// mint_msg, +// &SubMsg { +// msg: WasmMsg::Execute { +// contract_addr: String::from("liquidity0000"), +// msg: to_binary(&Cw20ExecuteMsg::Mint { +// recipient: String::from("addr0000"), +// amount: Uint128::from(50_000000000000000000u128), +// }) +// .unwrap(), +// funds: vec![], +// } +// .into(), +// id: 0, +// gas_limit: None, +// reply_on: ReplyOn::Never, +// } +// ); +// +// // Check wrong argument +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(50_000000000000000000u128), +// }, +// ], +// slippage_tolerance: None, +// receiver: None, +// }; +// +// let env = mock_env(); +// let info = mock_info( +// "addr0000", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(100_000000000000000000u128), +// }], +// ); +// let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); +// match res { +// ContractError::Std(StdError::GenericErr { msg, .. }) => assert_eq!( +// msg, +// "Native token balance mismatch between the argument and the transferred".to_string() +// ), +// _ => panic!("Must return generic error"), +// } +// +// // Initialize token amount to the 1:1 ratio +// deps.querier.with_balance(&[( +// &String::from(MOCK_CONTRACT_ADDR), +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::new(100_000000000000000000 + 100_000000000000000000 /* user deposit must be pre-applied */), +// }], +// )]); +// +// deps.querier.with_token_balances(&[ +// ( +// &String::from("liquidity0000"), +// &[( +// &String::from(MOCK_CONTRACT_ADDR), +// &Uint128::new(100_000000000000000000), +// )], +// ), +// ( +// &String::from("asset0000"), +// &[( +// &String::from(MOCK_CONTRACT_ADDR), +// &Uint128::new(100_000000000000000000), +// )], +// ), +// ]); +// +// // Failed because the price is under slippage_tolerance +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(98_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// ], +// slippage_tolerance: Some(Decimal::percent(1)), +// receiver: None, +// }; +// +// let env = mock_env_with_block_time(env.block.time.seconds() + 1000); +// let info = mock_info( +// "addr0001", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(100_000000000000000000u128), +// }], +// ); +// let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); +// assert_eq!(res, ContractError::MaxSlippageAssertion {}); +// +// // Initialize token balance to 1:1 +// deps.querier.with_balance(&[( +// &String::from(MOCK_CONTRACT_ADDR), +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::new(100_000000000000000000 + 98_000000000000000000 /* user deposit must be pre-applied */), +// }], +// )]); +// +// // Failed because the price is under slippage_tolerance +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(98_000000000000000000u128), +// }, +// ], +// slippage_tolerance: Some(Decimal::percent(1)), +// receiver: None, +// }; +// +// let env = mock_env_with_block_time(env.block.time.seconds() + 1000); +// let info = mock_info( +// "addr0001", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(98_000000000000000000u128), +// }], +// ); +// let res = execute(deps.as_mut(), env.clone(), info, msg).unwrap_err(); +// assert_eq!(res, ContractError::MaxSlippageAssertion {}); +// +// // Initialize token amount with a 1:1 ratio +// deps.querier.with_balance(&[( +// &String::from(MOCK_CONTRACT_ADDR), +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::new(100_000000000000000000 + 100_000000000000000000 /* user deposit must be pre-applied */), +// }], +// )]); +// +// // Successfully provides liquidity +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(99_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// ], +// slippage_tolerance: Some(Decimal::percent(1)), +// receiver: None, +// }; +// +// let env = mock_env_with_block_time(env.block.time.seconds() + 1000); +// let info = mock_info( +// "addr0001", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(100_000000000000000000u128), +// }], +// ); +// let _res = execute(deps.as_mut(), env.clone(), info, msg).unwrap(); +// +// // Initialize token balance to 1:1 +// deps.querier.with_balance(&[( +// &String::from(MOCK_CONTRACT_ADDR), +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::new(100_000000000000000000 + 99_000000000000000000 /* user deposit must be pre-applied */), +// }], +// )]); +// +// // Successfully provides liquidity +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(99_000000000000000000u128), +// }, +// ], +// slippage_tolerance: Some(Decimal::percent(1)), +// receiver: None, +// }; +// +// let env = mock_env_with_block_time(env.block.time.seconds() + 1000); +// let info = mock_info( +// "addr0001", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(99_000000000000000000u128), +// }], +// ); +// execute(deps.as_mut(), env, info, msg).unwrap(); +// +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::zero(), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(99_000000000000000000u128), +// }, +// ], +// slippage_tolerance: Some(Decimal::percent(1)), +// receiver: None, +// }; +// let info = mock_info( +// "addr0001", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(99_000000000000000000u128), +// }], +// ); +// let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); +// assert_eq!(err, ContractError::InvalidZeroAmount {}); +// +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: Uint128::from(100_000000000000000000u128), +// }, +// ], +// slippage_tolerance: Some(Decimal::percent(51)), +// receiver: None, +// }; +// let info = mock_info( +// "addr0001", +// &[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(100_000000000000000000u128), +// }], +// ); +// let err = execute(deps.as_mut(), mock_env(), info, msg).unwrap_err(); +// assert_eq!(err, ContractError::AllowedSpreadAssertion {}); +// } +// +// #[test] +// fn withdraw_liquidity() { +// let mut deps = mock_dependencies(&[Coin { +// denom: "uusd".to_string(), +// amount: Uint128::new(100u128), +// }]); +// +// deps.querier.with_token_balances(&[ +// ( +// &String::from("liquidity0000"), +// &[ +// (&String::from("addr0000"), &Uint128::new(100u128)), +// (&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(1000u128)), // MIN_LIQUIDITY_AMOUNT +// ], +// ), +// ( +// &String::from("asset0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(100u128))], +// ), +// ]); +// +// let msg = InstantiateMsg { +// asset_infos: vec![ +// AssetInfo::Native("uusd".to_string()), +// AssetInfo::Token("asset0000".to_string()), +// ], +// token_code_id: 10u64, +// +// factory_addr: String::from("factory"), +// init_params: None, +// staking_config: default_stake_config(), +// trading_starts: 0, +// fee_config: FeeConfig { +// total_fee_bps: 0, +// protocol_fee_bps: 0, +// }, +// circuit_breaker: None, +// }; +// +// let env = mock_env(); +// let info = mock_info("addr0000", &[]); +// // We can just call .unwrap() to assert this was a success +// let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); +// +// // Store liquidity token +// store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); +// +// // need to initialize oracle, because we don't call `provide_liquidity` in this test +// dex::oracle::initialize_oracle( +// &mut deps.storage, +// &mock_env_with_block_time(0), +// Decimal::one(), +// ) +// .unwrap(); +// +// // Withdraw liquidity +// let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { +// sender: String::from("addr0000"), +// msg: to_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), +// amount: Uint128::new(100u128), +// }); +// +// let env = mock_env(); +// let info = mock_info("liquidity0000", &[]); +// let res = execute(deps.as_mut(), env, info, msg).unwrap(); +// let log_withdrawn_share = res.attributes.get(2).expect("no log"); +// let log_refund_assets = res.attributes.get(3).expect("no log"); +// let msg_refund_0 = res.messages.get(0).expect("no message"); +// let msg_refund_1 = res.messages.get(1).expect("no message"); +// let msg_burn_liquidity = res.messages.get(2).expect("no message"); +// assert_eq!( +// msg_refund_0, +// &SubMsg { +// msg: CosmosMsg::Bank(BankMsg::Send { +// to_address: String::from("addr0000"), +// amount: vec![Coin { +// denom: "uusd".to_string(), +// amount: Uint128::from(9u128), +// }], +// }), +// id: 0, +// gas_limit: None, +// reply_on: ReplyOn::Never, +// } +// ); +// assert_eq!( +// msg_refund_1, +// &SubMsg { +// msg: WasmMsg::Execute { +// contract_addr: String::from("asset0000"), +// msg: to_binary(&Cw20ExecuteMsg::Transfer { +// recipient: String::from("addr0000"), +// amount: Uint128::from(9u128), +// }) +// .unwrap(), +// funds: vec![], +// } +// .into(), +// id: 0, +// gas_limit: None, +// reply_on: ReplyOn::Never, +// } +// ); +// assert_eq!( +// msg_burn_liquidity, +// &SubMsg { +// msg: WasmMsg::Execute { +// contract_addr: String::from("liquidity0000"), +// msg: to_binary(&Cw20ExecuteMsg::Burn { +// amount: Uint128::from(100u128), +// }) +// .unwrap(), +// funds: vec![], +// } +// .into(), +// id: 0, +// gas_limit: None, +// reply_on: ReplyOn::Never, +// } +// ); +// +// assert_eq!( +// log_withdrawn_share, +// &attr("withdrawn_share", 100u128.to_string()) +// ); +// assert_eq!( +// log_refund_assets, +// &attr("refund_assets", "9uusd, 9asset0000") +// ); +// } +// +// #[test] +// fn query_twap() { +// let mut deps = mock_dependencies(&[]); +// let mut env = mock_env(); +// +// let user = "user"; +// +// // setup some cw20 tokens, so the queries don't fail +// deps.querier.with_token_balances(&[ +// ( +// &"asset0000".into(), +// &[(&MOCK_CONTRACT_ADDR.into(), &0u128.into())], +// ), +// ( +// &"liquidity0000".into(), +// &[(&MOCK_CONTRACT_ADDR.into(), &0u128.into())], +// ), +// ]); +// +// let uusd = AssetInfoValidated::Native("uusd".to_string()); +// let token = AssetInfoValidated::Token(Addr::unchecked("asset0000")); +// +// // instantiate the contract +// let msg = InstantiateMsg { +// asset_infos: vec![uusd.clone().into(), token.clone().into()], +// token_code_id: 10u64, +// factory_addr: String::from("factory"), +// init_params: None, +// staking_config: default_stake_config(), +// trading_starts: 0, +// fee_config: FeeConfig { +// total_fee_bps: 0, +// protocol_fee_bps: 0, +// }, +// circuit_breaker: None, +// }; +// instantiate(deps.as_mut(), env.clone(), mock_info("owner", &[]), msg).unwrap(); +// +// // Store the liquidity token +// store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); +// +// // provide liquidity to get a first price +// let msg = ExecuteMsg::ProvideLiquidity { +// assets: vec![ +// Asset { +// info: uusd.clone().into(), +// amount: 1_000_000u128.into(), +// }, +// Asset { +// info: token.into(), +// amount: 1_000_000u128.into(), +// }, +// ], +// slippage_tolerance: None, +// receiver: None, +// }; +// // need to set balance manually to simulate funds being sent +// deps.querier +// .with_balance(&[(&MOCK_CONTRACT_ADDR.into(), &coins(1_000_000u128, "uusd"))]); +// execute( +// deps.as_mut(), +// env.clone(), +// mock_info(user, &coins(1_000_000u128, "uusd")), +// msg, +// ) +// .unwrap(); +// +// // set cw20 balance manually +// deps.querier.with_token_balances(&[ +// ( +// &"asset0000".into(), +// &[(&MOCK_CONTRACT_ADDR.into(), &1_000_000u128.into())], +// ), +// ( +// &"liquidity0000".into(), +// &[(&MOCK_CONTRACT_ADDR.into(), &0u128.into())], +// ), +// ]); +// +// // querying TWAP after first price change should fail, because only one price is recorded +// let err = query( +// deps.as_ref(), +// env.clone(), +// QueryMsg::Twap { +// duration: SamplePeriod::HalfHour, +// start_age: 1, +// end_age: Some(0), +// }, +// ) +// .unwrap_err(); +// +// assert_eq!( +// StdError::generic_err("start index is earlier than earliest recorded price data"), +// err +// ); +// +// // forward time half an hour +// const HALF_HOUR: u64 = 30 * 60; +// env.block.time = env.block.time.plus_seconds(HALF_HOUR); +// +// // swap to get a second price +// let msg = ExecuteMsg::Swap { +// offer_asset: Asset { +// info: uusd.into(), +// amount: 1_000u128.into(), +// }, +// to: None, +// max_spread: None, +// belief_price: None, +// ask_asset_info: None, +// referral_address: None, +// referral_commission: None, +// }; +// // need to set balance manually to simulate funds being sent +// deps.querier +// .with_balance(&[(&MOCK_CONTRACT_ADDR.into(), &coins(1_001_000u128, "uusd"))]); +// execute( +// deps.as_mut(), +// env.clone(), +// mock_info(user, &coins(1_000u128, "uusd")), +// msg, +// ) +// .unwrap(); +// +// // forward time half an hour again for the last change to accumulate +// env.block.time = env.block.time.plus_seconds(HALF_HOUR); +// +// // query twap after swap price change +// let twap: TwapResponse = from_binary( +// &query( +// deps.as_ref(), +// env, +// QueryMsg::Twap { +// duration: SamplePeriod::HalfHour, +// start_age: 1, +// end_age: Some(0), +// }, +// ) +// .unwrap(), +// ) +// .unwrap(); +// +// assert!(twap.a_per_b > Decimal::one()); +// assert!(twap.b_per_a < Decimal::one()); +// assert_approx_eq!( +// twap.a_per_b.numerator(), +// Decimal::from_ratio(1_001_000u128, 999_000u128).numerator(), +// "0.000002", +// "twap should be slightly below 1" +// ); +// assert_approx_eq!( +// twap.b_per_a.numerator(), +// Decimal::from_ratio(999_000u128, 1_001_000u128).numerator(), +// "0.000002", +// "twap should be slightly above 1" +// ); +// } +// +// #[test] +// fn try_native_to_token() { +// let total_share = Uint128::new(30000000000u128); +// let asset_pool_amount = Uint128::new(20000000000u128); +// let collateral_pool_amount = Uint128::new(30000000000u128); +// let offer_amount = Uint128::new(1500000000u128); +// +// let mut deps = mock_dependencies(&[Coin { +// denom: "uusd".to_string(), +// amount: collateral_pool_amount + offer_amount, /* user deposit must be pre-applied */ +// }]); +// +// deps.querier.with_token_balances(&[ +// ( +// &String::from("liquidity0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &total_share)], +// ), +// ( +// &String::from("asset0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &asset_pool_amount)], +// ), +// ]); +// +// let msg = InstantiateMsg { +// asset_infos: vec![ +// AssetInfo::Native("uusd".to_string()), +// AssetInfo::Token("asset0000".to_string()), +// ], +// token_code_id: 10u64, +// factory_addr: String::from("factory"), +// init_params: None, +// staking_config: default_stake_config(), +// trading_starts: 0, +// fee_config: FeeConfig { +// total_fee_bps: 30, +// protocol_fee_bps: 1660, +// }, +// circuit_breaker: None, +// }; +// +// let env = mock_env(); +// let info = mock_info("addr0000", &[]); +// // we can just call .unwrap() to assert this was a success +// let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); +// +// // Store liquidity token +// store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); +// +// // need to initialize oracle, because we don't call `provide_liquidity` in this test +// dex::oracle::initialize_oracle( +// &mut deps.storage, +// &mock_env_with_block_time(0), +// Decimal::one(), +// ) +// .unwrap(); +// +// // Normal swap +// let msg = ExecuteMsg::Swap { +// offer_asset: Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: offer_amount, +// }, +// ask_asset_info: None, +// belief_price: None, +// max_spread: Some(Decimal::percent(50)), +// to: None, +// referral_address: None, +// referral_commission: None, +// }; +// let env = mock_env_with_block_time(1000); +// let info = mock_info( +// "addr0000", +// &[Coin { +// denom: "uusd".to_string(), +// amount: offer_amount, +// }], +// ); +// +// let res = execute(deps.as_mut(), env, info, msg).unwrap(); +// let msg_transfer = res.messages.get(0).expect("no message"); +// +// // Current price is 1.5, so expected return without spread is 1000 +// // 952380952 = 20000000000 - (30000000000 * 20000000000) / (30000000000 + 1500000000) +// let expected_ret_amount = Uint128::new(952_380_952u128); +// +// // 47619047 = 1500000000 * (20000000000 / 30000000000) - 952380952 +// let expected_spread_amount = Uint128::new(47619047u128); +// +// let expected_commission_amount = expected_ret_amount.multiply_ratio(3u128, 1000u128); // 0.3% +// let expected_protocol_fee_amount = expected_commission_amount.multiply_ratio(166u128, 1000u128); // 0.166 +// +// let expected_return_amount = expected_ret_amount +// .checked_sub(expected_commission_amount) +// .unwrap(); +// +// // Check simulation result +// deps.querier.with_balance(&[( +// &String::from(MOCK_CONTRACT_ADDR), +// &[Coin { +// denom: "uusd".to_string(), +// amount: collateral_pool_amount, /* user deposit must be pre-applied */ +// }], +// )]); +// +// let err = query_simulation( +// deps.as_ref(), +// Asset { +// info: AssetInfo::Native("cny".to_string()), +// amount: offer_amount, +// }, +// false, +// None, +// ) +// .unwrap_err(); +// assert_eq!( +// err.to_string(), +// "Generic error: Given offer asset does not belong in the pair" +// ); +// +// let simulation_res: SimulationResponse = query_simulation( +// deps.as_ref(), +// Asset { +// info: AssetInfo::Native("uusd".to_string()), +// amount: offer_amount, +// }, +// false, +// None, +// ) +// .unwrap(); +// assert_eq!(expected_return_amount, simulation_res.return_amount); +// assert_eq!(expected_commission_amount, simulation_res.commission_amount); +// assert_eq!(expected_spread_amount, simulation_res.spread_amount); +// +// // Check reverse simulation result +// let err = query_reverse_simulation( +// deps.as_ref(), +// Asset { +// info: AssetInfo::Native("cny".to_string()), +// amount: expected_return_amount, +// }, +// false, +// None, +// ) +// .unwrap_err(); +// assert_eq!( +// err.to_string(), +// "Generic error: Given ask asset doesn't belong to pairs" +// ); +// +// let reverse_simulation_res: ReverseSimulationResponse = query_reverse_simulation( +// deps.as_ref(), +// Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: expected_return_amount, +// }, +// false, +// None, +// ) +// .unwrap(); +// assert!( +// (offer_amount.u128() as i128 - reverse_simulation_res.offer_amount.u128() as i128).abs() +// < 5i128 +// ); +// assert!( +// (expected_commission_amount.u128() as i128 +// - reverse_simulation_res.commission_amount.u128() as i128) +// .abs() +// < 5i128 +// ); +// assert!( +// (expected_spread_amount.u128() as i128 +// - reverse_simulation_res.spread_amount.u128() as i128) +// .abs() +// < 5i128 +// ); +// +// assert_eq!( +// res.attributes, +// vec![ +// attr("action", "swap"), +// attr("sender", "addr0000"), +// attr("receiver", "addr0000"), +// attr("offer_asset", "uusd"), +// attr("ask_asset", "asset0000"), +// attr("offer_amount", offer_amount.to_string()), +// attr("return_amount", expected_return_amount.to_string()), +// attr("spread_amount", expected_spread_amount.to_string()), +// attr("commission_amount", expected_commission_amount.to_string()), +// attr( +// "protocol_fee_amount", +// expected_protocol_fee_amount.to_string() +// ), +// ] +// ); +// +// assert_eq!( +// &SubMsg { +// msg: WasmMsg::Execute { +// contract_addr: String::from("asset0000"), +// msg: to_binary(&Cw20ExecuteMsg::Transfer { +// recipient: String::from("addr0000"), +// amount: expected_return_amount, +// }) +// .unwrap(), +// funds: vec![], +// } +// .into(), +// id: 0, +// gas_limit: None, +// reply_on: ReplyOn::Never, +// }, +// msg_transfer, +// ); +// } +// +// #[test] +// fn try_token_to_native() { +// let total_share = Uint128::new(20000000000u128); +// let asset_pool_amount = Uint128::new(30000000000u128); +// let collateral_pool_amount = Uint128::new(20000000000u128); +// let offer_amount = Uint128::new(1500000000u128); +// +// let mut deps = mock_dependencies(&[Coin { +// denom: "uusd".to_string(), +// amount: collateral_pool_amount, +// }]); +// +// deps.querier.with_token_balances(&[ +// ( +// &String::from("liquidity0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &total_share)], +// ), +// ( +// &String::from("asset0000"), +// &[( +// &String::from(MOCK_CONTRACT_ADDR), +// &(asset_pool_amount + offer_amount), +// )], +// ), +// ]); +// +// let msg = InstantiateMsg { +// asset_infos: vec![ +// AssetInfo::Native("uusd".to_string()), +// AssetInfo::Token("asset0000".to_string()), +// ], +// token_code_id: 10u64, +// factory_addr: String::from("factory"), +// init_params: None, +// staking_config: default_stake_config(), +// trading_starts: 0, +// fee_config: FeeConfig { +// total_fee_bps: 30, +// protocol_fee_bps: 1660, +// }, +// circuit_breaker: None, +// }; +// +// let env = mock_env(); +// let info = mock_info("addr0000", &[]); +// // We can just call .unwrap() to assert this was a success +// let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); +// +// // Store liquidity token +// store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); +// +// // need to initialize oracle, because we don't call `provide_liquidity` in this test +// dex::oracle::initialize_oracle( +// &mut deps.storage, +// &mock_env_with_block_time(0), +// Decimal::one(), +// ) +// .unwrap(); +// +// // Unauthorized access; can not execute swap directy for token swap +// let msg = ExecuteMsg::Swap { +// offer_asset: Asset { +// info: AssetInfo::Token("asset0000".to_string()), +// amount: offer_amount, +// }, +// ask_asset_info: None, +// belief_price: None, +// max_spread: None, +// to: None, +// referral_address: None, +// referral_commission: None, +// }; +// let env = mock_env_with_block_time(1000); +// let info = mock_info("addr0000", &[]); +// let res = execute(deps.as_mut(), env, info, msg).unwrap_err(); +// assert_eq!(res, ContractError::Unauthorized {}); +// +// // Normal sell +// let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { +// sender: String::from("addr0000"), +// amount: offer_amount, +// msg: to_binary(&Cw20HookMsg::Swap { +// ask_asset_info: None, +// belief_price: None, +// max_spread: Some(Decimal::percent(50)), +// to: None, +// referral_address: None, +// referral_commission: None, +// }) +// .unwrap(), +// }); +// let env = mock_env_with_block_time(1000); +// let info = mock_info("asset0000", &[]); +// +// let res = execute(deps.as_mut(), env, info, msg).unwrap(); +// let msg_transfer = res.messages.get(0).expect("no message"); +// +// // Current price is 1.5, so expected return without spread is 1000 +// // 952380952,3809524 = 20000000000 - (30000000000 * 20000000000) / (30000000000 + 1500000000) +// let expected_ret_amount = Uint128::new(952_380_952u128); +// +// // 47619047 = 1500000000 * (20000000000 / 30000000000) - 952380952,3809524 +// let expected_spread_amount = Uint128::new(47619047u128); +// +// let expected_commission_amount = expected_ret_amount.multiply_ratio(3u128, 1000u128); // 0.3% +// let expected_protocol_fee_amount = expected_commission_amount.multiply_ratio(166u128, 1000u128); +// let expected_return_amount = expected_ret_amount +// .checked_sub(expected_commission_amount) +// .unwrap(); +// +// // Check simulation res +// // Return asset token balance as normal +// deps.querier.with_token_balances(&[ +// ( +// &String::from("liquidity0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &total_share)], +// ), +// ( +// &String::from("asset0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &(asset_pool_amount))], +// ), +// ]); +// +// let simulation_res: SimulationResponse = query_simulation( +// deps.as_ref(), +// Asset { +// amount: offer_amount, +// info: AssetInfo::Token("asset0000".to_string()), +// }, +// false, +// None, +// ) +// .unwrap(); +// assert_eq!(expected_return_amount, simulation_res.return_amount); +// assert_eq!(expected_commission_amount, simulation_res.commission_amount); +// assert_eq!(expected_spread_amount, simulation_res.spread_amount); +// +// // Check reverse simulation result +// let reverse_simulation_res: ReverseSimulationResponse = query_reverse_simulation( +// deps.as_ref(), +// Asset { +// amount: expected_return_amount, +// info: AssetInfo::Native("uusd".to_string()), +// }, +// false, +// None, +// ) +// .unwrap(); +// assert!( +// (offer_amount.u128() as i128 - reverse_simulation_res.offer_amount.u128() as i128).abs() +// < 5i128 +// ); +// assert!( +// (expected_commission_amount.u128() as i128 +// - reverse_simulation_res.commission_amount.u128() as i128) +// .abs() +// < 5i128 +// ); +// assert!( +// (expected_spread_amount.u128() as i128 +// - reverse_simulation_res.spread_amount.u128() as i128) +// .abs() +// < 5i128 +// ); +// +// assert_eq!( +// res.attributes, +// vec![ +// attr("action", "swap"), +// attr("sender", "addr0000"), +// attr("receiver", "addr0000"), +// attr("offer_asset", "asset0000"), +// attr("ask_asset", "uusd"), +// attr("offer_amount", offer_amount.to_string()), +// attr("return_amount", expected_return_amount.to_string()), +// attr("spread_amount", expected_spread_amount.to_string()), +// attr("commission_amount", expected_commission_amount.to_string()), +// attr( +// "protocol_fee_amount", +// expected_protocol_fee_amount.to_string() +// ), +// ] +// ); +// +// assert_eq!( +// &SubMsg { +// msg: CosmosMsg::Bank(BankMsg::Send { +// to_address: String::from("addr0000"), +// amount: vec![Coin { +// denom: "uusd".to_string(), +// amount: expected_return_amount +// }], +// }), +// id: 0, +// gas_limit: None, +// reply_on: ReplyOn::Never, +// }, +// msg_transfer, +// ); +// +// // Failed due to trying to swap a non token (specifying an address of a non token contract) +// let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { +// sender: String::from("addr0000"), +// amount: offer_amount, +// msg: to_binary(&Cw20HookMsg::Swap { +// ask_asset_info: None, +// belief_price: None, +// max_spread: None, +// to: None, +// referral_address: None, +// referral_commission: None, +// }) +// .unwrap(), +// }); +// let env = mock_env_with_block_time(1000); +// let info = mock_info("liquidtity0000", &[]); +// let res = execute(deps.as_mut(), env, info, msg).unwrap_err(); +// assert_eq!(res, ContractError::Unauthorized {}); +// } +// +// #[test] +// fn test_max_spread() { +// assert_max_spread( +// Some(Decimal::from_ratio(1200u128, 1u128)), +// Some(Decimal::percent(1)), +// Uint128::from(1200000000u128), +// Uint128::from(989999u128), +// Uint128::zero(), +// ) +// .unwrap_err(); +// +// assert_max_spread( +// Some(Decimal::from_ratio(1200u128, 1u128)), +// Some(Decimal::percent(1)), +// Uint128::from(1200000000u128), +// Uint128::from(990000u128), +// Uint128::zero(), +// ) +// .unwrap(); +// +// assert_max_spread( +// None, +// Some(Decimal::percent(1)), +// Uint128::zero(), +// Uint128::from(989999u128), +// Uint128::from(10001u128), +// ) +// .unwrap_err(); +// +// assert_max_spread( +// None, +// Some(Decimal::percent(1)), +// Uint128::zero(), +// Uint128::from(990000u128), +// Uint128::from(10000u128), +// ) +// .unwrap(); +// +// assert_max_spread( +// Some(Decimal::from_ratio(1200u128, 1u128)), +// Some(Decimal::percent(51)), +// Uint128::from(1200000000u128), +// Uint128::from(989999u128), +// Uint128::zero(), +// ) +// .unwrap_err(); +// } +// +// #[test] +// fn test_query_pool() { +// let total_share_amount = Uint128::from(111u128); +// let asset_0_amount = Uint128::from(222u128); +// let asset_1_amount = Uint128::from(333u128); +// let mut deps = mock_dependencies(&[Coin { +// denom: "uusd".to_string(), +// amount: asset_0_amount, +// }]); +// +// deps.querier.with_token_balances(&[ +// ( +// &String::from("asset0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &asset_1_amount)], +// ), +// ( +// &String::from("liquidity0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &total_share_amount)], +// ), +// ]); +// +// let msg = InstantiateMsg { +// asset_infos: vec![ +// AssetInfo::Native("uusd".to_string()), +// AssetInfo::Token("asset0000".to_string()), +// ], +// token_code_id: 10u64, +// factory_addr: String::from("factory"), +// init_params: None, +// staking_config: default_stake_config(), +// trading_starts: 0, +// fee_config: FeeConfig { +// total_fee_bps: 0, +// protocol_fee_bps: 0, +// }, +// circuit_breaker: None, +// }; +// +// let env = mock_env(); +// let info = mock_info("addr0000", &[]); +// // We can just call .unwrap() to assert this was a success +// let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); +// +// // Store liquidity token +// store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); +// +// let res: PoolResponse = query_pool(deps.as_ref()).unwrap(); +// +// assert_eq!( +// res.assets, +// [ +// AssetValidated { +// info: AssetInfoValidated::Native("uusd".to_string()), +// amount: asset_0_amount +// }, +// AssetValidated { +// info: AssetInfoValidated::Token(Addr::unchecked("asset0000")), +// amount: asset_1_amount +// } +// ] +// ); +// assert_eq!(res.total_share, total_share_amount); +// } +// +// #[test] +// fn test_query_share() { +// let total_share_amount = Uint128::from(500u128); +// let asset_0_amount = Uint128::from(250u128); +// let asset_1_amount = Uint128::from(1000u128); +// let mut deps = mock_dependencies(&[Coin { +// denom: "uusd".to_string(), +// amount: asset_0_amount, +// }]); +// +// deps.querier.with_token_balances(&[ +// ( +// &String::from("asset0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &asset_1_amount)], +// ), +// ( +// &String::from("liquidity0000"), +// &[(&String::from(MOCK_CONTRACT_ADDR), &total_share_amount)], +// ), +// ]); +// +// let msg = InstantiateMsg { +// asset_infos: vec![ +// AssetInfo::Native("uusd".to_string()), +// AssetInfo::Token("asset0000".to_string()), +// ], +// token_code_id: 10u64, +// factory_addr: String::from("factory"), +// init_params: None, +// staking_config: default_stake_config(), +// trading_starts: 0, +// fee_config: FeeConfig { +// total_fee_bps: 0, +// protocol_fee_bps: 0, +// }, +// circuit_breaker: None, +// }; +// +// let env = mock_env(); +// let info = mock_info("addr0000", &[]); +// // We can just call .unwrap() to assert this was a success +// let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); +// +// // Store liquidity token +// store_liquidity_token(deps.as_mut(), "liquidity0000".to_string()); +// +// let res = query_share(deps.as_ref(), Uint128::new(250)).unwrap(); +// +// assert_eq!(res[0].amount, Uint128::new(125)); +// assert_eq!(res[1].amount, Uint128::new(500)); +// } +// +// #[test] +// fn test_accumulate_prices() { +// struct Case { +// block_time: u64, +// block_time_last: u64, +// last0: u128, +// last1: u128, +// x_amount: u128, +// y_amount: u128, +// } +// +// struct Result { +// block_time_last: u64, +// price_x: u128, +// price_y: u128, +// is_some: bool, +// } +// +// let price_precision = 10u128.pow(TWAP_PRECISION.into()); +// +// let test_cases: Vec<(Case, Result)> = vec![ +// ( +// Case { +// block_time: 1000, +// block_time_last: 0, +// last0: 0, +// last1: 0, +// x_amount: 250, +// y_amount: 500, +// }, +// Result { +// block_time_last: 1000, +// price_x: 2000, // 500/250*1000 +// price_y: 500, // 250/500*1000 +// is_some: true, +// }, +// ), +// // Same block height, no changes +// ( +// Case { +// block_time: 1000, +// block_time_last: 1000, +// last0: price_precision, +// last1: 2 * price_precision, +// x_amount: 250, +// y_amount: 500, +// }, +// Result { +// block_time_last: 1000, +// price_x: 1, +// price_y: 2, +// is_some: false, +// }, +// ), +// ( +// Case { +// block_time: 1500, +// block_time_last: 1000, +// last0: 500 * price_precision, +// last1: 2000 * price_precision, +// x_amount: 250, +// y_amount: 500, +// }, +// Result { +// block_time_last: 1500, +// price_x: 1500, // 500 + (500/250*500) +// price_y: 2250, // 2000 + (250/500*500) +// is_some: true, +// }, +// ), +// ]; +// +// for test_case in test_cases { +// let (case, result) = test_case; +// +// let env = mock_env_with_block_time(case.block_time); +// let config = accumulate_prices( +// &env, +// &Config { +// pair_info: PairInfo { +// asset_infos: vec![ +// AssetInfoValidated::Native("uusd".to_string()), +// AssetInfoValidated::Token(Addr::unchecked("asset0000")), +// ], +// contract_addr: Addr::unchecked("pair"), +// staking_addr: Addr::unchecked("stake"), +// liquidity_token: Addr::unchecked("lp_token"), +// pair_type: PairType::Xyk {}, // Implemented in mock querier +// fee_config: FeeConfig { +// total_fee_bps: 0, +// protocol_fee_bps: 0, +// }, +// }, +// factory_addr: Addr::unchecked("factory"), +// block_time_last: case.block_time_last, +// price0_cumulative_last: Uint128::new(case.last0), +// price1_cumulative_last: Uint128::new(case.last1), +// trading_starts: 0, +// }, +// Uint128::new(case.x_amount), +// Uint128::new(case.y_amount), +// ) +// .unwrap(); +// +// assert_eq!(result.is_some, config.is_some()); +// +// if let Some(config) = config { +// assert_eq!(config.2, result.block_time_last); +// assert_eq!( +// config.0 / Uint128::from(price_precision), +// Uint128::new(result.price_x) +// ); +// assert_eq!( +// config.1 / Uint128::from(price_precision), +// Uint128::new(result.price_y) +// ); +// } +// } +// } +// +// fn mock_env_with_block_time(time: u64) -> Env { +// let mut env = mock_env(); +// env.block = BlockInfo { +// height: 1, +// time: Timestamp::from_seconds(time), +// chain_id: "columbus".to_string(), +// }; +// env +// } +// +// #[test] +// fn compute_swap_rounding() { +// let offer_pool = Uint128::from(5_000_000_000_000_u128); +// let ask_pool = Uint128::from(1_000_000_000_u128); +// let return_amount = Uint128::from(0_u128); +// let spread_amount = Uint128::from(0_u128); +// let commission_amount = Uint128::from(0_u128); +// let offer_amount = Uint128::from(1_u128); +// +// assert_eq!( +// compute_swap(offer_pool, ask_pool, offer_amount, Decimal::zero()), +// Ok((return_amount, spread_amount, commission_amount)) +// ); +// } +// +// proptest! { +// #[test] +// fn compute_swap_overflow_test( +// offer_pool in 1_000_000..9_000_000_000_000_000_000u128, +// ask_pool in 1_000_000..9_000_000_000_000_000_000u128, +// offer_amount in 1..100_000_000_000u128, +// ) { +// +// let offer_pool = Uint128::from(offer_pool); +// let ask_pool = Uint128::from(ask_pool); +// let offer_amount = Uint128::from(offer_amount); +// let commission_amount = Decimal::zero(); +// +// // Make sure there are no overflows +// compute_swap( +// offer_pool, +// ask_pool, +// offer_amount, +// commission_amount, +// ).unwrap(); +// } +// } +// +// #[test] +// fn ensure_useful_error_messages_are_given_on_swaps() { +// const OFFER: Uint128 = Uint128::new(1_000_000_000_000); +// const ASK: Uint128 = Uint128::new(1_000_000_000_000); +// const AMOUNT: Uint128 = Uint128::new(1_000_000); +// const ZERO: Uint128 = Uint128::zero(); +// const DZERO: Decimal = Decimal::zero(); +// +// // Computing ask +// assert_eq!( +// compute_swap(ZERO, ZERO, ZERO, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_swap(ZERO, ZERO, AMOUNT, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_swap(ZERO, ASK, ZERO, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_swap(ZERO, ASK, AMOUNT, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_swap(OFFER, ZERO, ZERO, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_swap(OFFER, ZERO, AMOUNT, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_swap(OFFER, ASK, ZERO, DZERO).unwrap_err(), +// StdError::generic_err("Swap amount must not be zero") +// ); +// compute_swap(OFFER, ASK, AMOUNT, DZERO).unwrap(); +// +// // Computing offer +// assert_eq!( +// compute_offer_amount(ZERO, ZERO, ZERO, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_offer_amount(ZERO, ZERO, AMOUNT, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_offer_amount(ZERO, ASK, ZERO, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_offer_amount(ZERO, ASK, AMOUNT, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_offer_amount(OFFER, ZERO, ZERO, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_offer_amount(OFFER, ZERO, AMOUNT, DZERO).unwrap_err(), +// StdError::generic_err("One of the pools is empty") +// ); +// assert_eq!( +// compute_offer_amount(OFFER, ASK, ZERO, DZERO).unwrap_err(), +// StdError::generic_err("Swap amount must not be zero") +// ); +// compute_offer_amount(OFFER, ASK, AMOUNT, DZERO).unwrap(); +// } From 411200fd256b922027902ce9f82d71e5524a0dac Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 12 Oct 2023 12:12:24 +0200 Subject: [PATCH 8/9] Pair: Convince mock querier to use CoreumQueries type --- contracts/pair/src/mock_querier.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/pair/src/mock_querier.rs b/contracts/pair/src/mock_querier.rs index 09199a9..55bacb0 100644 --- a/contracts/pair/src/mock_querier.rs +++ b/contracts/pair/src/mock_querier.rs @@ -1,10 +1,12 @@ +use std::marker::PhantomData; +use std::collections::HashMap; + use coreum_wasm_sdk::core::{CoreumMsg, CoreumQueries}; use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ from_binary, from_slice, to_binary, Addr, Coin, Decimal, Empty, OwnedDeps, Querier, QuerierResult, QueryRequest, SystemError, SystemResult, Uint128, WasmQuery, }; -use std::collections::HashMap; use cw20::{BalanceResponse, Cw20QueryMsg, TokenInfoResponse}; use dex::factory::{ @@ -16,7 +18,7 @@ use dex::factory::{ /// This uses the Dex CustomQuerier. pub fn mock_dependencies( contract_balance: &[Coin], -) -> OwnedDeps { +) -> OwnedDeps { let custom_querier: WasmMockQuerier = WasmMockQuerier::new(MockQuerier::new(&[(MOCK_CONTRACT_ADDR, contract_balance)])); @@ -24,7 +26,7 @@ pub fn mock_dependencies( storage: MockStorage::default(), api: MockApi::default(), querier: custom_querier, - custom_query_type: Default::default(), + custom_query_type: PhantomData, } } From 0755664f71f01f3872e8b0c56e43e28ac9ab17bd Mon Sep 17 00:00:00 2001 From: Jakub Date: Thu, 12 Oct 2023 12:22:19 +0200 Subject: [PATCH 9/9] Pair: Proper initialization test --- contracts/pair/src/mock_querier.rs | 2 +- contracts/pair/src/testing.rs | 40 ++++++++++++++---------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/contracts/pair/src/mock_querier.rs b/contracts/pair/src/mock_querier.rs index 55bacb0..0b5267f 100644 --- a/contracts/pair/src/mock_querier.rs +++ b/contracts/pair/src/mock_querier.rs @@ -1,5 +1,5 @@ -use std::marker::PhantomData; use std::collections::HashMap; +use std::marker::PhantomData; use coreum_wasm_sdk::core::{CoreumMsg, CoreumQueries}; use cosmwasm_std::testing::{MockApi, MockQuerier, MockStorage, MOCK_CONTRACT_ADDR}; diff --git a/contracts/pair/src/testing.rs b/contracts/pair/src/testing.rs index be77a39..f88cc11 100644 --- a/contracts/pair/src/testing.rs +++ b/contracts/pair/src/testing.rs @@ -1,4 +1,7 @@ -use coreum_wasm_sdk::core::{CoreumMsg, CoreumQueries}; +use coreum_wasm_sdk::{ + assetft, + core::{CoreumMsg, CoreumQueries}, +}; use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ assert_approx_eq, attr, coins, from_binary, to_binary, Addr, BankMsg, BlockInfo, Coin, @@ -16,7 +19,8 @@ use dex::fee_config::FeeConfig; use dex::oracle::{SamplePeriod, TwapResponse}; use dex::pair::{ assert_max_spread, ContractError, Cw20HookMsg, ExecuteMsg, InstantiateMsg, PairInfo, - PoolResponse, ReverseSimulationResponse, SimulationResponse, StakeConfig, TWAP_PRECISION, + PoolResponse, ReverseSimulationResponse, SimulationResponse, StakeConfig, LP_TOKEN_PRECISION, + TWAP_PRECISION, }; use dex::pair::{MigrateMsg, QueryMsg}; @@ -90,28 +94,20 @@ fn proper_initialization() { assert_eq!( res.messages, vec![SubMsg { - msg: WasmMsg::Instantiate { - code_id: 10u64, - msg: to_binary(&TokenInstantiateMsg { - name: "UUSD-MAPP-LP".to_string(), - symbol: "uLP".to_string(), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: String::from(MOCK_CONTRACT_ADDR), - cap: None, - }), - marketing: None - }) - .unwrap(), - funds: vec![], - admin: Some("owner".to_owned()), - label: String::from("Dex LP token"), - } + msg: CoreumMsg::AssetFT(assetft::Msg::Issue { + symbol: "UUSD-MAPP-LP".to_string(), + subunit: "uLP".to_string(), + precision: LP_TOKEN_PRECISION, + initial_amount: Uint128::zero(), + description: Some("Dex LP Share token".to_string()), + features: Some(vec![0, 1, 2]), // 0 - minting, 1 - burnin + burn_rate: Some("0".into()), + send_commission_rate: None, + }) .into(), - id: 1, + id: 0, gas_limit: None, - reply_on: ReplyOn::Success + reply_on: ReplyOn::Never },] );