diff --git a/.github/workflows/check_artifacts.yml b/.github/workflows/check_artifacts.yml index 58619968d..f0bda5bb6 100644 --- a/.github/workflows/check_artifacts.yml +++ b/.github/workflows/check_artifacts.yml @@ -38,7 +38,7 @@ jobs: -v "$GITHUB_WORKSPACE":/code \ -v ~/.cargo/registry:/usr/local/cargo/registry \ -v ~/.cargo/git:/usr/local/cargo/git \ - cosmwasm/workspace-optimizer:0.15.1 + ghcr.io/astroport-fi/rust-optimizer:v0.15.1-astroport - name: Save artifacts cache uses: actions/cache/save@v3 diff --git a/.github/workflows/tests_and_checks.yml b/.github/workflows/tests_and_checks.yml index 488c39b9c..90c03f2df 100644 --- a/.github/workflows/tests_and_checks.yml +++ b/.github/workflows/tests_and_checks.yml @@ -53,7 +53,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --no-fail-fast --locked + args: --features tests-tube --no-fail-fast --locked env: RUST_BACKTRACE: 1 diff --git a/Cargo.lock b/Cargo.lock index fb43e5a7c..70185dba0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -115,8 +115,26 @@ dependencies = [ [[package]] name = "astroport" version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5ec8dd4298b362361b0e118107a060e5e58501a68273b3257059c96b723b57c" +dependencies = [ + "astroport-circular-buffer 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cosmwasm-schema", + "cosmwasm-std", + "cw-asset", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "cw20 1.1.2", + "itertools 0.12.1", + "uint 0.9.5", +] + +[[package]] +name = "astroport" +version = "5.0.0" dependencies = [ "astroport-circular-buffer 0.2.0", + "cosmos-sdk-proto 0.19.0", "cosmwasm-schema", "cosmwasm-std", "cw-asset", @@ -125,6 +143,7 @@ dependencies = [ "cw20 1.1.2", "injective-math", "itertools 0.12.1", + "prost 0.11.9", "test-case", "thiserror", "uint 0.9.5", @@ -170,14 +189,14 @@ dependencies = [ [[package]] name = "astroport-factory" -version = "1.7.0" +version = "1.8.0" dependencies = [ "anyhow", - "astroport 4.0.3", - "astroport-pair 1.5.1", + "astroport 5.0.0", + "astroport-pair 2.0.0", + "astroport-test", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.0.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -196,7 +215,7 @@ dependencies = [ "cosmos-sdk-proto 0.19.0", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.0.0", + "cw-multi-test 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cw-storage-plus 0.15.1", "cw-utils 1.0.3", "cw2 1.1.2", @@ -208,7 +227,7 @@ name = "astroport-governance" version = "1.2.0" source = "git+https://github.com/astroport-fi/astroport-governance#182dd5bc201dd634995b5e4dc9e2774495693703" dependencies = [ - "astroport 4.0.3", + "astroport 5.0.0", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 0.15.1", @@ -222,15 +241,15 @@ dependencies = [ "anyhow", "astro-token-converter", "astroport 4.0.3", - "astroport-factory 1.7.0", + "astroport-factory 1.8.0", "astroport-native-coin-registry", - "astroport-pair 1.5.1", + "astroport-pair 2.0.0", "astroport-pair-stable", + "astroport-test", "astroport-vesting 1.3.1", "astroport-vesting 1.4.0", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.0.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -241,44 +260,19 @@ dependencies = [ "thiserror", ] -[[package]] -name = "astroport-liquidity-manager" -version = "1.2.0" -dependencies = [ - "anyhow", - "astroport 4.0.3", - "astroport-factory 1.7.0", - "astroport-incentives", - "astroport-native-coin-registry", - "astroport-pair 1.5.1", - "astroport-pair-concentrated 3.0.0", - "astroport-pair-stable", - "astroport-pcl-common", - "cosmwasm-schema", - "cosmwasm-std", - "cw-multi-test 1.0.0", - "cw-storage-plus 1.2.0", - "cw20 1.1.2", - "cw20-base 1.1.0", - "derivative", - "itertools 0.12.1", - "serde_json", - "thiserror", -] - [[package]] name = "astroport-maker" version = "1.5.0" dependencies = [ "astro-satellite-package", "astroport 4.0.3", - "astroport-factory 1.7.0", + "astroport-factory 1.8.0", "astroport-native-coin-registry", - "astroport-pair 1.5.1", + "astroport-pair 2.0.0", "astroport-pair-stable", + "astroport-test", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.0.0", "cw-storage-plus 1.2.0", "cw2 1.1.2", "cw20 1.1.2", @@ -286,33 +280,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "astroport-mocks" -version = "0.2.0" -dependencies = [ - "anyhow", - "astroport 4.0.3", - "astroport-factory 1.7.0", - "astroport-native-coin-registry", - "astroport-pair 1.5.1", - "astroport-pair-concentrated 3.0.0", - "astroport-pair-stable", - "astroport-staking", - "astroport-vesting 1.4.0", - "astroport-xastro-token", - "cosmwasm-schema", - "cosmwasm-std", - "cw-multi-test 1.0.0", - "cw-utils 1.0.3", - "cw1-whitelist", - "cw20 0.15.1", - "cw20-base 1.1.0", - "cw3", - "injective-cosmwasm", - "schemars", - "serde", -] - [[package]] name = "astroport-native-coin-registry" version = "1.0.1" @@ -320,7 +287,7 @@ dependencies = [ "astroport 3.12.2", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.0.0", + "cw-multi-test 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cw-storage-plus 0.15.1", "cw2 1.1.2", "thiserror", @@ -332,13 +299,13 @@ version = "2.1.2" dependencies = [ "anyhow", "astroport 3.12.2", - "astroport-factory 1.7.0", + "astroport-factory 1.8.0", "astroport-native-coin-registry", - "astroport-pair 1.5.1", + "astroport-pair 2.0.0", "astroport-pair-stable", + "astroport-test", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.0.0", "cw-storage-plus 0.15.1", "cw2 1.1.2", "cw20 0.15.1", @@ -366,11 +333,13 @@ dependencies = [ [[package]] name = "astroport-pair" -version = "1.5.1" +version = "2.0.0" dependencies = [ - "astroport 4.0.3", - "astroport-factory 1.7.0", - "astroport-mocks", + "astroport 5.0.0", + "astroport-factory 1.8.0", + "astroport-incentives", + "astroport-test", + "astroport-tokenfactory-tracker", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -404,16 +373,18 @@ dependencies = [ [[package]] name = "astroport-pair-concentrated" -version = "3.0.0" +version = "4.0.0" dependencies = [ "anyhow", - "astroport 4.0.3", + "astroport 5.0.0", "astroport-circular-buffer 0.2.0", - "astroport-factory 1.7.0", - "astroport-mocks", + "astroport-factory 1.8.0", + "astroport-incentives", "astroport-native-coin-registry", "astroport-pair-concentrated 1.2.13", "astroport-pcl-common", + "astroport-test", + "astroport-tokenfactory-tracker", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -434,7 +405,7 @@ dependencies = [ "anyhow", "astro-token-converter", "astroport 4.0.3", - "astroport-factory 1.7.0", + "astroport-factory 1.8.0", "astroport-pair 1.3.3", "cosmwasm-schema", "cosmwasm-std", @@ -452,14 +423,15 @@ dependencies = [ [[package]] name = "astroport-pair-stable" -version = "3.5.0" +version = "4.0.0" dependencies = [ "anyhow", - "astroport 4.0.3", + "astroport 5.0.0", "astroport-circular-buffer 0.2.0", - "astroport-factory 1.7.0", - "astroport-mocks", + "astroport-factory 1.8.0", + "astroport-incentives", "astroport-native-coin-registry", + "astroport-test", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -480,16 +452,16 @@ name = "astroport-pair-transmuter" version = "1.1.1" dependencies = [ "anyhow", - "astroport 3.12.2", - "astroport-factory 1.7.0", + "astroport 5.0.0", + "astroport-factory 1.8.0", "astroport-native-coin-registry", + "astroport-test", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.0.0", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", - "cw20 0.15.1", + "cw20 1.1.2", "cw20-base 1.1.0", "derivative", "itertools 0.12.1", @@ -498,13 +470,15 @@ dependencies = [ [[package]] name = "astroport-pair-xyk-sale-tax" -version = "1.6.0" +version = "2.0.0" dependencies = [ - "astroport 4.0.3", - "astroport-factory 1.7.0", - "astroport-mocks", + "astroport 5.0.0", + "astroport-factory 1.8.0", + "astroport-incentives", "astroport-pair 1.3.3", - "astroport-pair 1.5.1", + "astroport-pair 2.0.0", + "astroport-test", + "astroport-tokenfactory-tracker", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -524,8 +498,9 @@ name = "astroport-pcl-common" version = "2.0.1" dependencies = [ "anyhow", - "astroport 4.0.3", - "astroport-factory 1.7.0", + "astroport 5.0.0", + "astroport-factory 1.8.0", + "astroport-test", "cosmwasm-schema", "cosmwasm-std", "cw-storage-plus 1.2.0", @@ -540,11 +515,11 @@ version = "1.2.1" dependencies = [ "anyhow", "astroport 3.12.2", - "astroport-factory 1.7.0", - "astroport-pair 1.5.1", + "astroport-factory 1.8.0", + "astroport-pair 2.0.0", + "astroport-test", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.0.0", "cw-storage-plus 0.15.1", "cw2 1.1.2", "cw20 0.15.1", @@ -571,6 +546,22 @@ dependencies = [ "thiserror", ] +[[package]] +name = "astroport-test" +version = "0.1.0" +dependencies = [ + "anyhow", + "astroport 5.0.0", + "cosmwasm-schema", + "cosmwasm-std", + "cw-multi-test 1.0.0 (git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks_1_0)", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "itertools 0.12.1", + "schemars", + "serde", +] + [[package]] name = "astroport-tokenfactory-tracker" version = "1.0.0" @@ -612,7 +603,7 @@ dependencies = [ "astroport-vesting 1.3.1", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.0.0", + "cw-multi-test 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cw-storage-plus 1.2.0", "cw-utils 1.0.3", "cw2 1.1.2", @@ -640,7 +631,7 @@ dependencies = [ "astroport 3.12.2", "cosmwasm-schema", "cosmwasm-std", - "cw-multi-test 1.0.0", + "cw-multi-test 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "cw-storage-plus 0.15.1", "cw2 0.15.1", "cw20 0.15.1", @@ -1178,6 +1169,25 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cw-multi-test" +version = "1.0.0" +source = "git+https://github.com/astroport-fi/cw-multi-test?branch=feat/bank_with_send_hooks_1_0#b4ba6101dfd67b73aff3b75a8759915bd215ae85" +dependencies = [ + "anyhow", + "bech32 0.11.0", + "cosmwasm-std", + "cw-storage-plus 1.2.0", + "cw-utils 1.0.3", + "derivative", + "itertools 0.12.1", + "prost 0.12.3", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", +] + [[package]] name = "cw-storage-plus" version = "0.15.1" @@ -1806,9 +1816,6 @@ name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -dependencies = [ - "serde", -] [[package]] name = "hmac" @@ -1950,24 +1957,6 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306" -[[package]] -name = "injective-cosmwasm" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4295a2d118cae0e21bba1c856464f6678b5db907cb085b3723d04efb65fa0d0d" -dependencies = [ - "cosmwasm-std", - "cw-storage-plus 0.15.1", - "ethereum-types", - "hex", - "injective-math", - "schemars", - "serde", - "serde_repr", - "subtle-encoding", - "tiny-keccak", -] - [[package]] name = "injective-math" version = "0.1.18" diff --git a/contracts/factory/Cargo.toml b/contracts/factory/Cargo.toml index 023eaf858..91f23831e 100644 --- a/contracts/factory/Cargo.toml +++ b/contracts/factory/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-factory" -version = "1.7.0" +version = "1.8.0" authors = ["Astroport"] edition = "2021" description = "Astroport factory contract - pair contract generator and directory" @@ -27,7 +27,7 @@ library = [] [dependencies] cosmwasm-std.workspace = true -astroport = { path = "../../packages/astroport", version = "4" } +astroport = { path = "../../packages/astroport", version = "5" } cw-storage-plus.workspace = true cw2.workspace = true thiserror.workspace = true @@ -36,7 +36,7 @@ cosmwasm-schema.workspace = true cw-utils.workspace = true [dev-dependencies] -cw-multi-test = "1.0.0" +astroport-test = { path = "../../packages/astroport_test" } cw20-base = { version = "1.1", features = ["library"] } astroport-pair = { path = "../pair" } cw20 = "1.1" diff --git a/contracts/factory/src/contract.rs b/contracts/factory/src/contract.rs index 2c19bd516..6de023f78 100644 --- a/contracts/factory/src/contract.rs +++ b/contracts/factory/src/contract.rs @@ -3,7 +3,7 @@ use std::collections::HashSet; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, to_json_binary, Binary, CosmosMsg, Deps, DepsMut, Empty, Env, MessageInfo, Order, Reply, + attr, ensure, to_json_binary, Binary, CosmosMsg, Deps, DepsMut, Env, MessageInfo, Order, Reply, ReplyOn, Response, StdError, StdResult, SubMsg, SubMsgResponse, SubMsgResult, WasmMsg, }; use cw2::{get_contract_version, set_contract_version}; @@ -13,8 +13,8 @@ use itertools::Itertools; use astroport::asset::{addr_opt_validate, AssetInfo, PairInfo}; use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; use astroport::factory::{ - Config, ConfigResponse, ExecuteMsg, FeeInfoResponse, InstantiateMsg, PairConfig, PairType, - PairsResponse, QueryMsg, + Config, ConfigResponse, ExecuteMsg, FeeInfoResponse, InstantiateMsg, MigrateMsg, PairConfig, + PairType, PairsResponse, QueryMsg, TrackerConfig, }; use astroport::incentives::ExecuteMsg::DeactivatePool; use astroport::pair::InstantiateMsg as PairInstantiateMsg; @@ -24,7 +24,7 @@ use crate::migration::migrate_pair_configs; use crate::querier::query_pair_info; use crate::state::{ check_asset_infos, pair_key, read_pairs, TmpPairInfo, CONFIG, OWNERSHIP_PROPOSAL, PAIRS, - PAIR_CONFIGS, TMP_PAIR_INFO, + PAIR_CONFIGS, TMP_PAIR_INFO, TRACKER_CONFIG, }; /// Contract name that is used for migration. @@ -78,6 +78,19 @@ pub fn instantiate( } CONFIG.save(deps.storage, &config)?; + if let Some(tracker_config) = msg.tracker_config { + TRACKER_CONFIG.save( + deps.storage, + &TrackerConfig { + code_id: tracker_config.code_id, + token_factory_addr: deps + .api + .addr_validate(&tracker_config.token_factory_addr)? + .to_string(), + }, + )?; + } + Ok(Response::new()) } @@ -184,6 +197,10 @@ pub fn execute( }) .map_err(Into::into) } + ExecuteMsg::UpdateTrackerConfig { + tracker_code_id, + token_factory_addr, + } => update_tracker_config(deps, info, tracker_code_id, token_factory_addr), } } @@ -315,7 +332,8 @@ pub fn execute_create_pair( factory_addr: env.contract.address.to_string(), init_params, })?, - funds: vec![], + // Pass executer funds to pair contract in order to pay for LP token creation + funds: info.funds, label: "Astroport pair".to_string(), } .into(), @@ -405,6 +423,35 @@ pub fn deregister( ])) } +pub fn update_tracker_config( + deps: DepsMut, + info: MessageInfo, + tracker_code_id: u64, + token_factory_addr: Option, +) -> Result { + let config = CONFIG.load(deps.storage)?; + + ensure!(info.sender == config.owner, ContractError::Unauthorized {}); + if let Some(mut tracker_config) = TRACKER_CONFIG.may_load(deps.storage)? { + tracker_config.code_id = tracker_code_id; + TRACKER_CONFIG.save(deps.storage, &tracker_config)?; + } else { + let tokenfactory_tracker = + token_factory_addr.ok_or(StdError::generic_err("token_factory_addr is required"))?; + TRACKER_CONFIG.save( + deps.storage, + &TrackerConfig { + code_id: tracker_code_id, + token_factory_addr: tokenfactory_tracker, + }, + )?; + } + + Ok(Response::new() + .add_attribute("action", "update_tracker_config") + .add_attribute("code_id", tracker_code_id.to_string())) +} + /// Exposes all the queries available in the contract. /// /// ## Queries @@ -428,6 +475,7 @@ pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { } QueryMsg::FeeInfo { pair_type } => to_json_binary(&query_fee_info(deps, pair_type)?), QueryMsg::BlacklistedPairTypes {} => to_json_binary(&query_blacklisted_pair_types(deps)?), + QueryMsg::TrackerConfig {} => to_json_binary(&query_tracker_config(deps)?), } } @@ -505,9 +553,20 @@ pub fn query_fee_info(deps: Deps, pair_type: PairType) -> StdResult StdResult { + let tracker_config = TRACKER_CONFIG.load(deps.storage).map_err(|_| { + StdError::generic_err("Tracker config is not set in the factory. It can't be provided") + })?; + + Ok(TrackerConfig { + code_id: tracker_config.code_id, + token_factory_addr: tracker_config.token_factory_addr, + }) +} + /// Manages the contract migration. #[cfg_attr(not(feature = "library"), entry_point)] -pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { +pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result { let contract_version = get_contract_version(deps.storage)?; match contract_version.contract.as_ref() { @@ -518,6 +577,20 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result { migrate_pair_configs(deps.storage)?; } + "1.7.0" => { + if let Some(tracker_config) = msg.tracker_config { + TRACKER_CONFIG.save( + deps.storage, + &TrackerConfig { + code_id: tracker_config.code_id, + token_factory_addr: deps + .api + .addr_validate(&tracker_config.token_factory_addr)? + .to_string(), + }, + )?; + } + } _ => return Err(ContractError::MigrationError {}), }, _ => return Err(ContractError::MigrationError {}), diff --git a/contracts/factory/src/state.rs b/contracts/factory/src/state.rs index 6b9e6c8f2..745214b6f 100644 --- a/contracts/factory/src/state.rs +++ b/contracts/factory/src/state.rs @@ -6,7 +6,7 @@ use itertools::Itertools; use crate::error::ContractError; use astroport::asset::AssetInfo; use astroport::common::OwnershipProposal; -use astroport::factory::{Config, PairConfig}; +use astroport::factory::{Config, PairConfig, TrackerConfig}; /// This is an intermediate structure for storing a pair's key. It is used in a submessage response. #[cw_serde] pub struct TmpPairInfo { @@ -22,6 +22,9 @@ pub const CONFIG: Item = Item::new("config"); /// Saves created pairs (from olders to latest) pub const PAIRS: Map<&[u8], Addr> = Map::new("pair_info"); +/// Track config for tracking contract +pub const TRACKER_CONFIG: Item = Item::new("tracker_config"); + /// Calculates a pair key from the specified parameters in the `asset_infos` variable. /// /// `asset_infos` is an array with multiple items of type [`AssetInfo`]. diff --git a/contracts/factory/src/testing.rs b/contracts/factory/src/testing.rs index e836fab06..f175a1ca3 100644 --- a/contracts/factory/src/testing.rs +++ b/contracts/factory/src/testing.rs @@ -68,6 +68,7 @@ fn proper_initialization() { owner: owner.clone(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let env = mock_env(); @@ -92,6 +93,7 @@ fn proper_initialization() { owner: owner.clone(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let env = mock_env(); @@ -129,6 +131,7 @@ fn proper_initialization() { owner: owner.clone(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let env = mock_env(); @@ -166,6 +169,7 @@ fn update_config() { generator_address: Some(String::from("generator")), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let env = mock_env(); @@ -230,6 +234,7 @@ fn update_owner() { generator_address: Some(String::from("generator")), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let env = mock_env(); @@ -318,6 +323,7 @@ fn update_pair_config() { generator_address: Some(String::from("generator")), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let env = mock_env(); @@ -432,6 +438,7 @@ fn create_pair() { generator_address: Some(String::from("generator")), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let env = mock_env(); @@ -531,6 +538,7 @@ fn register() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let env = mock_env(); @@ -560,7 +568,7 @@ fn register() { let pair0_info = PairInfo { asset_infos: asset_infos.clone(), contract_addr: Addr::unchecked("pair0000"), - liquidity_token: Addr::unchecked("liquidity0000"), + liquidity_token: "liquidity0000".to_owned(), pair_type: PairType::Xyk {}, }; @@ -602,7 +610,7 @@ fn register() { assert_eq!( pair_res, PairInfo { - liquidity_token: Addr::unchecked("liquidity0000"), + liquidity_token: "liquidity0000".to_owned(), contract_addr: Addr::unchecked("pair0000"), asset_infos: asset_infos.clone(), pair_type: PairType::Xyk {}, @@ -637,7 +645,7 @@ fn register() { let pair1_info = PairInfo { asset_infos: asset_infos_2.clone(), contract_addr: Addr::unchecked("pair0001"), - liquidity_token: Addr::unchecked("liquidity0001"), + liquidity_token: "liquidity0001".to_owned(), pair_type: PairType::Xyk {}, }; @@ -677,13 +685,13 @@ fn register() { pairs_res.pairs, vec![ PairInfo { - liquidity_token: Addr::unchecked("liquidity0000"), + liquidity_token: "liquidity0000".to_owned(), contract_addr: Addr::unchecked("pair0000"), asset_infos: asset_infos.clone(), pair_type: PairType::Xyk {}, }, PairInfo { - liquidity_token: Addr::unchecked("liquidity0001"), + liquidity_token: "liquidity0001".to_owned(), contract_addr: Addr::unchecked("pair0001"), asset_infos: asset_infos_2.clone(), pair_type: PairType::Xyk {}, @@ -701,7 +709,7 @@ fn register() { assert_eq!( pairs_res.pairs, vec![PairInfo { - liquidity_token: Addr::unchecked("liquidity0000"), + liquidity_token: "liquidity0000".to_owned(), contract_addr: Addr::unchecked("pair0000"), asset_infos: asset_infos.clone(), pair_type: PairType::Xyk {}, @@ -718,7 +726,7 @@ fn register() { assert_eq!( pairs_res.pairs, vec![PairInfo { - liquidity_token: Addr::unchecked("liquidity0001"), + liquidity_token: "liquidity0001".to_owned(), contract_addr: Addr::unchecked("pair0001"), asset_infos: asset_infos_2.clone(), pair_type: PairType::Xyk {}, @@ -765,7 +773,7 @@ fn register() { assert_eq!( pairs_res.pairs, vec![PairInfo { - liquidity_token: Addr::unchecked("liquidity0000"), + liquidity_token: "liquidity0000".to_owned(), contract_addr: Addr::unchecked("pair0000"), asset_infos: asset_infos.clone(), pair_type: PairType::Xyk {}, diff --git a/contracts/factory/tests/factory_helper.rs b/contracts/factory/tests/factory_helper.rs index cf58707cc..a76cc1240 100644 --- a/contracts/factory/tests/factory_helper.rs +++ b/contracts/factory/tests/factory_helper.rs @@ -2,10 +2,12 @@ use anyhow::Result as AnyResult; use astroport::asset::AssetInfo; -use astroport::factory::{PairConfig, PairType}; -use cosmwasm_std::{Addr, Binary}; +use astroport::factory::{PairConfig, PairType, TrackerConfig}; +use astroport_test::cw_multi_test::{AppResponse, ContractWrapper, Executor}; +use astroport_test::modules::stargate::StargateApp as TestApp; + +use cosmwasm_std::{Addr, Binary, StdResult}; use cw20::MinterResponse; -use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; pub struct FactoryHelper { pub owner: Addr, @@ -15,7 +17,7 @@ pub struct FactoryHelper { } impl FactoryHelper { - pub fn init(router: &mut App, owner: &Addr) -> Self { + pub fn init(router: &mut TestApp, owner: &Addr) -> Self { let astro_token_contract = Box::new(ContractWrapper::new_with_empty( cw20_base::contract::execute, cw20_base::contract::instantiate, @@ -96,6 +98,7 @@ impl FactoryHelper { owner: owner.to_string(), whitelist_code_id: 0, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory = router @@ -119,7 +122,7 @@ impl FactoryHelper { pub fn update_config( &mut self, - router: &mut App, + router: &mut TestApp, sender: &Addr, token_code_id: Option, fee_address: Option, @@ -140,7 +143,7 @@ impl FactoryHelper { pub fn create_pair( &mut self, - router: &mut App, + router: &mut TestApp, sender: &Addr, pair_type: PairType, tokens: [&Addr; 2], @@ -163,10 +166,32 @@ impl FactoryHelper { router.execute_contract(sender.clone(), self.factory.clone(), &msg, &[]) } + + pub fn update_tracker_config( + &mut self, + router: &mut TestApp, + sender: &Addr, + tracker_code_id: u64, + token_factory_addr: Option, + ) -> AnyResult { + let msg = astroport::factory::ExecuteMsg::UpdateTrackerConfig { + tracker_code_id, + token_factory_addr, + }; + + router.execute_contract(sender.clone(), self.factory.clone(), &msg, &[]) + } + + pub fn query_tracker_config(&mut self, router: &mut TestApp) -> StdResult { + let msg = astroport::factory::QueryMsg::TrackerConfig {}; + router + .wrap() + .query_wasm_smart::(self.factory.clone(), &msg) + } } pub fn instantiate_token( - app: &mut App, + app: &mut TestApp, token_code_id: u64, owner: &Addr, token_name: &str, diff --git a/contracts/factory/tests/integration.rs b/contracts/factory/tests/integration.rs index 4c94f3c79..aa2c30fea 100644 --- a/contracts/factory/tests/integration.rs +++ b/contracts/factory/tests/integration.rs @@ -2,22 +2,26 @@ mod factory_helper; -use cosmwasm_std::{attr, Addr}; +use cosmwasm_std::{attr, Addr, StdError}; use astroport::asset::{AssetInfo, PairInfo}; use astroport::factory::{ ConfigResponse, ExecuteMsg, FeeInfoResponse, InstantiateMsg, PairConfig, PairType, QueryMsg, + TrackerConfig, }; use crate::factory_helper::{instantiate_token, FactoryHelper}; use astroport_factory::error::ContractError; -use cw_multi_test::{App, ContractWrapper, Executor}; +use astroport_test::cw_multi_test::{AppBuilder, ContractWrapper, Executor}; +use astroport_test::modules::stargate::{MockStargate, StargateApp as TestApp}; -fn mock_app() -> App { - App::default() +fn mock_app() -> TestApp { + AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|_, _, _| {}) } -fn store_factory_code(app: &mut App) -> u64 { +fn store_factory_code(app: &mut TestApp) -> u64 { let factory_contract = Box::new( ContractWrapper::new_with_empty( astroport_factory::contract::execute, @@ -56,6 +60,7 @@ fn proper_initialization() { generator_address: Some(String::from("generator")), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = app @@ -190,7 +195,10 @@ fn test_create_pair() { // In multitest, contract names are counted in the order in which contracts are created assert_eq!("contract1", helper.factory.to_string()); assert_eq!("contract4", res.contract_addr.to_string()); - assert_eq!("contract5", res.liquidity_token.to_string()); + assert_eq!( + "factory/contract4/astroport/share", + res.liquidity_token.to_string() + ); // Create disabled pair type app.execute_contract( @@ -391,3 +399,88 @@ fn test_create_permissioned_pair() { ) .unwrap(); } + +#[test] +fn tracker_config() { + let mut app = mock_app(); + let owner = Addr::unchecked("owner"); + let mut helper = FactoryHelper::init(&mut app, &owner); + + // Should return an error since tracker config is not set + let err = helper.query_tracker_config(&mut app).unwrap_err(); + + assert_eq!( + err, + StdError::generic_err("Querier contract error: Generic error: Tracker config is not set in the factory. It can't be provided") + ); + + // should return an error since the sender is not the owner + let err = helper + .update_tracker_config(&mut app, &Addr::unchecked("not_owner"), 64, None) + .unwrap_err() + .downcast::() + .unwrap(); + + assert_eq!(err, ContractError::Unauthorized {}); + + // should return an error if trying to update code_id and token_factory_add is not provided + + let err = helper + .update_tracker_config(&mut app, &owner, 64, None) + .unwrap_err() + .downcast::() + .unwrap(); + + assert_eq!( + err, + ContractError::Std(StdError::generic_err("token_factory_addr is required")) + ); + + // should success if the sender is the owner and the token_factory_addr is provided + helper + .update_tracker_config(&mut app, &owner, 64, Some("token_factory_addr".to_string())) + .unwrap(); + + // should return the tracker config + let tracker_config = helper.query_tracker_config(&mut app).unwrap(); + assert_eq!(tracker_config.token_factory_addr, "token_factory_addr"); + assert_eq!(tracker_config.code_id, 64); + + // Query tracker config should work since the beggining if the tracker config is set when the contract is instantiated + let init_msg = astroport::factory::InstantiateMsg { + fee_address: None, + pair_configs: vec![PairConfig { + code_id: 0, + maker_fee_bps: 3333, + total_fee_bps: 30u16, + pair_type: PairType::Xyk {}, + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }], + token_code_id: 0, + generator_address: None, + owner: owner.to_string(), + whitelist_code_id: 0, + coin_registry_address: "registry".to_string(), + tracker_config: Some(TrackerConfig { + code_id: 64, + token_factory_addr: "token_factory_addr".to_string(), + }), + }; + + let factory = app + .instantiate_contract(3, owner.clone(), &init_msg, &[], "factory", None) + .unwrap(); + + let tracker_config = app + .wrap() + .query_wasm_smart::( + factory.clone(), + &astroport::factory::QueryMsg::TrackerConfig {}, + ) + .unwrap(); + + assert_eq!(tracker_config.token_factory_addr, "token_factory_addr"); + assert_eq!(tracker_config.code_id, 64); +} diff --git a/contracts/pair/Cargo.toml b/contracts/pair/Cargo.toml index c2e5bc6b2..6a98d27a5 100644 --- a/contracts/pair/Cargo.toml +++ b/contracts/pair/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-pair" -version = "1.5.1" +version = "2.0.0" authors = ["Astroport"] edition = "2021" description = "The Astroport constant product pool contract implementation" @@ -23,22 +23,26 @@ crate-type = ["cdylib", "rlib"] # for quicker tests, cargo test --lib # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] +injective = ["astroport/injective"] +sei = ["astroport/sei"] library = [] [dependencies] integer-sqrt = "0.1" -astroport = { path = "../../packages/astroport", version = "4" } +astroport = { path = "../../packages/astroport", version = "5" } cw2.workspace = true cw20 = "1.1" -cosmwasm-std.workspace = true +cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1", "stargate"] } cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true cw-utils.workspace = true [dev-dependencies] +astroport-incentives = { path = "../tokenomics/incentives" } cw20-base = { version = "1.1", features = ["library"] } astroport-factory = { path = "../factory" } proptest = "1.0" prost = "0.11.5" -astroport-mocks = { path = "../../packages/astroport_mocks" } +astroport-test = { path = "../../packages/astroport_test", features = ["cosmwasm_1_1"] } +astroport-tokenfactory-tracker = { path = "../periphery/tokenfactory_tracker" } diff --git a/contracts/pair/src/contract.rs b/contracts/pair/src/contract.rs index 5225c28be..8e31c2888 100644 --- a/contracts/pair/src/contract.rs +++ b/contracts/pair/src/contract.rs @@ -5,30 +5,39 @@ use std::vec; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, from_json, to_json_binary, Addr, Binary, CosmosMsg, Decimal, Decimal256, Deps, DepsMut, - Env, Fraction, MessageInfo, QuerierWrapper, Reply, ReplyOn, Response, StdError, StdResult, - SubMsg, SubMsgResponse, SubMsgResult, Uint128, Uint256, Uint64, WasmMsg, + attr, coin, ensure_eq, from_json, to_json_binary, wasm_execute, Addr, Binary, Coin, CosmosMsg, + CustomMsg, CustomQuery, Decimal, Decimal256, Deps, DepsMut, Env, Fraction, MessageInfo, + QuerierWrapper, Reply, Response, StdError, StdResult, SubMsg, SubMsgResponse, SubMsgResult, + Uint128, Uint256, Uint64, WasmMsg, }; use cw2::{get_contract_version, set_contract_version}; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg, MinterResponse}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use astroport::asset::{ - addr_opt_validate, check_swap_parameters, format_lp_token_name, Asset, AssetInfo, CoinsExt, - PairInfo, MINIMUM_LIQUIDITY_AMOUNT, + addr_opt_validate, check_swap_parameters, Asset, AssetInfo, CoinsExt, PairInfo, + MINIMUM_LIQUIDITY_AMOUNT, }; use astroport::factory::PairType; -use astroport::incentives::Cw20Msg as GeneratorHookMsg; +use astroport::incentives::ExecuteMsg as IncentiveExecuteMsg; use astroport::pair::{ - ConfigResponse, FeeShareConfig, XYKPoolConfig, XYKPoolParams, XYKPoolUpdateParams, + ConfigResponse, FeeShareConfig, ReplyIds, XYKPoolConfig, XYKPoolParams, XYKPoolUpdateParams, DEFAULT_SLIPPAGE, MAX_ALLOWED_SLIPPAGE, MAX_FEE_SHARE_BPS, }; use astroport::pair::{ CumulativePricesResponse, Cw20HookMsg, ExecuteMsg, InstantiateMsg, MigrateMsg, PoolResponse, QueryMsg, ReverseSimulationResponse, SimulationResponse, TWAP_PRECISION, }; -use astroport::querier::{query_factory_config, query_fee_info, query_supply}; -use astroport::{token::InstantiateMsg as TokenInstantiateMsg, U256}; -use cw_utils::parse_instantiate_response_data; + +use astroport::querier::{ + query_factory_config, query_fee_info, query_native_supply, query_tracker_config, +}; +use astroport::token_factory::{ + tf_before_send_hook_msg, tf_burn_msg, tf_create_denom_msg, tf_mint_msg, MsgCreateDenomResponse, +}; +use astroport::{tokenfactory_tracker, U256}; +use cw_utils::{ + one_coin, parse_reply_instantiate_data, MsgInstantiateContractResponse, PaymentError, +}; use crate::error::ContractError; use crate::state::{Config, BALANCES, CONFIG}; @@ -37,8 +46,8 @@ use crate::state::{Config, BALANCES, CONFIG}; const CONTRACT_NAME: &str = "astroport-pair"; /// Contract version that is used for migration. const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -/// A `reply` call code ID used for sub-messages. -const INSTANTIATE_TOKEN_REPLY_ID: u64 = 1; +/// Tokenfactory LP token subdenom +pub const LP_SUBDENOM: &str = "astroport/share"; /// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. #[cfg_attr(not(feature = "library"), entry_point)] @@ -71,7 +80,7 @@ pub fn instantiate( let config = Config { pair_info: PairInfo { contract_addr: env.contract.address.clone(), - liquidity_token: Addr::unchecked(""), + liquidity_token: "".to_owned(), asset_infos: msg.asset_infos.clone(), pair_type: PairType::Xyk {}, }, @@ -81,6 +90,7 @@ pub fn instantiate( price1_cumulative_last: Uint128::zero(), track_asset_balances, fee_share: None, + tracker_addr: None, }; if track_asset_balances { @@ -91,34 +101,13 @@ pub fn instantiate( CONFIG.save(deps.storage, &config)?; - let token_name = format_lp_token_name(&msg.asset_infos, &deps.querier)?; - - // Create the LP token contract - let sub_msg: Vec = vec![SubMsg { - msg: WasmMsg::Instantiate { - code_id: msg.token_code_id, - msg: to_json_binary(&TokenInstantiateMsg { - name: token_name, - symbol: "uLP".to_string(), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: env.contract.address.to_string(), - cap: None, - }), - marketing: None, - })?, - funds: vec![], - admin: None, - label: String::from("Astroport LP token"), - } - .into(), - id: INSTANTIATE_TOKEN_REPLY_ID, - gas_limit: None, - reply_on: ReplyOn::Success, - }]; + // Create LP token + let sub_msg: SubMsg<_> = SubMsg::reply_on_success( + tf_create_denom_msg(env.contract.address.to_string(), LP_SUBDENOM), + ReplyIds::CreateDenom as u64, + ); - Ok(Response::new().add_submessages(sub_msg).add_attribute( + Ok(Response::new().add_submessage(sub_msg).add_attribute( "asset_balances_tracking".to_owned(), if config.track_asset_balances { "enabled" @@ -131,33 +120,80 @@ 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 { - match msg { - Reply { - id: INSTANTIATE_TOKEN_REPLY_ID, - result: - SubMsgResult::Ok(SubMsgResponse { - data: Some(data), .. - }), - } => { - let mut config: Config = CONFIG.load(deps.storage)?; - - if config.pair_info.liquidity_token != Addr::unchecked("") { - return Err(ContractError::Unauthorized {}); - } +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { + match ReplyIds::try_from(msg.id)? { + ReplyIds::CreateDenom => { + if let SubMsgResult::Ok(SubMsgResponse { data: Some(b), .. }) = msg.result { + let MsgCreateDenomResponse { new_token_denom } = b.try_into()?; + let config = CONFIG.load(deps.storage)?; + + let tracking = config.track_asset_balances; + let mut sub_msgs = vec![]; + + #[cfg(any(feature = "injective", feature = "sei"))] + let tracking = false; + + if tracking { + let factory_config = query_factory_config(&deps.querier, &config.factory_addr)?; + let tracker_config = query_tracker_config(&deps.querier, config.factory_addr)?; + // Instantiate tracking contract + let sub_msg: Vec = vec![SubMsg::reply_on_success( + WasmMsg::Instantiate { + admin: Some(factory_config.owner.to_string()), + code_id: tracker_config.code_id, + msg: to_json_binary(&tokenfactory_tracker::InstantiateMsg { + tokenfactory_module_address: tracker_config + .token_factory_addr + .to_string(), + tracked_denom: new_token_denom.clone(), + })?, + funds: vec![], + label: format!("{new_token_denom} tracking contract"), + }, + ReplyIds::InstantiateTrackingContract as u64, + )]; + + sub_msgs.extend(sub_msg); + } - let init_response = parse_instantiate_response_data(data.as_slice()) - .map_err(|e| StdError::generic_err(format!("{e}")))?; + CONFIG.update(deps.storage, |mut config| { + if !config.pair_info.liquidity_token.is_empty() { + return Err(StdError::generic_err( + "Liquidity token is already set in the config", + )); + } - config.pair_info.liquidity_token = - deps.api.addr_validate(&init_response.contract_address)?; + config.pair_info.liquidity_token = new_token_denom.clone(); + Ok(config) + })?; - CONFIG.save(deps.storage, &config)?; + Ok(Response::new() + .add_submessages(sub_msgs) + .add_attribute("lp_denom", new_token_denom)) + } else { + Err(ContractError::FailedToParseReply {}) + } + } + ReplyIds::InstantiateTrackingContract => { + let MsgInstantiateContractResponse { + contract_address, .. + } = parse_reply_instantiate_data(msg)?; + + let config = CONFIG.update::<_, StdError>(deps.storage, |mut c| { + c.tracker_addr = Some(deps.api.addr_validate(&contract_address)?); + Ok(c) + })?; + + let set_hook_msg = tf_before_send_hook_msg( + env.contract.address, + config.pair_info.liquidity_token, + contract_address.clone(), + ); Ok(Response::new() - .add_attribute("liquidity_token_addr", config.pair_info.liquidity_token)) + .add_message(set_hook_msg) + .add_attribute("tracker_contract", contract_address)) } - _ => Err(ContractError::FailedToParseReply {}), } } @@ -174,6 +210,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Result provide_liquidity( deps, env, @@ -204,6 +246,7 @@ pub fn execute( slippage_tolerance, auto_stake, receiver, + min_lp_to_receive, ), ExecuteMsg::Swap { offer_asset, @@ -230,7 +273,11 @@ pub fn execute( to_addr, ) } - ExecuteMsg::UpdateConfig { params } => update_config(deps, env, info, params), + ExecuteMsg::UpdateConfig { params } => update_config(deps, info, params), + ExecuteMsg::WithdrawLiquidity { + assets, + min_assets_to_receive, + } => withdraw_liquidity(deps, env, info, assets, min_assets_to_receive), _ => Err(ContractError::NonSupported {}), } } @@ -284,14 +331,6 @@ pub fn receive_cw20( to_addr, ) } - Cw20HookMsg::WithdrawLiquidity { assets } => withdraw_liquidity( - deps, - env, - info, - Addr::unchecked(cw20_msg.sender), - cw20_msg.amount, - assets, - ), } } @@ -303,12 +342,14 @@ pub fn receive_cw20( /// the pool price can move until the provide liquidity transaction goes through. /// /// * **auto_stake** is an optional parameter which determines whether the LP tokens minted after -/// liquidity provision are automatically staked in the Generator contract on behalf of the LP token receiver. +/// liquidity provision are automatically staked in the Incentives contract on behalf of the LP token receiver. /// /// * **receiver** is an optional parameter which defines the receiver of the LP tokens. /// If no custom receiver is specified, the pair will mint LP tokens for the function caller. /// +/// * **min_lp_to_receive** is an optional parameter which specifies the minimum amount of LP tokens to receive. /// NOTE - the address that wants to provide liquidity should approve the pair contract to pull its relevant tokens. +#[allow(clippy::too_many_arguments)] pub fn provide_liquidity( deps: DepsMut, env: Env, @@ -317,37 +358,20 @@ pub fn provide_liquidity( slippage_tolerance: Option, auto_stake: Option, receiver: Option, + min_lp_to_receive: Option, ) -> Result { - if assets.len() != 2 { - return Err(StdError::generic_err("asset_infos must contain exactly two elements").into()); - } - assets[0].info.check(deps.api)?; - assets[1].info.check(deps.api)?; - - let auto_stake = auto_stake.unwrap_or(false); - let mut config = CONFIG.load(deps.storage)?; - info.funds - .assert_coins_properly_sent(&assets, &config.pair_info.asset_infos)?; + let mut pools = config .pair_info .query_pools(&deps.querier, &config.pair_info.contract_addr)?; - let deposits = [ - assets - .iter() - .find(|a| a.info.equal(&pools[0].info)) - .map(|a| a.amount) - .expect("Wrong asset info is given"), - assets - .iter() - .find(|a| a.info.equal(&pools[1].info)) - .map(|a| a.amount) - .expect("Wrong asset info is given"), - ]; - if deposits[0].is_zero() || deposits[1].is_zero() { - return Err(ContractError::InvalidZeroAmount {}); - } + let deposits = get_deposits_from_assets(deps.as_ref(), &assets, &pools)?; + + info.funds + .assert_coins_properly_sent(&assets, &config.pair_info.asset_infos)?; + + let auto_stake = auto_stake.unwrap_or(false); let mut messages = vec![]; for (i, pool) in pools.iter_mut().enumerate() { @@ -369,17 +393,10 @@ pub fn provide_liquidity( } } - let total_share = query_supply(&deps.querier, &config.pair_info.liquidity_token)?; - let share = if total_share.is_zero() { - // Initial share = collateral amount - let share = Uint128::new( - (U256::from(deposits[0].u128()) * U256::from(deposits[1].u128())) - .integer_sqrt() - .as_u128(), - ) - .checked_sub(MINIMUM_LIQUIDITY_AMOUNT) - .map_err(|_| ContractError::MinimumLiquidityAmountError {})?; + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; + let share = calculate_shares(&deposits, &pools, total_share, slippage_tolerance)?; + if total_share.is_zero() { messages.extend(mint_liquidity_token_message( deps.querier, &config, @@ -388,27 +405,16 @@ pub fn provide_liquidity( MINIMUM_LIQUIDITY_AMOUNT, false, )?); + } - // share cannot become zero after minimum liquidity subtraction - if share.is_zero() { - return Err(ContractError::MinimumLiquidityAmountError {}); - } - - share - } else { - // Assert slippage tolerance - assert_slippage_tolerance(slippage_tolerance, &deposits, &pools)?; + let min_amount_lp = min_lp_to_receive.unwrap_or(Uint128::zero()); - // min(1, 2) - // 1. sqrt(deposit_0 * exchange_rate_0_to_1 * deposit_0) * (total_share / sqrt(pool_0 * pool_0)) - // == deposit_0 * total_share / pool_0 - // 2. sqrt(deposit_1 * exchange_rate_1_to_0 * deposit_1) * (total_share / sqrt(pool_1 * pool_1)) - // == deposit_1 * total_share / pool_1 - std::cmp::min( - deposits[0].multiply_ratio(total_share, pools[0].amount), - deposits[1].multiply_ratio(total_share, pools[1].amount), - ) - }; + if share < min_amount_lp { + return Err(ContractError::ProvideSlippageViolation( + share, + min_amount_lp, + )); + } // Mint LP tokens for the sender or for the receiver (if set) let receiver = addr_opt_validate(deps.api, &receiver)?.unwrap_or_else(|| info.sender.clone()); @@ -451,81 +457,71 @@ pub fn provide_liquidity( ])) } -/// Mint LP tokens for a beneficiary and auto stake the tokens in the Generator contract (if auto staking is specified). +/// Mint LP tokens for a beneficiary and auto stake the tokens in the Incentive contract (if auto staking is specified). /// -/// * **recipient** is the LP token recipient. +/// * **recipient** LP token recipient. /// -/// * **amount** is the amount of LP tokens that will be minted for the recipient. +/// * **coin** denom and amount of LP tokens that will be minted for the recipient. /// /// * **auto_stake** determines whether the newly minted LP tokens will -/// be automatically staked in the Generator on behalf of the recipient. -fn mint_liquidity_token_message( - querier: QuerierWrapper, +/// be automatically staked in the Incentives contract on behalf of the recipient. +pub fn mint_liquidity_token_message( + querier: QuerierWrapper, config: &Config, contract_address: &Addr, recipient: &Addr, amount: Uint128, auto_stake: bool, -) -> Result, ContractError> { - let lp_token = &config.pair_info.liquidity_token; +) -> Result>, ContractError> +where + C: CustomQuery, + T: CustomMsg, +{ + let coin = coin(amount.into(), config.pair_info.liquidity_token.to_string()); // If no auto-stake - just mint to recipient if !auto_stake { - return Ok(vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: lp_token.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: recipient.to_string(), - amount, - })?, - funds: vec![], - })]); + return Ok(tf_mint_msg(contract_address, coin, recipient)); } - // Mint for the pair contract and stake into the Generator contract - let generator = query_factory_config(&querier, &config.factory_addr)?.generator_address; + // Mint for the pair contract and stake into the Incentives contract + let incentives_addr = query_factory_config(&querier, &config.factory_addr)?.generator_address; - if let Some(generator) = generator { - Ok(vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: lp_token.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: contract_address.to_string(), - amount, - })?, - funds: vec![], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: lp_token.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Send { - contract: generator.to_string(), - amount, - msg: to_json_binary(&GeneratorHookMsg::DepositFor(recipient.to_string()))?, - })?, - funds: vec![], - }), - ]) + if let Some(address) = incentives_addr { + let mut msgs = tf_mint_msg(contract_address, coin.clone(), contract_address); + msgs.push( + wasm_execute( + address, + &IncentiveExecuteMsg::Deposit { + recipient: Some(recipient.to_string()), + }, + vec![coin], + )? + .into(), + ); + Ok(msgs) } else { Err(ContractError::AutoStakeError {}) } } /// Withdraw liquidity from the pool. -/// * **sender** is the address that will receive assets back from the pair contract. -/// -/// * **amount** is the amount of LP tokens to burn. pub fn withdraw_liquidity( deps: DepsMut, env: Env, info: MessageInfo, - sender: Addr, - amount: Uint128, assets: Vec, + min_assets_to_receive: Option>, ) -> Result { let mut config = CONFIG.load(deps.storage).unwrap(); - if info.sender != config.pair_info.liquidity_token { - return Err(ContractError::Unauthorized {}); - } + let Coin { amount, denom } = one_coin(&info)?; + + ensure_eq!( + denom, + config.pair_info.liquidity_token, + PaymentError::MissingDenom(config.pair_info.liquidity_token.to_string()) + ); let (pools, total_share) = pool_info(deps.querier, &config)?; @@ -536,6 +532,8 @@ pub fn withdraw_liquidity( return Err(StdError::generic_err("Imbalanced withdraw is currently disabled").into()); }; + ensure_min_assets_to_receive(&config, refund_assets.clone(), min_assets_to_receive)?; + if config.track_asset_balances { for (i, pool) in pools.iter().enumerate() { BALANCES.save( @@ -549,7 +547,7 @@ pub fn withdraw_liquidity( // Accumulate prices for the pair assets if let Some((price0_cumulative_new, price1_cumulative_new, block_time)) = - accumulate_prices(env, &config, pools[0].amount, pools[1].amount)? + accumulate_prices(env.clone(), &config, pools[0].amount, pools[1].amount)? { config.price0_cumulative_last = price0_cumulative_new; config.price1_cumulative_last = price1_cumulative_new; @@ -558,19 +556,20 @@ pub fn withdraw_liquidity( } // Update the pool info - let messages: Vec = vec![ - refund_assets[0].clone().into_msg(sender.clone())?, - refund_assets[1].clone().into_msg(sender.clone())?, - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: config.pair_info.liquidity_token.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Burn { amount })?, - funds: vec![], - }), - ]; + let mut messages = refund_assets + .clone() + .into_iter() + .map(|asset| asset.into_msg(&info.sender)) + .collect::>>()?; + + messages.push(tf_burn_msg( + env.contract.address, + coin(amount.u128(), config.pair_info.liquidity_token.to_string()), + )); Ok(Response::new().add_messages(messages).add_attributes(vec![ attr("action", "withdraw_liquidity"), - attr("sender", sender), + attr("sender", &info.sender), attr("withdrawn_share", amount), attr( "refund_assets", @@ -781,7 +780,6 @@ pub fn swap( /// * **params** new parameter values. pub fn update_config( deps: DepsMut, - env: Env, info: MessageInfo, params: Binary, ) -> Result { @@ -795,27 +793,6 @@ pub fn update_config( let mut response = Response::default(); match from_json::(¶ms)? { - XYKPoolUpdateParams::EnableAssetBalancesTracking => { - if config.track_asset_balances { - return Err(ContractError::AssetBalancesTrackingIsAlreadyEnabled {}); - } - config.track_asset_balances = true; - - let pools = config - .pair_info - .query_pools(&deps.querier, &config.pair_info.contract_addr)?; - - for pool in pools.iter() { - BALANCES.save(deps.storage, &pool.info, &pool.amount, env.block.height)?; - } - - CONFIG.save(deps.storage, &config)?; - - response.attributes.push(attr( - "asset_balances_tracking".to_owned(), - "enabled".to_owned(), - )); - } XYKPoolUpdateParams::EnableFeeShare { fee_share_bps, fee_share_address, @@ -947,6 +924,9 @@ pub fn calculate_maker_fee( /// /// * **QueryMsg::AssetBalanceAt { asset_info, block_height }** Returns the balance of the specified asset that was in the pool /// just preceeding the moment of the specified block height creation. +/// * **QueryMsg::SimulateWithdraw { lp_amount }** Returns the amount of assets that could be withdrawn from the pool +/// using a specific amount of LP tokens. The result is returned in a vector that contains objects of type [`Asset`]. +/// * **QueryMsg::SimulateProvide { msg }** Simulates the liquidity provision in the pair contract. #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { @@ -965,6 +945,11 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { asset_info, block_height, } => to_json_binary(&query_asset_balances_at(deps, asset_info, block_height)?), + QueryMsg::SimulateWithdraw { lp_amount } => to_json_binary(&query_share(deps, lp_amount)?), + QueryMsg::SimulateProvide { + assets, + slippage_tolerance, + } => to_json_binary(&query_simulate_provide(deps, assets, slippage_tolerance)?), _ => Err(StdError::generic_err("Query is not supported")), } } @@ -1140,9 +1125,38 @@ pub fn query_config(deps: Deps) -> StdResult { })?), owner: factory_config.owner, factory_addr: config.factory_addr, + tracker_addr: config.tracker_addr, }) } +/// Returns the amount of LP tokens that will be minted +/// +/// * **assets** is an array with assets available in the pool. +/// +/// * **slippage_tolerance** is an optional parameter which is used to specify how much +/// the pool price can move until the provide liquidity transaction goes through. +/// +fn query_simulate_provide( + deps: Deps, + assets: Vec, + slippage_tolerance: Option, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + let pools = config + .pair_info + .query_pools(&deps.querier, &config.pair_info.contract_addr)?; + + let deposits = get_deposits_from_assets(deps, &assets, &pools) + .map_err(|e| StdError::generic_err(e.to_string()))?; + + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; + let share = calculate_shares(&deposits, &pools, total_share, slippage_tolerance) + .map_err(|e| StdError::generic_err(e.to_string()))?; + + Ok(share) +} + /// Returns the balance of the specified asset that was in the pool /// just preceeding the moment of the specified block height creation. /// It will return None (null) if the balance was not tracked up to the specified block height @@ -1239,6 +1253,91 @@ pub fn compute_offer_amount( Ok((offer_amount, spread_amount, commission_amount.try_into()?)) } +/// Returns shares for the provided deposits. +/// +/// * **deposits** is an array with asset amounts +/// +/// * **pools** is an array with total amount of assets in the pool +/// +/// * **total_share** is the total amount of LP tokens currently minted +/// +/// * **slippage_tolerance** is an optional parameter which is used to specify how much +/// the pool price can move until the provide liquidity transaction goes through. +pub fn calculate_shares( + deposits: &[Uint128; 2], + pools: &[Asset], + total_share: Uint128, + slippage_tolerance: Option, +) -> Result { + let share = if total_share.is_zero() { + // Initial share = collateral amount + let share = Uint128::new( + (U256::from(deposits[0].u128()) * U256::from(deposits[1].u128())) + .integer_sqrt() + .as_u128(), + ) + .checked_sub(MINIMUM_LIQUIDITY_AMOUNT) + .map_err(|_| ContractError::MinimumLiquidityAmountError {})?; + + // share cannot become zero after minimum liquidity subtraction + if share.is_zero() { + return Err(ContractError::MinimumLiquidityAmountError {}); + } + + share + } else { + // Assert slippage tolerance + assert_slippage_tolerance(slippage_tolerance, deposits, pools)?; + + // min(1, 2) + // 1. sqrt(deposit_0 * exchange_rate_0_to_1 * deposit_0) * (total_share / sqrt(pool_0 * pool_0)) + // == deposit_0 * total_share / pool_0 + // 2. sqrt(deposit_1 * exchange_rate_1_to_0 * deposit_1) * (total_share / sqrt(pool_1 * pool_1)) + // == deposit_1 * total_share / pool_1 + std::cmp::min( + deposits[0].multiply_ratio(total_share, pools[0].amount), + deposits[1].multiply_ratio(total_share, pools[1].amount), + ) + }; + Ok(share) +} + +/// Verify assets provided and returns deposit amounts. +/// +/// * **assets** is an array with assets available in the pool. +/// +/// * **pools** is the array with assets in the pool. +pub fn get_deposits_from_assets( + deps: Deps, + assets: &[Asset], + pools: &[Asset], +) -> Result<[Uint128; 2], ContractError> { + if assets.len() != 2 { + return Err(StdError::generic_err("asset_infos must contain exactly two elements").into()); + } + assets[0].info.check(deps.api)?; + assets[1].info.check(deps.api)?; + + let deposits = [ + assets + .iter() + .find(|a| a.info.equal(&pools[0].info)) + .map(|a| a.amount) + .expect("Wrong asset info is given"), + assets + .iter() + .find(|a| a.info.equal(&pools[1].info)) + .map(|a| a.amount) + .expect("Wrong asset info is given"), + ]; + + if deposits[0].is_zero() || deposits[1].is_zero() { + return Err(ContractError::InvalidZeroAmount {}); + } + + Ok(deposits) +} + /// If `belief_price` and `max_spread` are both specified, we compute a new spread, /// otherwise we just use the swap spread to check `max_spread`. /// @@ -1357,11 +1456,54 @@ pub fn pool_info(querier: QuerierWrapper, config: &Config) -> StdResult<(Vec, + min_assets_to_receive: Option>, +) -> Result<(), ContractError> { + if let Some(min_assets_to_receive) = min_assets_to_receive { + if refund_assets.len() != min_assets_to_receive.len() { + return Err(ContractError::WrongAssetLength { + expected: refund_assets.len(), + actual: min_assets_to_receive.len(), + }); + } + + for asset in &min_assets_to_receive { + if !config.pair_info.asset_infos.contains(&asset.info) { + return Err(ContractError::AssetMismatch {}); + } + } + + if refund_assets[0].info.ne(&min_assets_to_receive[0].info) { + refund_assets.swap(0, 1) + } + + if refund_assets[0].amount < min_assets_to_receive[0].amount { + return Err(ContractError::WithdrawSlippageViolation { + asset_name: refund_assets[0].info.to_string(), + received: refund_assets[0].amount, + expected: min_assets_to_receive[0].amount, + }); + } + + if refund_assets[1].amount < min_assets_to_receive[1].amount { + return Err(ContractError::WithdrawSlippageViolation { + asset_name: refund_assets[1].info.to_string(), + received: refund_assets[1].amount, + expected: min_assets_to_receive[1].amount, + }); + } + } + + Ok(()) +} + #[cfg(test)] mod tests { use cosmwasm_std::{Decimal, Uint128}; diff --git a/contracts/pair/src/error.rs b/contracts/pair/src/error.rs index 49346ba79..4cea8d7f4 100644 --- a/contracts/pair/src/error.rs +++ b/contracts/pair/src/error.rs @@ -1,5 +1,6 @@ use astroport::{asset::MINIMUM_LIQUIDITY_AMOUNT, pair::MAX_FEE_SHARE_BPS}; -use cosmwasm_std::{OverflowError, StdError}; +use cosmwasm_std::{OverflowError, StdError, Uint128}; +use cw_utils::{ParseReplyError, PaymentError}; use thiserror::Error; /// This enum describes pair contract errors @@ -8,6 +9,12 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("{0}")] + PaymentError(#[from] PaymentError), + + #[error("{0}")] + ParseReplyError(#[from] ParseReplyError), + #[error("Unauthorized")] Unauthorized {}, @@ -29,6 +36,19 @@ pub enum ContractError { #[error("Operation exceeds max splippage tolerance")] MaxSlippageAssertion {}, + #[error("Slippage is more than expected: received {0}, expected {1} LP tokens")] + ProvideSlippageViolation(Uint128, Uint128), + + #[error("Received {received} {asset_name} but expected {expected}")] + WithdrawSlippageViolation { + asset_name: String, + received: Uint128, + expected: Uint128, + }, + + #[error("Wrong asset length: expected {expected}, actual {actual}")] + WrongAssetLength { expected: usize, actual: usize }, + #[error("Doubling assets in asset infos")] DoublingAssets {}, @@ -38,7 +58,7 @@ pub enum ContractError { #[error("Pair type mismatch. Check factory pair configs")] PairTypeMismatch {}, - #[error("Generator address is not set in factory. Cannot auto-stake")] + #[error("Incentives address is not set in factory. Cannot auto-stake")] AutoStakeError {}, #[error("Initial liquidity must be more than {}", MINIMUM_LIQUIDITY_AMOUNT)] @@ -47,9 +67,6 @@ pub enum ContractError { #[error("Failed to migrate the contract")] MigrationError {}, - #[error("Asset balances tracking is already enabled")] - AssetBalancesTrackingIsAlreadyEnabled {}, - #[error("Failed to parse or process reply message")] FailedToParseReply {}, diff --git a/contracts/pair/src/migration.rs b/contracts/pair/src/migration.rs index a002ed834..5a072d97a 100644 --- a/contracts/pair/src/migration.rs +++ b/contracts/pair/src/migration.rs @@ -39,6 +39,7 @@ pub(crate) fn add_asset_balances_tracking_flag( price1_cumulative_last: old_config.price1_cumulative_last, track_asset_balances: false, fee_share: None, + tracker_addr: None, }; CONFIG.save(storage, &new_config)?; diff --git a/contracts/pair/src/state.rs b/contracts/pair/src/state.rs index 74b6de0d0..f902b03e3 100644 --- a/contracts/pair/src/state.rs +++ b/contracts/pair/src/state.rs @@ -23,6 +23,8 @@ pub struct Config { pub track_asset_balances: bool, // The config for swap fee sharing pub fee_share: Option, + /// Stores the tracker contract address + pub tracker_addr: Option, } /// Stores the config struct at the given key diff --git a/contracts/pair/src/testing.rs b/contracts/pair/src/testing.rs index 5bc52b668..fff034a0f 100644 --- a/contracts/pair/src/testing.rs +++ b/contracts/pair/src/testing.rs @@ -1,9 +1,10 @@ use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - attr, to_json_binary, Addr, BankMsg, BlockInfo, Coin, CosmosMsg, Decimal, DepsMut, Env, Reply, - ReplyOn, Response, StdError, SubMsg, SubMsgResponse, SubMsgResult, Timestamp, Uint128, WasmMsg, + attr, coin, to_json_binary, Addr, BankMsg, Binary, BlockInfo, Coin, CosmosMsg, Decimal, + DepsMut, Env, Reply, ReplyOn, Response, StdError, SubMsg, SubMsgResponse, SubMsgResult, + Timestamp, Uint128, WasmMsg, }; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg, MinterResponse}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use proptest::prelude::*; use astroport::asset::{Asset, AssetInfo, PairInfo}; @@ -12,14 +13,14 @@ use astroport::pair::{ Cw20HookMsg, ExecuteMsg, InstantiateMsg, PoolResponse, ReverseSimulationResponse, SimulationResponse, TWAP_PRECISION, }; -use astroport::token::InstantiateMsg as TokenInstantiateMsg; +use astroport::token_factory::{MsgBurn, MsgCreateDenom, MsgCreateDenomResponse, MsgMint}; -use crate::contract::compute_offer_amount; use crate::contract::reply; use crate::contract::{ accumulate_prices, assert_max_spread, compute_swap, execute, instantiate, query_pool, query_reverse_simulation, query_share, query_simulation, }; +use crate::contract::{compute_offer_amount, LP_SUBDENOM}; use crate::error::ContractError; use crate::mock_querier::mock_dependencies; use crate::state::{Config, CONFIG}; @@ -34,26 +35,21 @@ struct MsgInstantiateContractResponse { pub data: Vec, } -fn store_liquidity_token(deps: DepsMut, msg_id: u64, contract_addr: String) { - let instantiate_reply = MsgInstantiateContractResponse { - contract_address: contract_addr, - data: vec![], - }; - - let mut encoded_instantiate_reply = Vec::::with_capacity(instantiate_reply.encoded_len()); - instantiate_reply - .encode(&mut encoded_instantiate_reply) - .unwrap(); - +fn store_liquidity_token(deps: DepsMut, msg_id: u64, subdenom: String) { let reply_msg = Reply { id: msg_id, result: SubMsgResult::Ok(SubMsgResponse { events: vec![], - data: Some(encoded_instantiate_reply.into()), + data: Some( + MsgCreateDenomResponse { + new_token_denom: subdenom, + } + .into(), + ), }), }; - let _res = reply(deps, mock_env(), reply_msg.clone()).unwrap(); + reply(deps, mock_env(), reply_msg).unwrap(); } #[test] @@ -83,41 +79,35 @@ fn proper_initialization() { // We can just call .unwrap() to assert this was a success let env = mock_env(); let info = mock_info(sender, &[]); - let res = instantiate(deps.as_mut(), env, info, msg).unwrap(); + let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); assert_eq!( res.messages, vec![SubMsg { - msg: WasmMsg::Instantiate { - code_id: 10u64, - msg: to_json_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: None, - label: String::from("Astroport LP token"), - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgCreateDenom::TYPE_URL.to_string(), + value: Binary( + MsgCreateDenom { + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + subdenom: LP_SUBDENOM.to_string() + } + .encode_to_vec() + ) + }, id: 1, gas_limit: None, reply_on: ReplyOn::Success },] ); + let denom = format!("factory/{}/{}", env.contract.address, "astroport/share"); + // Store liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); // It worked, let's query the state let pair_info = CONFIG.load(deps.as_ref().storage).unwrap().pair_info; - assert_eq!(Addr::unchecked("liquidity0000"), pair_info.liquidity_token); + assert_eq!(denom, pair_info.liquidity_token); assert_eq!( pair_info.asset_infos, [ @@ -166,10 +156,11 @@ fn provide_liquidity() { 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(); + let _res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); + let denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); - // Store liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + // Store the liquidity token + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); // Successfully provide liquidity for the existing pool let msg = ExecuteMsg::ProvideLiquidity { @@ -190,6 +181,7 @@ fn provide_liquidity() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env(); @@ -226,16 +218,22 @@ fn provide_liquidity() { assert_eq!( mint_min_liquidity_msg, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from(MOCK_CONTRACT_ADDR), - amount: Uint128::from(1000_u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from( + MsgMint { + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(1000_u128).to_string(), + }), + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + #[cfg(not(any(feature = "injective", feature = "sei")))] + mint_to_address: String::from(MOCK_CONTRACT_ADDR), + } + .encode_to_vec() + ) + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -244,16 +242,22 @@ fn provide_liquidity() { assert_eq!( mint_receiver_msg, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from("addr0000"), - amount: Uint128::from(99_999999999999999000u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from( + MsgMint { + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(99_999999999999999000u128).to_string(), + }), + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + #[cfg(not(any(feature = "injective", feature = "sei")))] + mint_to_address: String::from("addr0000"), + } + .encode_to_vec() + ) + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -268,24 +272,21 @@ fn provide_liquidity() { denom: "uusd".to_string(), amount: Uint128::new(200_000000000000000000 + 200_000000000000000000 /* user deposit must be pre-applied */), }], + ), ( + &String::from("liquidity0000"), + &[Coin { + denom: denom.to_string(), + amount: Uint128::new(100_000000000000000000), + }], )]); - 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), - )], - ), - ]); + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[( + &String::from(MOCK_CONTRACT_ADDR), + &Uint128::new(200_000000000000000000), + )], + )]); let msg = ExecuteMsg::ProvideLiquidity { assets: vec![ @@ -305,6 +306,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(50)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -320,6 +322,7 @@ fn provide_liquidity() { let res: Response = execute(deps.as_mut(), env.clone(), 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 { @@ -342,16 +345,22 @@ fn provide_liquidity() { assert_eq!( mint_msg, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from("addr0000"), - amount: Uint128::from(50_000000000000000000u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from( + MsgMint { + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(50_000000000000000000u128).to_string(), + }), + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + #[cfg(not(any(feature = "injective", feature = "sei")))] + mint_to_address: String::from("addr0000"), + } + .encode_to_vec() + ) + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -377,6 +386,7 @@ fn provide_liquidity() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env(); @@ -440,6 +450,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -481,6 +492,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -522,6 +534,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -562,6 +575,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -592,6 +606,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let info = mock_info( "addr0001", @@ -621,6 +636,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(51)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let info = mock_info( "addr0001", @@ -640,6 +656,9 @@ fn withdraw_liquidity() { amount: Uint128::new(100u128), }]); + let env = mock_env(); + let info = mock_info("addr0000", &[]); + deps.querier.with_token_balances(&[ ( &String::from("liquidity0000"), @@ -666,24 +685,36 @@ fn withdraw_liquidity() { init_params: None, }; - let env = mock_env(); - let info = mock_info("addr0000", &[]); + let denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); + + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(100u128))], + )]); + + deps.querier.with_balance(&[( + &String::from("asset0000"), + &[Coin { + denom: denom.to_string(), + amount: Uint128::new(100u128), + }], + )]); + // We can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); + instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); - // Store liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + // Store the liquidity token + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); // Withdraw liquidity - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("addr0000"), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), - amount: Uint128::new(100u128), - }); + let msg = ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: None, + }; let env = mock_env(); - let info = mock_info("liquidity0000", &[]); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let info = mock_info("addr0000", &[coin(100u128, denom.clone())]); + let res = execute(deps.as_mut(), env.clone(), 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"); @@ -725,15 +756,22 @@ fn withdraw_liquidity() { assert_eq!( msg_burn_liquidity, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Burn { - amount: Uint128::from(100u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgBurn::TYPE_URL.to_string(), + value: Binary::from( + MsgBurn { + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(100u128).to_string(), + }), + #[cfg(not(any(feature = "injective", feature = "sei")))] + burn_from_address: "".to_string() + } + .encode_to_vec() + ), + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -1214,21 +1252,26 @@ 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 env = mock_env(); + let info = mock_info("addr0000", &[]); + 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 denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); + + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[(&String::from(MOCK_CONTRACT_ADDR), &asset_1_amount)], + )]); + + deps.querier.with_balance(&[( + &"addr0000".to_string(), + &[coin(total_share_amount.u128(), denom.clone())], + )]); let msg = InstantiateMsg { asset_infos: vec![ @@ -1244,13 +1287,11 @@ fn test_query_pool() { init_params: 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(); + instantiate(deps.as_mut(), env, info, msg).unwrap(); - // Store liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + // Store the liquidity token + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); let res: PoolResponse = query_pool(deps.as_ref()).unwrap(); @@ -1279,21 +1320,26 @@ 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 env = mock_env(); + let info = mock_info("addr0000", &[]); + 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 denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); + + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[(&String::from(MOCK_CONTRACT_ADDR), &asset_1_amount)], + )]); + + deps.querier.with_balance(&[( + &"addr0000".to_string(), + &[coin(total_share_amount.u128(), denom.clone())], + )]); let msg = InstantiateMsg { asset_infos: vec![ @@ -1309,13 +1355,11 @@ fn test_query_share() { init_params: 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(); + instantiate(deps.as_mut(), env, info, msg).unwrap(); // Store liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); let res = query_share(deps.as_ref(), Uint128::new(250)).unwrap(); @@ -1412,7 +1456,7 @@ fn test_accumulate_prices() { }, ], contract_addr: Addr::unchecked("pair"), - liquidity_token: Addr::unchecked("lp_token"), + liquidity_token: "lp_token".to_owned(), pair_type: PairType::Xyk {}, // Implemented in mock querier }, factory_addr: Addr::unchecked("factory"), @@ -1421,6 +1465,7 @@ fn test_accumulate_prices() { price1_cumulative_last: Uint128::new(case.last1), track_asset_balances: false, fee_share: None, + tracker_addr: None, }, Uint128::new(case.x_amount), Uint128::new(case.y_amount), diff --git a/contracts/pair/tests/integration.rs b/contracts/pair/tests/integration.rs index a2d01c1a0..c9fb35576 100644 --- a/contracts/pair/tests/integration.rs +++ b/contracts/pair/tests/integration.rs @@ -1,12 +1,9 @@ #![cfg(not(tarpaulin_include))] -use cosmwasm_std::{attr, to_json_binary, Addr, Coin, Decimal, Uint128, Uint64}; -use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; - -use astroport::asset::{native_asset_info, Asset, AssetInfo, PairInfo}; +use astroport::asset::{native_asset_info, Asset, AssetInfo, PairInfo, MINIMUM_LIQUIDITY_AMOUNT}; use astroport::factory::{ ExecuteMsg as FactoryExecuteMsg, InstantiateMsg as FactoryInstantiateMsg, PairConfig, PairType, - QueryMsg as FactoryQueryMsg, + QueryMsg as FactoryQueryMsg, TrackerConfig, }; use astroport::pair::{ ConfigResponse, CumulativePricesResponse, Cw20HookMsg, ExecuteMsg, FeeShareConfig, @@ -14,19 +11,31 @@ use astroport::pair::{ MAX_FEE_SHARE_BPS, TWAP_PRECISION, }; use astroport::token::InstantiateMsg as TokenInstantiateMsg; -use astroport_mocks::cw_multi_test::{App, ContractWrapper, Executor}; +use astroport::tokenfactory_tracker::{ + ConfigResponse as TrackerConfigResponse, QueryMsg as TrackerQueryMsg, +}; + +use astroport_test::cw_multi_test::{AppBuilder, ContractWrapper, Executor, TOKEN_FACTORY_MODULE}; +use astroport_test::modules::stargate::{MockStargate, StargateApp as TestApp}; + +use astroport_pair::contract::LP_SUBDENOM; use astroport_pair::error::ContractError; +use cosmwasm_std::{ + attr, coin, to_json_binary, Addr, Coin, Decimal, DepsMut, Empty, Env, MessageInfo, Response, + StdResult, Uint128, Uint64, +}; +use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; + const OWNER: &str = "owner"; -fn mock_app(owner: Addr, coins: Vec) -> App { - App::new(|router, _, storage| { - // initialization moved to App construction - router.bank.init_balance(storage, &owner, coins).unwrap() - }) +fn mock_app(owner: Addr, coins: Vec) -> TestApp { + AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|router, _, storage| router.bank.init_balance(storage, &owner, coins).unwrap()) } -fn store_token_code(app: &mut App) -> u64 { +fn store_token_code(app: &mut TestApp) -> u64 { let astro_token_contract = Box::new(ContractWrapper::new_with_empty( cw20_base::contract::execute, cw20_base::contract::instantiate, @@ -36,7 +45,7 @@ fn store_token_code(app: &mut App) -> u64 { app.store_code(astro_token_contract) } -fn store_pair_code(app: &mut App) -> u64 { +fn store_pair_code(app: &mut TestApp) -> u64 { let pair_contract = Box::new( ContractWrapper::new_with_empty( astroport_pair::contract::execute, @@ -49,7 +58,7 @@ fn store_pair_code(app: &mut App) -> u64 { app.store_code(pair_contract) } -fn store_factory_code(app: &mut App) -> u64 { +fn store_factory_code(app: &mut TestApp) -> u64 { let factory_contract = Box::new( ContractWrapper::new_with_empty( astroport_factory::contract::execute, @@ -62,7 +71,31 @@ fn store_factory_code(app: &mut App) -> u64 { app.store_code(factory_contract) } -fn instantiate_pair(mut router: &mut App, owner: &Addr) -> Addr { +fn store_generator_code(app: &mut TestApp) -> u64 { + let generator_contract = Box::new(ContractWrapper::new_with_empty( + astroport_incentives::execute::execute, + astroport_incentives::instantiate::instantiate, + astroport_incentives::query::query, + )); + + app.store_code(generator_contract) +} + +fn store_tracker_contract(app: &mut TestApp) -> u64 { + let tracker_contract = Box::new( + ContractWrapper::new_with_empty( + |_: DepsMut, _: Env, _: MessageInfo, _: Empty| -> StdResult { + unimplemented!() + }, + astroport_tokenfactory_tracker::contract::instantiate, + astroport_tokenfactory_tracker::query::query, + ) + .with_sudo_empty(astroport_tokenfactory_tracker::contract::sudo), + ); + app.store_code(tracker_contract) +} + +fn instantiate_pair(mut router: &mut TestApp, owner: &Addr) -> Addr { let token_contract_code_id = store_token_code(&mut router); let pair_contract_code_id = store_pair_code(&mut router); @@ -84,6 +117,7 @@ fn instantiate_pair(mut router: &mut App, owner: &Addr) -> Addr { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = router @@ -127,7 +161,10 @@ fn instantiate_pair(mut router: &mut App, owner: &Addr) -> Addr { .query_wasm_smart(pair.clone(), &QueryMsg::Pair {}) .unwrap(); assert_eq!("contract1", res.contract_addr); - assert_eq!("contract2", res.liquidity_token); + assert_eq!( + format!("factory/contract1/{}", LP_SUBDENOM), + res.liquidity_token + ); pair } @@ -203,6 +240,7 @@ fn test_provide_and_withdraw_liquidity() { Uint128::new(100_000_000), None, None, + None, ); let res = router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) @@ -221,25 +259,95 @@ fn test_provide_and_withdraw_liquidity() { res.events[1].attributes[5], attr("share", 99999000u128.to_string()) ); - assert_eq!(res.events[3].attributes[1], attr("action", "mint")); - assert_eq!(res.events[3].attributes[2], attr("to", "contract1")); - assert_eq!( - res.events[3].attributes[3], - attr("amount", 1000.to_string()) + + // Provide with min_lp_to_receive with a bigger amount than expected. + let min_lp_amount_to_receive: Uint128 = router + .wrap() + .query_wasm_smart( + pair_instance.clone(), + &QueryMsg::SimulateProvide { + assets: vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::new(100_000_000), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + amount: Uint128::new(100_000_000), + }, + ], + slippage_tolerance: None, + }, + ) + .unwrap(); + + let double_amount_to_receive = min_lp_amount_to_receive * Uint128::new(2); + + let (msg, coins) = provide_liquidity_msg( + Uint128::new(100), + Uint128::new(100), + None, + None, + Some(double_amount_to_receive.clone()), ); - assert_eq!(res.events[5].attributes[1], attr("action", "mint")); - assert_eq!(res.events[5].attributes[2], attr("to", "alice")); + + let err = router + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) + .unwrap_err(); + assert_eq!( - res.events[5].attributes[3], - attr("amount", 99999000.to_string()) + err.downcast::().unwrap(), + ContractError::ProvideSlippageViolation(Uint128::new(100), double_amount_to_receive) + ); + + // Provide with min_lp_to_receive with amount expected + let min_lp_amount_to_receive: Uint128 = router + .wrap() + .query_wasm_smart( + pair_instance.clone(), + &QueryMsg::SimulateProvide { + assets: vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::new(100), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + amount: Uint128::new(100), + }, + ], + slippage_tolerance: None, + }, + ) + .unwrap(); + + let (msg, coins) = provide_liquidity_msg( + Uint128::new(100), + Uint128::new(100), + None, + None, + Some(min_lp_amount_to_receive), ); + router + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) + .unwrap(); + // Provide liquidity for receiver let (msg, coins) = provide_liquidity_msg( Uint128::new(100), Uint128::new(100), Some("bob".to_string()), None, + None, ); let res = router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) @@ -258,74 +366,108 @@ fn test_provide_and_withdraw_liquidity() { res.events[1].attributes[5], attr("share", 100u128.to_string()) ); - assert_eq!(res.events[3].attributes[1], attr("action", "mint")); - assert_eq!(res.events[3].attributes[2], attr("to", "bob")); - assert_eq!(res.events[3].attributes[3], attr("amount", 100.to_string())); - // Checking withdraw liquidity - let token_contract_code_id = store_token_code(&mut router); - let foo_token = router - .instantiate_contract( - token_contract_code_id, - owner.clone(), - &astroport::token::InstantiateMsg { - name: "Foo token".to_string(), - symbol: "FOO".to_string(), - decimals: 6, - initial_balances: vec![Cw20Coin { - address: alice_address.to_string(), - amount: Uint128::from(1000000000u128), - }], - mint: None, - marketing: None, + let msg = ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: None, + }; + // Try to send withdraw liquidity with uluna token + let err = router + .execute_contract( + alice_address.clone(), + pair_instance.clone(), + &msg, + &[coin(50u128, "uluna")], + ) + .unwrap_err(); + + assert_eq!( + err.root_cause().to_string(), + format!( + "Must send reserve token 'factory/contract1/{}'", + LP_SUBDENOM + ) + ); + + // Withdraw liquidity doubling the minimum to recieve + let min_assets_to_receive: Vec = router + .wrap() + .query_wasm_smart( + pair_instance.clone(), + &QueryMsg::SimulateWithdraw { + lp_amount: Uint128::new(100), }, - &[], - String::from("FOO"), - None, ) .unwrap(); - let msg = Cw20ExecuteMsg::Send { - contract: pair_instance.to_string(), - amount: Uint128::from(50u8), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), - }; - // Try to send withdraw liquidity with FOO token let err = router - .execute_contract(alice_address.clone(), foo_token.clone(), &msg, &[]) + .execute_contract( + alice_address.clone(), + pair_instance.clone(), + &ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: Some( + min_assets_to_receive + .iter() + .map(|a| Asset { + info: a.info.clone(), + amount: a.amount * Uint128::new(2), + }) + .collect(), + ), + }, + &[coin(100u128, lp_token.clone())], + ) .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Unauthorized"); - // Withdraw with LP token is successful - router - .execute_contract(alice_address.clone(), lp_token.clone(), &msg, &[]) + + assert_eq!( + err.downcast::().unwrap(), + ContractError::WithdrawSlippageViolation { + asset_name: "uusd".to_string(), + expected: Uint128::new(198), + received: Uint128::new(99) + } + ); + + // Withdraw liquidity with minimum to receive + + let min_assets_to_receive: Vec = router + .wrap() + .query_wasm_smart( + pair_instance.clone(), + &QueryMsg::SimulateWithdraw { + lp_amount: Uint128::new(100), + }, + ) .unwrap(); - let err = router + router .execute_contract( alice_address.clone(), pair_instance.clone(), - &ExecuteMsg::Swap { - offer_asset: Asset { - info: AssetInfo::NativeToken { - denom: "cny".to_string(), - }, - amount: Uint128::from(10u8), - }, - ask_asset_info: None, - belief_price: None, - max_spread: None, - to: None, + &ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: Some(min_assets_to_receive), }, - &[Coin { - denom: "cny".to_string(), - amount: Uint128::from(10u8), - }], + &[coin(100u128, lp_token.clone())], + ) + .unwrap(); + + // Withdraw with LP token is successful + router + .execute_contract( + alice_address.clone(), + pair_instance.clone(), + &msg, + &[coin(50u128, lp_token.clone())], ) + .unwrap(); + + let err = router + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &[]) .unwrap_err(); - assert_eq!( - err.root_cause().to_string(), - "Asset mismatch between the requested and the stored asset in contract" - ); + + assert_eq!(err.root_cause().to_string(), "No funds sent"); // Check pair config let config: ConfigResponse = router @@ -344,7 +486,8 @@ fn test_provide_and_withdraw_liquidity() { .unwrap() ), owner, - factory_addr: config.factory_addr + factory_addr: config.factory_addr, + tracker_addr: config.tracker_addr, } ) } @@ -354,6 +497,7 @@ fn provide_liquidity_msg( uluna_amount: Uint128, receiver: Option, slippage_tolerance: Option, + min_lp_to_receive: Option, ) -> (ExecuteMsg, [Coin; 2]) { let msg = ExecuteMsg::ProvideLiquidity { assets: vec![ @@ -373,6 +517,7 @@ fn provide_liquidity_msg( slippage_tolerance: Option::from(slippage_tolerance), auto_stake: None, receiver, + min_lp_to_receive, }; let coins = [ @@ -489,6 +634,7 @@ fn test_compatibility_of_tokens_with_different_precision() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = app @@ -594,6 +740,7 @@ fn test_compatibility_of_tokens_with_different_precision() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) @@ -676,6 +823,7 @@ fn test_if_twap_is_calculated_correctly_when_pool_idles() { Uint128::new(1000000_000000), None, Option::from(Decimal::one()), + None, ); app.execute_contract(user1.clone(), pair_instance.clone(), &msg, &coins) .unwrap(); @@ -695,6 +843,7 @@ fn test_if_twap_is_calculated_correctly_when_pool_idles() { Uint128::new(1000000_000000), None, Some(Decimal::percent(50)), + None, ); app.execute_contract(user1.clone(), pair_instance.clone(), &msg, &coins) .unwrap(); @@ -842,21 +991,13 @@ fn asset_balances_tracking_works_correctly() { let mut app = mock_app( owner.clone(), vec![ - Coin { - denom: "test1".to_owned(), - amount: Uint128::new(5_000000), - }, - Coin { - denom: "test2".to_owned(), - amount: Uint128::new(5_000000), - }, Coin { denom: "uluna".to_owned(), - amount: Uint128::new(1000_000000), + amount: Uint128::new(10000_000000), }, Coin { denom: "uusd".to_owned(), - amount: Uint128::new(1000_000000), + amount: Uint128::new(10000_000000), }, ], ); @@ -880,6 +1021,10 @@ fn asset_balances_tracking_works_correctly() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: Some(TrackerConfig { + code_id: store_tracker_contract(&mut app), + token_factory_addr: TOKEN_FACTORY_MODULE.to_string(), + }), }; let factory_instance = app @@ -893,18 +1038,23 @@ fn asset_balances_tracking_works_correctly() { ) .unwrap(); - // Instantiate pair without asset balances tracking + // Instantiate new pair with asset balances tracking starting from instantiation let msg = FactoryExecuteMsg::CreatePair { asset_infos: vec![ AssetInfo::NativeToken { - denom: "test1".to_string(), + denom: "uluna".to_string(), }, AssetInfo::NativeToken { - denom: "test2".to_string(), + denom: "uusd".to_string(), }, ], pair_type: PairType::Xyk {}, - init_params: None, + init_params: Some( + to_json_binary(&XYKPoolParams { + track_asset_balances: Some(true), + }) + .unwrap(), + ), }; app.execute_contract(owner.clone(), factory_instance.clone(), &msg, &[]) @@ -913,10 +1063,10 @@ fn asset_balances_tracking_works_correctly() { let msg = FactoryQueryMsg::Pair { asset_infos: vec![ AssetInfo::NativeToken { - denom: "test1".to_string(), + denom: "uluna".to_string(), }, AssetInfo::NativeToken { - denom: "test2".to_string(), + denom: "uusd".to_string(), }, ], }; @@ -927,90 +1077,91 @@ fn asset_balances_tracking_works_correctly() { .unwrap(); let pair_instance = res.contract_addr; + let lp_token_address = res.liquidity_token; - // Check that asset balances are not tracked - // The query AssetBalanceAt returns None for this case - let res: Option = app + // Provide liquidity + let (msg, send_funds) = provide_liquidity_msg( + Uint128::new(999_000000), + Uint128::new(1000_000000), + None, + None, + None, + ); + app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &send_funds) + .unwrap(); + + let owner_lp_balance = app .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "test1".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) + .query_balance(owner.to_string(), &lp_token_address) .unwrap(); - assert!(res.is_none()); + assert_eq!(owner_lp_balance.amount, Uint128::new(999498874)); + // Check that asset balances changed after providing liqudity + app.update_block(|b| b.height += 1); let res: Option = app .wrap() .query_wasm_smart( &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test2".to_owned(), + denom: "uluna".to_owned(), }, block_height: app.block_info().height.into(), }, ) .unwrap(); - assert!(res.is_none()); - - // Enable asset balances tracking - let msg = ExecuteMsg::UpdateConfig { - params: to_json_binary(&XYKPoolUpdateParams::EnableAssetBalancesTracking).unwrap(), - }; - app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) - .unwrap(); + assert_eq!(res.unwrap(), Uint128::new(1000_000000)); - // Check that asset balances were not tracked before this was enabled - // The query AssetBalanceAt returns None for this case let res: Option = app .wrap() .query_wasm_smart( &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test1".to_owned(), + denom: "uusd".to_owned(), }, block_height: app.block_info().height.into(), }, ) .unwrap(); - assert!(res.is_none()); + assert_eq!(res.unwrap(), Uint128::new(999_000000)); - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "test2".to_owned(), - }, - block_height: app.block_info().height.into(), + // Swap + + let msg = ExecuteMsg::Swap { + offer_asset: Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_owned(), }, - ) + amount: Uint128::new(1_000000), + }, + ask_asset_info: None, + belief_price: None, + max_spread: None, + to: None, + }; + let send_funds = vec![Coin { + denom: "uusd".to_owned(), + amount: Uint128::new(1_000000), + }]; + app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &send_funds) .unwrap(); - assert!(res.is_none()); - // Check that asset balances had zero balances before next block upon tracking enabing + // Check that asset balances changed after swaping app.update_block(|b| b.height += 1); - let res: Option = app .wrap() .query_wasm_smart( &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test1".to_owned(), + denom: "uluna".to_owned(), }, block_height: app.block_info().height.into(), }, ) .unwrap(); - assert!(res.unwrap().is_zero()); + assert_eq!(res.unwrap(), Uint128::new(999_000000)); let res: Option = app .wrap() @@ -1018,50 +1169,26 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test2".to_owned(), + denom: "uusd".to_owned(), }, block_height: app.block_info().height.into(), }, ) .unwrap(); - assert!(res.unwrap().is_zero()); - - // Provide liquidity - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::NativeToken { - denom: "test1".to_string(), - }, - amount: Uint128::new(5_000000), - }, - Asset { - info: AssetInfo::NativeToken { - denom: "test2".to_string(), - }, - amount: Uint128::new(5_000000), - }, - ], - slippage_tolerance: None, - auto_stake: None, - receiver: None, - }; + assert_eq!(res.unwrap(), Uint128::new(1000_000000)); - let send_funds = [ - Coin { - denom: "test1".to_string(), - amount: Uint128::new(5_000000), - }, - Coin { - denom: "test2".to_string(), - amount: Uint128::new(5_000000), + app.execute_contract( + owner.clone(), + pair_instance.clone(), + &ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: None, }, - ]; - - app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &send_funds) - .unwrap(); + &[coin(500_000000u128, lp_token_address)], + ) + .unwrap(); - // Check that asset balances changed after providing liqudity + // Check that asset balances changed after withdrawing app.update_block(|b| b.height += 1); let res: Option = app .wrap() @@ -1069,13 +1196,13 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test1".to_owned(), + denom: "uluna".to_owned(), }, block_height: app.block_info().height.into(), }, ) .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(5_000000)); + assert_eq!(res.unwrap(), Uint128::new(499_250063)); let res: Option = app .wrap() @@ -1083,298 +1210,14 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test2".to_owned(), + denom: "uusd".to_owned(), }, block_height: app.block_info().height.into(), }, ) .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(5_000000)); - - // Instantiate new pair with asset balances tracking starting from instantiation - let msg = FactoryExecuteMsg::CreatePair { - asset_infos: vec![ - AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - ], - pair_type: PairType::Xyk {}, - init_params: Some( - to_json_binary(&XYKPoolParams { - track_asset_balances: Some(true), - }) - .unwrap(), - ), - }; - - app.execute_contract(owner.clone(), factory_instance.clone(), &msg, &[]) - .unwrap(); - - let msg = FactoryQueryMsg::Pair { - asset_infos: vec![ - AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - ], - }; - - let res: PairInfo = app - .wrap() - .query_wasm_smart(&factory_instance, &msg) - .unwrap(); - - let pair_instance = res.contract_addr; - let lp_token_address = res.liquidity_token; - - // Check that asset balances were not tracked before instantiation - // The query AssetBalanceAt returns None for this case - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.is_none()); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.is_none()); - - // Check that enabling asset balances tracking can not be done if it is already enabled - let msg = ExecuteMsg::UpdateConfig { - params: to_json_binary(&XYKPoolUpdateParams::EnableAssetBalancesTracking).unwrap(), - }; - assert_eq!( - app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) - .unwrap_err() - .downcast_ref::() - .unwrap(), - &ContractError::AssetBalancesTrackingIsAlreadyEnabled {} - ); - - // Check that asset balances were not tracked before instantiation - // The query AssetBalanceAt returns None for this case - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.is_none()); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.is_none()); - - // Check that asset balances had zero balances before next block upon instantiation - app.update_block(|b| b.height += 1); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.unwrap().is_zero()); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.unwrap().is_zero()); - - // Provide liquidity - let (msg, send_funds) = provide_liquidity_msg( - Uint128::new(999_000000), - Uint128::new(1000_000000), - None, - None, - ); - app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &send_funds) - .unwrap(); - - let msg = Cw20QueryMsg::Balance { - address: owner.to_string(), - }; - let owner_lp_balance: BalanceResponse = app - .wrap() - .query_wasm_smart(&lp_token_address, &msg) - .unwrap(); - assert_eq!(owner_lp_balance.balance, Uint128::new(999498874)); - - // Check that asset balances changed after providing liqudity - app.update_block(|b| b.height += 1); - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(1000_000000)); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(999_000000)); - - // Swap - - let msg = ExecuteMsg::Swap { - offer_asset: Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - amount: Uint128::new(1_000000), - }, - ask_asset_info: None, - belief_price: None, - max_spread: None, - to: None, - }; - let send_funds = vec![Coin { - denom: "uusd".to_owned(), - amount: Uint128::new(1_000000), - }]; - app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &send_funds) - .unwrap(); - - // Check that asset balances changed after swaping - app.update_block(|b| b.height += 1); - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(999_000000)); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(1000_000000)); - - // Withdraw liqudity - let msg = Cw20ExecuteMsg::Send { - contract: pair_instance.to_string(), - amount: Uint128::new(500_000000), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), - }; - - app.execute_contract(owner.clone(), lp_token_address, &msg, &[]) - .unwrap(); - - // Check that asset balances changed after withdrawing - app.update_block(|b| b.height += 1); - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(499_250063)); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(499_749812)); -} + assert_eq!(res.unwrap(), Uint128::new(499_749812)); +} #[test] fn update_pair_config() { @@ -1406,6 +1249,10 @@ fn update_pair_config() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: Some(TrackerConfig { + code_id: store_tracker_contract(&mut router), + token_factory_addr: TOKEN_FACTORY_MODULE.to_string(), + }), }; let factory_instance = router @@ -1461,48 +1308,8 @@ fn update_pair_config() { .unwrap() ), owner: Addr::unchecked("owner"), - factory_addr: Addr::unchecked("contract0") - } - ); - - let msg = ExecuteMsg::UpdateConfig { - params: to_json_binary(&XYKPoolUpdateParams::EnableAssetBalancesTracking).unwrap(), - }; - assert_eq!( - router - .execute_contract( - Addr::unchecked("not_owner").clone(), - pair.clone(), - &msg, - &[] - ) - .unwrap_err() - .downcast_ref::() - .unwrap(), - &ContractError::Unauthorized {} - ); - - router - .execute_contract(owner.clone(), pair.clone(), &msg, &[]) - .unwrap(); - - let res: ConfigResponse = router - .wrap() - .query_wasm_smart(pair.clone(), &QueryMsg::Config {}) - .unwrap(); - assert_eq!( - res, - ConfigResponse { - block_time_last: 0, - params: Some( - to_json_binary(&XYKPoolConfig { - track_asset_balances: true, - fee_share: None, - }) - .unwrap() - ), - owner: Addr::unchecked("owner"), - factory_addr: Addr::unchecked("contract0") + factory_addr: Addr::unchecked("contract0"), + tracker_addr: None } ); } @@ -1537,6 +1344,7 @@ fn enable_disable_fee_sharing() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = router @@ -1592,7 +1400,8 @@ fn enable_disable_fee_sharing() { .unwrap() ), owner: Addr::unchecked("owner"), - factory_addr: Addr::unchecked("contract0") + factory_addr: Addr::unchecked("contract0"), + tracker_addr: None } ); @@ -1642,60 +1451,274 @@ fn enable_disable_fee_sharing() { }; router - .execute_contract(owner.clone(), pair.clone(), &msg, &[]) + .execute_contract(owner.clone(), pair.clone(), &msg, &[]) + .unwrap(); + + let res: ConfigResponse = router + .wrap() + .query_wasm_smart(pair.clone(), &QueryMsg::Config {}) + .unwrap(); + assert_eq!( + res, + ConfigResponse { + block_time_last: 0, + params: Some( + to_json_binary(&XYKPoolConfig { + track_asset_balances: false, + fee_share: Some(FeeShareConfig { + bps: fee_share_bps, + recipient: Addr::unchecked(fee_share_contract), + }), + }) + .unwrap() + ), + owner: Addr::unchecked("owner"), + factory_addr: Addr::unchecked("contract0"), + tracker_addr: None + } + ); + + // Disable fee sharing + let msg = ExecuteMsg::UpdateConfig { + params: to_json_binary(&XYKPoolUpdateParams::DisableFeeShare).unwrap(), + }; + + router + .execute_contract(owner.clone(), pair.clone(), &msg, &[]) + .unwrap(); + + let res: ConfigResponse = router + .wrap() + .query_wasm_smart(pair.clone(), &QueryMsg::Config {}) + .unwrap(); + assert_eq!( + res, + ConfigResponse { + block_time_last: 0, + params: Some( + to_json_binary(&XYKPoolConfig { + track_asset_balances: false, + fee_share: None, + }) + .unwrap() + ), + owner: Addr::unchecked("owner"), + factory_addr: Addr::unchecked("contract0"), + tracker_addr: None + } + ); +} + +#[test] +fn provide_liquidity_with_autostaking_to_generator() { + let owner = Addr::unchecked("owner"); + let alice_address = Addr::unchecked("alice"); + let mut router = mock_app( + owner.clone(), + vec![ + Coin { + denom: "uusd".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + Coin { + denom: "cny".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + ], + ); + + // Set Alice's balances + router + .send_tokens( + owner.clone(), + alice_address.clone(), + &[ + Coin { + denom: "uusd".to_string(), + amount: Uint128::new(233_000_000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::new(2_00_000_000u128), + }, + Coin { + denom: "cny".to_string(), + amount: Uint128::from(100_000_000u128), + }, + ], + ) + .unwrap(); + + let token_contract_code_id = store_token_code(&mut router); + + let pair_contract_code_id = store_pair_code(&mut router); + let factory_code_id = store_factory_code(&mut router); + + let generator_code_id = store_generator_code(&mut router); + + let init_msg = FactoryInstantiateMsg { + fee_address: None, + pair_configs: vec![PairConfig { + code_id: pair_contract_code_id, + maker_fee_bps: 0, + pair_type: PairType::Xyk {}, + total_fee_bps: 0, + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }], + token_code_id: token_contract_code_id, + generator_address: None, + owner: owner.to_string(), + whitelist_code_id: 234u64, + coin_registry_address: "coin_registry".to_string(), + tracker_config: Some(TrackerConfig { + code_id: store_tracker_contract(&mut router), + token_factory_addr: TOKEN_FACTORY_MODULE.to_string(), + }), + }; + + let factory_instance = router + .instantiate_contract( + factory_code_id, + owner.clone(), + &init_msg, + &[], + "FACTORY", + None, + ) + .unwrap(); + + let generator_instance = router + .instantiate_contract( + generator_code_id, + owner.clone(), + &astroport::incentives::InstantiateMsg { + astro_token: native_asset_info("astro".to_string()), + factory: factory_instance.to_string(), + owner: owner.to_string(), + guardian: None, + incentivization_fee_info: None, + vesting_contract: "vesting".to_string(), + }, + &[], + "generator", + None, + ) + .unwrap(); + + router + .execute_contract( + owner.clone(), + factory_instance.clone(), + &astroport::factory::ExecuteMsg::UpdateConfig { + token_code_id: None, + fee_address: None, + generator_address: Some(generator_instance.to_string()), + whitelist_code_id: None, + coin_registry_address: None, + }, + &[], + ) + .unwrap(); + + let msg = FactoryExecuteMsg::CreatePair { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + ], + pair_type: PairType::Xyk {}, + init_params: Some( + to_json_binary(&XYKPoolParams { + track_asset_balances: Some(true), + }) + .unwrap(), + ), + }; + + router + .execute_contract(owner.clone(), factory_instance.clone(), &msg, &[]) .unwrap(); - let res: ConfigResponse = router + let uusd_amount = Uint128::new(100_000_000); + let uluna_amount = Uint128::new(100_000_000); + + let msg = ExecuteMsg::ProvideLiquidity { + assets: vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: uusd_amount.clone(), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + amount: uluna_amount.clone(), + }, + ], + slippage_tolerance: None, + auto_stake: Some(true), + receiver: None, + min_lp_to_receive: None, + }; + + let coins = [ + Coin { + denom: "uluna".to_string(), + amount: uluna_amount.clone(), + }, + Coin { + denom: "uusd".to_string(), + amount: uusd_amount.clone(), + }, + ]; + + let res: PairInfo = router .wrap() - .query_wasm_smart(pair.clone(), &QueryMsg::Config {}) + .query_wasm_smart( + &factory_instance, + &FactoryQueryMsg::Pair { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + ], + }, + ) .unwrap(); - assert_eq!( - res, - ConfigResponse { - block_time_last: 0, - params: Some( - to_json_binary(&XYKPoolConfig { - track_asset_balances: false, - fee_share: Some(FeeShareConfig { - bps: fee_share_bps, - recipient: Addr::unchecked(fee_share_contract), - }), - }) - .unwrap() - ), - owner: Addr::unchecked("owner"), - factory_addr: Addr::unchecked("contract0") - } - ); - // Disable fee sharing - let msg = ExecuteMsg::UpdateConfig { - params: to_json_binary(&XYKPoolUpdateParams::DisableFeeShare).unwrap(), - }; + let pair_instance = res.contract_addr; + let lp_token_address = res.liquidity_token; router - .execute_contract(owner.clone(), pair.clone(), &msg, &[]) + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) .unwrap(); - let res: ConfigResponse = router + let amount: Uint128 = router .wrap() - .query_wasm_smart(pair.clone(), &QueryMsg::Config {}) + .query_wasm_smart( + generator_instance.to_string(), + &astroport::incentives::QueryMsg::Deposit { + lp_token: lp_token_address.to_string(), + user: alice_address.to_string(), + }, + ) .unwrap(); - assert_eq!( - res, - ConfigResponse { - block_time_last: 0, - params: Some( - to_json_binary(&XYKPoolConfig { - track_asset_balances: false, - fee_share: None, - }) - .unwrap() - ), - owner: Addr::unchecked("owner"), - factory_addr: Addr::unchecked("contract0") - } - ); + + assert_eq!(amount, Uint128::new(99999000)); } #[test] @@ -1761,6 +1784,7 @@ fn test_imbalanced_withdraw_is_disabled() { Uint128::new(100_000_000), None, None, + None, ); router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) @@ -1772,28 +1796,30 @@ fn test_imbalanced_withdraw_is_disabled() { Uint128::new(100), Some("bob".to_string()), None, + None, ); router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) .unwrap(); // Check that imbalanced withdraw is currently disabled - let msg_imbalance = Cw20ExecuteMsg::Send { - contract: pair_instance.to_string(), - amount: Uint128::from(50u8), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { - assets: vec![Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128::from(100u8), - }], - }) - .unwrap(), + let msg_imbalance = ExecuteMsg::WithdrawLiquidity { + assets: vec![Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::from(100u8), + }], + min_assets_to_receive: None, }; let err = router - .execute_contract(alice_address.clone(), lp_token.clone(), &msg_imbalance, &[]) + .execute_contract( + alice_address.clone(), + pair_instance.clone(), + &msg_imbalance, + &[coin(100u128, lp_token)], + ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), @@ -1927,6 +1953,7 @@ fn test_fee_share( let pair_code_id = store_pair_code(&mut app); let factory_code_id = store_factory_code(&mut app); + let tracker_code_id = store_tracker_contract(&mut app); let init_msg = FactoryInstantiateMsg { fee_address: Some(maker_address.to_string()), @@ -1944,6 +1971,10 @@ fn test_fee_share( owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: Some(TrackerConfig { + code_id: tracker_code_id, + token_factory_addr: TOKEN_FACTORY_MODULE.to_string(), + }), }; let factory_instance = app @@ -2034,6 +2065,7 @@ fn test_fee_share( slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) @@ -2205,6 +2237,7 @@ fn test_provide_liquidity_without_funds() { Uint128::new(100_000_000), None, None, + None, ); router @@ -2224,3 +2257,176 @@ fn test_provide_liquidity_without_funds() { "Generic error: Native token balance mismatch between the argument (100000000uusd) and the transferred (0uusd)" ); } + +#[test] +fn test_tracker_contract() { + let owner = Addr::unchecked("owner"); + let alice = Addr::unchecked("alice"); + let mut app = mock_app( + owner.clone(), + vec![ + Coin { + denom: "test1".to_owned(), + amount: Uint128::new(5_000000), + }, + Coin { + denom: "test2".to_owned(), + amount: Uint128::new(5_000000), + }, + Coin { + denom: "uluna".to_owned(), + amount: Uint128::new(1000_000000), + }, + Coin { + denom: "uusd".to_owned(), + amount: Uint128::new(1000_000000), + }, + ], + ); + let token_code_id = store_token_code(&mut app); + let pair_code_id = store_pair_code(&mut app); + let factory_code_id = store_factory_code(&mut app); + + let init_msg = FactoryInstantiateMsg { + fee_address: None, + pair_configs: vec![PairConfig { + code_id: pair_code_id, + maker_fee_bps: 0, + pair_type: PairType::Xyk {}, + total_fee_bps: 0, + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }], + token_code_id, + generator_address: Some(String::from("generator")), + owner: owner.to_string(), + whitelist_code_id: 234u64, + coin_registry_address: "coin_registry".to_string(), + tracker_config: Some(TrackerConfig { + code_id: store_tracker_contract(&mut app), + token_factory_addr: TOKEN_FACTORY_MODULE.to_string(), + }), + }; + + let factory_instance = app + .instantiate_contract( + factory_code_id, + owner.clone(), + &init_msg, + &[], + "FACTORY", + None, + ) + .unwrap(); + + // Instantiate pair without asset balances tracking + let msg = FactoryExecuteMsg::CreatePair { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + ], + pair_type: PairType::Xyk {}, + init_params: Some( + to_json_binary(&XYKPoolParams { + track_asset_balances: Some(true), + }) + .unwrap(), + ), + }; + + app.execute_contract(owner.clone(), factory_instance.clone(), &msg, &[]) + .unwrap(); + + let msg = FactoryQueryMsg::Pair { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + ], + }; + + let res: PairInfo = app + .wrap() + .query_wasm_smart(&factory_instance, &msg) + .unwrap(); + + let pair_instance = res.contract_addr; + let lp_token = res.liquidity_token; + + // Provide liquidity + let (msg, send_funds) = provide_liquidity_msg( + Uint128::new(999_000000), + Uint128::new(1000_000000), + None, + None, + None, + ); + app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &send_funds) + .unwrap(); + + let owner_lp_funds = app + .wrap() + .query_balance(owner.clone(), lp_token.clone()) + .unwrap(); + + let total_supply = owner_lp_funds.amount + MINIMUM_LIQUIDITY_AMOUNT; + + // Set Alice's balances + app.send_tokens( + owner.clone(), + alice.clone(), + &[Coin { + denom: lp_token.to_string(), + amount: Uint128::new(100), + }], + ) + .unwrap(); + + let config: ConfigResponse = app + .wrap() + .query_wasm_smart(pair_instance.clone(), &QueryMsg::Config {}) + .unwrap(); + + let tracker_addr = config.tracker_addr.unwrap(); + + let tracker_config: TrackerConfigResponse = app + .wrap() + .query_wasm_smart(tracker_addr.clone(), &TrackerQueryMsg::Config {}) + .unwrap(); + assert_eq!( + tracker_config.token_factory_module, + TOKEN_FACTORY_MODULE.to_string() + ); + assert_eq!(tracker_config.tracked_denom, lp_token.to_string()); + + let tracker_total_supply: Uint128 = app + .wrap() + .query_wasm_smart( + tracker_addr.clone(), + &TrackerQueryMsg::TotalSupplyAt { timestamp: None }, + ) + .unwrap(); + + assert_eq!(total_supply, tracker_total_supply); + + let alice_balance: Uint128 = app + .wrap() + .query_wasm_smart( + tracker_addr, + &TrackerQueryMsg::BalanceAt { + address: alice.to_string(), + timestamp: None, + }, + ) + .unwrap(); + + assert_eq!(alice_balance, Uint128::new(100)); +} diff --git a/contracts/pair_astro_converter/Cargo.toml b/contracts/pair_astro_converter/Cargo.toml index 525a6d551..316ffdf9b 100644 --- a/contracts/pair_astro_converter/Cargo.toml +++ b/contracts/pair_astro_converter/Cargo.toml @@ -24,10 +24,10 @@ backtraces = ["cosmwasm-std/backtraces"] library = [] [dependencies] -astroport = { path = "../../packages/astroport", version = "4" } +astroport = "4" cw2.workspace = true cw20 = "1.1" -cosmwasm-std.workspace = true +cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1", "stargate"] } cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true diff --git a/contracts/pair_concentrated/Cargo.toml b/contracts/pair_concentrated/Cargo.toml index 45ba58a68..0a1717b15 100644 --- a/contracts/pair_concentrated/Cargo.toml +++ b/contracts/pair_concentrated/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "astroport-pair-concentrated" -version = "3.0.0" +version = "4.0.0" authors = ["Astroport"] edition = "2021" description = "The Astroport concentrated liquidity pair" license = "GPL-3.0-only" repository = "https://github.com/astroport-fi/astroport" homepage = "https://astroport.fi" +metadata = { build_variants = ["injective", "sei"] } exclude = [ # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. @@ -24,15 +25,17 @@ crate-type = ["cdylib", "rlib"] # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] library = [] +injective = ["astroport/injective", "astroport-pcl-common/injective"] +sei = ["astroport/sei", "astroport-pcl-common/sei"] [dependencies] -astroport = { path = "../../packages/astroport", version = "4" } +astroport = { path = "../../packages/astroport", version = "5" } astroport-factory = { path = "../factory", features = ["library"], version = "1" } astroport-circular-buffer = { path = "../../packages/circular_buffer", version = "0.2" } astroport-pcl-common = { path = "../../packages/astroport_pcl_common", version = "2" } cw2.workspace = true cw20 = "1.1" -cosmwasm-std.workspace = true +cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1", "stargate"] } cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true @@ -42,9 +45,11 @@ astroport-pair-concentrated_v1 = { package = "astroport-pair-concentrated", vers [dev-dependencies] cw20-base = "1.1" -astroport-mocks = { path = "../../packages/astroport_mocks" } +astroport-incentives = { path = "../tokenomics/incentives" } +astroport-test = { path = "../../packages/astroport_test" } astroport-factory = { path = "../factory" } proptest = "1.0" anyhow = "1.0" derivative = "2.2" astroport-native-coin-registry = { path = "../periphery/native_coin_registry" } +astroport-tokenfactory-tracker = { path = "../periphery/tokenfactory_tracker" } diff --git a/contracts/pair_concentrated/src/contract.rs b/contracts/pair_concentrated/src/contract.rs index 50c8e6ce7..a7155b8af 100644 --- a/contracts/pair_concentrated/src/contract.rs +++ b/contracts/pair_concentrated/src/contract.rs @@ -1,56 +1,64 @@ use std::vec; +use astroport::token_factory::{ + tf_before_send_hook_msg, tf_burn_msg, tf_create_denom_msg, MsgCreateDenomResponse, +}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, from_json, wasm_execute, wasm_instantiate, Addr, Attribute, Binary, CosmosMsg, Decimal, - Decimal256, DepsMut, Env, MessageInfo, Reply, Response, StdError, StdResult, SubMsg, - SubMsgResponse, SubMsgResult, Uint128, + attr, coin, ensure_eq, from_json, to_json_binary, wasm_execute, Addr, Binary, Coin, CosmosMsg, + Decimal, Decimal256, DepsMut, Env, MessageInfo, Reply, Response, StdError, StdResult, SubMsg, + SubMsgResponse, SubMsgResult, Uint128, WasmMsg, }; use cw2::{get_contract_version, set_contract_version}; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg, MinterResponse}; -use cw_utils::parse_instantiate_response_data; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; +use cw_utils::{ + one_coin, parse_reply_instantiate_data, MsgInstantiateContractResponse, PaymentError, +}; use itertools::Itertools; use astroport::asset::AssetInfoExt; use astroport::asset::{ - addr_opt_validate, format_lp_token_name, token_asset, Asset, AssetInfo, CoinsExt, - Decimal256Ext, PairInfo, MINIMUM_LIQUIDITY_AMOUNT, + addr_opt_validate, token_asset, Asset, AssetInfo, CoinsExt, PairInfo, MINIMUM_LIQUIDITY_AMOUNT, }; use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; -use astroport::cosmwasm_ext::{AbsDiff, DecimalToInteger, IntegerToDecimal}; +use astroport::cosmwasm_ext::{DecimalToInteger, IntegerToDecimal}; use astroport::factory::PairType; use astroport::observation::{PrecommitObservation, OBSERVATIONS_SIZE}; use astroport::pair::{ - Cw20HookMsg, ExecuteMsg, FeeShareConfig, InstantiateMsg, MAX_FEE_SHARE_BPS, MIN_TRADE_SIZE, + Cw20HookMsg, ExecuteMsg, FeeShareConfig, InstantiateMsg, ReplyIds, MAX_FEE_SHARE_BPS, + MIN_TRADE_SIZE, }; use astroport::pair_concentrated::{ ConcentratedPoolParams, ConcentratedPoolUpdateParams, MigrateMsg, UpdatePoolParams, }; -use astroport::querier::{query_factory_config, query_fee_info, query_supply}; -use astroport::token::InstantiateMsg as TokenInstantiateMsg; +use astroport::querier::{ + query_factory_config, query_fee_info, query_native_supply, query_tracker_config, +}; +use astroport::tokenfactory_tracker; use astroport_circular_buffer::BufferManager; use astroport_pcl_common::state::{ AmpGamma, Config, PoolParams, PoolState, Precisions, PriceState, }; use astroport_pcl_common::utils::{ - accumulate_prices, assert_max_spread, assert_slippage_tolerance, before_swap_check, - calc_last_prices, calc_provide_fee, check_asset_infos, check_assets, check_cw20_in_pool, - check_pair_registered, compute_swap, get_share_in_assets, mint_liquidity_token_message, + accumulate_prices, assert_max_spread, before_swap_check, calc_last_prices, check_asset_infos, + check_cw20_in_pool, compute_swap, get_share_in_assets, mint_liquidity_token_message, }; use astroport_pcl_common::{calc_d, get_xcp}; use crate::error::ContractError; use crate::migration::{migrate_config, migrate_config_v2}; use crate::state::{BALANCES, CONFIG, OBSERVATIONS, OWNERSHIP_PROPOSAL}; -use crate::utils::{accumulate_swap_sizes, query_pools}; +use crate::utils::{ + accumulate_swap_sizes, calculate_shares, get_assets_with_precision, query_pools, +}; /// Contract name that is used for migration. const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); /// Contract version that is used for migration. const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -/// A `reply` call code ID used for sub-messages. -const INSTANTIATE_TOKEN_REPLY_ID: u64 = 1; +/// Tokenfactory LP token subdenom +pub const LP_SUBDENOM: &str = "astroport/share"; /// An LP token's precision. pub(crate) const LP_TOKEN_PRECISION: u8 = 6; @@ -125,7 +133,7 @@ pub fn instantiate( let config = Config { pair_info: PairInfo { contract_addr: env.contract.address.clone(), - liquidity_token: Addr::unchecked(""), + liquidity_token: "".to_owned(), asset_infos: msg.asset_infos.clone(), pair_type: PairType::Custom("concentrated".to_string()), }, @@ -137,6 +145,7 @@ pub fn instantiate( owner: None, track_asset_balances: params.track_asset_balances.unwrap_or_default(), fee_share: None, + tracker_addr: None, }; if config.track_asset_balances { @@ -149,27 +158,10 @@ pub fn instantiate( BufferManager::init(deps.storage, OBSERVATIONS, OBSERVATIONS_SIZE)?; - let token_name = format_lp_token_name(&msg.asset_infos, &deps.querier)?; - // Create LP token let sub_msg = SubMsg::reply_on_success( - wasm_instantiate( - msg.token_code_id, - &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, - }, - vec![], - String::from("Astroport LP token"), - )?, - INSTANTIATE_TOKEN_REPLY_ID, + tf_create_denom_msg(env.contract.address.to_string(), LP_SUBDENOM), + ReplyIds::CreateDenom as u64, ); Ok(Response::new().add_submessage(sub_msg).add_attribute( @@ -185,30 +177,81 @@ 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 { - match msg { - Reply { - id: INSTANTIATE_TOKEN_REPLY_ID, - result: - SubMsgResult::Ok(SubMsgResponse { - data: Some(data), .. - }), - } => { - let mut config = CONFIG.load(deps.storage)?; +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { + match ReplyIds::try_from(msg.id)? { + ReplyIds::CreateDenom => { + if let SubMsgResult::Ok(SubMsgResponse { data: Some(b), .. }) = msg.result { + let MsgCreateDenomResponse { new_token_denom } = b.try_into()?; + let config = CONFIG.load(deps.storage)?; + + let tracking = config.track_asset_balances; + let mut sub_msgs = vec![]; + + #[cfg(any(feature = "injective", feature = "sei"))] + let tracking = false; + + if tracking { + let factory_config = + query_factory_config(&deps.querier, config.factory_addr.clone())?; + let tracker_config = query_tracker_config(&deps.querier, config.factory_addr)?; + // Instantiate tracking contract + let sub_msg: Vec = vec![SubMsg::reply_on_success( + WasmMsg::Instantiate { + admin: Some(factory_config.owner.to_string()), + code_id: tracker_config.code_id, + msg: to_json_binary(&tokenfactory_tracker::InstantiateMsg { + tokenfactory_module_address: tracker_config + .token_factory_addr + .to_string(), + tracked_denom: new_token_denom.clone(), + })?, + funds: vec![], + label: format!("{new_token_denom} tracking contract"), + }, + ReplyIds::InstantiateTrackingContract as u64, + )]; + + sub_msgs.extend(sub_msg); + } + + CONFIG.update(deps.storage, |mut config| { + if !config.pair_info.liquidity_token.is_empty() { + return Err(StdError::generic_err( + "Liquidity token is already set in the config", + )); + } - if config.pair_info.liquidity_token != Addr::unchecked("") { - return Err(ContractError::Unauthorized {}); + config.pair_info.liquidity_token = new_token_denom.clone(); + Ok(config) + })?; + + Ok(Response::new() + .add_submessages(sub_msgs) + .add_attribute("lp_denom", new_token_denom)) + } else { + Err(ContractError::FailedToParseReply {}) } + } + ReplyIds::InstantiateTrackingContract => { + let MsgInstantiateContractResponse { + contract_address, .. + } = parse_reply_instantiate_data(msg)?; + + let config = CONFIG.update::<_, StdError>(deps.storage, |mut c| { + c.tracker_addr = Some(deps.api.addr_validate(&contract_address)?); + Ok(c) + })?; + + let set_hook_msg = tf_before_send_hook_msg( + env.contract.address, + config.pair_info.liquidity_token, + contract_address.clone(), + ); - let init_response = parse_instantiate_response_data(data.as_slice()) - .map_err(|e| StdError::generic_err(format!("{e}")))?; - config.pair_info.liquidity_token = - deps.api.addr_validate(&init_response.contract_address)?; - CONFIG.save(deps.storage, &config)?; Ok(Response::new() - .add_attribute("liquidity_token_addr", config.pair_info.liquidity_token)) + .add_message(set_hook_msg) + .add_attribute("tracker_contract", contract_address)) } - _ => Err(ContractError::FailedToParseReply {}), } } @@ -249,6 +292,7 @@ pub fn execute( slippage_tolerance, auto_stake, receiver, + .. } => provide_liquidity( deps, env, @@ -324,6 +368,7 @@ pub fn execute( }) .map_err(Into::into) } + ExecuteMsg::WithdrawLiquidity { assets, .. } => withdraw_liquidity(deps, env, info, assets), } } @@ -359,14 +404,6 @@ fn receive_cw20( to_addr, ) } - Cw20HookMsg::WithdrawLiquidity { assets } => withdraw_liquidity( - deps, - env, - info, - Addr::unchecked(cw20_msg.sender), - cw20_msg.amount, - assets, - ), } } @@ -378,7 +415,7 @@ fn receive_cw20( /// the pool price can move until the provide liquidity transaction goes through. /// /// * **auto_stake** is an optional parameter which determines whether the LP tokens minted after -/// liquidity provision are automatically staked in the Generator contract on behalf of the LP token receiver. +/// liquidity provision are automatically staked in the Incentives contract on behalf of the LP token receiver. /// /// * **receiver** is an optional parameter which defines the receiver of the LP tokens. /// If no custom receiver is specified, the pair will mint LP tokens for the function caller. @@ -395,64 +432,25 @@ pub fn provide_liquidity( ) -> Result { let mut config = CONFIG.load(deps.storage)?; - if !check_pair_registered( - deps.querier, - &config.factory_addr, - &config.pair_info.asset_infos, - )? { - return Err(ContractError::PairIsNotRegistered {}); - } - - match assets.len() { - 0 => { - return Err(StdError::generic_err("Nothing to provide").into()); - } - 1 => { - // Append omitted asset with explicit zero amount - let (given_ind, _) = config - .pair_info - .asset_infos - .iter() - .find_position(|pool| pool.equal(&assets[0].info)) - .ok_or_else(|| ContractError::InvalidAsset(assets[0].info.to_string()))?; - assets.push(Asset { - info: config.pair_info.asset_infos[1 ^ given_ind].clone(), - amount: Uint128::zero(), - }); - } - 2 => {} - _ => { - return Err(ContractError::InvalidNumberOfAssets( - config.pair_info.asset_infos.len(), - )) - } - } - - check_assets(deps.api, &assets)?; - - info.funds - .assert_coins_properly_sent(&assets, &config.pair_info.asset_infos)?; + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)? + .to_decimal256(LP_TOKEN_PRECISION)?; let precisions = Precisions::new(deps.storage)?; - let mut pools = query_pools(deps.querier, &env.contract.address, &config, &precisions)?; - if pools[0].info.equal(&assets[1].info) { - assets.swap(0, 1); - } + let mut pools = query_pools(deps.querier, &env.contract.address, &config, &precisions)?; - // precisions.get_precision() also validates that the asset belongs to the pool - let deposits = [ - Decimal256::with_precision(assets[0].amount, precisions.get_precision(&assets[0].info)?)?, - Decimal256::with_precision(assets[1].amount, precisions.get_precision(&assets[1].info)?)?, - ]; + let old_real_price = config.pool_state.price_state.last_price; - let total_share = query_supply(&deps.querier, &config.pair_info.liquidity_token)? - .to_decimal256(LP_TOKEN_PRECISION)?; + let deposits = get_assets_with_precision( + deps.as_ref(), + &config, + &mut assets, + pools.clone(), + &precisions, + )?; - // Initial provide can not be one-sided - if total_share.is_zero() && (deposits[0].is_zero() || deposits[1].is_zero()) { - return Err(ContractError::InvalidZeroAmount {}); - } + info.funds + .assert_coins_properly_sent(&assets, &config.pair_info.asset_infos)?; let mut messages = vec![]; for (i, pool) in pools.iter_mut().enumerate() { @@ -480,23 +478,16 @@ pub fn provide_liquidity( } } - let mut new_xp = pools - .iter() - .enumerate() - .map(|(ind, pool)| pool.amount + deposits[ind]) - .collect_vec(); - new_xp[1] *= config.pool_state.price_state.price_scale; - - let amp_gamma = config.pool_state.get_amp_gamma(&env); - let new_d = calc_d(&new_xp, &_gamma)?; - let old_real_price = config.pool_state.price_state.last_price; - - let share = if total_share.is_zero() { - let xcp = get_xcp(new_d, config.pool_state.price_state.price_scale); - let mint_amount = xcp - .checked_sub(MINIMUM_LIQUIDITY_AMOUNT.to_decimal256(LP_TOKEN_PRECISION)?) - .map_err(|_| ContractError::MinimumLiquidityAmountError {})?; + let (share_uint128, slippage) = calculate_shares( + &env, + &mut config, + &mut pools, + total_share, + deposits.clone(), + slippage_tolerance, + )?; + if total_share.is_zero() { messages.extend(mint_liquidity_token_message( deps.querier, &config, @@ -505,62 +496,8 @@ pub fn provide_liquidity( MINIMUM_LIQUIDITY_AMOUNT, false, )?); - - // share cannot become zero after minimum liquidity subtraction - if mint_amount.is_zero() { - return Err(ContractError::MinimumLiquidityAmountError {}); - } - - config.pool_state.price_state.xcp_profit_real = Decimal256::one(); - config.pool_state.price_state.xcp_profit = Decimal256::one(); - - mint_amount - } else { - let mut old_xp = pools.iter().map(|a| a.amount).collect_vec(); - old_xp[1] *= config.pool_state.price_state.price_scale; - let old_d = calc_d(&old_xp, &_gamma)?; - let share = (total_share * new_d / old_d).saturating_sub(total_share); - - let mut ideposits = deposits; - ideposits[1] *= config.pool_state.price_state.price_scale; - - share * (Decimal256::one() - calc_provide_fee(&ideposits, &new_xp, &config.pool_params)) - }; - - // calculate accrued share - let share_ratio = share / (total_share + share); - let balanced_share = [ - new_xp[0] * share_ratio, - new_xp[1] * share_ratio / config.pool_state.price_state.price_scale, - ]; - let assets_diff = [ - deposits[0].diff(balanced_share[0]), - deposits[1].diff(balanced_share[1]), - ]; - - let mut slippage = Decimal256::zero(); - - // If deposit doesn't diverge too much from the balanced share, we don't update the price - if assets_diff[0] >= MIN_TRADE_SIZE && assets_diff[1] >= MIN_TRADE_SIZE { - slippage = assert_slippage_tolerance( - &deposits, - share, - &config.pool_state.price_state, - slippage_tolerance, - )?; - - let last_price = assets_diff[0] / assets_diff[1]; - config.pool_state.update_price( - &config.pool_params, - &env, - total_share + share, - &new_xp, - last_price, - )?; } - let share_uint128 = share.to_uint(LP_TOKEN_PRECISION)?; - // Mint LP tokens for the sender or for the receiver (if set) let receiver = addr_opt_validate(deps.api, &receiver)?.unwrap_or_else(|| info.sender.clone()); let auto_stake = auto_stake.unwrap_or(false); @@ -607,22 +544,22 @@ pub fn provide_liquidity( /// /// * **sender** address that will receive assets back from the pair contract /// -/// * **amount** amount of provided LP tokens -/// /// * **assets** defines number of coins a user wants to withdraw per each asset. fn withdraw_liquidity( deps: DepsMut, env: Env, info: MessageInfo, - sender: Addr, - amount: Uint128, assets: Vec, ) -> Result { let mut config = CONFIG.load(deps.storage)?; - if info.sender != config.pair_info.liquidity_token { - return Err(ContractError::Unauthorized {}); - } + let Coin { amount, denom } = one_coin(&info)?; + + ensure_eq!( + denom, + config.pair_info.liquidity_token, + PaymentError::MissingDenom(config.pair_info.liquidity_token.to_string()) + ); let precisions = Precisions::new(deps.storage)?; let pools = query_pools( @@ -632,7 +569,7 @@ fn withdraw_liquidity( &precisions, )?; - let total_share = query_supply(&deps.querier, &config.pair_info.liquidity_token)?; + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; let mut messages = vec![]; let refund_assets = if assets.is_empty() { @@ -670,17 +607,13 @@ fn withdraw_liquidity( refund_assets .iter() .cloned() - .map(|asset| asset.into_msg(&sender)) + .map(|asset| asset.into_msg(&info.sender)) .collect::>>()?, ); - messages.push( - wasm_execute( - &config.pair_info.liquidity_token, - &Cw20ExecuteMsg::Burn { amount }, - vec![], - )? - .into(), - ); + messages.push(tf_burn_msg( + env.contract.address, + coin(amount.u128(), config.pair_info.liquidity_token.to_string()), + )); if config.track_asset_balances { for (i, pool) in pools.iter().enumerate() { @@ -700,7 +633,7 @@ fn withdraw_liquidity( Ok(Response::new().add_messages(messages).add_attributes(vec![ attr("action", "withdraw_liquidity"), - attr("sender", sender), + attr("sender", info.sender), attr("withdrawn_share", amount), attr("refund_assets", refund_assets.iter().join(", ")), ])) @@ -786,7 +719,7 @@ fn swap( spread_amount, )?; - let total_share = query_supply(&deps.querier, &config.pair_info.liquidity_token)? + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)? .to_decimal256(LP_TOKEN_PRECISION)?; // Skip very small trade sizes which could significantly mess up the price due to rounding errors, @@ -903,36 +836,23 @@ fn update_config( return Err(ContractError::Unauthorized {}); } - let mut attrs: Vec = vec![]; + let mut response = Response::default(); - let action = match from_json::(¶ms)? { + match from_json::(¶ms)? { ConcentratedPoolUpdateParams::Update(update_params) => { config.pool_params.update_params(update_params)?; - "update_params" + + response.attributes.push(attr("action", "update_params")); } ConcentratedPoolUpdateParams::Promote(promote_params) => { config.pool_state.promote_params(&env, promote_params)?; - "promote_params" + response.attributes.push(attr("action", "promote_params")); } ConcentratedPoolUpdateParams::StopChangingAmpGamma {} => { config.pool_state.stop_promotion(&env); - "stop_changing_amp_gamma" - } - ConcentratedPoolUpdateParams::EnableAssetBalancesTracking {} => { - if config.track_asset_balances { - return Err(ContractError::AssetBalancesTrackingIsAlreadyEnabled {}); - } - config.track_asset_balances = true; - - let pools = config - .pair_info - .query_pools(&deps.querier, &config.pair_info.contract_addr)?; - - for pool in pools.iter() { - BALANCES.save(deps.storage, &pool.info, &pool.amount, env.block.height)?; - } - - "enable_asset_balances_tracking" + response + .attributes + .push(attr("action", "stop_changing_amp_gamma")); } ConcentratedPoolUpdateParams::EnableFeeShare { fee_share_bps, @@ -953,25 +873,24 @@ fn update_config( recipient: deps.api.addr_validate(&fee_share_address)?, }); - CONFIG.save(deps.storage, &config)?; - - attrs.push(attr("fee_share_bps", fee_share_bps.to_string())); - attrs.push(attr("fee_share_address", fee_share_address)); - "enable_fee_share" + response.attributes.extend(vec![ + attr("action", "enable_fee_share"), + attr("fee_share_bps", fee_share_bps.to_string()), + attr("fee_share_address", fee_share_address), + ]); } ConcentratedPoolUpdateParams::DisableFeeShare => { // Disable fee sharing for this contract by setting bps and // address back to None config.fee_share = None; - CONFIG.save(deps.storage, &config)?; - "disable_fee_share" + response + .attributes + .push(attr("action", "disable_fee_share")); } }; CONFIG.save(deps.storage, &config)?; - Ok(Response::new() - .add_attribute("action", action) - .add_attributes(attrs)) + Ok(response) } #[cfg_attr(not(feature = "library"), entry_point)] diff --git a/contracts/pair_concentrated/src/error.rs b/contracts/pair_concentrated/src/error.rs index b72e859f8..64ca02ad7 100644 --- a/contracts/pair_concentrated/src/error.rs +++ b/contracts/pair_concentrated/src/error.rs @@ -1,6 +1,8 @@ use cosmwasm_std::{ConversionOverflowError, OverflowError, StdError}; use thiserror::Error; +use cw_utils::{ParseReplyError, PaymentError}; + use astroport::{asset::MINIMUM_LIQUIDITY_AMOUNT, pair::MAX_FEE_SHARE_BPS}; use astroport_circular_buffer::error::BufferError; use astroport_pcl_common::error::PclError; @@ -14,12 +16,18 @@ pub enum ContractError { #[error("{0}")] ConversionOverflowError(#[from] ConversionOverflowError), + #[error("{0}")] + ParseReplyError(#[from] ParseReplyError), + #[error("{0}")] OverflowError(#[from] OverflowError), #[error("{0}")] CircularBuffer(#[from] BufferError), + #[error("{0}")] + PaymentError(#[from] PaymentError), + #[error("{0}")] PclError(#[from] PclError), @@ -53,9 +61,6 @@ pub enum ContractError { #[error("Contract can't be migrated!")] MigrationError {}, - #[error("Asset balances tracking is already enabled")] - AssetBalancesTrackingIsAlreadyEnabled {}, - #[error( "Fee share is 0 or exceeds maximum allowed value of {} bps", MAX_FEE_SHARE_BPS diff --git a/contracts/pair_concentrated/src/migration.rs b/contracts/pair_concentrated/src/migration.rs index f4afe61ec..2bc10cbe7 100644 --- a/contracts/pair_concentrated/src/migration.rs +++ b/contracts/pair_concentrated/src/migration.rs @@ -20,6 +20,7 @@ pub(crate) fn migrate_config(storage: &mut dyn Storage) -> Result<(), StdError> owner: old_config.owner, track_asset_balances: old_config.track_asset_balances, fee_share: None, + tracker_addr: None, }; CONFIG.save(storage, &new_config)?; @@ -63,6 +64,7 @@ pub(crate) fn migrate_config_v2(storage: &mut dyn Storage, env: &Env) -> Result< owner: old_config.owner, track_asset_balances: old_config.track_asset_balances, fee_share: old_config.fee_share, + tracker_addr: None, }; CONFIG.save(storage, &new_config)?; diff --git a/contracts/pair_concentrated/src/queries.rs b/contracts/pair_concentrated/src/queries.rs index e1706dcaf..bd5a5684c 100644 --- a/contracts/pair_concentrated/src/queries.rs +++ b/contracts/pair_concentrated/src/queries.rs @@ -13,7 +13,7 @@ use astroport::pair::{ SimulationResponse, }; use astroport::pair_concentrated::{ConcentratedPoolConfig, QueryMsg}; -use astroport::querier::{query_factory_config, query_fee_info, query_supply}; +use astroport::querier::{query_factory_config, query_fee_info, query_native_supply}; use astroport_pcl_common::state::Precisions; use astroport_pcl_common::utils::{ accumulate_prices, before_swap_check, calc_last_prices, compute_offer_amount, compute_swap, @@ -24,7 +24,7 @@ use astroport_pcl_common::{calc_d, get_xcp}; use crate::contract::LP_TOKEN_PRECISION; use crate::error::ContractError; use crate::state::{BALANCES, CONFIG, OBSERVATIONS}; -use crate::utils::{pool_info, query_pools}; +use crate::utils::{calculate_shares, get_assets_with_precision, pool_info, query_pools}; /// Exposes all the queries available in the contract. /// @@ -79,6 +79,18 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { asset_info, block_height, } => to_json_binary(&query_asset_balances_at(deps, asset_info, block_height)?), + QueryMsg::SimulateProvide { + assets, + slippage_tolerance, + } => to_json_binary(&query_simulate_provide( + deps, + env, + assets, + slippage_tolerance, + )?), + QueryMsg::SimulateWithdraw { lp_amount } => to_json_binary( + &query_share(deps, lp_amount).map_err(|err| StdError::generic_err(err.to_string()))?, + ), } } @@ -109,7 +121,8 @@ fn query_share(deps: Deps, amount: Uint128) -> Result, ContractError> &config, &precisions, )?; - let total_share = query_supply(&deps.querier, &config.pair_info.liquidity_token)?; + let total_share = + query_native_supply(&deps.querier, config.pair_info.liquidity_token.to_string())?; let refund_assets = get_share_in_assets(&pools, amount.saturating_sub(Uint128::one()), total_share); @@ -242,7 +255,7 @@ fn query_cumulative_prices( /// Compute the current LP token virtual price. pub fn query_lp_price(deps: Deps, env: Env) -> StdResult { let config = CONFIG.load(deps.storage)?; - let total_lp = query_supply(&deps.querier, &config.pair_info.liquidity_token)? + let total_lp = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)? .to_decimal256(LP_TOKEN_PRECISION)?; if !total_lp.is_zero() { let precisions = Precisions::new(deps.storage)?; @@ -292,6 +305,7 @@ pub fn query_config(deps: Deps, env: Env) -> StdResult { })?), owner: config.owner.unwrap_or(factory_config.owner), factory_addr: config.factory_addr, + tracker_addr: config.tracker_addr, }) } @@ -327,27 +341,50 @@ pub fn query_asset_balances_at( BALANCES.may_load_at_height(deps.storage, &asset_info, block_height.u64()) } +pub fn query_simulate_provide( + deps: Deps, + env: Env, + mut assets: Vec, + slippage_tolerance: Option, +) -> StdResult { + let mut config = CONFIG.load(deps.storage)?; + + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)? + .to_decimal256(LP_TOKEN_PRECISION)?; + + let precisions = Precisions::new(deps.storage)?; + + let mut pools = query_pools(deps.querier, &env.contract.address, &config, &precisions) + .map_err(|e| StdError::generic_err(e.to_string()))?; + + let deposits = + get_assets_with_precision(deps, &config, &mut assets, pools.clone(), &precisions) + .map_err(|e| StdError::generic_err(e.to_string()))?; + + let (share_uint128, _) = calculate_shares( + &env, + &mut config, + &mut pools, + total_share, + deposits.clone(), + slippage_tolerance, + ) + .map_err(|e| StdError::generic_err(e.to_string()))?; + + Ok(share_uint128) +} + #[cfg(test)] mod testing { - use std::error::Error; - use std::str::FromStr; - - use cosmwasm_std::testing::{mock_dependencies, mock_env}; - use cosmwasm_std::Timestamp; use astroport::observation::{query_observation, Observation, OracleObservation}; use astroport_circular_buffer::BufferManager; + use astroport_test::convert::f64_to_dec; + use cosmwasm_std::testing::{mock_dependencies, mock_env}; + use cosmwasm_std::Timestamp; use super::*; - pub fn f64_to_dec(val: f64) -> T - where - T: FromStr, - T::Err: Error, - { - T::from_str(&val.to_string()).unwrap() - } - #[test] fn observations_full_buffer() { let mut deps = mock_dependencies(); diff --git a/contracts/pair_concentrated/src/utils.rs b/contracts/pair_concentrated/src/utils.rs index ca4489c20..4a64a15af 100644 --- a/contracts/pair_concentrated/src/utils.rs +++ b/contracts/pair_concentrated/src/utils.rs @@ -1,13 +1,23 @@ -use cosmwasm_std::{Addr, Decimal, Env, QuerierWrapper, StdResult, Storage, Uint128}; +use astroport::cosmwasm_ext::{AbsDiff, DecimalToInteger, IntegerToDecimal}; +use astroport_pcl_common::utils::{ + assert_slippage_tolerance, calc_provide_fee, check_assets, check_pair_registered, +}; +use astroport_pcl_common::{calc_d, get_xcp}; +use cosmwasm_std::{ + Addr, Decimal, Decimal256, Deps, Env, QuerierWrapper, StdError, StdResult, Storage, Uint128, +}; -use astroport::asset::{Asset, DecimalAsset}; +use astroport::asset::{Asset, Decimal256Ext, DecimalAsset, MINIMUM_LIQUIDITY_AMOUNT}; use astroport::observation::{safe_sma_buffer_not_full, safe_sma_calculation}; use astroport::observation::{Observation, PrecommitObservation}; -use astroport::querier::query_supply; +use astroport::pair::MIN_TRADE_SIZE; +use astroport::querier::query_native_supply; use astroport_circular_buffer::error::BufferResult; use astroport_circular_buffer::BufferManager; use astroport_pcl_common::state::{Config, Precisions}; +use itertools::Itertools; +use crate::contract::LP_TOKEN_PRECISION; use crate::error::ContractError; use crate::state::OBSERVATIONS; @@ -19,7 +29,8 @@ pub(crate) fn pool_info( let pools = config .pair_info .query_pools(&querier, &config.pair_info.contract_addr)?; - let total_share = query_supply(&querier, &config.pair_info.liquidity_token)?; + + let total_share = query_native_supply(&querier, config.pair_info.liquidity_token.to_string())?; Ok((pools, total_share)) } @@ -103,6 +114,144 @@ pub fn accumulate_swap_sizes(storage: &mut dyn Storage, env: &Env) -> BufferResu Ok(()) } +pub(crate) fn get_assets_with_precision( + deps: Deps, + config: &Config, + assets: &mut Vec, + pools: Vec, + precisions: &Precisions, +) -> Result, ContractError> { + if !check_pair_registered( + deps.querier, + &config.factory_addr, + &config.pair_info.asset_infos, + )? { + return Err(ContractError::PairIsNotRegistered {}); + } + + match assets.len() { + 0 => { + return Err(StdError::generic_err("Nothing to provide").into()); + } + 1 => { + // Append omitted asset with explicit zero amount + let (given_ind, _) = config + .pair_info + .asset_infos + .iter() + .find_position(|pool| pool.equal(&assets[0].info)) + .ok_or_else(|| ContractError::InvalidAsset(assets[0].info.to_string()))?; + assets.push(Asset { + info: config.pair_info.asset_infos[1 ^ given_ind].clone(), + amount: Uint128::zero(), + }); + } + 2 => {} + _ => { + return Err(ContractError::InvalidNumberOfAssets( + config.pair_info.asset_infos.len(), + )) + } + } + + check_assets(deps.api, assets)?; + + if pools[0].info.equal(&assets[1].info) { + assets.swap(0, 1); + } + + // precisions.get_precision() also validates that the asset belongs to the pool + Ok(vec![ + Decimal256::with_precision(assets[0].amount, precisions.get_precision(&assets[0].info)?)?, + Decimal256::with_precision(assets[1].amount, precisions.get_precision(&assets[1].info)?)?, + ]) +} + +pub(crate) fn calculate_shares( + env: &Env, + config: &mut Config, + pools: &mut [DecimalAsset], + total_share: Decimal256, + deposits: Vec, + slippage_tolerance: Option, +) -> Result<(Uint128, Decimal256), ContractError> { + // Initial provide can not be one-sided + if total_share.is_zero() && (deposits[0].is_zero() || deposits[1].is_zero()) { + return Err(ContractError::InvalidZeroAmount {}); + } + + let mut new_xp = pools + .iter() + .enumerate() + .map(|(ind, pool)| pool.amount + deposits[ind]) + .collect_vec(); + new_xp[1] *= config.pool_state.price_state.price_scale; + + let amp_gamma = config.pool_state.get_amp_gamma(env); + let new_d = calc_d(&new_xp, &_gamma)?; + + let share = if total_share.is_zero() { + let xcp = get_xcp(new_d, config.pool_state.price_state.price_scale); + let mint_amount = xcp + .checked_sub(MINIMUM_LIQUIDITY_AMOUNT.to_decimal256(LP_TOKEN_PRECISION)?) + .map_err(|_| ContractError::MinimumLiquidityAmountError {})?; + + // share cannot become zero after minimum liquidity subtraction + if mint_amount.is_zero() { + return Err(ContractError::MinimumLiquidityAmountError {}); + } + + config.pool_state.price_state.xcp_profit_real = Decimal256::one(); + config.pool_state.price_state.xcp_profit = Decimal256::one(); + + mint_amount + } else { + let mut old_xp = pools.iter().map(|a| a.amount).collect_vec(); + old_xp[1] *= config.pool_state.price_state.price_scale; + let old_d = calc_d(&old_xp, &_gamma)?; + let share = (total_share * new_d / old_d).saturating_sub(total_share); + + let mut ideposits = deposits.clone(); + ideposits[1] *= config.pool_state.price_state.price_scale; + + share * (Decimal256::one() - calc_provide_fee(&ideposits, &new_xp, &config.pool_params)) + }; + + // calculate accrued share + let share_ratio = share / (total_share + share); + let balanced_share = [ + new_xp[0] * share_ratio, + new_xp[1] * share_ratio / config.pool_state.price_state.price_scale, + ]; + let assets_diff = [ + deposits[0].diff(balanced_share[0]), + deposits[1].diff(balanced_share[1]), + ]; + + let mut slippage = Decimal256::zero(); + + // If deposit doesn't diverge too much from the balanced share, we don't update the price + if assets_diff[0] >= MIN_TRADE_SIZE && assets_diff[1] >= MIN_TRADE_SIZE { + slippage = assert_slippage_tolerance( + &deposits, + share, + &config.pool_state.price_state, + slippage_tolerance, + )?; + + let last_price = assets_diff[0] / assets_diff[1]; + config.pool_state.update_price( + &config.pool_params, + env, + total_share + share, + &new_xp, + last_price, + )?; + } + + Ok((share.to_uint(LP_TOKEN_PRECISION)?, slippage)) +} + #[cfg(test)] mod tests { use std::fmt::Display; diff --git a/contracts/pair_concentrated/tests/helper.rs b/contracts/pair_concentrated/tests/helper.rs index db6832471..b7d720cd6 100644 --- a/contracts/pair_concentrated/tests/helper.rs +++ b/contracts/pair_concentrated/tests/helper.rs @@ -2,15 +2,13 @@ #![allow(dead_code)] use std::collections::HashMap; -use std::error::Error; -use std::fmt::Display; -use std::str::FromStr; use anyhow::Result as AnyResult; + use cosmwasm_schema::cw_serde; use cosmwasm_std::{ - coin, from_json, to_json_binary, Addr, Coin, Decimal, Decimal256, Empty, StdError, StdResult, - Uint128, + coin, from_json, to_json_binary, Addr, Coin, Decimal, Decimal256, DepsMut, Empty, Env, + MessageInfo, Response, StdError, StdResult, Uint128, }; use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg}; use derivative::Derivative; @@ -26,11 +24,17 @@ use astroport::pair::{ use astroport::pair_concentrated::{ ConcentratedPoolConfig, ConcentratedPoolParams, ConcentratedPoolUpdateParams, QueryMsg, }; -use astroport_mocks::cw_multi_test::{App, AppResponse, Contract, ContractWrapper, Executor}; use astroport_pair_concentrated::contract::{execute, instantiate, reply}; use astroport_pair_concentrated::queries::query; use astroport_pcl_common::state::Config; +use astroport_test::coins::TestCoin; +use astroport_test::convert::f64_to_dec; +use astroport_test::cw_multi_test::{ + AppBuilder, AppResponse, Contract, ContractWrapper, Executor, TOKEN_FACTORY_MODULE, +}; +use astroport_test::modules::stargate::{MockStargate, StargateApp as TestApp}; + const INIT_BALANCE: u128 = u128::MAX; pub fn common_pcl_params() -> ConcentratedPoolParams { @@ -56,42 +60,6 @@ pub struct AmpGammaResponse { pub future_time: u64, } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum TestCoin { - Cw20(String), - Cw20Precise(String, u8), - Native(String), -} - -impl TestCoin { - pub fn denom(&self) -> Option { - match self { - TestCoin::Native(denom) => Some(denom.clone()), - _ => None, - } - } - - pub fn cw20_init_data(&self) -> Option<(String, u8)> { - match self { - TestCoin::Cw20(name) => Some((name.clone(), 6u8)), - TestCoin::Cw20Precise(name, precision) => Some((name.clone(), *precision)), - _ => None, - } - } - - pub fn native(denom: &str) -> Self { - Self::Native(denom.to_string()) - } - - pub fn cw20(name: &str) -> Self { - Self::Cw20(name.to_string()) - } - - pub fn cw20precise(name: &str, precision: u8) -> Self { - Self::Cw20Precise(name.to_string(), precision) - } -} - pub fn init_native_coins(test_coins: &[TestCoin]) -> Vec { let mut test_coins: Vec = test_coins .iter() @@ -137,18 +105,39 @@ fn factory_contract() -> Box> { .with_reply_empty(astroport_factory::contract::reply), ) } +fn generator() -> Box> { + Box::new(ContractWrapper::new_with_empty( + astroport_incentives::execute::execute, + astroport_incentives::instantiate::instantiate, + astroport_incentives::query::query, + )) +} + +fn tracker_contract() -> Box> { + Box::new( + ContractWrapper::new_with_empty( + |_: DepsMut, _: Env, _: MessageInfo, _: Empty| -> StdResult { + unimplemented!() + }, + astroport_tokenfactory_tracker::contract::instantiate, + astroport_tokenfactory_tracker::query::query, + ) + .with_sudo_empty(astroport_tokenfactory_tracker::contract::sudo), + ) +} #[derive(Derivative)] #[derivative(Debug)] pub struct Helper { #[derivative(Debug = "ignore")] - pub app: App, + pub app: TestApp, pub owner: Addr, pub assets: HashMap, pub factory: Addr, pub pair_addr: Addr, - pub lp_token: Addr, + pub lp_token: String, pub fake_maker: Addr, + pub generator: Addr, } impl Helper { @@ -157,14 +146,17 @@ impl Helper { test_coins: Vec, params: ConcentratedPoolParams, ) -> AnyResult { - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, owner, init_native_coins(&test_coins)) - .unwrap() - }); + let mut app = AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|router, _, storage| { + router + .bank + .init_balance(storage, owner, init_native_coins(&test_coins)) + .unwrap() + }); let token_code_id = app.store_code(token_contract()); + let tracker_code_id = app.store_code(tracker_contract()); let asset_infos_vec = test_coins .iter() @@ -190,7 +182,6 @@ impl Helper { let pair_code_id = app.store_code(pair_contract()); let factory_code_id = app.store_code(factory_contract()); let pair_type = PairType::Custom("concentrated".to_string()); - let fake_maker = Addr::unchecked("fake_maker"); let coin_registry_id = app.store_code(coin_registry_contract()); @@ -217,6 +208,7 @@ impl Helper { ("uusd".to_owned(), 6), ("wsteth".to_owned(), 18), ("eth".to_owned(), 18), + ("uusdc".to_owned(), 6), ], }, &[], @@ -238,6 +230,10 @@ impl Helper { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: coin_registry_address.to_string(), + tracker_config: Some(astroport::factory::TrackerConfig { + code_id: tracker_code_id, + token_factory_addr: TOKEN_FACTORY_MODULE.to_string(), + }), }; let factory = app.instantiate_contract( @@ -249,6 +245,40 @@ impl Helper { None, )?; + let generator = app.store_code(generator()); + + let generator_address = app + .instantiate_contract( + generator, + owner.clone(), + &astroport::incentives::InstantiateMsg { + astro_token: native_asset_info("astro".to_string()), + factory: factory.to_string(), + owner: owner.to_string(), + guardian: None, + incentivization_fee_info: None, + vesting_contract: "vesting".to_string(), + }, + &[], + "generator", + None, + ) + .unwrap(); + + app.execute_contract( + owner.clone(), + factory.clone(), + &astroport::factory::ExecuteMsg::UpdateConfig { + token_code_id: None, + fee_address: None, + generator_address: Some(generator_address.to_string()), + whitelist_code_id: None, + coin_registry_address: None, + }, + &[], + ) + .unwrap(); + let asset_infos = asset_infos_vec .clone() .into_iter() @@ -272,6 +302,7 @@ impl Helper { owner: owner.clone(), assets: asset_infos_vec.into_iter().collect(), factory, + generator: generator_address, pair_addr: resp.contract_addr, lp_token: resp.liquidity_token, fake_maker, @@ -286,6 +317,27 @@ impl Helper { ) } + pub fn provide_liquidity_with_auto_staking( + &mut self, + sender: &Addr, + assets: &[Asset], + slippage_tolerance: Option, + ) -> AnyResult { + let funds = + assets.mock_coins_sent(&mut self.app, sender, &self.pair_addr, SendType::Allowance); + + let msg = ExecuteMsg::ProvideLiquidity { + assets: assets.to_vec(), + slippage_tolerance: Some(slippage_tolerance.unwrap_or(f64_to_dec(0.5))), + auto_stake: Some(true), + receiver: None, + min_lp_to_receive: None, + }; + + self.app + .execute_contract(sender.clone(), self.pair_addr.clone(), &msg, &funds) + } + pub fn provide_liquidity_with_slip_tolerance( &mut self, sender: &Addr, @@ -300,6 +352,7 @@ impl Helper { slippage_tolerance, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; self.app @@ -312,14 +365,15 @@ impl Helper { amount: u128, assets: Vec, ) -> AnyResult { - let msg = Cw20ExecuteMsg::Send { - contract: self.pair_addr.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { assets }).unwrap(), - }; - - self.app - .execute_contract(sender.clone(), self.lp_token.clone(), &msg, &[]) + self.app.execute_contract( + sender.clone(), + self.pair_addr.clone(), + &ExecuteMsg::WithdrawLiquidity { + assets, + min_assets_to_receive: None, + }, + &[coin(amount, self.lp_token.to_string())], + ) } pub fn swap( @@ -377,6 +431,19 @@ impl Helper { } } + pub fn query_incentives_deposit(&self, denom: impl Into, user: &Addr) -> Uint128 { + self.app + .wrap() + .query_wasm_smart( + &self.generator, + &astroport::incentives::QueryMsg::Deposit { + lp_token: denom.into(), + user: user.to_string(), + }, + ) + .unwrap() + } + pub fn simulate_swap( &self, offer_asset: &Asset, @@ -412,7 +479,7 @@ impl Helper { } fn init_token( - app: &mut App, + app: &mut TestApp, token_code: u64, name: String, decimals: u8, @@ -455,16 +522,19 @@ impl Helper { resp.balance.u128() } + pub fn native_balance(&self, denom: impl Into, user: &Addr) -> u128 { + self.app + .wrap() + .query_balance(user, denom) + .unwrap() + .amount + .u128() + } + pub fn coin_balance(&self, coin: &TestCoin, user: &Addr) -> u128 { match &self.assets[coin] { AssetInfo::Token { contract_addr } => self.token_balance(contract_addr, user), - AssetInfo::NativeToken { denom } => self - .app - .wrap() - .query_balance(user, denom) - .unwrap() - .amount - .u128(), + AssetInfo::NativeToken { denom } => self.native_balance(denom, user), } } @@ -582,7 +652,7 @@ pub enum SendType { pub trait AssetExt { fn mock_coin_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -592,7 +662,7 @@ pub trait AssetExt { impl AssetExt for Asset { fn mock_coin_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -628,7 +698,7 @@ impl AssetExt for Asset { pub trait AssetsExt { fn mock_coins_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -638,7 +708,7 @@ pub trait AssetsExt { impl AssetsExt for &[Asset] { fn mock_coins_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -655,7 +725,7 @@ pub trait AppExtension { fn next_block(&mut self, time: u64); } -impl AppExtension for App { +impl AppExtension for TestApp { fn next_block(&mut self, time: u64) { self.update_block(|block| { block.time = block.time.plus_seconds(time); @@ -663,15 +733,3 @@ impl AppExtension for App { }); } } - -pub fn f64_to_dec(val: f64) -> T -where - T: FromStr, - T::Err: Error, -{ - T::from_str(&val.to_string()).unwrap() -} - -pub fn dec_to_f64(val: impl Display) -> f64 { - f64::from_str(&val.to_string()).unwrap() -} diff --git a/contracts/pair_concentrated/tests/pair_concentrated_integration.rs b/contracts/pair_concentrated/tests/pair_concentrated_integration.rs index 1a94114e1..e35305f2e 100644 --- a/contracts/pair_concentrated/tests/pair_concentrated_integration.rs +++ b/contracts/pair_concentrated/tests/pair_concentrated_integration.rs @@ -2,7 +2,7 @@ use std::str::FromStr; -use cosmwasm_std::{Addr, Decimal, Decimal256, Uint128}; +use cosmwasm_std::{Addr, Coin, Decimal, Decimal256, StdError, Uint128}; use itertools::{max, Itertools}; use astroport::asset::{ @@ -14,12 +14,18 @@ use astroport::pair::{ExecuteMsg, PoolResponse, MAX_FEE_SHARE_BPS}; use astroport::pair_concentrated::{ ConcentratedPoolParams, ConcentratedPoolUpdateParams, PromoteParams, QueryMsg, UpdatePoolParams, }; -use astroport_mocks::cw_multi_test::{next_block, Executor}; +use astroport::tokenfactory_tracker::{ + ConfigResponse as TrackerConfigResponse, QueryMsg as TrackerQueryMsg, +}; use astroport_pair_concentrated::error::ContractError; use astroport_pcl_common::consts::{AMP_MAX, AMP_MIN, MA_HALF_TIME_LIMITS}; use astroport_pcl_common::error::PclError; -use crate::helper::{common_pcl_params, dec_to_f64, f64_to_dec, AppExtension, Helper, TestCoin}; +use astroport_test::coins::TestCoin; +use astroport_test::convert::{dec_to_f64, f64_to_dec}; +use astroport_test::cw_multi_test::{Executor, TOKEN_FACTORY_MODULE}; + +use crate::helper::{common_pcl_params, AppExtension, Helper}; mod helper; @@ -27,7 +33,7 @@ mod helper; fn check_observe_queries() { let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("USDC")]; + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); @@ -161,8 +167,8 @@ fn check_wrong_initialization() { fn check_create_pair_with_unsupported_denom() { let owner = Addr::unchecked("owner"); - let wrong_coins = vec![TestCoin::native("rc"), TestCoin::cw20("USDC")]; - let valid_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("USDC")]; + let wrong_coins = vec![TestCoin::native("rc"), TestCoin::native("uusdc")]; + let valid_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; let params = ConcentratedPoolParams { price_scale: Decimal::from_ratio(2u8, 1u8), @@ -182,7 +188,7 @@ fn check_create_pair_with_unsupported_denom() { fn provide_and_withdraw() { let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("USDC")]; + let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("uusdc")]; let params = ConcentratedPoolParams { price_scale: Decimal::from_ratio(2u8, 1u8), @@ -205,28 +211,17 @@ fn provide_and_withdraw() { helper.assets[&test_coins[0]].with_balance(100_000_000000u128), random_coin.clone(), ]; + helper.give_me_money(&wrong_assets, &user1); - let err = helper.provide_liquidity(&user1, &wrong_assets).unwrap_err(); - assert_eq!( - "Generic error: Asset random-coin is not in the pool", - err.root_cause().to_string() - ); - // Provide with asset which does not belong to the pair - let err = helper - .provide_liquidity( - &user1, - &[ - random_coin.clone(), - helper.assets[&test_coins[0]].with_balance(100_000_000000u128), - ], - ) - .unwrap_err(); + // Provide with empty assets + let err = helper.provide_liquidity(&user1, &[]).unwrap_err(); assert_eq!( - "Generic error: Asset random-coin is not in the pool", + "Generic error: Nothing to provide", err.root_cause().to_string() ); + // Provide just one asset which does not belong to the pair let err = helper .provide_liquidity(&user1, &[random_coin.clone()]) .unwrap_err(); @@ -235,12 +230,6 @@ fn provide_and_withdraw() { err.root_cause().to_string() ); - let err = helper.provide_liquidity(&user1, &[]).unwrap_err(); - assert_eq!( - "Generic error: Nothing to provide", - err.root_cause().to_string() - ); - // Try to provide 3 assets let err = helper .provide_liquidity( @@ -296,13 +285,16 @@ fn provide_and_withdraw() { // This is normal provision helper.provide_liquidity(&user1, &assets).unwrap(); - assert_eq!(70710_677118, helper.token_balance(&helper.lp_token, &user1)); + assert_eq!( + 70710_677118, + helper.native_balance(&helper.lp_token, &user1) + ); assert_eq!(0, helper.coin_balance(&test_coins[0], &user1)); assert_eq!(0, helper.coin_balance(&test_coins[1], &user1)); assert_eq!( helper - .query_share(helper.token_balance(&helper.lp_token, &user1)) + .query_share(helper.native_balance(&helper.lp_token, &user1)) .unwrap(), vec![ helper.assets[&test_coins[0]].with_balance(99999998584u128), @@ -319,7 +311,7 @@ fn provide_and_withdraw() { helper.provide_liquidity(&user2, &assets).unwrap(); assert_eq!( 70710_677118 + MINIMUM_LIQUIDITY_AMOUNT.u128(), - helper.token_balance(&helper.lp_token, &user2) + helper.native_balance(&helper.lp_token, &user2) ); // Changing order of assets does not matter @@ -332,7 +324,7 @@ fn provide_and_withdraw() { helper.provide_liquidity(&user3, &assets).unwrap(); assert_eq!( 70710_677118 + MINIMUM_LIQUIDITY_AMOUNT.u128(), - helper.token_balance(&helper.lp_token, &user3) + helper.native_balance(&helper.lp_token, &user3) ); // After initial provide one-sided provide is allowed @@ -344,14 +336,20 @@ fn provide_and_withdraw() { helper.give_me_money(&assets, &user4); helper.provide_liquidity(&user4, &assets).unwrap(); // LP amount is less than for prev users as provide is imbalanced - assert_eq!(62217_722016, helper.token_balance(&helper.lp_token, &user4)); + assert_eq!( + 62217_722016, + helper.native_balance(&helper.lp_token, &user4) + ); // One of assets may be omitted let user5 = Addr::unchecked("user5"); let assets = vec![helper.assets[&test_coins[0]].with_balance(140_000_000000u128)]; helper.give_me_money(&assets, &user5); helper.provide_liquidity(&user5, &assets).unwrap(); - assert_eq!(57271_023590, helper.token_balance(&helper.lp_token, &user5)); + assert_eq!( + 57271_023590, + helper.native_balance(&helper.lp_token, &user5) + ); // check that imbalanced withdraw is currently disabled let withdraw_assets = vec![ @@ -373,7 +371,7 @@ fn provide_and_withdraw() { assert_eq!( 70710_677118 - 7071_067711, - helper.token_balance(&helper.lp_token, &user1) + helper.native_balance(&helper.lp_token, &user1) ); assert_eq!(9382_010960, helper.coin_balance(&test_coins[0], &user1)); assert_eq!(5330_688045, helper.coin_balance(&test_coins[1], &user1)); @@ -385,7 +383,7 @@ fn provide_and_withdraw() { assert_eq!( 70710_677118 + MINIMUM_LIQUIDITY_AMOUNT.u128() - 35355_339059, - helper.token_balance(&helper.lp_token, &user2) + helper.native_balance(&helper.lp_token, &user2) ); assert_eq!(46910_055478, helper.coin_balance(&test_coins[0], &user2)); assert_eq!(26653_440612, helper.coin_balance(&test_coins[1], &user2)); @@ -418,7 +416,7 @@ fn check_imbalanced_provide() { assert_eq!( 200495_366531, - helper.token_balance(&helper.lp_token, &user1) + helper.native_balance(&helper.lp_token, &user1) ); assert_eq!(0, helper.coin_balance(&test_coins[0], &user1)); assert_eq!(0, helper.coin_balance(&test_coins[1], &user1)); @@ -440,7 +438,7 @@ fn check_imbalanced_provide() { assert_eq!( 200495_366531, - helper.token_balance(&helper.lp_token, &user1) + helper.native_balance(&helper.lp_token, &user1) ); assert_eq!(0, helper.coin_balance(&test_coins[0], &user1)); assert_eq!(0, helper.coin_balance(&test_coins[1], &user1)); @@ -473,7 +471,7 @@ fn provide_with_different_precision() { helper.provide_liquidity(&user, &assets).unwrap(); - let lp_amount = helper.token_balance(&helper.lp_token, &user); + let lp_amount = helper.native_balance(&helper.lp_token, &user); assert!( 100_000000 - lp_amount < tolerance, "LP token balance assert failed for {user}" @@ -483,7 +481,7 @@ fn provide_with_different_precision() { helper.withdraw_liquidity(&user, lp_amount, vec![]).unwrap(); - assert_eq!(0, helper.token_balance(&helper.lp_token, &user)); + assert_eq!(0, helper.native_balance(&helper.lp_token, &user)); assert!( 100_00000 - helper.coin_balance(&test_coins[0], &user) < tolerance, "Withdrawn amount of coin0 assert failed for {user}" @@ -541,6 +539,71 @@ fn swap_different_precisions() { ); } +#[test] +fn simulate_provide() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("uusdc")]; + + let params = ConcentratedPoolParams { + price_scale: Decimal::from_ratio(2u8, 1u8), + ..common_pcl_params() + }; + + let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_000000u128), + helper.assets[&test_coins[1]].with_balance(50_000_000000u128), + ]; + + let user1 = Addr::unchecked("user1"); + + let shares: Uint128 = helper + .app + .wrap() + .query_wasm_smart( + helper.pair_addr.to_string(), + &QueryMsg::SimulateProvide { + assets: assets.clone(), + slippage_tolerance: None, + }, + ) + .unwrap(); + + helper.give_me_money(&assets, &user1); + helper.provide_liquidity(&user1, &assets).unwrap(); + + assert_eq!( + shares.u128(), + helper.native_balance(&helper.lp_token, &user1) + ); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000_0000u128), + helper.assets[&test_coins[1]].with_balance(50_000_000000u128), + ]; + + let err = helper + .app + .wrap() + .query_wasm_smart::( + helper.pair_addr.to_string(), + &QueryMsg::SimulateProvide { + assets: assets.clone(), + slippage_tolerance: Option::from(Decimal::percent(1)), + }, + ) + .unwrap_err(); + + assert_eq!( + err, + StdError::generic_err( + "Querier contract error: Generic error: Operation exceeds max spread limit" + ) + ); +} + #[test] fn check_reverse_swap() { let owner = Addr::unchecked("owner"); @@ -685,7 +748,7 @@ fn check_swaps_with_price_update() { let owner = Addr::unchecked("owner"); let half = Decimal::from_ratio(1u8, 2u8); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("USDC")]; + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); @@ -727,7 +790,7 @@ fn check_swaps_with_price_update() { fn provides_and_swaps() { let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("USDC")]; + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); @@ -771,7 +834,7 @@ fn provides_and_swaps() { fn check_amp_gamma_change() { let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("USDC")]; + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; let params = ConcentratedPoolParams { amp: f64_to_dec(40f64), @@ -864,7 +927,7 @@ fn check_prices() { let test_coins = vec![TestCoin::native("uusd"), TestCoin::cw20("USDX")]; let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); - helper.app.update_block(next_block); + helper.app.next_block(50_000); let check_prices = |helper: &Helper| { let prices = helper.query_prices().unwrap(); @@ -1051,81 +1114,6 @@ fn query_d_test() { ); } -#[test] -fn asset_balances_tracking_without_in_params() { - let owner = Addr::unchecked("owner"); - let user1 = Addr::unchecked("user1"); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusd")]; - - // Instantiate pair without asset balances tracking - let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); - - let assets = vec![ - helper.assets[&test_coins[0]].with_balance(5_000000u128), - helper.assets[&test_coins[1]].with_balance(5_000000u128), - ]; - - // Check that asset balances are not tracked - // The query AssetBalanceAt returns None for this case - let res = helper - .query_asset_balance_at(&assets[0].info, helper.app.block_info().height) - .unwrap(); - assert!(res.is_none()); - - let res = helper - .query_asset_balance_at(&assets[1].info, helper.app.block_info().height) - .unwrap(); - assert!(res.is_none()); - - // Enable asset balances tracking - helper - .update_config( - &owner, - &ConcentratedPoolUpdateParams::EnableAssetBalancesTracking {}, - ) - .unwrap(); - - // Check that asset balances were not tracked before this was enabled - // The query AssetBalanceAt returns None for this case - let res = helper - .query_asset_balance_at(&assets[0].info, helper.app.block_info().height) - .unwrap(); - assert!(res.is_none()); - - let res = helper - .query_asset_balance_at(&assets[1].info, helper.app.block_info().height) - .unwrap(); - assert!(res.is_none()); - - // Check that asset balances had zero balances before next block upon tracking enabling - helper.app.update_block(|b| b.height += 1); - - let res = helper - .query_asset_balance_at(&assets[0].info, helper.app.block_info().height) - .unwrap(); - assert!(res.unwrap().is_zero()); - - let res = helper - .query_asset_balance_at(&assets[1].info, helper.app.block_info().height) - .unwrap(); - assert!(res.unwrap().is_zero()); - - helper.give_me_money(&assets, &user1); - helper.provide_liquidity(&user1, &assets).unwrap(); - - // Check that asset balances changed after providing liqudity - helper.app.update_block(|b| b.height += 1); - let res = helper - .query_asset_balance_at(&assets[0].info, helper.app.block_info().height) - .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(5_000000)); - - let res = helper - .query_asset_balance_at(&assets[1].info, helper.app.block_info().height) - .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(5_000000)); -} - #[test] fn asset_balances_tracking_with_in_params() { let owner = Addr::unchecked("owner"); @@ -1144,17 +1132,6 @@ fn asset_balances_tracking_with_in_params() { helper.assets[&test_coins[1]].with_balance(5_000000u128), ]; - // Check that enabling asset balances tracking can not be done if it is already enabled - let err = helper - .update_config( - &owner, - &ConcentratedPoolUpdateParams::EnableAssetBalancesTracking {}, - ) - .unwrap_err(); - assert_eq!( - err.downcast::().unwrap(), - ContractError::AssetBalancesTrackingIsAlreadyEnabled {} - ); // Check that asset balances were not tracked before instantiation // The query AssetBalanceAt returns None for this case let res = helper @@ -1204,7 +1181,7 @@ fn asset_balances_tracking_with_in_params() { .unwrap(); assert_eq!( - helper.token_balance(&helper.lp_token, &owner), + helper.native_balance(&helper.lp_token, &owner), 999_498998u128 ); @@ -1268,7 +1245,7 @@ fn asset_balances_tracking_with_in_params() { fn provides_and_swaps_and_withdraw() { let owner = Addr::unchecked("owner"); let half = Decimal::from_ratio(1u8, 2u8); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("USDC")]; + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; let params = ConcentratedPoolParams { price_scale: Decimal::from_ratio(1u8, 2u8), @@ -1312,7 +1289,7 @@ fn provides_and_swaps_and_withdraw() { .unwrap(); assert_eq!(res.total_share.u128(), 141_421_356_237u128); - let owner_balance = helper.token_balance(&helper.lp_token, &owner); + let owner_balance = helper.native_balance(&helper.lp_token, &owner); helper .withdraw_liquidity(&owner, owner_balance, vec![]) @@ -1326,6 +1303,31 @@ fn provides_and_swaps_and_withdraw() { assert_eq!(res.total_share.u128(), 1000u128); } +#[test] +fn provide_liquidity_with_autostaking_to_generator() { + let owner = Addr::unchecked("owner"); + + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; + + let params = ConcentratedPoolParams { + price_scale: Decimal::from_ratio(1u8, 2u8), + ..common_pcl_params() + }; + let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(100_000u128), + helper.assets[&test_coins[1]].with_balance(100_000u128), + ]; + + helper + .provide_liquidity_with_auto_staking(&owner, &assets, None) + .unwrap(); + + let amount = helper.query_incentives_deposit(helper.lp_token.to_string(), &owner); + assert_eq!(amount, Uint128::new(99003)); +} + #[test] fn provide_withdraw_provide() { let owner = Addr::unchecked("owner"); @@ -1354,7 +1356,7 @@ fn provide_withdraw_provide() { helper.app.next_block(600); // Withdraw all - let lp_amount = helper.token_balance(&helper.lp_token, &owner); + let lp_amount = helper.native_balance(&helper.lp_token, &owner); helper .withdraw_liquidity(&owner, lp_amount, vec![]) .unwrap(); @@ -1505,7 +1507,7 @@ fn test_frontrun_before_initial_provide() { fn check_correct_fee_share() { let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("USDC")]; + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; let mut helper = Helper::new(&owner, test_coins.clone(), common_pcl_params()).unwrap(); @@ -1824,7 +1826,7 @@ fn check_lsd_swaps_with_price_update() { fn test_provide_liquidity_without_funds() { let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("USDC")]; + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusdc")]; let params = ConcentratedPoolParams { price_scale: Decimal::from_ratio(2u8, 1u8), @@ -1851,6 +1853,7 @@ fn test_provide_liquidity_without_funds() { slippage_tolerance: Some(f64_to_dec(0.5)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let err = helper @@ -1863,3 +1866,86 @@ fn test_provide_liquidity_without_funds() { "Generic error: Native token balance mismatch between the argument (100000000000uluna) and the transferred (0uluna)" ) } + +#[test] +fn test_tracker_contract() { + let owner = Addr::unchecked("owner"); + let alice = Addr::unchecked("alice"); + let test_coins = vec![TestCoin::native("uluna"), TestCoin::native("uusd")]; + + let params = ConcentratedPoolParams { + track_asset_balances: Some(true), + ..common_pcl_params() + }; + + // Instantiate pair with asset balances tracking + let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap(); + + let assets = vec![ + helper.assets[&test_coins[0]].with_balance(5_000000u128), + helper.assets[&test_coins[1]].with_balance(5_000000u128), + ]; + + helper.provide_liquidity(&owner, &assets).unwrap(); + + let config = helper.query_config().unwrap(); + + let tracker_addr = config.tracker_addr.unwrap(); + + let tracker_config: TrackerConfigResponse = helper + .app + .wrap() + .query_wasm_smart(tracker_addr.clone(), &TrackerQueryMsg::Config {}) + .unwrap(); + assert_eq!( + tracker_config.token_factory_module, + TOKEN_FACTORY_MODULE.to_string() + ); + assert_eq!(tracker_config.tracked_denom, helper.lp_token.to_string()); + + let owner_lp_funds = helper + .app + .wrap() + .query_balance(owner.clone(), helper.lp_token.clone()) + .unwrap(); + + let total_supply = owner_lp_funds.amount + MINIMUM_LIQUIDITY_AMOUNT; + + // Set Alice's balances + helper + .app + .send_tokens( + owner.clone(), + alice.clone(), + &[Coin { + denom: helper.lp_token.to_string(), + amount: Uint128::new(100), + }], + ) + .unwrap(); + + let tracker_total_supply: Uint128 = helper + .app + .wrap() + .query_wasm_smart( + tracker_addr.clone(), + &TrackerQueryMsg::TotalSupplyAt { timestamp: None }, + ) + .unwrap(); + + assert_eq!(total_supply, tracker_total_supply); + + let alice_balance: Uint128 = helper + .app + .wrap() + .query_wasm_smart( + tracker_addr, + &TrackerQueryMsg::BalanceAt { + address: alice.to_string(), + timestamp: None, + }, + ) + .unwrap(); + + assert_eq!(alice_balance, Uint128::new(100)); +} diff --git a/contracts/pair_concentrated/tests/pair_concentrated_simulation.rs b/contracts/pair_concentrated/tests/pair_concentrated_simulation.rs index f3cd28f31..16dc00d93 100644 --- a/contracts/pair_concentrated/tests/pair_concentrated_simulation.rs +++ b/contracts/pair_concentrated/tests/pair_concentrated_simulation.rs @@ -4,7 +4,10 @@ extern crate core; mod helper; -use crate::helper::{common_pcl_params, dec_to_f64, f64_to_dec, AppExtension, Helper, TestCoin}; +use astroport_test::coins::TestCoin; +use astroport_test::convert::{dec_to_f64, f64_to_dec}; + +use crate::helper::{common_pcl_params, AppExtension, Helper}; use astroport::asset::AssetInfoExt; use astroport::cosmwasm_ext::AbsDiff; use astroport::pair_concentrated::{ConcentratedPoolParams, ConcentratedPoolUpdateParams}; @@ -178,7 +181,7 @@ fn simulate_provide_case(case: Vec<(impl Into, u128, u128, u64)>) { let price_scale = dec_to_f64(config.pool_state.price_state.price_scale); for (user, &(coin0_amnt, coin1_amnt, cnt)) in &accounts { - let lp_amount = helper.token_balance(&helper.lp_token, user); + let lp_amount = helper.native_balance(&helper.lp_token, user); if cnt != 0 { helper.withdraw_liquidity(user, lp_amount, vec![]).unwrap(); } diff --git a/contracts/pair_concentrated_inj/Cargo.toml b/contracts/pair_concentrated_inj/Cargo.toml index 808a20701..8ebce64db 100644 --- a/contracts/pair_concentrated_inj/Cargo.toml +++ b/contracts/pair_concentrated_inj/Cargo.toml @@ -43,7 +43,7 @@ hex = "0.4.3" [dev-dependencies] cw20-base = "1.1" -astroport-mocks = { path = "../../packages/astroport_mocks" } +astroport-test = { path = "../../packages/astroport_test" } astroport-factory = { path = "../factory" } proptest = "1.0" anyhow = "1.0" diff --git a/contracts/pair_concentrated_inj/src/contract.rs b/contracts/pair_concentrated_inj/src/contract.rs index 6be558711..47b55ab2c 100644 --- a/contracts/pair_concentrated_inj/src/contract.rs +++ b/contracts/pair_concentrated_inj/src/contract.rs @@ -346,7 +346,7 @@ fn receive_cw20( /// the pool price can move until the provide liquidity transaction goes through. /// /// * **auto_stake** is an optional parameter which determines whether the LP tokens minted after -/// liquidity provision are automatically staked in the Generator contract on behalf of the LP token receiver. +/// liquidity provision are automatically staked in the Incentives contract on behalf of the LP token receiver. /// /// * **receiver** is an optional parameter which defines the receiver of the LP tokens. /// If no custom receiver is specified, the pair will mint LP tokens for the function caller. diff --git a/contracts/pair_stable/Cargo.toml b/contracts/pair_stable/Cargo.toml index a329a9b32..7c8e39591 100644 --- a/contracts/pair_stable/Cargo.toml +++ b/contracts/pair_stable/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-pair-stable" -version = "3.5.0" +version = "4.0.0" authors = ["Astroport"] edition = "2021" description = "The Astroport stableswap pair contract implementation" @@ -23,13 +23,15 @@ crate-type = ["cdylib", "rlib"] # for quicker tests, cargo test --lib # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] +injective = ["astroport/injective"] +sei = ["astroport/sei"] library = [] [dependencies] -astroport = { path = "../../packages/astroport", version = "4" } +astroport = { path = "../../packages/astroport", version = "5" } cw2.workspace = true cw20 = "1.1" -cosmwasm-std.workspace = true +cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1", "stargate"] } cw-storage-plus.workspace = true thiserror.workspace = true itertools.workspace = true @@ -46,4 +48,5 @@ astroport-factory = { path = "../factory" } derivative = "2.2" prost = "0.11.5" astroport-native-coin-registry = { path = "../periphery/native_coin_registry" } -astroport-mocks = { path = "../../packages/astroport_mocks" } +astroport-incentives = { path = "../tokenomics/incentives" } +astroport-test = { path = "../../packages/astroport_test" } diff --git a/contracts/pair_stable/src/contract.rs b/contracts/pair_stable/src/contract.rs index 3fb44a3ed..cc5dbcd61 100644 --- a/contracts/pair_stable/src/contract.rs +++ b/contracts/pair_stable/src/contract.rs @@ -2,21 +2,22 @@ use std::collections::HashMap; use std::str::FromStr; use std::vec; +use astroport::token_factory::{tf_burn_msg, tf_create_denom_msg, MsgCreateDenomResponse}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, from_json, to_json_binary, wasm_execute, wasm_instantiate, Addr, Binary, CosmosMsg, - Decimal, Decimal256, Deps, DepsMut, Env, Fraction, MessageInfo, QuerierWrapper, Reply, - Response, StdError, StdResult, SubMsg, SubMsgResponse, SubMsgResult, Uint128, WasmMsg, + attr, coin, ensure_eq, from_json, to_json_binary, Addr, Binary, Coin, CosmosMsg, Decimal, + Decimal256, Deps, DepsMut, Env, Fraction, MessageInfo, QuerierWrapper, Reply, Response, + StdError, StdResult, SubMsg, SubMsgResponse, SubMsgResult, Uint128, WasmMsg, }; use cw2::{get_contract_version, set_contract_version}; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg, MinterResponse}; -use cw_utils::parse_instantiate_response_data; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; +use cw_utils::{one_coin, PaymentError}; use itertools::Itertools; use astroport::asset::{ - addr_opt_validate, check_swap_parameters, format_lp_token_name, Asset, AssetInfo, CoinsExt, - Decimal256Ext, DecimalAsset, PairInfo, MINIMUM_LIQUIDITY_AMOUNT, + addr_opt_validate, check_swap_parameters, Asset, AssetInfo, CoinsExt, Decimal256Ext, + DecimalAsset, PairInfo, MINIMUM_LIQUIDITY_AMOUNT, }; use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner}; @@ -34,8 +35,7 @@ use astroport::pair::{ Cw20HookMsg, ExecuteMsg, MigrateMsg, PoolResponse, QueryMsg, ReverseSimulationResponse, SimulationResponse, StablePoolConfig, }; -use astroport::querier::{query_factory_config, query_fee_info, query_supply}; -use astroport::token::InstantiateMsg as TokenInstantiateMsg; +use astroport::querier::{query_factory_config, query_fee_info, query_native_supply}; use astroport::DecimalCheckedOps; use astroport_circular_buffer::BufferManager; @@ -47,17 +47,20 @@ use crate::state::{ get_precision, store_precisions, Config, CONFIG, OBSERVATIONS, OWNERSHIP_PROPOSAL, }; use crate::utils::{ - accumulate_prices, accumulate_swap_sizes, adjust_precision, check_asset_infos, check_assets, - check_cw20_in_pool, compute_current_amp, compute_swap, determine_base_quote_amount, - get_share_in_assets, mint_liquidity_token_message, select_pools, SwapResult, + accumulate_prices, accumulate_swap_sizes, adjust_precision, calculate_shares, + check_asset_infos, check_cw20_in_pool, compute_current_amp, compute_swap, + determine_base_quote_amount, get_assets_collection, get_share_in_assets, + mint_liquidity_token_message, select_pools, SwapResult, }; /// Contract name that is used for migration. const CONTRACT_NAME: &str = "astroport-pair-stable"; /// Contract version that is used for migration. const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -/// A `reply` call code ID of sub-message. -const INSTANTIATE_TOKEN_REPLY_ID: u64 = 1; +/// Reply ID for create denom reply +const CREATE_DENOM_REPLY_ID: u64 = 1; +/// Tokenfactory LP token subdenom +pub const LP_SUBDENOM: &str = "astroport/share"; /// Number of assets in the pool. const N_COINS: usize = 2; @@ -104,7 +107,7 @@ pub fn instantiate( owner: addr_opt_validate(deps.api, ¶ms.owner)?, pair_info: PairInfo { contract_addr: env.contract.address.clone(), - liquidity_token: Addr::unchecked(""), + liquidity_token: "".to_owned(), asset_infos: msg.asset_infos.clone(), pair_type: PairType::Stable {}, }, @@ -117,32 +120,16 @@ pub fn instantiate( greatest_precision, cumulative_prices, fee_share: None, + tracker_addr: None, }; CONFIG.save(deps.storage, &config)?; BufferManager::init(deps.storage, OBSERVATIONS, OBSERVATIONS_SIZE)?; - let token_name = format_lp_token_name(&msg.asset_infos, &deps.querier)?; - // Create LP token let sub_msg = SubMsg::reply_on_success( - wasm_instantiate( - msg.token_code_id, - &TokenInstantiateMsg { - name: token_name, - symbol: "uLP".to_string(), - decimals: greatest_precision, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: env.contract.address.to_string(), - cap: None, - }), - marketing: None, - }, - vec![], - String::from("Astroport LP token"), - )?, - INSTANTIATE_TOKEN_REPLY_ID, + tf_create_denom_msg(env.contract.address.to_string(), LP_SUBDENOM), + CREATE_DENOM_REPLY_ID, ); Ok(Response::new().add_submessage(sub_msg)) @@ -153,25 +140,25 @@ pub fn instantiate( pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result { match msg { Reply { - id: INSTANTIATE_TOKEN_REPLY_ID, + id: CREATE_DENOM_REPLY_ID, result: SubMsgResult::Ok(SubMsgResponse { data: Some(data), .. }), } => { - let mut config = CONFIG.load(deps.storage)?; - - if config.pair_info.liquidity_token != Addr::unchecked("") { - return Err(ContractError::Unauthorized {}); - } + let MsgCreateDenomResponse { new_token_denom } = data.try_into()?; + + CONFIG.update(deps.storage, |mut config| { + if !config.pair_info.liquidity_token.is_empty() { + return Err(StdError::generic_err( + "Liquidity token is already set in the config", + )); + } + config.pair_info.liquidity_token = new_token_denom.clone(); + Ok(config) + })?; - let init_response = parse_instantiate_response_data(data.as_slice()) - .map_err(|e| StdError::generic_err(format!("{e}")))?; - config.pair_info.liquidity_token = - deps.api.addr_validate(&init_response.contract_address)?; - CONFIG.save(deps.storage, &config)?; - Ok(Response::new() - .add_attribute("liquidity_token_addr", config.pair_info.liquidity_token)) + Ok(Response::new().add_attribute("lp_denom", new_token_denom)) } _ => Err(ContractError::FailedToParseReply {}), } @@ -191,6 +178,7 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Result provide_liquidity(deps, env, info, assets, auto_stake, receiver), + } => provide_liquidity( + deps, + env, + info, + assets, + auto_stake, + receiver, + min_lp_to_receive, + ), ExecuteMsg::Swap { offer_asset, ask_asset_info, @@ -280,6 +281,10 @@ pub fn execute( }) .map_err(|e| e.into()) } + ExecuteMsg::WithdrawLiquidity { + assets, + min_assets_to_receive, + } => withdraw_liquidity(deps, env, info, assets, min_assets_to_receive), } } @@ -321,14 +326,6 @@ pub fn receive_cw20( to_addr, ) } - Cw20HookMsg::WithdrawLiquidity { assets } => withdraw_liquidity( - deps, - env, - info, - Addr::unchecked(cw20_msg.sender), - cw20_msg.amount, - assets, - ), } } @@ -337,10 +334,11 @@ pub fn receive_cw20( /// * **assets** vector with assets available in the pool. /// /// * **auto_stake** determines whether the resulting LP tokens are automatically staked in -/// the Generator contract to receive token incentives. +/// the Incentives contract to receive token incentives. /// /// * **receiver** address that receives LP tokens. If this address isn't specified, the function will default to the caller. /// +/// * **min_lp_to_receive** is an optional parameter which specifies the minimum amount of LP tokens to receive. /// NOTE - the address that wants to provide liquidity should approve the pair contract to pull its relevant tokens. pub fn provide_liquidity( deps: DepsMut, @@ -349,66 +347,25 @@ pub fn provide_liquidity( assets: Vec, auto_stake: Option, receiver: Option, + min_lp_to_receive: Option, ) -> Result { - check_assets(deps.api, &assets)?; - - let auto_stake = auto_stake.unwrap_or(false); let mut config = CONFIG.load(deps.storage)?; - info.funds - .assert_coins_properly_sent(&assets, &config.pair_info.asset_infos)?; - - if assets.len() != config.pair_info.asset_infos.len() { - return Err(ContractError::InvalidNumberOfAssets( - config.pair_info.asset_infos.len(), - )); - } - let pools: HashMap<_, _> = config + let pools = config .pair_info .query_pools(&deps.querier, &env.contract.address)? .into_iter() .map(|pool| (pool.info, pool.amount)) .collect(); - let mut non_zero_flag = false; - - let mut assets_collection = assets - .clone() - .into_iter() - .map(|asset| { - // Check that at least one asset is non-zero - if !asset.amount.is_zero() { - non_zero_flag = true; - } - - // Get appropriate pool - let pool = pools - .get(&asset.info) - .copied() - .ok_or_else(|| ContractError::InvalidAsset(asset.info.to_string()))?; - - Ok((asset, pool)) - }) - .collect::, ContractError>>()?; - - // If some assets are omitted then add them explicitly with 0 deposit - pools.iter().for_each(|(pool_info, pool_amount)| { - if !assets.iter().any(|asset| asset.info.eq(pool_info)) { - assets_collection.push(( - Asset { - amount: Uint128::zero(), - info: pool_info.clone(), - }, - *pool_amount, - )); - } - }); + let mut assets_collection = + get_assets_collection(deps.as_ref(), &config, &pools, assets.clone())?; - if !non_zero_flag { - return Err(ContractError::InvalidZeroAmount {}); - } + info.funds + .assert_coins_properly_sent(&assets, &config.pair_info.asset_infos)?; let mut messages = vec![]; + for (deposit, pool) in assets_collection.iter_mut() { // We cannot put a zero amount into an empty pool. if deposit.amount.is_zero() && pool.is_zero() { @@ -436,39 +393,13 @@ pub fn provide_liquidity( } } - let assets_collection = assets_collection - .iter() - .cloned() - .map(|(asset, pool)| { - let coin_precision = get_precision(deps.storage, &asset.info)?; - Ok(( - asset.to_decimal_asset(coin_precision)?, - Decimal256::with_precision(pool, coin_precision)?, - )) - }) - .collect::>>()?; + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; - let amp = compute_current_amp(&config, &env)?; + let auto_stake = auto_stake.unwrap_or(false); - // Invariant (D) after deposit added - let new_balances = assets_collection - .iter() - .map(|(deposit, pool)| Ok(pool + deposit.amount)) - .collect::>>()?; - let deposit_d = compute_d(amp, &new_balances)?; - - let total_share = query_supply(&deps.querier, &config.pair_info.liquidity_token)?; - let share = if total_share.is_zero() { - let share = deposit_d - .to_uint128_with_precision(config.greatest_precision)? - .checked_sub(MINIMUM_LIQUIDITY_AMOUNT) - .map_err(|_| ContractError::MinimumLiquidityAmountError {})?; - - // share cannot become zero after minimum liquidity subtraction - if share.is_zero() { - return Err(ContractError::MinimumLiquidityAmountError {}); - } + let share = calculate_shares(deps.as_ref(), &env, &config, total_share, assets_collection)?; + if total_share.is_zero() { messages.extend(mint_liquidity_token_message( deps.querier, &config, @@ -477,26 +408,16 @@ pub fn provide_liquidity( MINIMUM_LIQUIDITY_AMOUNT, false, )?); + } - share - } else { - // Initial invariant (D) - let old_balances = assets_collection - .iter() - .map(|(_, pool)| *pool) - .collect_vec(); - let init_d = compute_d(amp, &old_balances)?; - - let share = Decimal256::with_precision(total_share, config.greatest_precision)? - .checked_multiply_ratio(deposit_d.saturating_sub(init_d), init_d)? - .to_uint128_with_precision(config.greatest_precision)?; + let min_amount_lp = min_lp_to_receive.unwrap_or(Uint128::zero()); - if share.is_zero() { - return Err(ContractError::LiquidityAmountTooSmall {}); - } - - share - }; + if share < min_amount_lp { + return Err(ContractError::ProvideSlippageViolation( + share, + min_amount_lp, + )); + } // Mint LP token for the caller (or for the receiver if it was set) let receiver = addr_opt_validate(deps.api, &receiver)?.unwrap_or_else(|| info.sender.clone()); @@ -534,22 +455,22 @@ pub fn provide_liquidity( } /// Withdraw liquidity from the pool. -/// * **sender** is the address that will receive assets back from the pair contract. -/// -/// * **amount** is the amount of LP tokens to burn. pub fn withdraw_liquidity( deps: DepsMut, env: Env, info: MessageInfo, - sender: Addr, - amount: Uint128, assets: Vec, + min_assets_to_receive: Option>, ) -> Result { let mut config = CONFIG.load(deps.storage)?; - if info.sender != config.pair_info.liquidity_token { - return Err(ContractError::Unauthorized {}); - } + let Coin { amount, denom } = one_coin(&info)?; + + ensure_eq!( + denom, + config.pair_info.liquidity_token, + PaymentError::MissingDenom(config.pair_info.liquidity_token.to_string()) + ); let (pools, total_share) = pool_info(deps.querier, &config)?; @@ -560,19 +481,17 @@ pub fn withdraw_liquidity( return Err(StdError::generic_err("Imbalanced withdraw is currently disabled").into()); }; + ensure_min_assets_to_receive(&config, refund_assets.clone(), min_assets_to_receive)?; + let mut messages = refund_assets .clone() .into_iter() - .map(|asset| asset.into_msg(&sender)) + .map(|asset| asset.into_msg(&info.sender)) .collect::>>()?; - messages.push( - wasm_execute( - &config.pair_info.liquidity_token, - &Cw20ExecuteMsg::Burn { amount }, - vec![], - )? - .into(), - ); + messages.push(tf_burn_msg( + env.contract.address.to_string(), + coin(amount.u128(), config.pair_info.liquidity_token.to_string()), + )); let pools = pools .iter() @@ -588,7 +507,7 @@ pub fn withdraw_liquidity( Ok(Response::new().add_messages(messages).add_attributes(vec![ attr("action", "withdraw_liquidity"), - attr("sender", sender), + attr("sender", info.sender), attr("withdrawn_share", amount), attr("refund_assets", refund_assets.iter().join(", ")), ])) @@ -822,6 +741,9 @@ pub fn calculate_maker_fee( /// pool using a [`CumulativePricesResponse`] object. /// /// * **QueryMsg::Config {}** Returns the configuration for the pair contract using a [`ConfigResponse`] object. +/// * **QueryMsg::SimulateWithdraw { lp_amount }** Returns the amount of assets that could be withdrawn from the pool +/// using a specific amount of LP tokens. The result is returned in a vector that contains objects of type [`Asset`]. +/// * **QueryMsg::SimulateProvide { msg }** Simulates the liquidity provision in the pair contract. #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { @@ -846,6 +768,11 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { to_json_binary(&query_observation(deps, env, OBSERVATIONS, seconds_ago)?) } QueryMsg::Config {} => to_json_binary(&query_config(deps, env)?), + QueryMsg::SimulateWithdraw { lp_amount } => to_json_binary(&query_share(deps, lp_amount)?), + QueryMsg::SimulateProvide { assets, .. } => to_json_binary( + &query_simulate_provide(deps, env, assets) + .map_err(|e| StdError::generic_err(e.to_string()))?, + ), QueryMsg::QueryComputeD {} => to_json_binary(&query_compute_d(deps, env)?), _ => Err(StdError::generic_err("Query is not supported")), } @@ -1067,6 +994,7 @@ pub fn query_config(deps: Deps, env: Env) -> StdResult { })?), owner: config.owner.unwrap_or(factory_config.owner), factory_addr: config.factory_addr, + tracker_addr: config.tracker_addr, }) } @@ -1151,7 +1079,7 @@ pub fn pool_info(querier: QuerierWrapper, config: &Config) -> StdResult<(Vec StdResult { .map_err(|_| StdError::generic_err("Failed to calculate the D"))? .to_uint128_with_precision(config.greatest_precision) } + +fn ensure_min_assets_to_receive( + config: &Config, + mut refund_assets: Vec, + min_assets_to_receive: Option>, +) -> Result<(), ContractError> { + if let Some(min_assets_to_receive) = min_assets_to_receive { + if refund_assets.len() != min_assets_to_receive.len() { + return Err(ContractError::WrongAssetLength { + expected: refund_assets.len(), + actual: min_assets_to_receive.len(), + }); + } + + for asset in &min_assets_to_receive { + if !config.pair_info.asset_infos.contains(&asset.info) { + return Err(ContractError::AssetMismatch {}); + } + } + + if refund_assets[0].info.ne(&min_assets_to_receive[0].info) { + refund_assets.swap(0, 1) + } + + if refund_assets[0].amount < min_assets_to_receive[0].amount { + return Err(ContractError::WithdrawSlippageViolation { + asset_name: refund_assets[0].info.to_string(), + received: refund_assets[0].amount, + expected: min_assets_to_receive[0].amount, + }); + } + + if refund_assets[1].amount < min_assets_to_receive[1].amount { + return Err(ContractError::WithdrawSlippageViolation { + asset_name: refund_assets[1].info.to_string(), + received: refund_assets[1].amount, + expected: min_assets_to_receive[1].amount, + }); + } + } + + Ok(()) +} + +fn query_simulate_provide( + deps: Deps, + env: Env, + assets: Vec, +) -> Result { + let config = CONFIG.load(deps.storage)?; + + let pools: HashMap<_, _> = config + .pair_info + .query_pools(&deps.querier, &config.pair_info.contract_addr)? + .into_iter() + .map(|pool| (pool.info, pool.amount)) + .collect(); + + let assets_collection = get_assets_collection(deps, &config, &pools, assets)?; + + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; + let share = calculate_shares(deps, &env, &config, total_share, assets_collection)?; + + Ok(share) +} diff --git a/contracts/pair_stable/src/error.rs b/contracts/pair_stable/src/error.rs index ed0ae3941..315fb4839 100644 --- a/contracts/pair_stable/src/error.rs +++ b/contracts/pair_stable/src/error.rs @@ -1,4 +1,7 @@ -use cosmwasm_std::{CheckedMultiplyRatioError, ConversionOverflowError, OverflowError, StdError}; +use cosmwasm_std::{ + CheckedMultiplyRatioError, ConversionOverflowError, OverflowError, StdError, Uint128, +}; +use cw_utils::PaymentError; use thiserror::Error; use astroport::{asset::MINIMUM_LIQUIDITY_AMOUNT, pair::MAX_FEE_SHARE_BPS}; @@ -18,6 +21,9 @@ pub enum ContractError { #[error("{0}")] CircularBuffer(#[from] BufferError), + #[error("{0}")] + PaymentError(#[from] PaymentError), + #[error("Unauthorized")] Unauthorized {}, @@ -63,7 +69,7 @@ pub enum ContractError { #[error("You need to provide init params")] InitParamsNotFound {}, - #[error("Generator address is not set in factory. Cannot autostake")] + #[error("Incentives address is not set in factory. Cannot autostake")] AutoStakeError {}, #[error("It is not possible to provide liquidity with one token for an empty pool")] @@ -95,6 +101,19 @@ pub enum ContractError { MAX_FEE_SHARE_BPS )] FeeShareOutOfBounds {}, + + #[error("Slippage is more than expected: received {0}, expected {1} LP tokens")] + ProvideSlippageViolation(Uint128, Uint128), + + #[error("Received {received} {asset_name} but expected {expected}")] + WithdrawSlippageViolation { + asset_name: String, + received: Uint128, + expected: Uint128, + }, + + #[error("Wrong asset length: expected {expected}, actual {actual}")] + WrongAssetLength { expected: usize, actual: usize }, } impl From for ContractError { diff --git a/contracts/pair_stable/src/migration.rs b/contracts/pair_stable/src/migration.rs index 5ab3175ff..5f17f8e3a 100644 --- a/contracts/pair_stable/src/migration.rs +++ b/contracts/pair_stable/src/migration.rs @@ -86,6 +86,7 @@ pub fn migrate_config_to_v210(mut deps: DepsMut) -> StdResult { greatest_precision, cumulative_prices, fee_share: None, + tracker_addr: None, }; CONFIG.save(deps.storage, &cfg)?; @@ -137,6 +138,7 @@ pub fn migrate_config_from_v21(deps: DepsMut) -> StdResult<()> { greatest_precision: cfg_v212.greatest_precision, cumulative_prices: cfg_v212.cumulative_prices, fee_share: None, + tracker_addr: None, }; CONFIG.save(deps.storage, &cfg)?; diff --git a/contracts/pair_stable/src/state.rs b/contracts/pair_stable/src/state.rs index 0a1ec9f73..7d522d6af 100644 --- a/contracts/pair_stable/src/state.rs +++ b/contracts/pair_stable/src/state.rs @@ -33,6 +33,8 @@ pub struct Config { pub cumulative_prices: Vec<(AssetInfo, AssetInfo, Uint128)>, // The config for swap fee sharing pub fee_share: Option, + /// The tracker contract address + pub tracker_addr: Option, } /// Circular buffer to store trade size observations diff --git a/contracts/pair_stable/src/testing.rs b/contracts/pair_stable/src/testing.rs index 721cd38e5..b9295aee8 100644 --- a/contracts/pair_stable/src/testing.rs +++ b/contracts/pair_stable/src/testing.rs @@ -1,13 +1,11 @@ -use std::error::Error; -use std::str::FromStr; - +use astroport::token_factory::{MsgBurn, MsgCreateDenom, MsgCreateDenomResponse, MsgMint}; use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - attr, coin, from_json, to_json_binary, Addr, BankMsg, BlockInfo, Coin, CosmosMsg, Decimal, - DepsMut, Env, Reply, ReplyOn, Response, SubMsg, SubMsgResponse, SubMsgResult, Timestamp, - Uint128, WasmMsg, + attr, coin, from_json, to_json_binary, Addr, BankMsg, Binary, BlockInfo, Coin, CosmosMsg, + Decimal, DepsMut, Env, Reply, ReplyOn, Response, SubMsg, SubMsgResponse, SubMsgResult, + Timestamp, Uint128, WasmMsg, }; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg, MinterResponse}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use itertools::Itertools; use proptest::prelude::*; use prost::Message; @@ -21,17 +19,17 @@ use astroport::pair::{ ConfigResponse, Cw20HookMsg, ExecuteMsg, InstantiateMsg, PoolResponse, QueryMsg, SimulationResponse, StablePoolParams, }; -use astroport::token::InstantiateMsg as TokenInstantiateMsg; use astroport_circular_buffer::BufferManager; use crate::contract::{ assert_max_spread, execute, instantiate, query, query_pool, query_reverse_simulation, - query_share, query_simulation, reply, + query_share, query_simulation, reply, LP_SUBDENOM, }; use crate::error::ContractError; use crate::mock_querier::mock_dependencies; use crate::state::{CONFIG, OBSERVATIONS}; use crate::utils::{compute_swap, select_pools}; +use astroport_test::convert::f64_to_dec; #[derive(Clone, PartialEq, Message)] struct MsgInstantiateContractResponse { @@ -41,22 +39,17 @@ struct MsgInstantiateContractResponse { pub data: Vec, } -fn store_liquidity_token(deps: DepsMut, msg_id: u64, contract_addr: String) { - let instantiate_reply = MsgInstantiateContractResponse { - contract_address: contract_addr, - data: vec![], - }; - - let mut encoded_instantiate_reply = Vec::::with_capacity(instantiate_reply.encoded_len()); - instantiate_reply - .encode(&mut encoded_instantiate_reply) - .unwrap(); - +fn store_liquidity_token(deps: DepsMut, msg_id: u64, subdenom: String) { let reply_msg = Reply { id: msg_id, result: SubMsgResult::Ok(SubMsgResponse { events: vec![], - data: Some(encoded_instantiate_reply.into()), + data: Some( + MsgCreateDenomResponse { + new_token_denom: subdenom, + } + .into(), + ), }), }; @@ -96,41 +89,36 @@ fn proper_initialization() { // We can just call .unwrap() to assert this was a success let env = mock_env(); let info = mock_info(sender, &[]); - let res = instantiate(deps.as_mut(), env, info, msg).unwrap(); + let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); + assert_eq!( res.messages, vec![SubMsg { - msg: WasmMsg::Instantiate { - code_id: 10u64, - msg: to_json_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: None, - label: String::from("Astroport LP token"), - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgCreateDenom::TYPE_URL.to_string(), + value: Binary( + MsgCreateDenom { + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + subdenom: LP_SUBDENOM.to_string() + } + .encode_to_vec() + ) + }, id: 1, gas_limit: None, reply_on: ReplyOn::Success },] ); + let denom = format!("factory/{}/{}", env.contract.address, "astroport/share"); + // Store liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); // It worked, let's query the state let pair_info = CONFIG.load(deps.as_ref().storage).unwrap().pair_info; - assert_eq!(Addr::unchecked("liquidity0000"), pair_info.liquidity_token); + assert_eq!(denom, pair_info.liquidity_token); assert_eq!( pair_info.asset_infos, vec![ @@ -185,10 +173,11 @@ fn provide_liquidity() { 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(); + let _res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); + let denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); // Store the liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); // Successfully provide liquidity for the existing pool let msg = ExecuteMsg::ProvideLiquidity { @@ -209,6 +198,7 @@ fn provide_liquidity() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env(); @@ -219,6 +209,7 @@ fn provide_liquidity() { amount: Uint128::from(100_000000000000000000u128), }], ); + let res = execute(deps.as_mut(), env.clone().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"); @@ -247,16 +238,22 @@ fn provide_liquidity() { assert_eq!( mint_min_liquidity_msg, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from(MOCK_CONTRACT_ADDR), - amount: Uint128::from(1000_u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from( + MsgMint { + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(1000_u128).to_string(), + }), + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + #[cfg(not(any(feature = "injective", feature = "sei")))] + mint_to_address: String::from(MOCK_CONTRACT_ADDR), + } + .encode_to_vec() + ) + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -266,16 +263,22 @@ fn provide_liquidity() { assert_eq!( mint_receiver_msg, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from("addr0000"), - amount: Uint128::from(299_814_698_523_989_456_628u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from( + MsgMint { + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(299_814_698_523_989_456_628u128).to_string(), + }), + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + #[cfg(not(any(feature = "injective", feature = "sei")))] + mint_to_address: String::from("addr0000"), + } + .encode_to_vec() + ) + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -288,26 +291,21 @@ fn provide_liquidity() { &[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), - )], - ), + }]), + ( + &String::from("liquidity0000"), + &[coin(100_000000000000000000u128, denom.to_string())], + ), ]); + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[( + &String::from(MOCK_CONTRACT_ADDR), + &Uint128::new(200_000000000000000000), + )], + )]); + let msg = ExecuteMsg::ProvideLiquidity { assets: vec![ Asset { @@ -326,6 +324,7 @@ fn provide_liquidity() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -340,6 +339,7 @@ fn provide_liquidity() { let res: Response = execute(deps.as_mut(), env.clone(), 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 { @@ -362,16 +362,22 @@ fn provide_liquidity() { assert_eq!( mint_msg, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from("addr0000"), - amount: Uint128::new(74_981_956_874_579_206461), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from( + MsgMint { + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(74_981_956_874_579_206461u128).to_string(), + }), + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + #[cfg(not(any(feature = "injective", feature = "sei")))] + mint_to_address: String::from("addr0000"), + } + .encode_to_vec() + ) + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -397,6 +403,7 @@ fn provide_liquidity() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env(); @@ -473,6 +480,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -513,6 +521,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -533,16 +542,8 @@ fn withdraw_liquidity() { amount: Uint128::new(100u128), }]); - deps.querier.with_token_balances(&[ - ( - &String::from("liquidity0000"), - &[(&String::from("addr0000"), &Uint128::new(100u128))], - ), - ( - &String::from("asset0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(100u128))], - ), - ]); + let env = mock_env(); + let info = mock_info("addr0000", &[]); let msg = InstantiateMsg { asset_infos: vec![ @@ -564,24 +565,36 @@ fn withdraw_liquidity() { ), }; - let env = mock_env(); - let info = mock_info("addr0000", &[]); + let denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); + + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(100u128))], + )]); + + deps.querier.with_balance(&[( + &String::from("asset0000"), + &[Coin { + denom: denom.to_string(), + amount: Uint128::new(100u128), + }], + )]); + // We can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); + instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); // Store the liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); // Withdraw liquidity - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("addr0000"), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), - amount: Uint128::new(100u128), - }); + let msg = ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: None, + }; let env = mock_env(); - let info = mock_info("liquidity0000", &[]); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let info = mock_info("addr0000", &[coin(100u128, denom.clone())]); + let res = execute(deps.as_mut(), env.clone(), 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"); @@ -623,15 +636,22 @@ fn withdraw_liquidity() { assert_eq!( msg_burn_liquidity, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Burn { - amount: Uint128::from(100u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgBurn::TYPE_URL.to_string(), + value: Binary::from( + MsgBurn { + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(100u128).to_string(), + }), + #[cfg(not(any(feature = "injective", feature = "sei")))] + burn_from_address: "".to_string() + } + .encode_to_vec() + ), + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -1034,21 +1054,26 @@ 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 env = mock_env(); + let info = mock_info("addr0000", &[]); + 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 denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); + + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[(&String::from(MOCK_CONTRACT_ADDR), &asset_1_amount)], + )]); + + deps.querier.with_balance(&[( + &"addr0000".to_string(), + &[coin(total_share_amount.u128(), denom.clone())], + )]); let msg = InstantiateMsg { asset_infos: vec![ @@ -1070,13 +1095,11 @@ fn test_query_pool() { ), }; - 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(); + instantiate(deps.as_mut(), env, info, msg).unwrap(); // Store the liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); let res: PoolResponse = query_pool(deps.as_ref()).unwrap(); @@ -1105,21 +1128,26 @@ 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 env = mock_env(); + let info = mock_info("addr0000", &[]); + 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 denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); + + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[(&String::from(MOCK_CONTRACT_ADDR), &asset_1_amount)], + )]); + + deps.querier.with_balance(&[( + &"addr0000".to_string(), + &[coin(total_share_amount.u128(), denom.clone())], + )]); let msg = InstantiateMsg { asset_infos: vec![ @@ -1141,13 +1169,11 @@ fn test_query_share() { ), }; - 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(); + instantiate(deps.as_mut(), env, info, msg).unwrap(); // Store the liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); let res = query_share(deps.as_ref(), Uint128::new(250)).unwrap(); @@ -1155,14 +1181,6 @@ fn test_query_share() { assert_eq!(res[1].amount, Uint128::new(500)); } -pub fn f64_to_dec(val: f64) -> T -where - T: FromStr, - T::Err: Error, -{ - T::from_str(&val.to_string()).unwrap() -} - #[test] fn observations_full_buffer() { let mut deps = mock_dependencies(&[]); diff --git a/contracts/pair_stable/src/utils.rs b/contracts/pair_stable/src/utils.rs index 55a4a26ec..71341e299 100644 --- a/contracts/pair_stable/src/utils.rs +++ b/contracts/pair_stable/src/utils.rs @@ -1,13 +1,16 @@ use std::cmp::Ordering; +use std::collections::HashMap; +use astroport::incentives::ExecuteMsg as IncentiveExecuteMsg; +use astroport::token_factory::tf_mint_msg; use cosmwasm_std::{ - to_json_binary, wasm_execute, Addr, Api, CosmosMsg, Decimal, Decimal256, Env, QuerierWrapper, - StdResult, Storage, Uint128, Uint64, + coin, wasm_execute, Addr, Api, CosmosMsg, CustomMsg, CustomQuery, Decimal, Decimal256, Deps, + Env, QuerierWrapper, StdResult, Storage, Uint128, Uint64, }; -use cw20::Cw20ExecuteMsg; + use itertools::Itertools; -use astroport::asset::{Asset, AssetInfo, Decimal256Ext, DecimalAsset}; +use astroport::asset::{Asset, AssetInfo, Decimal256Ext, DecimalAsset, MINIMUM_LIQUIDITY_AMOUNT}; use astroport::observation::{ safe_sma_buffer_not_full, safe_sma_calculation, Observation, PrecommitObservation, }; @@ -17,7 +20,7 @@ use astroport_circular_buffer::error::BufferResult; use astroport_circular_buffer::BufferManager; use crate::error::ContractError; -use crate::math::calc_y; +use crate::math::{calc_y, compute_d}; use crate::state::{get_precision, Config, OBSERVATIONS}; /// Helper function to check if the given asset infos are valid. @@ -158,63 +161,49 @@ pub(crate) fn adjust_precision( }) } -/// Mint LP tokens for a beneficiary and auto stake the tokens in the Generator contract (if auto staking is specified). +/// Mint LP tokens for a beneficiary and auto stake the tokens in the Incentive contract (if auto staking is specified). /// /// * **recipient** LP token recipient. /// -/// * **amount** amount of LP tokens that will be minted for the recipient. +/// * **coin** denom and amount of LP tokens that will be minted for the recipient. /// -/// * **auto_stake** whether the newly minted LP tokens will be automatically staked in the Generator on behalf of the recipient. -pub(crate) fn mint_liquidity_token_message( - querier: QuerierWrapper, +/// * **auto_stake** determines whether the newly minted LP tokens will +/// be automatically staked in the Incentives contract on behalf of the recipient. +pub fn mint_liquidity_token_message( + querier: QuerierWrapper, config: &Config, contract_address: &Addr, recipient: &Addr, amount: Uint128, auto_stake: bool, -) -> Result, ContractError> { - let lp_token = &config.pair_info.liquidity_token; +) -> Result>, ContractError> +where + C: CustomQuery, + T: CustomMsg, +{ + let coin = coin(amount.into(), config.pair_info.liquidity_token.to_string()); // If no auto-stake - just mint to recipient if !auto_stake { - return Ok(vec![wasm_execute( - lp_token, - &Cw20ExecuteMsg::Mint { - recipient: recipient.to_string(), - amount, - }, - vec![], - )? - .into()]); + return Ok(tf_mint_msg(contract_address, coin, recipient)); } - // Mint for the pair contract and stake into the Generator contract - let generator = query_factory_config(&querier, &config.factory_addr)?.generator_address; + // Mint for the pair contract and stake into the Incentives contract + let incentives_addr = query_factory_config(&querier, &config.factory_addr)?.generator_address; - if let Some(generator) = generator { - Ok(vec![ - wasm_execute( - lp_token, - &Cw20ExecuteMsg::Mint { - recipient: contract_address.to_string(), - amount, - }, - vec![], - )? - .into(), + if let Some(address) = incentives_addr { + let mut msgs = tf_mint_msg(contract_address, coin.clone(), contract_address); + msgs.push( wasm_execute( - lp_token, - &Cw20ExecuteMsg::Send { - contract: generator.to_string(), - amount, - msg: to_json_binary(&astroport::incentives::Cw20Msg::DepositFor( - recipient.to_string(), - ))?, + address, + &IncentiveExecuteMsg::Deposit { + recipient: Some(recipient.to_string()), }, - vec![], + vec![coin], )? .into(), - ]) + ); + Ok(msgs) } else { Err(ContractError::AutoStakeError {}) } @@ -422,3 +411,119 @@ pub(crate) fn determine_base_quote_amount( Ok((base_amount, quote_amount)) } + +pub(crate) fn calculate_shares( + deps: Deps, + env: &Env, + config: &Config, + total_share: Uint128, + assets_collection: Vec<(Asset, Uint128)>, +) -> Result { + let amp = compute_current_amp(config, env)?; + + let assets_collection = assets_collection + .iter() + .cloned() + .map(|(asset, pool)| { + let coin_precision = get_precision(deps.storage, &asset.info)?; + Ok(( + asset.to_decimal_asset(coin_precision)?, + Decimal256::with_precision(pool, coin_precision)?, + )) + }) + .collect::>>()?; + + // Invariant (D) after deposit added + let new_balances = assets_collection + .iter() + .map(|(deposit, pool)| Ok(pool + deposit.amount)) + .collect::>>()?; + let deposit_d = compute_d(amp, &new_balances)?; + + let share = if total_share.is_zero() { + let share = deposit_d + .to_uint128_with_precision(config.greatest_precision)? + .checked_sub(MINIMUM_LIQUIDITY_AMOUNT) + .map_err(|_| ContractError::MinimumLiquidityAmountError {})?; + + // share cannot become zero after minimum liquidity subtraction + if share.is_zero() { + return Err(ContractError::MinimumLiquidityAmountError {}); + } + + share + } else { + // Initial invariant (D) + let old_balances = assets_collection + .iter() + .map(|(_, pool)| *pool) + .collect_vec(); + let init_d = compute_d(amp, &old_balances)?; + + let share = Decimal256::with_precision(total_share, config.greatest_precision)? + .checked_multiply_ratio(deposit_d.saturating_sub(init_d), init_d)? + .to_uint128_with_precision(config.greatest_precision)?; + + if share.is_zero() { + return Err(ContractError::LiquidityAmountTooSmall {}); + } + + share + }; + Ok(share) +} + +pub(crate) fn get_assets_collection( + deps: Deps, + config: &Config, + pools: &HashMap, + assets: Vec, +) -> Result, ContractError> { + check_assets(deps.api, &assets)?; + + if assets.len() != config.pair_info.asset_infos.len() { + return Err(ContractError::InvalidNumberOfAssets( + config.pair_info.asset_infos.len(), + )); + } + + let mut non_zero_flag = false; + + let mut assets_collection = assets + .clone() + .into_iter() + .map(|asset| { + // Check that at least one asset is non-zero + if !asset.amount.is_zero() { + non_zero_flag = true; + } + + // Get appropriate pool + let pool = pools + .get(&asset.info) + .copied() + .ok_or_else(|| ContractError::InvalidAsset(asset.info.to_string()))?; + + Ok((asset, pool)) + }) + .collect::, ContractError>>()?; + + // If some assets are omitted then add them explicitly with 0 deposit + pools.iter().for_each(|(pool_info, pool_amount)| { + if !assets.iter().any(|asset| asset.info.eq(pool_info)) { + assets_collection.push(( + Asset { + amount: Uint128::zero(), + info: pool_info.clone(), + }, + *pool_amount, + )); + } + }); + + if !non_zero_flag { + return Err(ContractError::InvalidZeroAmount {}); + } + + Ok(assets_collection) +} diff --git a/contracts/pair_stable/tests/helper.rs b/contracts/pair_stable/tests/helper.rs index 440c1db8e..86b710673 100644 --- a/contracts/pair_stable/tests/helper.rs +++ b/contracts/pair_stable/tests/helper.rs @@ -1,11 +1,13 @@ #![cfg(not(tarpaulin_include))] +#![allow(dead_code)] use std::collections::HashMap; -use std::error::Error; -use std::str::FromStr; use anyhow::Result as AnyResult; -use astroport_mocks::cw_multi_test::{App, AppResponse, Contract, ContractWrapper, Executor}; + +use astroport_test::coins::TestCoin; +use astroport_test::cw_multi_test::{AppBuilder, AppResponse, Contract, ContractWrapper, Executor}; +use astroport_test::modules::stargate::{MockStargate, StargateApp as TestApp}; use cosmwasm_std::{coin, to_json_binary, Addr, Coin, Decimal, Empty, StdResult, Uint128}; use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg}; use derivative::Derivative; @@ -23,42 +25,6 @@ use astroport_pair_stable::contract::{execute, instantiate, query, reply}; const INIT_BALANCE: u128 = 1_000_000_000000; -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum TestCoin { - Cw20(String), - Cw20Precise(String, u8), - Native(String), -} - -impl TestCoin { - pub fn denom(&self) -> Option { - match self { - TestCoin::Native(denom) => Some(denom.clone()), - _ => None, - } - } - - pub fn cw20_init_data(&self) -> Option<(String, u8)> { - match self { - TestCoin::Cw20(name) => Some((name.clone(), 6u8)), - TestCoin::Cw20Precise(name, precision) => Some((name.clone(), *precision)), - _ => None, - } - } - - pub fn native(denom: &str) -> Self { - Self::Native(denom.to_string()) - } - - pub fn cw20(name: &str) -> Self { - Self::Cw20(name.to_string()) - } - - pub fn cw20precise(name: &str, precision: u8) -> Self { - Self::Cw20Precise(name.to_string(), precision) - } -} - pub fn init_native_coins(test_coins: &[TestCoin]) -> Vec { test_coins .iter() @@ -107,12 +73,12 @@ fn store_coin_registry_code() -> Box> { #[derivative(Debug)] pub struct Helper { #[derivative(Debug = "ignore")] - pub app: App, + pub app: TestApp, pub owner: Addr, pub assets: HashMap, pub factory: Addr, pub pair_addr: Addr, - pub lp_token: Addr, + pub lp_token: String, pub amp: u64, } @@ -123,12 +89,14 @@ impl Helper { amp: u64, swap_fee: Option, ) -> AnyResult { - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, &owner, init_native_coins(&test_coins)) - .unwrap() - }); + let mut app = AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|router, _, storage| { + router + .bank + .init_balance(storage, owner, init_native_coins(&test_coins)) + .unwrap() + }); let mut asset_infos_vec: Vec<_> = test_coins .clone() @@ -195,6 +163,7 @@ impl Helper { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: coin_registry_address.to_string(), + tracker_config: None, }; let factory = app.instantiate_contract( @@ -235,7 +204,12 @@ impl Helper { }) } - pub fn provide_liquidity(&mut self, sender: &Addr, assets: &[Asset]) -> AnyResult { + pub fn provide_liquidity( + &mut self, + sender: &Addr, + assets: &[Asset], + min_lp_to_receive: Option, + ) -> AnyResult { let funds = assets.mock_coins_sent(&mut self.app, sender, &self.pair_addr, SendType::Allowance); @@ -244,6 +218,7 @@ impl Helper { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive, }; self.app @@ -255,15 +230,17 @@ impl Helper { sender: &Addr, amount: u128, assets: Vec, + min_assets_to_receive: Option>, ) -> AnyResult { - let msg = Cw20ExecuteMsg::Send { - contract: self.pair_addr.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { assets }).unwrap(), - }; - - self.app - .execute_contract(sender.clone(), self.lp_token.clone(), &msg, &[]) + self.app.execute_contract( + sender.clone(), + self.pair_addr.clone(), + &ExecuteMsg::WithdrawLiquidity { + assets, + min_assets_to_receive, + }, + &[coin(amount, self.lp_token.to_string())], + ) } pub fn swap( @@ -352,7 +329,7 @@ impl Helper { } fn init_token( - app: &mut App, + app: &mut TestApp, token_code: u64, name: String, decimals: u8, @@ -395,16 +372,19 @@ impl Helper { resp.balance.u128() } + pub fn native_balance(&self, denom: impl Into, user: &Addr) -> u128 { + self.app + .wrap() + .query_balance(user, denom) + .unwrap() + .amount + .u128() + } + pub fn coin_balance(&self, coin: &TestCoin, user: &Addr) -> u128 { match &self.assets[coin] { AssetInfo::Token { contract_addr } => self.token_balance(contract_addr, user), - AssetInfo::NativeToken { denom } => self - .app - .wrap() - .query_balance(user, denom) - .unwrap() - .amount - .u128(), + AssetInfo::NativeToken { denom } => self.native_balance(denom, user), } } @@ -440,7 +420,7 @@ pub enum SendType { pub trait AssetExt { fn mock_coin_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -450,7 +430,7 @@ pub trait AssetExt { impl AssetExt for Asset { fn mock_coin_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -486,7 +466,7 @@ impl AssetExt for Asset { pub trait AssetsExt { fn mock_coins_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -496,7 +476,7 @@ pub trait AssetsExt { impl AssetsExt for &[Asset] { fn mock_coins_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -513,7 +493,7 @@ pub trait AppExtension { fn next_block(&mut self, time: u64); } -impl AppExtension for App { +impl AppExtension for TestApp { fn next_block(&mut self, time: u64) { self.update_block(|block| { block.time = block.time.plus_seconds(time); @@ -521,11 +501,3 @@ impl AppExtension for App { }); } } - -pub fn f64_to_dec(val: f64) -> T -where - T: FromStr, - T::Err: Error, -{ - T::from_str(&val.to_string()).unwrap() -} diff --git a/contracts/pair_stable/tests/integration.rs b/contracts/pair_stable/tests/integration.rs index 9be489b2f..0790eeb50 100644 --- a/contracts/pair_stable/tests/integration.rs +++ b/contracts/pair_stable/tests/integration.rs @@ -1,13 +1,6 @@ #![cfg(not(tarpaulin_include))] -use std::str::FromStr; - -use cosmwasm_std::{ - attr, from_json, to_json_binary, Addr, Coin, Decimal, QueryRequest, Uint128, WasmQuery, -}; -use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; - -use astroport::asset::{Asset, AssetInfo, PairInfo}; +use astroport::asset::{native_asset_info, Asset, AssetInfo, PairInfo}; use astroport::factory::{ ExecuteMsg as FactoryExecuteMsg, InstantiateMsg as FactoryInstantiateMsg, PairConfig, PairType, QueryMsg as FactoryQueryMsg, @@ -18,21 +11,35 @@ use astroport::pair::{ PoolResponse, QueryMsg, StablePoolConfig, StablePoolParams, StablePoolUpdateParams, MAX_FEE_SHARE_BPS, TWAP_PRECISION, }; -use astroport::token::InstantiateMsg as TokenInstantiateMsg; -use astroport_mocks::cw_multi_test::{App, ContractWrapper, Executor}; + +use astroport_pair_stable::contract::LP_SUBDENOM; use astroport_pair_stable::error::ContractError; + +use std::str::FromStr; + +use astroport::token::InstantiateMsg as TokenInstantiateMsg; use astroport_pair_stable::math::{MAX_AMP, MAX_AMP_CHANGE, MIN_AMP_CHANGING_TIME}; +use astroport_test::cw_multi_test::{AppBuilder, ContractWrapper, Executor}; +use astroport_test::modules::stargate::{MockStargate, StargateApp as TestApp}; +use cosmwasm_std::{ + attr, coin, from_json, to_json_binary, Addr, Coin, Decimal, QueryRequest, Uint128, WasmQuery, +}; +use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; const OWNER: &str = "owner"; -fn mock_app(owner: Addr, coins: Vec) -> App { - App::new(|router, _, storage| { - // initialization moved to App construction - router.bank.init_balance(storage, &owner, coins).unwrap() - }) +mod helper; + +fn mock_app(owner: Addr, coins: Vec) -> TestApp { + AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|router, _, storage| { + // initialization moved to App construction + router.bank.init_balance(storage, &owner, coins).unwrap() + }) } -fn store_token_code(app: &mut App) -> u64 { +fn store_token_code(app: &mut TestApp) -> u64 { let astro_token_contract = Box::new(ContractWrapper::new_with_empty( cw20_base::contract::execute, cw20_base::contract::instantiate, @@ -42,7 +49,7 @@ fn store_token_code(app: &mut App) -> u64 { app.store_code(astro_token_contract) } -fn store_pair_code(app: &mut App) -> u64 { +fn store_pair_code(app: &mut TestApp) -> u64 { let pair_contract = Box::new( ContractWrapper::new_with_empty( astroport_pair_stable::contract::execute, @@ -55,7 +62,7 @@ fn store_pair_code(app: &mut App) -> u64 { app.store_code(pair_contract) } -fn store_factory_code(app: &mut App) -> u64 { +fn store_factory_code(app: &mut TestApp) -> u64 { let factory_contract = Box::new( ContractWrapper::new_with_empty( astroport_factory::contract::execute, @@ -68,7 +75,7 @@ fn store_factory_code(app: &mut App) -> u64 { app.store_code(factory_contract) } -fn store_coin_registry_code(app: &mut App) -> u64 { +fn store_coin_registry_code(app: &mut TestApp) -> u64 { let coin_registry_contract = Box::new(ContractWrapper::new_with_empty( astroport_native_coin_registry::contract::execute, astroport_native_coin_registry::contract::instantiate, @@ -78,7 +85,17 @@ fn store_coin_registry_code(app: &mut App) -> u64 { app.store_code(coin_registry_contract) } -fn instantiate_coin_registry(mut app: &mut App, coins: Option>) -> Addr { +fn store_generator_code(app: &mut TestApp) -> u64 { + let generator_contract = Box::new(ContractWrapper::new_with_empty( + astroport_incentives::execute::execute, + astroport_incentives::instantiate::instantiate, + astroport_incentives::query::query, + )); + + app.store_code(generator_contract) +} + +fn instantiate_coin_registry(mut app: &mut TestApp, coins: Option>) -> Addr { let coin_registry_id = store_coin_registry_code(&mut app); let coin_registry_address = app .instantiate_contract( @@ -108,7 +125,7 @@ fn instantiate_coin_registry(mut app: &mut App, coins: Option> coin_registry_address } -fn instantiate_pair(mut router: &mut App, owner: &Addr) -> Addr { +fn instantiate_pair(mut router: &mut TestApp, owner: &Addr) -> Addr { let coin_registry_address = instantiate_coin_registry( &mut router, Some(vec![("uusd".to_string(), 6), ("uluna".to_string(), 6)]), @@ -134,6 +151,7 @@ fn instantiate_pair(mut router: &mut App, owner: &Addr) -> Addr { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: coin_registry_address.to_string(), + tracker_config: None, }; let factory_addr = router @@ -212,7 +230,10 @@ fn instantiate_pair(mut router: &mut App, owner: &Addr) -> Addr { .query_wasm_smart(pair.clone(), &QueryMsg::Pair {}) .unwrap(); assert_eq!("contract2", res.contract_addr); - assert_eq!("contract3", res.liquidity_token); + assert_eq!( + format!("factory/contract2/{}", LP_SUBDENOM), + res.liquidity_token + ); pair } @@ -244,11 +265,11 @@ fn test_provide_and_withdraw_liquidity() { &[ Coin { denom: "uusd".to_string(), - amount: Uint128::new(233_000u128), + amount: Uint128::new(533_000u128), }, Coin { denom: "uluna".to_string(), - amount: Uint128::new(200_000u128), + amount: Uint128::new(500_000u128), }, ], ) @@ -257,11 +278,15 @@ fn test_provide_and_withdraw_liquidity() { // Init pair let pair_instance = instantiate_pair(&mut router, &owner); - let res: Result = router.wrap().query(&QueryRequest::Wasm(WasmQuery::Smart { - contract_addr: pair_instance.to_string(), - msg: to_json_binary(&QueryMsg::Pair {}).unwrap(), - })); - let res = res.unwrap(); + let res: PairInfo = router + .wrap() + .query(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: pair_instance.to_string(), + msg: to_json_binary(&QueryMsg::Pair {}).unwrap(), + })) + .unwrap(); + + let lp_token = res.liquidity_token; assert_eq!( res.asset_infos, @@ -276,7 +301,7 @@ fn test_provide_and_withdraw_liquidity() { ); // Try to provide liquidity less then MINIMUM_LIQUIDITY_AMOUNT - let (msg, coins) = provide_liquidity_msg(Uint128::new(100), Uint128::new(100), None); + let (msg, coins) = provide_liquidity_msg(Uint128::new(100), Uint128::new(100), None, None); let err = router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) .unwrap_err(); @@ -286,7 +311,7 @@ fn test_provide_and_withdraw_liquidity() { ); // Try to provide liquidity equal to MINIMUM_LIQUIDITY_AMOUNT - let (msg, coins) = provide_liquidity_msg(Uint128::new(500), Uint128::new(500), None); + let (msg, coins) = provide_liquidity_msg(Uint128::new(500), Uint128::new(500), None, None); let err = router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) .unwrap_err(); @@ -296,7 +321,8 @@ fn test_provide_and_withdraw_liquidity() { ); // Provide liquidity - let (msg, coins) = provide_liquidity_msg(Uint128::new(100000), Uint128::new(100000), None); + let (msg, coins) = + provide_liquidity_msg(Uint128::new(100000), Uint128::new(100000), None, None); let res = router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) .unwrap(); @@ -315,25 +341,91 @@ fn test_provide_and_withdraw_liquidity() { attr("share", 199000u128.to_string()) ); - assert_eq!(res.events[3].attributes[1], attr("action", "mint")); - assert_eq!(res.events[3].attributes[2], attr("to", "contract2")); - assert_eq!( - res.events[3].attributes[3], - attr("amount", 1000.to_string()) + // Provide with min_lp_to_receive with a bigger amount than expected. + let min_lp_amount_to_receive: Uint128 = router + .wrap() + .query_wasm_smart( + pair_instance.clone(), + &QueryMsg::SimulateProvide { + assets: vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::new(100000), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + amount: Uint128::new(100000), + }, + ], + slippage_tolerance: None, + }, + ) + .unwrap(); + + let double_amount_to_receive = min_lp_amount_to_receive * Uint128::new(2); + + let (msg, coins) = provide_liquidity_msg( + Uint128::new(100000), + Uint128::new(100000), + None, + Some(double_amount_to_receive.clone()), ); - assert_eq!(res.events[5].attributes[1], attr("action", "mint")); - assert_eq!(res.events[5].attributes[2], attr("to", "alice")); + let err = router + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) + .unwrap_err(); + assert_eq!( - res.events[5].attributes[3], - attr("amount", 199000u128.to_string()) + err.downcast::().unwrap(), + ContractError::ProvideSlippageViolation(Uint128::new(200000), double_amount_to_receive) ); + // Provide with min_lp_to_receive with amount expected + let min_lp_amount_to_receive: Uint128 = router + .wrap() + .query_wasm_smart( + pair_instance.clone(), + &QueryMsg::SimulateProvide { + assets: vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::new(100000), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + amount: Uint128::new(100000), + }, + ], + slippage_tolerance: None, + }, + ) + .unwrap(); + + let (msg, coins) = provide_liquidity_msg( + Uint128::new(100000), + Uint128::new(100000), + None, + Some(min_lp_amount_to_receive), + ); + + router + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) + .unwrap(); + // Provide liquidity for a custom receiver let (msg, coins) = provide_liquidity_msg( Uint128::new(100000), Uint128::new(100000), Some("bob".to_string()), + None, ); let res = router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) @@ -352,18 +444,90 @@ fn test_provide_and_withdraw_liquidity() { res.events[1].attributes[5], attr("share", 200000u128.to_string()) ); - assert_eq!(res.events[3].attributes[1], attr("action", "mint")); - assert_eq!(res.events[3].attributes[2], attr("to", "bob")); + + // Withdraw liquidity doubling the minimum to recieve + let min_assets_to_receive: Vec = router + .wrap() + .query_wasm_smart( + pair_instance.clone(), + &QueryMsg::SimulateWithdraw { + lp_amount: Uint128::new(100), + }, + ) + .unwrap(); + + let err = router + .execute_contract( + alice_address.clone(), + pair_instance.clone(), + &ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: Some( + min_assets_to_receive + .iter() + .map(|a| Asset { + info: a.info.clone(), + amount: a.amount * Uint128::new(2), + }) + .collect(), + ), + }, + &[coin(100u128, lp_token.clone())], + ) + .unwrap_err(); + assert_eq!( - res.events[3].attributes[3], - attr("amount", 200000.to_string()) + err.downcast::().unwrap(), + ContractError::WithdrawSlippageViolation { + asset_name: "uusd".to_string(), + expected: Uint128::new(98), + received: Uint128::new(49) + } ); + + // Withdraw liquidity with minimum to receive + + let min_assets_to_receive: Vec = router + .wrap() + .query_wasm_smart( + pair_instance.clone(), + &QueryMsg::SimulateWithdraw { + lp_amount: Uint128::new(100), + }, + ) + .unwrap(); + + router + .execute_contract( + alice_address.clone(), + pair_instance.clone(), + &ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: Some(min_assets_to_receive), + }, + &[coin(100u128, lp_token.clone())], + ) + .unwrap(); + + // Withdraw with LP token is successful + router + .execute_contract( + alice_address.clone(), + pair_instance.clone(), + &ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: None, + }, + &[coin(50u128, lp_token.clone())], + ) + .unwrap(); } fn provide_liquidity_msg( uusd_amount: Uint128, uluna_amount: Uint128, receiver: Option, + min_lp_to_receive: Option, ) -> (ExecuteMsg, [Coin; 2]) { let msg = ExecuteMsg::ProvideLiquidity { assets: vec![ @@ -383,6 +547,7 @@ fn provide_liquidity_msg( slippage_tolerance: None, auto_stake: None, receiver, + min_lp_to_receive, }; let coins = [ @@ -498,6 +663,7 @@ fn provide_lp_for_single_token() { owner: String::from("owner0000"), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = app @@ -607,6 +773,7 @@ fn provide_lp_for_single_token() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let err = app @@ -635,6 +802,7 @@ fn provide_lp_for_single_token() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) @@ -659,6 +827,7 @@ fn provide_lp_for_single_token() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) @@ -691,6 +860,7 @@ fn provide_lp_for_single_token() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) @@ -833,6 +1003,7 @@ fn test_compatibility_of_tokens_with_different_precision() { owner: String::from("owner0000"), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = app @@ -922,6 +1093,7 @@ fn test_compatibility_of_tokens_with_different_precision() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) @@ -1012,6 +1184,7 @@ fn test_if_twap_is_calculated_correctly_when_pool_idles() { Uint128::new(1000000_000000), Uint128::new(1000000_000000), None, + None, ); app.execute_contract(user1.clone(), pair_instance.clone(), &msg, &coins) .unwrap(); @@ -1030,6 +1203,7 @@ fn test_if_twap_is_calculated_correctly_when_pool_idles() { Uint128::new(3000000_000000), Uint128::new(1000000_000000), None, + None, ); app.execute_contract(user1.clone(), pair_instance.clone(), &msg, &coins) .unwrap(); @@ -1145,6 +1319,7 @@ fn update_pair_config() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: coin_registry_address.to_string(), + tracker_config: None, }; let factory_instance = router @@ -1392,6 +1567,7 @@ fn enable_disable_fee_sharing() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: coin_registry_address.to_string(), + tracker_config: None, }; let factory_instance = router @@ -1571,6 +1747,7 @@ fn check_observe_queries() { Uint128::new(1000000_000000), Uint128::new(1000000_000000), None, + None, ); app.execute_contract(user1.clone(), pair_instance.clone(), &msg, &coins) .unwrap(); @@ -1617,6 +1794,221 @@ fn check_observe_queries() { ); } +#[test] +fn provide_liquidity_with_autostaking_to_generator() { + let owner = Addr::unchecked("owner"); + let alice_address = Addr::unchecked("alice"); + let mut router = mock_app( + owner.clone(), + vec![ + Coin { + denom: "uusd".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + Coin { + denom: "cny".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + ], + ); + + // Set Alice's balances + router + .send_tokens( + owner.clone(), + alice_address.clone(), + &[ + Coin { + denom: "uusd".to_string(), + amount: Uint128::new(233_000_000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::new(2_00_000_000u128), + }, + Coin { + denom: "cny".to_string(), + amount: Uint128::from(100_000_000u128), + }, + ], + ) + .unwrap(); + + let token_contract_code_id = store_token_code(&mut router); + + let pair_contract_code_id = store_pair_code(&mut router); + let factory_code_id = store_factory_code(&mut router); + + let generator_code_id = store_generator_code(&mut router); + + let coin_registry_address = instantiate_coin_registry( + &mut router, + Some(vec![("uusd".to_string(), 6), ("uluna".to_string(), 6)]), + ); + + let init_msg = FactoryInstantiateMsg { + fee_address: None, + pair_configs: vec![PairConfig { + code_id: pair_contract_code_id, + maker_fee_bps: 0, + pair_type: PairType::Stable {}, + total_fee_bps: 0, + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }], + token_code_id: token_contract_code_id, + generator_address: None, + owner: owner.to_string(), + whitelist_code_id: 234u64, + coin_registry_address: coin_registry_address.to_string(), + tracker_config: None, + }; + + let factory_instance = router + .instantiate_contract( + factory_code_id, + owner.clone(), + &init_msg, + &[], + "FACTORY", + None, + ) + .unwrap(); + + let generator_instance = router + .instantiate_contract( + generator_code_id, + owner.clone(), + &astroport::incentives::InstantiateMsg { + astro_token: native_asset_info("astro".to_string()), + factory: factory_instance.to_string(), + owner: owner.to_string(), + guardian: None, + incentivization_fee_info: None, + vesting_contract: "vesting".to_string(), + }, + &[], + "generator", + None, + ) + .unwrap(); + + router + .execute_contract( + owner.clone(), + factory_instance.clone(), + &astroport::factory::ExecuteMsg::UpdateConfig { + token_code_id: None, + fee_address: None, + generator_address: Some(generator_instance.to_string()), + whitelist_code_id: None, + coin_registry_address: None, + }, + &[], + ) + .unwrap(); + + let msg = FactoryExecuteMsg::CreatePair { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + ], + pair_type: PairType::Stable {}, + init_params: Some( + to_json_binary(&StablePoolParams { + amp: 100, + owner: None, + }) + .unwrap(), + ), + }; + + router + .execute_contract(owner.clone(), factory_instance.clone(), &msg, &[]) + .unwrap(); + + let uusd_amount = Uint128::new(100_000_000); + let uluna_amount = Uint128::new(100_000_000); + + let msg = ExecuteMsg::ProvideLiquidity { + assets: vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: uusd_amount.clone(), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + amount: uluna_amount.clone(), + }, + ], + slippage_tolerance: None, + auto_stake: Some(true), + receiver: None, + min_lp_to_receive: None, + }; + + let coins = [ + Coin { + denom: "uluna".to_string(), + amount: uluna_amount.clone(), + }, + Coin { + denom: "uusd".to_string(), + amount: uusd_amount.clone(), + }, + ]; + + let res: PairInfo = router + .wrap() + .query_wasm_smart( + &factory_instance, + &FactoryQueryMsg::Pair { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + ], + }, + ) + .unwrap(); + + let pair_instance = res.contract_addr; + let lp_token_address = res.liquidity_token; + + router + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) + .unwrap(); + + let amount: Uint128 = router + .wrap() + .query_wasm_smart( + generator_instance.to_string(), + &astroport::incentives::QueryMsg::Deposit { + lp_token: lp_token_address.to_string(), + user: alice_address.to_string(), + }, + ) + .unwrap(); + + assert_eq!(amount, Uint128::new(199999000)); +} + #[test] fn test_imbalance_withdraw_is_disabled() { let owner = Addr::unchecked("owner"); @@ -1688,7 +2080,8 @@ fn test_imbalance_withdraw_is_disabled() { ); // Provide liquidity - let (msg, coins) = provide_liquidity_msg(Uint128::new(100000), Uint128::new(100000), None); + let (msg, coins) = + provide_liquidity_msg(Uint128::new(100000), Uint128::new(100000), None, None); router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) .unwrap(); @@ -1698,28 +2091,30 @@ fn test_imbalance_withdraw_is_disabled() { Uint128::new(100000), Uint128::new(100000), Some("bob".to_string()), + None, ); router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) .unwrap(); // Check that imbalanced withdraw is currently disabled - let msg_imbalance = Cw20ExecuteMsg::Send { - contract: pair_instance.to_string(), - amount: Uint128::from(50u8), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { - assets: vec![Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128::from(100u8), - }], - }) - .unwrap(), + let msg_imbalance = ExecuteMsg::WithdrawLiquidity { + assets: vec![Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::from(100u8), + }], + min_assets_to_receive: None, }; let err = router - .execute_contract(alice_address.clone(), lp_token.clone(), &msg_imbalance, &[]) + .execute_contract( + alice_address.clone(), + pair_instance.clone(), + &msg_imbalance, + &[coin(100u128, lp_token)], + ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), @@ -1792,8 +2187,12 @@ fn test_provide_liquidity_without_funds() { ); // provide some liquidity to assume contract have funds (to prevent underflow err) - let (msg, coins) = - provide_liquidity_msg(Uint128::new(100_000_000), Uint128::new(100_000_000), None); + let (msg, coins) = provide_liquidity_msg( + Uint128::new(100_000_000), + Uint128::new(100_000_000), + None, + None, + ); router .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) @@ -1956,6 +2355,7 @@ fn test_fee_share( owner: String::from("owner0000"), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = app @@ -2045,6 +2445,7 @@ fn test_fee_share( slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) diff --git a/contracts/pair_stable/tests/stablepool_tests.rs b/contracts/pair_stable/tests/stablepool_tests.rs index b4a11d84e..236ded7bd 100644 --- a/contracts/pair_stable/tests/stablepool_tests.rs +++ b/contracts/pair_stable/tests/stablepool_tests.rs @@ -8,9 +8,11 @@ use astroport::asset::AssetInfoExt; use astroport::cosmwasm_ext::AbsDiff; use astroport::observation::OracleObservation; use astroport_pair_stable::error::ContractError; +use astroport_test::coins::TestCoin; +use astroport_test::convert::f64_to_dec; use helper::AppExtension; -use crate::helper::{f64_to_dec, Helper, TestCoin}; +use crate::helper::Helper; mod helper; @@ -35,9 +37,9 @@ fn provide_and_withdraw_no_fee() { ]; helper.give_me_money(&assets, &user1); - helper.provide_liquidity(&user1, &assets).unwrap(); + helper.provide_liquidity(&user1, &assets, None).unwrap(); - assert_eq!(299999000, helper.token_balance(&helper.lp_token, &user1)); + assert_eq!(299999000, helper.native_balance(&helper.lp_token, &user1)); assert_eq!(0, helper.coin_balance(&test_coins[0], &user1)); assert_eq!(0, helper.coin_balance(&test_coins[1], &user1)); assert_eq!(0, helper.coin_balance(&test_coins[2], &user1)); @@ -50,8 +52,8 @@ fn provide_and_withdraw_no_fee() { helper.assets[&test_coins[2]].with_balance(100_000000u128), ]; helper.give_me_money(&assets, &user2); - helper.provide_liquidity(&user2, &assets).unwrap(); - assert_eq!(300_000000, helper.token_balance(&helper.lp_token, &user2)); + helper.provide_liquidity(&user2, &assets, None).unwrap(); + assert_eq!(300_000000, helper.native_balance(&helper.lp_token, &user2)); // The user3 makes imbalanced provide thus he is charged with fees let user3 = Addr::unchecked("user3"); @@ -60,8 +62,8 @@ fn provide_and_withdraw_no_fee() { helper.assets[&test_coins[1]].with_balance(100_000000u128), ]; helper.give_me_money(&assets, &user3); - helper.provide_liquidity(&user3, &assets).unwrap(); - assert_eq!(299_629321, helper.token_balance(&helper.lp_token, &user3)); + helper.provide_liquidity(&user3, &assets, None).unwrap(); + assert_eq!(299_629321, helper.native_balance(&helper.lp_token, &user3)); // Providing last asset with explicit zero amount should give nearly the same result let user4 = Addr::unchecked("user4"); @@ -71,14 +73,14 @@ fn provide_and_withdraw_no_fee() { helper.assets[&test_coins[2]].with_balance(0u128), ]; helper.give_me_money(&assets, &user4); - helper.provide_liquidity(&user4, &assets).unwrap(); - assert_eq!(299_056292, helper.token_balance(&helper.lp_token, &user4)); + helper.provide_liquidity(&user4, &assets, None).unwrap(); + assert_eq!(299_056292, helper.native_balance(&helper.lp_token, &user4)); helper - .withdraw_liquidity(&user1, 299999000, vec![]) + .withdraw_liquidity(&user1, 299999000, vec![], None) .unwrap(); - assert_eq!(0, helper.token_balance(&helper.lp_token, &user1)); + assert_eq!(0, helper.native_balance(&helper.lp_token, &user1)); // Previous imbalanced provides resulted in different share in assets assert_eq!(150163977, helper.coin_balance(&test_coins[0], &user1)); assert_eq!(100109318, helper.coin_balance(&test_coins[1], &user1)); @@ -90,11 +92,12 @@ fn provide_and_withdraw_no_fee() { &user2, 300_000000, vec![helper.assets[&test_coins[0]].with_balance(300_000000u128)], + None, ) .unwrap(); // Previous imbalanced provides resulted in small LP balance residual - assert_eq!(619390, helper.token_balance(&helper.lp_token, &user2)); + assert_eq!(619390, helper.native_balance(&helper.lp_token, &user2)); assert_eq!(300_000000, helper.coin_balance(&test_coins[0], &user2)); assert_eq!(0, helper.coin_balance(&test_coins[1], &user2)); assert_eq!(0, helper.coin_balance(&test_coins[2], &user2)); @@ -105,6 +108,7 @@ fn provide_and_withdraw_no_fee() { &user3, 100_000000, vec![helper.assets[&test_coins[1]].with_balance(101_000000u128)], + None, ) .unwrap_err(); assert_eq!( @@ -118,13 +122,14 @@ fn provide_and_withdraw_no_fee() { &user3, 200_892384, vec![helper.assets[&test_coins[1]].with_balance(101_000000u128)], + None, ) .unwrap(); // initial balance - spent amount; 100 goes back to the user3 assert_eq!( 299_629321 - 100679731, - helper.token_balance(&helper.lp_token, &user3) + helper.native_balance(&helper.lp_token, &user3) ); assert_eq!(0, helper.coin_balance(&test_coins[0], &user3)); assert_eq!(101_000000, helper.coin_balance(&test_coins[1], &user3)); @@ -151,29 +156,33 @@ fn provide_with_different_precision() { ]; helper.give_me_money(&assets, &user); - helper.provide_liquidity(&user, &assets).unwrap(); + helper.provide_liquidity(&user, &assets, None).unwrap(); } let user1 = Addr::unchecked("user1"); - assert_eq!(19999000, helper.token_balance(&helper.lp_token, &user1)); + assert_eq!(19999000, helper.native_balance(&helper.lp_token, &user1)); assert_eq!(0, helper.coin_balance(&test_coins[0], &user1)); assert_eq!(0, helper.coin_balance(&test_coins[1], &user1)); - helper.withdraw_liquidity(&user1, 19999000, vec![]).unwrap(); + helper + .withdraw_liquidity(&user1, 19999000, vec![], None) + .unwrap(); - assert_eq!(0, helper.token_balance(&helper.lp_token, &user1)); + assert_eq!(0, helper.native_balance(&helper.lp_token, &user1)); assert_eq!(999950, helper.coin_balance(&test_coins[0], &user1)); assert_eq!(9999500, helper.coin_balance(&test_coins[1], &user1)); let user2 = Addr::unchecked("user2"); - assert_eq!(20000000, helper.token_balance(&helper.lp_token, &user2)); + assert_eq!(20000000, helper.native_balance(&helper.lp_token, &user2)); assert_eq!(0, helper.coin_balance(&test_coins[0], &user2)); assert_eq!(0, helper.coin_balance(&test_coins[1], &user2)); - helper.withdraw_liquidity(&user2, 20000000, vec![]).unwrap(); + helper + .withdraw_liquidity(&user2, 20000000, vec![], None) + .unwrap(); - assert_eq!(0, helper.token_balance(&helper.lp_token, &user2)); + assert_eq!(0, helper.native_balance(&helper.lp_token, &user2)); assert_eq!(999999, helper.coin_balance(&test_coins[0], &user2)); assert_eq!(9999999, helper.coin_balance(&test_coins[1], &user2)); } @@ -193,7 +202,7 @@ fn swap_different_precisions() { helper.assets[&test_coins[0]].with_balance(100_000_0000u128), helper.assets[&test_coins[1]].with_balance(100_000_00000u128), ]; - helper.provide_liquidity(&owner, &assets).unwrap(); + helper.provide_liquidity(&owner, &assets, None).unwrap(); let user = Addr::unchecked("user"); // 100 x FOO tokens @@ -243,7 +252,7 @@ fn check_swaps() { helper.assets[&test_coins[1]].with_balance(100_000_000000u128), helper.assets[&test_coins[2]].with_balance(100_000_000000u128), ]; - helper.provide_liquidity(&owner, &assets).unwrap(); + helper.provide_liquidity(&owner, &assets, None).unwrap(); let user = Addr::unchecked("user"); let offer_asset = helper.assets[&test_coins[0]].with_balance(100_000000u128); @@ -347,7 +356,7 @@ fn check_withdraw_charges_fees() { helper.assets[&test_coins[1]].with_balance(100_000_000_000000u128), helper.assets[&test_coins[2]].with_balance(100_000_000_000000u128), ]; - helper.provide_liquidity(&owner, &assets).unwrap(); + helper.provide_liquidity(&owner, &assets, None).unwrap(); let user1 = Addr::unchecked("user1"); let offer_asset = helper.assets[&test_coins[0]].with_balance(100_000000u128); @@ -369,15 +378,18 @@ fn check_withdraw_charges_fees() { helper.give_me_money(&[offer_asset.clone()], &user2); // Provide 100 x LUNA - helper.provide_liquidity(&user2, &[offer_asset]).unwrap(); + helper + .provide_liquidity(&user2, &[offer_asset], None) + .unwrap(); // Withdraw 100 x USDC - let lp_tokens_amount = helper.token_balance(&helper.lp_token, &user2); + let lp_tokens_amount = helper.native_balance(&helper.lp_token, &user2); let err = helper .withdraw_liquidity( &user2, lp_tokens_amount, vec![helper.assets[&test_coins[1]].with_balance(100_000000u128)], + None, ) .unwrap_err(); assert_eq!( @@ -390,11 +402,12 @@ fn check_withdraw_charges_fees() { &user2, lp_tokens_amount, vec![helper.assets[&test_coins[1]].with_balance(usual_swap_amount)], + None, ) .unwrap(); // A small residual of LP tokens is left - assert_eq!(8, helper.token_balance(&helper.lp_token, &user2)); + assert_eq!(8, helper.native_balance(&helper.lp_token, &user2)); assert_eq!( usual_swap_amount, helper.coin_balance(&test_coins[1], &user2) @@ -433,7 +446,7 @@ fn check_twap_based_prices() { helper.assets[&test_coins[0]].with_balance(100_000_000_000000u128), helper.assets[&test_coins[1]].with_balance(100_000_000_000000u128), ]; - helper.provide_liquidity(&owner, &assets).unwrap(); + helper.provide_liquidity(&owner, &assets, None).unwrap(); helper.app.next_block(1000); check_prices(&helper); @@ -459,7 +472,7 @@ fn check_twap_based_prices() { helper.give_me_money(&assets, &user1); // Imbalanced provide - helper.provide_liquidity(&user1, &assets).unwrap(); + helper.provide_liquidity(&user1, &assets, None).unwrap(); helper.app.next_block(14 * 86400); check_prices(&helper); @@ -488,7 +501,7 @@ fn check_pool_prices() { helper.assets[&test_coins[0]].with_balance(100_000_000_000000u128), helper.assets[&test_coins[1]].with_balance(100_000_000_000000u128), ]; - helper.provide_liquidity(&owner, &assets).unwrap(); + helper.provide_liquidity(&owner, &assets, None).unwrap(); helper.app.next_block(1000); let err = helper.query_observe(0).unwrap_err(); @@ -525,7 +538,7 @@ fn check_pool_prices() { helper.give_me_money(&assets, &user1); // Imbalanced provide - helper.provide_liquidity(&user1, &assets).unwrap(); + helper.provide_liquidity(&user1, &assets, None).unwrap(); helper.app.next_block(14 * 86400); let offer_asset = helper.assets[&test_coins[1]].with_balance(10_000_000000u128); diff --git a/contracts/pair_transmuter/Cargo.toml b/contracts/pair_transmuter/Cargo.toml index 033cf4631..847451177 100644 --- a/contracts/pair_transmuter/Cargo.toml +++ b/contracts/pair_transmuter/Cargo.toml @@ -13,15 +13,17 @@ crate-type = ["cdylib", "rlib"] [features] library = [] +injective = ["astroport/injective"] +sei = ["astroport/sei"] [dependencies] -astroport = "3" -cosmwasm-std.workspace = true +astroport = { version = "5", path = "../../packages/astroport" } +cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1", "stargate"] } cw-storage-plus = "1.2.0" cosmwasm-schema = "1.5.0" thiserror.workspace = true cw2.workspace = true -cw20 = "0.15" +cw20 = "1.1" cw-utils.workspace = true itertools.workspace = true @@ -29,6 +31,6 @@ itertools.workspace = true anyhow = "1" derivative = "2" cw20-base = "1.1" -cw-multi-test = "1.0.0" astroport-factory = { path = "../factory" } astroport-native-coin-registry = { path = "../periphery/native_coin_registry", version = "1" } +astroport-test = { path = "../../packages/astroport_test" } diff --git a/contracts/pair_transmuter/src/contract.rs b/contracts/pair_transmuter/src/contract.rs index 5ad465761..d3e1113d1 100644 --- a/contracts/pair_transmuter/src/contract.rs +++ b/contracts/pair_transmuter/src/contract.rs @@ -1,21 +1,19 @@ +use astroport::token_factory::{ + tf_burn_msg, tf_create_denom_msg, tf_mint_msg, MsgCreateDenomResponse, +}; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, ensure, from_json, wasm_execute, wasm_instantiate, Addr, DepsMut, Empty, Env, - MessageInfo, Reply, Response, StdError, StdResult, SubMsg, SubMsgResponse, SubMsgResult, - Uint128, + attr, coin, ensure, ensure_eq, BankMsg, Coin, DepsMut, Empty, Env, MessageInfo, Reply, + Response, StdError, StdResult, SubMsg, SubMsgResponse, SubMsgResult, Uint128, }; use cw2::set_contract_version; -use cw20::Cw20ReceiveMsg; -use cw_utils::parse_instantiate_response_data; +use cw_utils::{one_coin, PaymentError}; use itertools::Itertools; -use astroport::asset::{ - addr_opt_validate, format_lp_token_name, Asset, AssetInfo, CoinsExt, PairInfo, -}; +use astroport::asset::{addr_opt_validate, Asset, AssetInfo, CoinsExt, PairInfo}; use astroport::factory::PairType; -use astroport::pair::{Cw20HookMsg, ExecuteMsg, InstantiateMsg}; -use astroport::token::MinterResponse; +use astroport::pair::{ExecuteMsg, InstantiateMsg}; use crate::error::ContractError; use crate::state::{Config, CONFIG}; @@ -27,8 +25,10 @@ use crate::utils::{ const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); /// Contract version that is used for migration. const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -/// A `reply` call code ID of sub-message. -const INSTANTIATE_TOKEN_REPLY_ID: u64 = 1; +/// Reply ID for create denom reply +const CREATE_DENOM_REPLY_ID: u64 = 1; +/// Tokenfactory LP token subdenom +pub const LP_SUBDENOM: &str = "astroport/share"; #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( @@ -49,7 +49,7 @@ pub fn instantiate( let pair_info = PairInfo { contract_addr: env.contract.address.clone(), - liquidity_token: Addr::unchecked(""), + liquidity_token: "".to_owned(), asset_infos: msg.asset_infos.clone(), pair_type: PairType::Custom("transmuter".to_string()), }; @@ -58,55 +58,40 @@ pub fn instantiate( CONFIG.save(deps.storage, &config)?; - let token_name = format_lp_token_name(&msg.asset_infos, &deps.querier)?; - // Create LP token let sub_msg = SubMsg::reply_on_success( - wasm_instantiate( - msg.token_code_id, - &astroport::token::InstantiateMsg { - name: token_name, - symbol: "uLP".to_string(), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: env.contract.address.to_string(), - cap: None, - }), - marketing: None, - }, - vec![], - String::from("Astroport LP token"), - )?, - INSTANTIATE_TOKEN_REPLY_ID, + tf_create_denom_msg(env.contract.address.to_string(), LP_SUBDENOM), + CREATE_DENOM_REPLY_ID, ); Ok(Response::new().add_submessage(sub_msg)) } +/// 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 { match msg { Reply { - id: INSTANTIATE_TOKEN_REPLY_ID, + id: CREATE_DENOM_REPLY_ID, result: SubMsgResult::Ok(SubMsgResponse { data: Some(data), .. }), } => { - let mut config = CONFIG.load(deps.storage)?; + let MsgCreateDenomResponse { new_token_denom } = data.try_into()?; - if !config.pair_info.liquidity_token.as_str().is_empty() { - return Err( - StdError::generic_err("Liquidity token is already set in the config").into(), - ); - } + CONFIG.update(deps.storage, |mut config| { + if !config.pair_info.liquidity_token.is_empty() { + return Err(StdError::generic_err( + "Liquidity token is already set in the config", + )); + } + + config.pair_info.liquidity_token = new_token_denom.clone(); + Ok(config) + })?; - let init_response = parse_instantiate_response_data(data.as_slice())?; - config.pair_info.liquidity_token = Addr::unchecked(init_response.contract_address); - CONFIG.save(deps.storage, &config)?; - Ok(Response::new() - .add_attribute("liquidity_token_addr", config.pair_info.liquidity_token)) + Ok(Response::new().add_attribute("lp_denom", new_token_denom)) } _ => Err(ContractError::FailedToParseReply {}), } @@ -115,12 +100,11 @@ pub fn reply(deps: DepsMut, _env: Env, msg: Reply) -> Result Result { match msg { - ExecuteMsg::Receive(cw20msg) => receive_cw20(deps, info, cw20msg), ExecuteMsg::ProvideLiquidity { assets, auto_stake, @@ -132,7 +116,7 @@ pub fn execute( StdError::generic_err("Auto stake is not supported") ); - provide_liquidity(deps, info, assets, receiver) + provide_liquidity(deps, env, info, assets, receiver) } ExecuteMsg::Swap { offer_asset, @@ -140,26 +124,7 @@ pub fn execute( ask_asset_info, .. } => swap(deps, info, offer_asset, ask_asset_info, to), - _ => Err(ContractError::NotSupported {}), - } -} - -/// Receives a message of type [`Cw20ReceiveMsg`] and processes it depending on the received template. -/// -/// * **cw20_msg** is the CW20 receive message to process. -pub fn receive_cw20( - deps: DepsMut, - info: MessageInfo, - cw20_msg: Cw20ReceiveMsg, -) -> Result { - match from_json(&cw20_msg.msg)? { - Cw20HookMsg::WithdrawLiquidity { assets } => withdraw_liquidity( - deps, - info, - Addr::unchecked(cw20_msg.sender), - cw20_msg.amount, - assets, - ), + ExecuteMsg::WithdrawLiquidity { assets, .. } => withdraw_liquidity(deps, env, info, assets), _ => Err(ContractError::NotSupported {}), } } @@ -168,31 +133,29 @@ pub fn receive_cw20( /// This function will burn the LP tokens and send back the assets in proportion to the withdrawn. /// All unused LP tokens will be sent back to the sender. /// -/// * **sender** is the address that will receive assets back from the pair contract. -/// -/// * **burn_amount** is the amount of LP tokens to burn. -/// /// * **assets** is the vector of assets to withdraw. If this vector is empty, the function will withdraw balanced respective to share. pub fn withdraw_liquidity( deps: DepsMut, + env: Env, info: MessageInfo, - sender: Addr, - mut burn_amount: Uint128, assets: Vec, ) -> Result { let config = CONFIG.load(deps.storage)?; - if info.sender != config.pair_info.liquidity_token { - return Err(ContractError::Unauthorized {}); - } + let Coin { mut amount, denom } = one_coin(&info)?; + ensure_eq!( + denom, + config.pair_info.liquidity_token, + PaymentError::MissingDenom(config.pair_info.liquidity_token.to_string()) + ); let (pools, total_share) = pool_info(deps.querier, &config)?; let mut messages = vec![]; let refund_assets = if assets.is_empty() { // Usual withdraw (balanced) - get_share_in_assets(&pools, burn_amount, total_share)? + get_share_in_assets(&pools, amount, total_share)? } else { let required = assets @@ -217,28 +180,24 @@ pub fn withdraw_liquidity( })?; let unused = - burn_amount + amount .checked_sub(required) .map_err(|_| ContractError::InsufficientLpTokens { required, - available: burn_amount, + available: amount, })?; if !unused.is_zero() { messages.push( - wasm_execute( - &config.pair_info.liquidity_token, - &cw20::Cw20ExecuteMsg::Transfer { - recipient: sender.to_string(), - amount: unused, - }, - vec![], - )? + BankMsg::Send { + to_address: info.sender.to_string(), + amount: vec![coin(unused.u128(), &config.pair_info.liquidity_token)], + } .into(), ); } - burn_amount = required; + amount = required; assets }; @@ -247,23 +206,17 @@ pub fn withdraw_liquidity( .clone() .into_iter() .filter(|asset| !asset.amount.is_zero()) - .map(|asset| asset.into_msg(&sender)) + .map(|asset| asset.into_msg(&info.sender)) .collect::>>()?; messages.extend(send_msgs); - messages.push( - wasm_execute( - &config.pair_info.liquidity_token, - &cw20::Cw20ExecuteMsg::Burn { - amount: burn_amount, - }, - vec![], - )? - .into(), - ); + messages.push(tf_burn_msg( + env.contract.address, + coin(amount.u128(), config.pair_info.liquidity_token.to_string()), + )); Ok(Response::new().add_messages(messages).add_attributes(vec![ attr("action", "withdraw_liquidity"), - attr("withdrawn_share", burn_amount), + attr("withdrawn_share", amount), attr("refund_assets", refund_assets.iter().join(", ")), ])) } @@ -275,6 +228,7 @@ pub fn withdraw_liquidity( /// * **receiver** address that receives LP tokens. If this address isn't specified, the function will default to the caller. pub fn provide_liquidity( deps: DepsMut, + env: Env, info: MessageInfo, assets: Vec, receiver: Option, @@ -293,21 +247,21 @@ pub fn provide_liquidity( // Mint LP token for the caller (or for the receiver if it was set) let receiver = addr_opt_validate(deps.api, &receiver)?.unwrap_or_else(|| info.sender.clone()); - let mint_msg = wasm_execute( - &config.pair_info.liquidity_token, - &cw20::Cw20ExecuteMsg::Mint { - recipient: receiver.to_string(), - amount: share, - }, - vec![], - )?; - - Ok(Response::new().add_message(mint_msg).add_attributes([ - attr("action", "provide_liquidity"), - attr("receiver", receiver), - attr("assets", assets.iter().join(", ")), - attr("share", share), - ])) + + let coin = coin(share.into(), config.pair_info.liquidity_token.to_string()); + + Ok(Response::new() + .add_messages(tf_mint_msg( + env.contract.address, + coin.clone(), + receiver.clone(), + )) + .add_attributes([ + attr("action", "provide_liquidity"), + attr("receiver", receiver), + attr("assets", assets.iter().join(", ")), + attr("share", share), + ])) } /// Performs an swap operation with the specified parameters. diff --git a/contracts/pair_transmuter/src/error.rs b/contracts/pair_transmuter/src/error.rs index d9d1bb83e..bf76c08bd 100644 --- a/contracts/pair_transmuter/src/error.rs +++ b/contracts/pair_transmuter/src/error.rs @@ -1,5 +1,5 @@ use cosmwasm_std::{CheckedFromRatioError, StdError, Uint128}; -use cw_utils::ParseReplyError; +use cw_utils::{ParseReplyError, PaymentError}; use thiserror::Error; /// This enum describes pair contract errors @@ -8,6 +8,9 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("{0}")] + PaymentError(#[from] PaymentError), + #[error("{0}")] CheckedFromRatioError(#[from] CheckedFromRatioError), diff --git a/contracts/pair_transmuter/src/queries.rs b/contracts/pair_transmuter/src/queries.rs index 2a4386b28..b07f8e896 100644 --- a/contracts/pair_transmuter/src/queries.rs +++ b/contracts/pair_transmuter/src/queries.rs @@ -69,6 +69,7 @@ pub fn query_config(deps: Deps) -> StdResult { params: None, owner: factory_config.owner, factory_addr: config.factory_addr, + tracker_addr: None, }) } diff --git a/contracts/pair_transmuter/src/utils.rs b/contracts/pair_transmuter/src/utils.rs index b8fcfe00c..e70f6003c 100644 --- a/contracts/pair_transmuter/src/utils.rs +++ b/contracts/pair_transmuter/src/utils.rs @@ -2,7 +2,7 @@ use cosmwasm_std::{Api, Decimal, Deps, QuerierWrapper, StdResult, Uint128}; use itertools::Itertools; use astroport::asset::{Asset, AssetInfo, AssetInfoExt}; -use astroport::querier::query_supply; +use astroport::querier::query_native_supply; use crate::error::ContractError; use crate::state::{Config, CONFIG}; @@ -33,7 +33,7 @@ pub fn pool_info(querier: QuerierWrapper, config: &Config) -> StdResult<(Vec Option { - match self { - TestCoin::Native(denom) => Some(denom.clone()), - _ => None, - } - } - - pub fn cw20_init_data(&self) -> Option<(String, u8)> { - match self { - TestCoin::Cw20(name) => Some((name.clone(), 6u8)), - TestCoin::Cw20Precise(name, precision) => Some((name.clone(), *precision)), - _ => None, - } - } - - pub fn native(denom: &str) -> Self { - Self::Native(denom.to_string()) - } - - pub fn cw20(name: &str) -> Self { - Self::Cw20(name.to_string()) - } - - pub fn cw20precise(name: &str, precision: u8) -> Self { - Self::Cw20Precise(name.to_string(), precision) - } -} - pub fn init_native_coins(test_coins: &[TestCoin]) -> Vec { let test_coins: Vec = test_coins .iter() @@ -120,12 +86,12 @@ fn coin_registry_contract() -> Box> { #[derivative(Debug)] pub struct Helper { #[derivative(Debug = "ignore")] - pub app: App, + pub app: TestApp, pub owner: Addr, pub assets: HashMap, pub factory: Addr, pub pair_addr: Addr, - pub lp_token: Addr, + pub lp_token: String, pub fake_maker: Addr, } @@ -135,12 +101,14 @@ impl Helper { test_coins: Vec, native_coins: Vec<(String, u8)>, // decimals for native coins ) -> AnyResult { - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, owner, init_native_coins(&test_coins)) - .unwrap() - }); + let mut app = AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|router, _, storage| { + router + .bank + .init_balance(storage, owner, init_native_coins(&test_coins)) + .unwrap() + }); let token_code_id = app.store_code(token_contract()); @@ -210,6 +178,7 @@ impl Helper { owner: owner.to_string(), whitelist_code_id: 0, coin_registry_address: coin_registry_address.to_string(), + tracker_config: None, }; let factory = app.instantiate_contract( @@ -259,6 +228,7 @@ impl Helper { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; self.app @@ -271,14 +241,15 @@ impl Helper { amount: u128, assets: Vec, ) -> AnyResult { - let msg = Cw20ExecuteMsg::Send { - contract: self.pair_addr.to_string(), - amount: Uint128::from(amount), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { assets }).unwrap(), - }; - - self.app - .execute_contract(sender.clone(), self.lp_token.clone(), &msg, &[]) + self.app.execute_contract( + sender.clone(), + self.pair_addr.clone(), + &ExecuteMsg::WithdrawLiquidity { + assets, + min_assets_to_receive: None, + }, + &[coin(amount, self.lp_token.clone())], + ) } pub fn swap( @@ -362,7 +333,7 @@ impl Helper { } fn init_token( - app: &mut App, + app: &mut TestApp, token_code: u64, name: String, decimals: u8, @@ -405,16 +376,19 @@ impl Helper { resp.balance.u128() } + pub fn native_balance(&self, denom: impl Into, user: &Addr) -> u128 { + self.app + .wrap() + .query_balance(user, denom) + .unwrap() + .amount + .u128() + } + pub fn coin_balance(&self, coin: &TestCoin, user: &Addr) -> u128 { match &self.assets[coin] { AssetInfo::Token { contract_addr } => self.token_balance(contract_addr, user), - AssetInfo::NativeToken { denom } => self - .app - .wrap() - .query_balance(user, denom) - .unwrap() - .amount - .u128(), + AssetInfo::NativeToken { denom } => self.native_balance(denom, user), } } @@ -461,7 +435,7 @@ pub enum SendType { pub trait AssetExt { fn mock_coin_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -471,7 +445,7 @@ pub trait AssetExt { impl AssetExt for Asset { fn mock_coin_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -507,7 +481,7 @@ impl AssetExt for Asset { pub trait AssetsExt { fn mock_coins_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -517,7 +491,7 @@ pub trait AssetsExt { impl AssetsExt for &[Asset] { fn mock_coins_sent( &self, - app: &mut App, + app: &mut TestApp, user: &Addr, spender: &Addr, typ: SendType, @@ -542,15 +516,3 @@ impl AppExtension for App { }); } } - -pub fn f64_to_dec(val: f64) -> T -where - T: FromStr, - T::Err: Error, -{ - T::from_str(&val.to_string()).unwrap() -} - -pub fn dec_to_f64(val: impl Display) -> f64 { - f64::from_str(&val.to_string()).unwrap() -} diff --git a/contracts/pair_transmuter/tests/transmuter_integration.rs b/contracts/pair_transmuter/tests/transmuter_integration.rs index ea19bdba9..63ef4d1be 100644 --- a/contracts/pair_transmuter/tests/transmuter_integration.rs +++ b/contracts/pair_transmuter/tests/transmuter_integration.rs @@ -1,5 +1,4 @@ use cosmwasm_std::{Addr, StdError}; -use cw_multi_test::Executor; use astroport::asset::{Asset, AssetInfo, AssetInfoExt}; use astroport::pair::{ @@ -7,8 +6,10 @@ use astroport::pair::{ SimulationResponse, }; use astroport_pair_transmuter::error::ContractError; +use astroport_test::coins::TestCoin; +use astroport_test::cw_multi_test::Executor; -use crate::helper::{Helper, TestCoin}; +use crate::helper::Helper; mod helper; @@ -103,6 +104,7 @@ fn test_provide_and_withdraw() { slippage_tolerance: None, auto_stake: Some(true), receiver: None, + min_lp_to_receive: None, }, &[ helper.assets[&test_coins[0]] @@ -132,6 +134,7 @@ fn test_provide_and_withdraw() { slippage_tolerance: None, auto_stake: Some(false), receiver: None, + min_lp_to_receive: None, }, &[ helper.assets[&test_coins[0]] @@ -146,7 +149,7 @@ fn test_provide_and_withdraw() { ) .unwrap(); - let lp_balance = helper.token_balance(&helper.lp_token, &user); + let lp_balance = helper.native_balance(&helper.lp_token, &user); assert_eq!(lp_balance, 2 * 100_000_000000u128); // withdraw half. balanced @@ -154,7 +157,7 @@ fn test_provide_and_withdraw() { .withdraw_liquidity(&user, 100_000_000000u128, vec![]) .unwrap(); - let lp_balance = helper.token_balance(&helper.lp_token, &user); + let lp_balance = helper.native_balance(&helper.lp_token, &user); assert_eq!(lp_balance, 100_000_000000u128); let pool_info = helper.query_pool().unwrap(); @@ -191,7 +194,7 @@ fn test_provide_and_withdraw() { // LP tokens left assert_eq!( - helper.token_balance(&helper.lp_token, &user), + helper.native_balance(&helper.lp_token, &user), 50_000_000000u128 ); @@ -261,7 +264,7 @@ fn test_provide_and_withdraw() { // 5k LP tokens returned to user balance assert_eq!( - helper.token_balance(&helper.lp_token, &user), + helper.native_balance(&helper.lp_token, &user), 45_000_000000u128 ); @@ -475,6 +478,7 @@ fn test_provide_liquidity_without_funds() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let err = helper @@ -532,6 +536,7 @@ fn test_queries() { params: None, owner: owner.clone(), factory_addr: helper.factory.clone(), + tracker_addr: None } ); @@ -682,7 +687,7 @@ fn test_unbalanced_withdraw() { ]; helper.give_me_money(&provide_assets, &user); helper.provide_liquidity(&user, &provide_assets).unwrap(); - let lp_balance = helper.token_balance(&helper.lp_token, &user); + let lp_balance = helper.native_balance(&helper.lp_token, &user); assert_eq!(lp_balance, 200_000_000000u128); // withdraw imbalanced helper @@ -692,7 +697,7 @@ fn test_unbalanced_withdraw() { vec![helper.assets[&test_coins[0]].with_balance(100_000_000000u128)], ) .unwrap(); - let lp_balance = helper.token_balance(&helper.lp_token, &user); + let lp_balance = helper.native_balance(&helper.lp_token, &user); assert_eq!(lp_balance, 100_000_000000u128); let pool_info = helper.query_pool().unwrap(); assert_eq!( @@ -833,7 +838,7 @@ fn test_provide_and_withdraw_different_decimals() { helper.provide_liquidity(&user, &provide_assets).unwrap(); - let lp_balance = helper.token_balance(&helper.lp_token, &user); + let lp_balance = helper.native_balance(&helper.lp_token, &user); assert_eq!(lp_balance, 2 * 100_000_00000000u128); // withdraw half. balanced @@ -841,7 +846,7 @@ fn test_provide_and_withdraw_different_decimals() { .withdraw_liquidity(&user, 100_000_00000000u128, vec![]) .unwrap(); - let lp_balance = helper.token_balance(&helper.lp_token, &user); + let lp_balance = helper.native_balance(&helper.lp_token, &user); assert_eq!(lp_balance, 100_000_00000000u128); let pool_info = helper.query_pool().unwrap(); @@ -878,7 +883,7 @@ fn test_provide_and_withdraw_different_decimals() { // LP tokens left assert_eq!( - helper.token_balance(&helper.lp_token, &user), + helper.native_balance(&helper.lp_token, &user), 50_000_00000000u128 ); @@ -918,7 +923,7 @@ fn test_provide_and_withdraw_different_decimals() { // 5k LP tokens returned to user balance assert_eq!( - helper.token_balance(&helper.lp_token, &user), + helper.native_balance(&helper.lp_token, &user), 45_000_00000000u128 ); diff --git a/contracts/pair_xyk_sale_tax/Cargo.toml b/contracts/pair_xyk_sale_tax/Cargo.toml index c1497b3e4..e663ba29c 100644 --- a/contracts/pair_xyk_sale_tax/Cargo.toml +++ b/contracts/pair_xyk_sale_tax/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport-pair-xyk-sale-tax" -version = "1.6.0" +version = "2.0.0" authors = ["Astroport", "Sturdy"] edition = "2021" description = "The Astroport constant product pool contract implementation" @@ -23,25 +23,29 @@ crate-type = ["cdylib", "rlib"] # for quicker tests, cargo test --lib # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] +injective = ["astroport/injective"] +sei = ["astroport/sei"] library = [] [dependencies] integer-sqrt = "0.1" -astroport = { path = "../../packages/astroport", version = "4" } +astroport = { path = "../../packages/astroport", version = "5" } cw2.workspace = true cw20 = "1.1" -cosmwasm-std.workspace = true +cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1", "stargate"] } cw-storage-plus.workspace = true thiserror.workspace = true cosmwasm-schema.workspace = true cw-utils.workspace = true -astroport-pair = { path = "../pair", features = ["library"], version = "1.5" } +astroport-pair = { path = "../pair", features = ["library"], version = "2" } [dev-dependencies] cw20-base = "1.1" astroport-factory = { path = "../factory" } +astroport-incentives = { path = "../tokenomics/incentives" } proptest = "1.0" prost = "0.11.5" -astroport-mocks = { path = "../../packages/astroport_mocks" } +astroport-test = { path = "../../packages/astroport_test" } astroport-pair-1_3_3 = { package = "astroport-pair", version = "=1.3.3" } test-case = "3.3.1" +astroport-tokenfactory-tracker = { path = "../periphery/tokenfactory_tracker" } diff --git a/contracts/pair_xyk_sale_tax/src/contract.rs b/contracts/pair_xyk_sale_tax/src/contract.rs index e8283c1cb..e2ea422fe 100644 --- a/contracts/pair_xyk_sale_tax/src/contract.rs +++ b/contracts/pair_xyk_sale_tax/src/contract.rs @@ -6,27 +6,35 @@ use cosmwasm_schema::cw_serde; #[cfg(not(feature = "library"))] use cosmwasm_std::entry_point; use cosmwasm_std::{ - attr, coins, from_json, to_json_binary, Addr, BankMsg, Binary, CosmosMsg, Decimal, Decimal256, - Deps, DepsMut, Env, Fraction, MessageInfo, QuerierWrapper, Reply, ReplyOn, Response, StdError, - StdResult, SubMsg, SubMsgResponse, SubMsgResult, Uint128, Uint256, Uint64, WasmMsg, + attr, coin, coins, ensure_eq, from_json, to_json_binary, wasm_execute, Addr, BankMsg, Binary, + Coin, CosmosMsg, CustomMsg, CustomQuery, Decimal, Decimal256, Deps, DepsMut, Env, Fraction, + MessageInfo, QuerierWrapper, Reply, Response, StdError, StdResult, SubMsg, SubMsgResponse, + SubMsgResult, Uint128, Uint256, Uint64, WasmMsg, }; use cw2::set_contract_version; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg, MinterResponse}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use astroport::asset::{ - addr_opt_validate, check_swap_parameters, format_lp_token_name, Asset, AssetInfo, CoinsExt, - PairInfo, MINIMUM_LIQUIDITY_AMOUNT, + addr_opt_validate, check_swap_parameters, Asset, AssetInfo, CoinsExt, PairInfo, + MINIMUM_LIQUIDITY_AMOUNT, }; use astroport::factory::PairType; -use astroport::incentives::Cw20Msg as GeneratorHookMsg; -use astroport::pair::{ConfigResponse, DEFAULT_SLIPPAGE, MAX_ALLOWED_SLIPPAGE}; +use astroport::incentives::ExecuteMsg as IncentiveExecuteMsg; +use astroport::pair::{ConfigResponse, ReplyIds, DEFAULT_SLIPPAGE, MAX_ALLOWED_SLIPPAGE}; use astroport::pair::{ CumulativePricesResponse, Cw20HookMsg, ExecuteMsg, InstantiateMsg, PoolResponse, QueryMsg, ReverseSimulationResponse, SimulationResponse, TWAP_PRECISION, }; -use astroport::querier::{query_factory_config, query_fee_info, query_supply}; -use astroport::{token::InstantiateMsg as TokenInstantiateMsg, U256}; -use cw_utils::parse_instantiate_response_data; +use astroport::querier::{ + query_factory_config, query_fee_info, query_native_supply, query_tracker_config, +}; +use astroport::token_factory::{ + tf_before_send_hook_msg, tf_burn_msg, tf_create_denom_msg, tf_mint_msg, MsgCreateDenomResponse, +}; +use astroport::{tokenfactory_tracker, U256}; +use cw_utils::{ + one_coin, parse_reply_instantiate_data, MsgInstantiateContractResponse, PaymentError, +}; use crate::error::ContractError; use crate::state::{Config, BALANCES, CONFIG}; @@ -39,8 +47,10 @@ use astroport_pair::state::{Config as XykConfig, CONFIG as XYK_CONFIG}; const CONTRACT_NAME: &str = env!("CARGO_PKG_NAME"); /// Contract version that is used for migration. const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -/// A `reply` call code ID used for sub-messages. -const INSTANTIATE_TOKEN_REPLY_ID: u64 = 1; +/// Reply ID for create denom reply +const CREATE_DENOM_REPLY_ID: u64 = 1; +/// Tokenfactory LP token subdenom +pub const LP_SUBDENOM: &str = "astroport/share"; /// Creates a new contract with the specified parameters in the [`InstantiateMsg`]. #[cfg_attr(not(feature = "library"), entry_point)] @@ -67,7 +77,7 @@ pub fn instantiate( let config = Config { pair_info: PairInfo { contract_addr: env.contract.address.clone(), - liquidity_token: Addr::unchecked(""), + liquidity_token: "".to_owned(), asset_infos: msg.asset_infos.clone(), pair_type: PairType::Custom(CONTRACT_NAME.to_string()), }, @@ -78,6 +88,7 @@ pub fn instantiate( track_asset_balances: init_params.track_asset_balances, tax_configs: init_params.tax_configs.check(deps.api, &msg.asset_infos)?, tax_config_admin: deps.api.addr_validate(&init_params.tax_config_admin)?, + tracker_addr: None, }; if init_params.track_asset_balances { @@ -88,34 +99,13 @@ pub fn instantiate( CONFIG.save(deps.storage, &config)?; - let token_name = format_lp_token_name(&msg.asset_infos, &deps.querier)?; - - // Create the LP token contract - let sub_msg: Vec = vec![SubMsg { - msg: WasmMsg::Instantiate { - code_id: msg.token_code_id, - msg: to_json_binary(&TokenInstantiateMsg { - name: token_name, - symbol: "uLP".to_string(), - decimals: 6, - initial_balances: vec![], - mint: Some(MinterResponse { - minter: env.contract.address.to_string(), - cap: None, - }), - marketing: None, - })?, - funds: vec![], - admin: None, - label: String::from("Astroport LP token"), - } - .into(), - id: INSTANTIATE_TOKEN_REPLY_ID, - gas_limit: None, - reply_on: ReplyOn::Success, - }]; + // Create LP token + let sub_msg = SubMsg::reply_on_success( + tf_create_denom_msg(env.contract.address.to_string(), LP_SUBDENOM), + CREATE_DENOM_REPLY_ID, + ); - Ok(Response::new().add_submessages(sub_msg).add_attribute( + Ok(Response::new().add_submessage(sub_msg).add_attribute( "asset_balances_tracking".to_owned(), if config.track_asset_balances { "enabled" @@ -128,33 +118,79 @@ 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 { - match msg { - Reply { - id: INSTANTIATE_TOKEN_REPLY_ID, - result: - SubMsgResult::Ok(SubMsgResponse { - data: Some(data), .. - }), - } => { - let mut config: Config = CONFIG.load(deps.storage)?; - - if config.pair_info.liquidity_token != Addr::unchecked("") { - return Err(ContractError::Unauthorized {}); - } +pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { + match ReplyIds::try_from(msg.id)? { + ReplyIds::CreateDenom => { + if let SubMsgResult::Ok(SubMsgResponse { data: Some(b), .. }) = msg.result { + let MsgCreateDenomResponse { new_token_denom } = b.try_into()?; + let config = CONFIG.load(deps.storage)?; + + let tracking = config.track_asset_balances; + let mut sub_msgs = vec![]; + + #[cfg(any(feature = "injective", feature = "sei"))] + let tracking = false; + + if tracking { + let factory_config = + query_factory_config(&deps.querier, config.factory_addr.clone())?; + let tracker_config = query_tracker_config(&deps.querier, config.factory_addr)?; + // Instantiate tracking contract + let sub_msg: Vec = vec![SubMsg::reply_on_success( + WasmMsg::Instantiate { + admin: Some(factory_config.owner.to_string()), + code_id: tracker_config.code_id, + msg: to_json_binary(&tokenfactory_tracker::InstantiateMsg { + tokenfactory_module_address: tracker_config + .token_factory_addr + .to_string(), + tracked_denom: new_token_denom.clone(), + })?, + funds: vec![], + label: format!("{new_token_denom} tracking contract"), + }, + ReplyIds::InstantiateTrackingContract as u64, + )]; + + sub_msgs.extend(sub_msg); + } - let init_response = parse_instantiate_response_data(data.as_slice()) - .map_err(|e| StdError::generic_err(format!("{e}")))?; + CONFIG.update(deps.storage, |mut config| { + if !config.pair_info.liquidity_token.is_empty() { + return Err(ContractError::Unauthorized {}); + } - config.pair_info.liquidity_token = - deps.api.addr_validate(&init_response.contract_address)?; + config.pair_info.liquidity_token = new_token_denom.clone(); + Ok(config) + })?; - CONFIG.save(deps.storage, &config)?; + Ok(Response::new() + .add_submessages(sub_msgs) + .add_attribute("lp_denom", new_token_denom)) + } else { + Err(ContractError::FailedToParseReply {}) + } + } + ReplyIds::InstantiateTrackingContract => { + let MsgInstantiateContractResponse { + contract_address, .. + } = parse_reply_instantiate_data(msg)?; + + let config = CONFIG.update::<_, StdError>(deps.storage, |mut c| { + c.tracker_addr = Some(deps.api.addr_validate(&contract_address)?); + Ok(c) + })?; + + let set_hook_msg = tf_before_send_hook_msg( + env.contract.address, + config.pair_info.liquidity_token, + contract_address.clone(), + ); Ok(Response::new() - .add_attribute("liquidity_token_addr", config.pair_info.liquidity_token)) + .add_message(set_hook_msg) + .add_attribute("tracker_contract", contract_address)) } - _ => Err(ContractError::FailedToParseReply {}), } } @@ -193,6 +229,7 @@ pub fn execute( slippage_tolerance, auto_stake, receiver, + .. } => provide_liquidity( deps, env, @@ -227,7 +264,8 @@ pub fn execute( to_addr, ) } - ExecuteMsg::UpdateConfig { params } => update_config(deps, env, info, params), + ExecuteMsg::UpdateConfig { params } => update_config(deps, info, params), + ExecuteMsg::WithdrawLiquidity { assets, .. } => withdraw_liquidity(deps, env, info, assets), _ => Err(ContractError::NonSupported {}), } } @@ -281,14 +319,6 @@ pub fn receive_cw20( to_addr, ) } - Cw20HookMsg::WithdrawLiquidity { assets } => withdraw_liquidity( - deps, - env, - info, - Addr::unchecked(cw20_msg.sender), - cw20_msg.amount, - assets, - ), } } @@ -300,7 +330,7 @@ pub fn receive_cw20( /// the pool price can move until the provide liquidity transaction goes through. /// /// * **auto_stake** is an optional parameter which determines whether the LP tokens minted after -/// liquidity provision are automatically staked in the Generator contract on behalf of the LP token receiver. +/// liquidity provision are automatically staked in the Incentives contract on behalf of the LP token receiver. /// /// * **receiver** is an optional parameter which defines the receiver of the LP tokens. /// If no custom receiver is specified, the pair will mint LP tokens for the function caller. @@ -315,38 +345,21 @@ pub fn provide_liquidity( auto_stake: Option, receiver: Option, ) -> Result { - if assets.len() != 2 { - return Err(StdError::generic_err("asset_infos must contain exactly two elements").into()); - } - assets[0].info.check(deps.api)?; - assets[1].info.check(deps.api)?; - - let auto_stake = auto_stake.unwrap_or(false); - let mut config = CONFIG.load(deps.storage)?; - info.funds - .assert_coins_properly_sent(&assets, &config.pair_info.asset_infos)?; + let mut pools = config .pair_info .query_pools(&deps.querier, &config.pair_info.contract_addr)?; - let deposits = [ - assets - .iter() - .find(|a| a.info.equal(&pools[0].info)) - .map(|a| a.amount) - .expect("Wrong asset info is given"), - assets - .iter() - .find(|a| a.info.equal(&pools[1].info)) - .map(|a| a.amount) - .expect("Wrong asset info is given"), - ]; - if deposits[0].is_zero() || deposits[1].is_zero() { - return Err(ContractError::InvalidZeroAmount {}); - } + let deposits = get_deposits_from_assets(deps.as_ref(), &assets, &pools)?; + + info.funds + .assert_coins_properly_sent(&assets, &config.pair_info.asset_infos)?; + + let auto_stake = auto_stake.unwrap_or(false); let mut messages = vec![]; + for (i, pool) in pools.iter_mut().enumerate() { // If the asset is a token contract, then we need to execute a TransferFrom msg to receive assets if let AssetInfo::Token { contract_addr, .. } = &pool.info { @@ -366,17 +379,11 @@ pub fn provide_liquidity( } } - let total_share = query_supply(&deps.querier, &config.pair_info.liquidity_token)?; - let share = if total_share.is_zero() { - // Initial share = collateral amount - let share = Uint128::new( - (U256::from(deposits[0].u128()) * U256::from(deposits[1].u128())) - .integer_sqrt() - .as_u128(), - ) - .checked_sub(MINIMUM_LIQUIDITY_AMOUNT) - .map_err(|_| ContractError::MinimumLiquidityAmountError {})?; + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; + + let share = calculate_shares(&deposits, &pools, total_share, slippage_tolerance)?; + if total_share.is_zero() { messages.extend(mint_liquidity_token_message( deps.querier, &config, @@ -385,27 +392,7 @@ pub fn provide_liquidity( MINIMUM_LIQUIDITY_AMOUNT, false, )?); - - // share cannot become zero after minimum liquidity subtraction - if share.is_zero() { - return Err(ContractError::MinimumLiquidityAmountError {}); - } - - share - } else { - // Assert slippage tolerance - assert_slippage_tolerance(slippage_tolerance, &deposits, &pools)?; - - // min(1, 2) - // 1. sqrt(deposit_0 * exchange_rate_0_to_1 * deposit_0) * (total_share / sqrt(pool_0 * pool_0)) - // == deposit_0 * total_share / pool_0 - // 2. sqrt(deposit_1 * exchange_rate_1_to_0 * deposit_1) * (total_share / sqrt(pool_1 * pool_1)) - // == deposit_1 * total_share / pool_1 - std::cmp::min( - deposits[0].multiply_ratio(total_share, pools[0].amount), - deposits[1].multiply_ratio(total_share, pools[1].amount), - ) - }; + } // Mint LP tokens for the sender or for the receiver (if set) let receiver = addr_opt_validate(deps.api, &receiver)?.unwrap_or_else(|| info.sender.clone()); @@ -448,59 +435,49 @@ pub fn provide_liquidity( ])) } -/// Mint LP tokens for a beneficiary and auto stake the tokens in the Generator contract (if auto staking is specified). +/// Mint LP tokens for a beneficiary and auto stake the tokens in the Incentive contract (if auto staking is specified). /// -/// * **recipient** is the LP token recipient. +/// * **recipient** LP token recipient. /// -/// * **amount** is the amount of LP tokens that will be minted for the recipient. +/// * **coin** denom and amount of LP tokens that will be minted for the recipient. /// /// * **auto_stake** determines whether the newly minted LP tokens will -/// be automatically staked in the Generator on behalf of the recipient. -fn mint_liquidity_token_message( - querier: QuerierWrapper, +/// be automatically staked in the Incentives contract on behalf of the recipient. +pub fn mint_liquidity_token_message( + querier: QuerierWrapper, config: &Config, contract_address: &Addr, recipient: &Addr, amount: Uint128, auto_stake: bool, -) -> Result, ContractError> { - let lp_token = &config.pair_info.liquidity_token; +) -> Result>, ContractError> +where + C: CustomQuery, + T: CustomMsg, +{ + let coin = coin(amount.into(), config.pair_info.liquidity_token.to_string()); // If no auto-stake - just mint to recipient if !auto_stake { - return Ok(vec![CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: lp_token.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: recipient.to_string(), - amount, - })?, - funds: vec![], - })]); + return Ok(tf_mint_msg(contract_address, coin, recipient)); } - // Mint for the pair contract and stake into the Generator contract - let generator = query_factory_config(&querier, &config.factory_addr)?.generator_address; + // Mint for the pair contract and stake into the Incentives contract + let incentives_addr = query_factory_config(&querier, &config.factory_addr)?.generator_address; - if let Some(generator) = generator { - Ok(vec![ - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: lp_token.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: contract_address.to_string(), - amount, - })?, - funds: vec![], - }), - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: lp_token.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Send { - contract: generator.to_string(), - amount, - msg: to_json_binary(&GeneratorHookMsg::DepositFor(recipient.to_string()))?, - })?, - funds: vec![], - }), - ]) + if let Some(address) = incentives_addr { + let mut msgs = tf_mint_msg(contract_address, coin.clone(), contract_address); + msgs.push( + wasm_execute( + address, + &IncentiveExecuteMsg::Deposit { + recipient: Some(recipient.to_string()), + }, + vec![coin], + )? + .into(), + ); + Ok(msgs) } else { Err(ContractError::AutoStakeError {}) } @@ -514,15 +491,17 @@ pub fn withdraw_liquidity( deps: DepsMut, env: Env, info: MessageInfo, - sender: Addr, - amount: Uint128, assets: Vec, ) -> Result { let mut config = CONFIG.load(deps.storage).unwrap(); - if info.sender != config.pair_info.liquidity_token { - return Err(ContractError::Unauthorized {}); - } + let Coin { amount, denom } = one_coin(&info)?; + + ensure_eq!( + denom, + config.pair_info.liquidity_token, + PaymentError::MissingDenom(config.pair_info.liquidity_token.to_string()) + ); let (pools, total_share) = pool_info(deps.querier, &config)?; @@ -546,7 +525,7 @@ pub fn withdraw_liquidity( // Accumulate prices for the pair assets if let Some((price0_cumulative_new, price1_cumulative_new, block_time)) = - accumulate_prices(env, &config, pools[0].amount, pools[1].amount)? + accumulate_prices(env.clone(), &config, pools[0].amount, pools[1].amount)? { config.price0_cumulative_last = price0_cumulative_new; config.price1_cumulative_last = price1_cumulative_new; @@ -555,19 +534,19 @@ pub fn withdraw_liquidity( } // Update the pool info - let messages: Vec = vec![ - refund_assets[0].clone().into_msg(sender.clone())?, - refund_assets[1].clone().into_msg(sender.clone())?, - CosmosMsg::Wasm(WasmMsg::Execute { - contract_addr: config.pair_info.liquidity_token.to_string(), - msg: to_json_binary(&Cw20ExecuteMsg::Burn { amount })?, - funds: vec![], - }), - ]; + let mut messages = refund_assets + .clone() + .into_iter() + .map(|asset| asset.into_msg(&info.sender)) + .collect::>>()?; + messages.push(tf_burn_msg( + env.contract.address, + coin(amount.u128(), config.pair_info.liquidity_token.to_string()), + )); Ok(Response::new().add_messages(messages).add_attributes(vec![ attr("action", "withdraw_liquidity"), - attr("sender", sender), + attr("sender", info.sender), attr("withdrawn_share", amount), attr( "refund_assets", @@ -771,7 +750,6 @@ pub fn swap( /// * **params** new parameter values. pub fn update_config( deps: DepsMut, - env: Env, info: MessageInfo, params: Binary, ) -> Result { @@ -782,34 +760,8 @@ pub fn update_config( return Err(ContractError::Unauthorized {}); } - let mut response = Response::default(); - let config_updates = from_json::(¶ms)?; - let track_asset_balances = config_updates.track_asset_balances.unwrap_or_default(); - if track_asset_balances { - if info.sender != factory_config.owner { - return Err(ContractError::Unauthorized {}); - } - - if config.track_asset_balances { - return Err(ContractError::AssetBalancesTrackingIsAlreadyEnabled {}); - } - config.track_asset_balances = true; - - let pools = config - .pair_info - .query_pools(&deps.querier, &config.pair_info.contract_addr)?; - - for pool in pools.iter() { - BALANCES.save(deps.storage, &pool.info, &pool.amount, env.block.height)?; - } - response.attributes.push(attr( - "asset_balances_tracking".to_owned(), - "enabled".to_owned(), - )); - } - if let Some(new_tax_config) = config_updates.tax_configs { if info.sender != config.tax_config_admin { return Err(ContractError::Unauthorized {}); @@ -825,7 +777,7 @@ pub fn update_config( CONFIG.save(deps.storage, &config)?; - Ok(response) + Ok(Response::default()) } /// Accumulate token prices for the assets in the pool. @@ -916,6 +868,9 @@ pub fn calculate_maker_fee( /// /// * **QueryMsg::AssetBalanceAt { asset_info, block_height }** Returns the balance of the specified asset that was in the pool /// just preceeding the moment of the specified block height creation. +/// * **QueryMsg::SimulateProvide { assets, slippage_tolerance }** Returns the amount of LP tokens that will be minted +/// +/// * **QueryMsg::SimulateWithdraw { lp_amount }** Returns the amount of assets that could be withdrawn from the pool using a specific amount of LP tokens. #[cfg_attr(not(feature = "library"), entry_point)] pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { match msg { @@ -934,6 +889,11 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { asset_info, block_height, } => to_json_binary(&query_asset_balances_at(deps, asset_info, block_height)?), + QueryMsg::SimulateProvide { + assets, + slippage_tolerance, + } => to_json_binary(&query_simulate_provide(deps, assets, slippage_tolerance)?), + QueryMsg::SimulateWithdraw { lp_amount } => to_json_binary(&query_share(deps, lp_amount)?), _ => Err(StdError::generic_err("Query is not supported")), } } @@ -1121,6 +1081,7 @@ pub fn query_config(deps: Deps) -> StdResult { })?), owner: factory_config.owner, factory_addr: config.factory_addr, + tracker_addr: config.tracker_addr, }) } @@ -1135,6 +1096,34 @@ pub fn query_asset_balances_at( BALANCES.may_load_at_height(deps.storage, &asset_info, block_height.u64()) } +/// Returns the amount of LP tokens that will be minted +/// +/// * **assets** is an array with assets available in the pool. +/// +/// * **slippage_tolerance** is an optional parameter which is used to specify how much +/// the pool price can move until the provide liquidity transaction goes through. +/// +fn query_simulate_provide( + deps: Deps, + assets: Vec, + slippage_tolerance: Option, +) -> StdResult { + let config = CONFIG.load(deps.storage)?; + + let pools = config + .pair_info + .query_pools(&deps.querier, &config.pair_info.contract_addr)?; + + let deposits = get_deposits_from_assets(deps, &assets, &pools) + .map_err(|e| StdError::generic_err(e.to_string()))?; + + let total_share = query_native_supply(&deps.querier, &config.pair_info.liquidity_token)?; + let share = calculate_shares(&deposits, &pools, total_share, slippage_tolerance) + .map_err(|e| StdError::generic_err(e.to_string()))?; + + Ok(share) +} + /// Helper struct to represent the result of the function `compute_swap`. #[cw_serde] pub struct SwapResult { @@ -1255,6 +1244,91 @@ pub fn compute_offer_amount( Ok((offer_amount, spread_amount, commission_amount.try_into()?)) } +/// Returns shares for the provided deposits. +/// +/// * **deposits** is an array with asset amounts +/// +/// * **pools** is an array with total amount of assets in the pool +/// +/// * **total_share** is the total amount of LP tokens currently minted +/// +/// * **slippage_tolerance** is an optional parameter which is used to specify how much +/// the pool price can move until the provide liquidity transaction goes through. +pub fn calculate_shares( + deposits: &[Uint128; 2], + pools: &[Asset], + total_share: Uint128, + slippage_tolerance: Option, +) -> Result { + let share = if total_share.is_zero() { + // Initial share = collateral amount + let share = Uint128::new( + (U256::from(deposits[0].u128()) * U256::from(deposits[1].u128())) + .integer_sqrt() + .as_u128(), + ) + .checked_sub(MINIMUM_LIQUIDITY_AMOUNT) + .map_err(|_| ContractError::MinimumLiquidityAmountError {})?; + + // share cannot become zero after minimum liquidity subtraction + if share.is_zero() { + return Err(ContractError::MinimumLiquidityAmountError {}); + } + + share + } else { + // Assert slippage tolerance + assert_slippage_tolerance(slippage_tolerance, deposits, pools)?; + + // min(1, 2) + // 1. sqrt(deposit_0 * exchange_rate_0_to_1 * deposit_0) * (total_share / sqrt(pool_0 * pool_0)) + // == deposit_0 * total_share / pool_0 + // 2. sqrt(deposit_1 * exchange_rate_1_to_0 * deposit_1) * (total_share / sqrt(pool_1 * pool_1)) + // == deposit_1 * total_share / pool_1 + std::cmp::min( + deposits[0].multiply_ratio(total_share, pools[0].amount), + deposits[1].multiply_ratio(total_share, pools[1].amount), + ) + }; + Ok(share) +} + +/// Verify assets provided and returns deposit amounts. +/// +/// * **assets** is an array with assets available in the pool. +/// +/// * **pools** is the array with assets in the pool. +pub fn get_deposits_from_assets( + deps: Deps, + assets: &[Asset], + pools: &[Asset], +) -> Result<[Uint128; 2], ContractError> { + if assets.len() != 2 { + return Err(StdError::generic_err("asset_infos must contain exactly two elements").into()); + } + assets[0].info.check(deps.api)?; + assets[1].info.check(deps.api)?; + + let deposits = [ + assets + .iter() + .find(|a| a.info.equal(&pools[0].info)) + .map(|a| a.amount) + .expect("Wrong asset info is given"), + assets + .iter() + .find(|a| a.info.equal(&pools[1].info)) + .map(|a| a.amount) + .expect("Wrong asset info is given"), + ]; + + if deposits[0].is_zero() || deposits[1].is_zero() { + return Err(ContractError::InvalidZeroAmount {}); + } + + Ok(deposits) +} + /// If `belief_price` and `max_spread` are both specified, we compute a new spread, /// otherwise we just use the swap spread to check `max_spread`. /// @@ -1369,6 +1443,7 @@ pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result StdResult<(Vec, /// The address that is allowed to updated the tax configs pub tax_config_admin: Addr, + /// Stores the tracker contract address + pub tracker_addr: Option, } /// Stores the config struct at the given key diff --git a/contracts/pair_xyk_sale_tax/src/testing.rs b/contracts/pair_xyk_sale_tax/src/testing.rs index 8d8000c03..023ed6d10 100644 --- a/contracts/pair_xyk_sale_tax/src/testing.rs +++ b/contracts/pair_xyk_sale_tax/src/testing.rs @@ -1,10 +1,12 @@ use astroport::pair_xyk_sale_tax::{SaleTaxInitParams, TaxConfigChecked, TaxConfigsChecked}; +use astroport::token_factory::{MsgBurn, MsgCreateDenom, MsgCreateDenomResponse, MsgMint}; use cosmwasm_std::testing::{mock_env, mock_info, MOCK_CONTRACT_ADDR}; use cosmwasm_std::{ - attr, to_json_binary, Addr, BankMsg, BlockInfo, Coin, CosmosMsg, Decimal, DepsMut, Env, Reply, - ReplyOn, Response, StdError, SubMsg, SubMsgResponse, SubMsgResult, Timestamp, Uint128, WasmMsg, + attr, coin, to_json_binary, Addr, BankMsg, Binary, BlockInfo, Coin, CosmosMsg, Decimal, + DepsMut, Env, Reply, ReplyOn, Response, StdError, SubMsg, SubMsgResponse, SubMsgResult, + Timestamp, Uint128, WasmMsg, }; -use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg, MinterResponse}; +use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg}; use proptest::prelude::*; use astroport::asset::{Asset, AssetInfo, PairInfo}; @@ -13,12 +15,10 @@ use astroport::pair::{ Cw20HookMsg, ExecuteMsg, InstantiateMsg, PoolResponse, ReverseSimulationResponse, SimulationResponse, TWAP_PRECISION, }; -use astroport::token::InstantiateMsg as TokenInstantiateMsg; -use crate::contract::reply; use crate::contract::{ accumulate_prices, assert_max_spread, compute_swap, execute, instantiate, query_pool, - query_reverse_simulation, query_share, query_simulation, + query_reverse_simulation, query_share, query_simulation, reply, LP_SUBDENOM, }; use crate::contract::{compute_offer_amount, SwapResult}; use crate::error::ContractError; @@ -35,26 +35,21 @@ struct MsgInstantiateContractResponse { pub data: Vec, } -fn store_liquidity_token(deps: DepsMut, msg_id: u64, contract_addr: String) { - let instantiate_reply = MsgInstantiateContractResponse { - contract_address: contract_addr, - data: vec![], - }; - - let mut encoded_instantiate_reply = Vec::::with_capacity(instantiate_reply.encoded_len()); - instantiate_reply - .encode(&mut encoded_instantiate_reply) - .unwrap(); - +fn store_liquidity_token(deps: DepsMut, msg_id: u64, subdenom: String) { let reply_msg = Reply { id: msg_id, result: SubMsgResult::Ok(SubMsgResponse { events: vec![], - data: Some(encoded_instantiate_reply.into()), + data: Some( + MsgCreateDenomResponse { + new_token_denom: subdenom, + } + .into(), + ), }), }; - let _res = reply(deps, mock_env(), reply_msg.clone()).unwrap(); + reply(deps, mock_env(), reply_msg).unwrap(); } #[test] @@ -84,29 +79,21 @@ fn proper_initialization() { // We can just call .unwrap() to assert this was a success let env = mock_env(); let info = mock_info(sender, &[]); - let res = instantiate(deps.as_mut(), env, info, msg).unwrap(); + let res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); assert_eq!( res.messages, vec![SubMsg { - msg: WasmMsg::Instantiate { - code_id: 10u64, - msg: to_json_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: None, - label: String::from("Astroport LP token"), - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgCreateDenom::TYPE_URL.to_string(), + value: Binary( + MsgCreateDenom { + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + subdenom: LP_SUBDENOM.to_string() + } + .encode_to_vec() + ) + }, id: 1, gas_limit: None, reply_on: ReplyOn::Success @@ -167,10 +154,11 @@ fn provide_liquidity() { 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(); + let _res = instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); + let denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); - // Store liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + // Store the liquidity token + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); // Successfully provide liquidity for the existing pool let msg = ExecuteMsg::ProvideLiquidity { @@ -191,6 +179,7 @@ fn provide_liquidity() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env(); @@ -227,16 +216,22 @@ fn provide_liquidity() { assert_eq!( mint_min_liquidity_msg, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from(MOCK_CONTRACT_ADDR), - amount: Uint128::from(1000_u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from( + MsgMint { + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(1000_u128).to_string(), + }), + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + #[cfg(not(any(feature = "injective", feature = "sei")))] + mint_to_address: String::from(MOCK_CONTRACT_ADDR), + } + .encode_to_vec() + ) + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -245,16 +240,22 @@ fn provide_liquidity() { assert_eq!( mint_receiver_msg, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from("addr0000"), - amount: Uint128::from(99_999999999999999000u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from( + MsgMint { + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(99_999999999999999000u128).to_string(), + }), + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + #[cfg(not(any(feature = "injective", feature = "sei")))] + mint_to_address: String::from("addr0000"), + } + .encode_to_vec() + ) + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -269,24 +270,19 @@ fn provide_liquidity() { denom: "uusd".to_string(), amount: Uint128::new(200_000000000000000000 + 200_000000000000000000 /* user deposit must be pre-applied */), }], - )]); + ), + ( + &String::from("liquidity0000"), + &[coin(100_000000000000000000u128, denom.to_string())], + ),]); - 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), - )], - ), - ]); + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[( + &String::from(MOCK_CONTRACT_ADDR), + &Uint128::new(200_000000000000000000), + )], + )]); let msg = ExecuteMsg::ProvideLiquidity { assets: vec![ @@ -306,6 +302,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(50)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -343,16 +340,22 @@ fn provide_liquidity() { assert_eq!( mint_msg, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Mint { - recipient: String::from("addr0000"), - amount: Uint128::from(50_000000000000000000u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from( + MsgMint { + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(50_000000000000000000u128).to_string(), + }), + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + #[cfg(not(any(feature = "injective", feature = "sei")))] + mint_to_address: String::from("addr0000"), + } + .encode_to_vec() + ) + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -378,6 +381,7 @@ fn provide_liquidity() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env(); @@ -441,6 +445,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -482,6 +487,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -523,6 +529,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -563,6 +570,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let env = mock_env_with_block_time(env.block.time.seconds() + 1000); @@ -593,6 +601,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(1)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let info = mock_info( "addr0001", @@ -622,6 +631,7 @@ fn provide_liquidity() { slippage_tolerance: Some(Decimal::percent(51)), auto_stake: None, receiver: None, + min_lp_to_receive: None, }; let info = mock_info( "addr0001", @@ -641,16 +651,8 @@ fn withdraw_liquidity() { amount: Uint128::new(100u128), }]); - deps.querier.with_token_balances(&[ - ( - &String::from("liquidity0000"), - &[(&String::from("addr0000"), &Uint128::new(100u128))], - ), - ( - &String::from("asset0000"), - &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(100u128))], - ), - ]); + let env = mock_env(); + let info = mock_info("addr0000", &[]); let msg = InstantiateMsg { asset_infos: vec![ @@ -667,24 +669,36 @@ fn withdraw_liquidity() { init_params: Some(to_json_binary(&SaleTaxInitParams::default()).unwrap()), }; - let env = mock_env(); - let info = mock_info("addr0000", &[]); + let denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); + + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[(&String::from(MOCK_CONTRACT_ADDR), &Uint128::new(100u128))], + )]); + + deps.querier.with_balance(&[( + &String::from("asset0000"), + &[Coin { + denom: denom.to_string(), + amount: Uint128::new(100u128), + }], + )]); + // We can just call .unwrap() to assert this was a success - let _res = instantiate(deps.as_mut(), env, info, msg).unwrap(); + instantiate(deps.as_mut(), env.clone(), info, msg).unwrap(); - // Store liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + // Store the liquidity token + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); // Withdraw liquidity - let msg = ExecuteMsg::Receive(Cw20ReceiveMsg { - sender: String::from("addr0000"), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), - amount: Uint128::new(100u128), - }); + let msg = ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: None, + }; let env = mock_env(); - let info = mock_info("liquidity0000", &[]); - let res = execute(deps.as_mut(), env, info, msg).unwrap(); + let info = mock_info("addr0000", &[coin(100u128, denom.clone())]); + let res = execute(deps.as_mut(), env.clone(), 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"); @@ -726,15 +740,22 @@ fn withdraw_liquidity() { assert_eq!( msg_burn_liquidity, &SubMsg { - msg: WasmMsg::Execute { - contract_addr: String::from("liquidity0000"), - msg: to_json_binary(&Cw20ExecuteMsg::Burn { - amount: Uint128::from(100u128), - }) - .unwrap(), - funds: vec![], - } - .into(), + msg: CosmosMsg::Stargate { + type_url: MsgBurn::TYPE_URL.to_string(), + value: Binary::from( + MsgBurn { + #[cfg(not(feature = "sei"))] + sender: env.contract.address.to_string(), + amount: Some(astroport::token_factory::ProtoCoin { + denom: denom.to_string(), + amount: Uint128::from(100u128).to_string(), + }), + #[cfg(not(any(feature = "injective", feature = "sei")))] + burn_from_address: "".to_string() + } + .encode_to_vec() + ), + }, id: 0, gas_limit: None, reply_on: ReplyOn::Never, @@ -1237,21 +1258,26 @@ 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 env = mock_env(); + let info = mock_info("addr0000", &[]); + 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 denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); + + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[(&String::from(MOCK_CONTRACT_ADDR), &asset_1_amount)], + )]); + + deps.querier.with_balance(&[( + &"addr0000".to_string(), + &[coin(total_share_amount.u128(), denom.clone())], + )]); let msg = InstantiateMsg { asset_infos: vec![ @@ -1267,13 +1293,11 @@ fn test_query_pool() { init_params: Some(to_json_binary(&SaleTaxInitParams::default()).unwrap()), }; - 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(); + instantiate(deps.as_mut(), env, info, msg).unwrap(); - // Store liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + // Store the liquidity token + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); let res: PoolResponse = query_pool(deps.as_ref()).unwrap(); @@ -1302,21 +1326,26 @@ 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 env = mock_env(); + let info = mock_info("addr0000", &[]); + 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 denom = format!("factory/{}/{}", env.contract.address, "share/astroport"); + + deps.querier.with_token_balances(&[( + &String::from("asset0000"), + &[(&String::from(MOCK_CONTRACT_ADDR), &asset_1_amount)], + )]); + + deps.querier.with_balance(&[( + &"addr0000".to_string(), + &[coin(total_share_amount.u128(), denom.clone())], + )]); let msg = InstantiateMsg { asset_infos: vec![ @@ -1332,13 +1361,11 @@ fn test_query_share() { init_params: Some(to_json_binary(&SaleTaxInitParams::default()).unwrap()), }; - 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(); + instantiate(deps.as_mut(), env, info, msg).unwrap(); - // Store liquidity token - store_liquidity_token(deps.as_mut(), 1, "liquidity0000".to_string()); + // Store the liquidity token + store_liquidity_token(deps.as_mut(), 1, denom.to_string()); let res = query_share(deps.as_ref(), Uint128::new(250)).unwrap(); @@ -1435,7 +1462,7 @@ fn test_accumulate_prices() { }, ], contract_addr: Addr::unchecked("pair"), - liquidity_token: Addr::unchecked("lp_token"), + liquidity_token: "lp_token".to_owned(), pair_type: PairType::Xyk {}, // Implemented in mock querier }, factory_addr: Addr::unchecked("factory"), @@ -1445,6 +1472,7 @@ fn test_accumulate_prices() { track_asset_balances: false, tax_configs: TaxConfigsChecked::default(), tax_config_admin: Addr::unchecked("tax_config_admin"), + tracker_addr: None, }, Uint128::new(case.x_amount), Uint128::new(case.y_amount), diff --git a/contracts/pair_xyk_sale_tax/tests/integration.rs b/contracts/pair_xyk_sale_tax/tests/integration.rs index eb307b5a6..d39f86b7e 100644 --- a/contracts/pair_xyk_sale_tax/tests/integration.rs +++ b/contracts/pair_xyk_sale_tax/tests/integration.rs @@ -1,13 +1,9 @@ #![cfg(not(tarpaulin_include))] -use cosmwasm_std::{attr, coin, to_json_binary, Addr, Coin, Decimal, Uint128}; -use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg, MinterResponse}; -use test_case::test_case; - -use astroport::asset::{native_asset_info, Asset, AssetInfo, PairInfo}; +use astroport::asset::{native_asset_info, Asset, AssetInfo, PairInfo, MINIMUM_LIQUIDITY_AMOUNT}; use astroport::factory::{ ExecuteMsg as FactoryExecuteMsg, InstantiateMsg as FactoryInstantiateMsg, PairConfig, PairType, - QueryMsg as FactoryQueryMsg, + QueryMsg as FactoryQueryMsg, TrackerConfig, }; use astroport::pair::{ ConfigResponse, CumulativePricesResponse, Cw20HookMsg, ExecuteMsg, InstantiateMsg, QueryMsg, @@ -17,19 +13,30 @@ use astroport::pair_xyk_sale_tax::{ MigrateMsg, SaleTaxConfigUpdates, SaleTaxInitParams, TaxConfigUnchecked, TaxConfigsUnchecked, }; use astroport::token::InstantiateMsg as TokenInstantiateMsg; -use astroport_mocks::cw_multi_test::{App, ContractWrapper, Executor}; +use astroport::tokenfactory_tracker::{ + ConfigResponse as TrackerConfigResponse, QueryMsg as TrackerQueryMsg, +}; + +use astroport_pair::contract::LP_SUBDENOM; use astroport_pair_xyk_sale_tax::error::ContractError; +use astroport_test::cw_multi_test::{AppBuilder, ContractWrapper, Executor, TOKEN_FACTORY_MODULE}; +use astroport_test::modules::stargate::{MockStargate, StargateApp as TestApp}; +use cosmwasm_std::{ + attr, coin, to_json_binary, Addr, Coin, Decimal, DepsMut, Empty, Env, MessageInfo, Response, + StdError, StdResult, Uint128, +}; +use cw20::{Cw20Coin, Cw20ExecuteMsg, MinterResponse}; +use test_case::test_case; const OWNER: &str = "owner"; -fn mock_app(owner: Addr, coins: Vec) -> App { - App::new(|router, _, storage| { - // initialization moved to App construction - router.bank.init_balance(storage, &owner, coins).unwrap() - }) +fn mock_app(owner: Addr, coins: Vec) -> TestApp { + AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|router, _, storage| router.bank.init_balance(storage, &owner, coins).unwrap()) } -fn store_token_code(app: &mut App) -> u64 { +fn store_token_code(app: &mut TestApp) -> u64 { let astro_token_contract = Box::new(ContractWrapper::new_with_empty( cw20_base::contract::execute, cw20_base::contract::instantiate, @@ -39,7 +46,7 @@ fn store_token_code(app: &mut App) -> u64 { app.store_code(astro_token_contract) } -fn store_standard_xyk_pair_code(app: &mut App, version: &str) -> u64 { +fn store_standard_xyk_pair_code(app: &mut TestApp, version: &str) -> u64 { let code_id = match version { "1.3.1" => { let code = Box::new( @@ -70,7 +77,7 @@ fn store_standard_xyk_pair_code(app: &mut App, version: &str) -> u64 { code_id } -fn store_pair_code(app: &mut App) -> u64 { +fn store_pair_code(app: &mut TestApp) -> u64 { let pair_contract = Box::new( ContractWrapper::new_with_empty( astroport_pair_xyk_sale_tax::contract::execute, @@ -84,7 +91,7 @@ fn store_pair_code(app: &mut App) -> u64 { app.store_code(pair_contract) } -fn store_factory_code(app: &mut App) -> u64 { +fn store_factory_code(app: &mut TestApp) -> u64 { let factory_contract = Box::new( ContractWrapper::new_with_empty( astroport_factory::contract::execute, @@ -97,7 +104,31 @@ fn store_factory_code(app: &mut App) -> u64 { app.store_code(factory_contract) } -fn instantiate_pair(mut router: &mut App, owner: &Addr) -> Addr { +fn store_generator_code(app: &mut TestApp) -> u64 { + let generator_contract = Box::new(ContractWrapper::new_with_empty( + astroport_incentives::execute::execute, + astroport_incentives::instantiate::instantiate, + astroport_incentives::query::query, + )); + + app.store_code(generator_contract) +} + +fn store_tracker_contract(app: &mut TestApp) -> u64 { + let tracker_contract = Box::new( + ContractWrapper::new_with_empty( + |_: DepsMut, _: Env, _: MessageInfo, _: Empty| -> StdResult { + unimplemented!() + }, + astroport_tokenfactory_tracker::contract::instantiate, + astroport_tokenfactory_tracker::query::query, + ) + .with_sudo_empty(astroport_tokenfactory_tracker::contract::sudo), + ); + app.store_code(tracker_contract) +} + +fn instantiate_pair(mut router: &mut TestApp, owner: &Addr) -> Addr { let token_contract_code_id = store_token_code(&mut router); let pair_contract_code_id = store_pair_code(&mut router); @@ -119,6 +150,7 @@ fn instantiate_pair(mut router: &mut App, owner: &Addr) -> Addr { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = router @@ -162,12 +194,15 @@ fn instantiate_pair(mut router: &mut App, owner: &Addr) -> Addr { .query_wasm_smart(pair.clone(), &QueryMsg::Pair {}) .unwrap(); assert_eq!("contract1", res.contract_addr); - assert_eq!("contract2", res.liquidity_token); + assert_eq!( + format!("factory/contract1/{}", LP_SUBDENOM), + res.liquidity_token + ); pair } -fn instantiate_standard_xyk_pair(mut router: &mut App, owner: &Addr, version: &str) -> Addr { +fn instantiate_standard_xyk_pair(mut router: &mut TestApp, owner: &Addr, version: &str) -> Addr { let token_contract_code_id = store_token_code(&mut router); let pair_contract_code_id = store_standard_xyk_pair_code(&mut router, version); @@ -189,6 +224,7 @@ fn instantiate_standard_xyk_pair(mut router: &mut App, owner: &Addr, version: &s owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = router @@ -232,7 +268,10 @@ fn instantiate_standard_xyk_pair(mut router: &mut App, owner: &Addr, version: &s .query_wasm_smart(pair.clone(), &QueryMsg::Pair {}) .unwrap(); assert_eq!("contract1", res.contract_addr); - assert_eq!("contract2", res.liquidity_token); + assert_eq!( + format!("factory/contract1/{}", LP_SUBDENOM), + res.liquidity_token + ); pair } @@ -326,18 +365,6 @@ fn test_provide_and_withdraw_liquidity() { res.events[1].attributes[5], attr("share", 99999000u128.to_string()) ); - assert_eq!(res.events[3].attributes[1], attr("action", "mint")); - assert_eq!(res.events[3].attributes[2], attr("to", "contract1")); - assert_eq!( - res.events[3].attributes[3], - attr("amount", 1000.to_string()) - ); - assert_eq!(res.events[5].attributes[1], attr("action", "mint")); - assert_eq!(res.events[5].attributes[2], attr("to", "alice")); - assert_eq!( - res.events[5].attributes[3], - attr("amount", 99999000.to_string()) - ); // Provide liquidity for receiver let (msg, coins) = provide_liquidity_msg( @@ -363,75 +390,46 @@ fn test_provide_and_withdraw_liquidity() { res.events[1].attributes[5], attr("share", 100u128.to_string()) ); - assert_eq!(res.events[3].attributes[1], attr("action", "mint")); - assert_eq!(res.events[3].attributes[2], attr("to", "bob")); - assert_eq!(res.events[3].attributes[3], attr("amount", 100.to_string())); - - // Checking withdraw liquidity - let token_contract_code_id = store_token_code(&mut router); - let foo_token = router - .instantiate_contract( - token_contract_code_id, - owner.clone(), - &astroport::token::InstantiateMsg { - name: "Foo token".to_string(), - symbol: "FOO".to_string(), - decimals: 6, - initial_balances: vec![Cw20Coin { - address: alice_address.to_string(), - amount: Uint128::from(1000000000u128), - }], - mint: None, - marketing: None, - }, - &[], - String::from("FOO"), - None, - ) - .unwrap(); - let msg = Cw20ExecuteMsg::Send { - contract: pair_instance.to_string(), - amount: Uint128::from(50u8), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), + let msg = ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: None, }; - // Try to send withdraw liquidity with FOO token - let err = router - .execute_contract(alice_address.clone(), foo_token.clone(), &msg, &[]) - .unwrap_err(); - assert_eq!(err.root_cause().to_string(), "Unauthorized"); - // Withdraw with LP token is successful - router - .execute_contract(alice_address.clone(), lp_token.clone(), &msg, &[]) - .unwrap(); + // Try to send withdraw liquidity with uluna token let err = router .execute_contract( alice_address.clone(), pair_instance.clone(), - &ExecuteMsg::Swap { - offer_asset: Asset { - info: AssetInfo::NativeToken { - denom: "cny".to_string(), - }, - amount: Uint128::from(10u8), - }, - ask_asset_info: None, - belief_price: None, - max_spread: None, - to: None, - }, - &[Coin { - denom: "cny".to_string(), - amount: Uint128::from(10u8), - }], + &msg, + &[coin(50u128, "uluna")], ) .unwrap_err(); + assert_eq!( err.root_cause().to_string(), - "Asset mismatch between the requested and the stored asset in contract" + format!( + "Must send reserve token 'factory/contract1/{}'", + LP_SUBDENOM + ) ); + // Withdraw with LP token is successful + router + .execute_contract( + alice_address.clone(), + pair_instance.clone(), + &msg, + &[coin(50u128, lp_token.clone())], + ) + .unwrap(); + + let err = router + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &[]) + .unwrap_err(); + + assert_eq!(err.root_cause().to_string(), "No funds sent"); + // Check pair config let config: ConfigResponse = router .wrap() @@ -443,7 +441,8 @@ fn test_provide_and_withdraw_liquidity() { block_time_last: router.block_info().time.seconds(), params: Some(to_json_binary(&SaleTaxInitParams::default()).unwrap()), owner, - factory_addr: config.factory_addr + factory_addr: config.factory_addr, + tracker_addr: config.tracker_addr } ) } @@ -472,6 +471,7 @@ fn provide_liquidity_msg( slippage_tolerance: Option::from(slippage_tolerance), auto_stake: None, receiver, + min_lp_to_receive: None, }; let coins = [ @@ -488,6 +488,142 @@ fn provide_liquidity_msg( (msg, coins) } +#[test] +fn simulate_provide() { + let owner = Addr::unchecked("owner"); + let alice_address = Addr::unchecked("alice"); + + let mut router = mock_app( + owner.clone(), + vec![ + Coin { + denom: "uusd".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + ], + ); + + // Set Alice's balances + router + .send_tokens( + owner.clone(), + alice_address.clone(), + &[ + Coin { + denom: "uusd".to_string(), + amount: Uint128::new(233_000_000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::new(2_00_000_000u128), + }, + ], + ) + .unwrap(); + + let pair_instance = instantiate_pair(&mut router, &owner); + + let res: PairInfo = router + .wrap() + .query_wasm_smart(pair_instance.to_string(), &QueryMsg::Pair {}) + .unwrap(); + + let lp_token = res.liquidity_token; + + assert_eq!( + res.asset_infos, + [ + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + ], + ); + + let assets = vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::new(100_000_000), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + amount: Uint128::new(100_000_000), + }, + ]; + + // Provide liquidity + let (msg, coins) = provide_liquidity_msg( + Uint128::new(100_000_000), + Uint128::new(100_000_000), + None, + None, + ); + + let shares: Uint128 = router + .wrap() + .query_wasm_smart( + pair_instance.to_string(), + &QueryMsg::SimulateProvide { + assets: assets.clone(), + slippage_tolerance: None, + }, + ) + .unwrap(); + + router + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) + .unwrap(); + + let user_balance = router + .wrap() + .query_balance(alice_address, lp_token) + .unwrap(); + assert_eq!(shares, user_balance.amount); + + let assets = vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::new(100_000_0000u128), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + amount: Uint128::new(50_000_000000u128), + }, + ]; + + let err = router + .wrap() + .query_wasm_smart::( + pair_instance.to_string(), + &QueryMsg::SimulateProvide { + assets: assets.clone(), + slippage_tolerance: Option::from(Decimal::percent(1)), + }, + ) + .unwrap_err(); + + assert_eq!( + err, + StdError::generic_err( + "Querier contract error: Generic error: Operation exceeds max splippage tolerance" + ) + ); +} + #[test] fn test_compatibility_of_tokens_with_different_precision() { let owner = Addr::unchecked(OWNER); @@ -560,6 +696,7 @@ fn test_compatibility_of_tokens_with_different_precision() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = app @@ -650,6 +787,7 @@ fn test_compatibility_of_tokens_with_different_precision() { slippage_tolerance: None, auto_stake: None, receiver: None, + min_lp_to_receive: None, }; app.execute_contract( @@ -934,6 +1072,10 @@ fn asset_balances_tracking_works_correctly() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: Some(TrackerConfig { + code_id: store_tracker_contract(&mut app), + token_factory_addr: TOKEN_FACTORY_MODULE.to_string(), + }), }; let factory_instance = app @@ -947,21 +1089,22 @@ fn asset_balances_tracking_works_correctly() { ) .unwrap(); - // Instantiate pair without asset balances tracking + // Instantiate new pair with asset balances tracking starting from instantiation let msg = FactoryExecuteMsg::CreatePair { asset_infos: vec![ AssetInfo::NativeToken { - denom: "test1".to_string(), + denom: "uluna".to_string(), }, AssetInfo::NativeToken { - denom: "test2".to_string(), + denom: "uusd".to_string(), }, ], pair_type: PairType::Custom(env!("CARGO_PKG_NAME").to_string()), init_params: Some( to_json_binary(&SaleTaxInitParams { + track_asset_balances: true, tax_configs: TaxConfigsUnchecked::new(), - ..Default::default() + tax_config_admin: "tax_config_admin".to_string(), }) .unwrap(), ), @@ -973,10 +1116,10 @@ fn asset_balances_tracking_works_correctly() { let msg = FactoryQueryMsg::Pair { asset_infos: vec![ AssetInfo::NativeToken { - denom: "test1".to_string(), + denom: "uluna".to_string(), }, AssetInfo::NativeToken { - denom: "test2".to_string(), + denom: "uusd".to_string(), }, ], }; @@ -987,8 +1130,9 @@ fn asset_balances_tracking_works_correctly() { .unwrap(); let pair_instance = res.contract_addr; + let lp_token_address = res.liquidity_token; - // Check that asset balances are not tracked + // Check that asset balances were not tracked before instantiation // The query AssetBalanceAt returns None for this case let res: Option = app .wrap() @@ -996,7 +1140,7 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test1".to_owned(), + denom: "uluna".to_owned(), }, block_height: app.block_info().height.into(), }, @@ -1010,7 +1154,7 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test2".to_owned(), + denom: "uusd".to_owned(), }, block_height: app.block_info().height.into(), }, @@ -1018,18 +1162,7 @@ fn asset_balances_tracking_works_correctly() { .unwrap(); assert!(res.is_none()); - // Enable asset balances tracking - let msg = ExecuteMsg::UpdateConfig { - params: to_json_binary(&SaleTaxConfigUpdates { - track_asset_balances: Some(true), - ..Default::default() - }) - .unwrap(), - }; - app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) - .unwrap(); - - // Check that asset balances were not tracked before this was enabled + // Check that asset balances were not tracked before instantiation // The query AssetBalanceAt returns None for this case let res: Option = app .wrap() @@ -1037,7 +1170,7 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test1".to_owned(), + denom: "uluna".to_owned(), }, block_height: app.block_info().height.into(), }, @@ -1051,7 +1184,7 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test2".to_owned(), + denom: "uusd".to_owned(), }, block_height: app.block_info().height.into(), }, @@ -1059,7 +1192,7 @@ fn asset_balances_tracking_works_correctly() { .unwrap(); assert!(res.is_none()); - // Check that asset balances had zero balances before next block upon tracking enabing + // Check that asset balances had zero balances before next block upon instantiation app.update_block(|b| b.height += 1); let res: Option = app @@ -1068,7 +1201,7 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test1".to_owned(), + denom: "uluna".to_owned(), }, block_height: app.block_info().height.into(), }, @@ -1082,7 +1215,7 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test2".to_owned(), + denom: "uusd".to_owned(), }, block_height: app.block_info().height.into(), }, @@ -1091,40 +1224,21 @@ fn asset_balances_tracking_works_correctly() { assert!(res.unwrap().is_zero()); // Provide liquidity - let msg = ExecuteMsg::ProvideLiquidity { - assets: vec![ - Asset { - info: AssetInfo::NativeToken { - denom: "test1".to_string(), - }, - amount: Uint128::new(5_000000), - }, - Asset { - info: AssetInfo::NativeToken { - denom: "test2".to_string(), - }, - amount: Uint128::new(5_000000), - }, - ], - slippage_tolerance: None, - auto_stake: None, - receiver: None, - }; - - let send_funds = [ - Coin { - denom: "test1".to_string(), - amount: Uint128::new(5_000000), - }, - Coin { - denom: "test2".to_string(), - amount: Uint128::new(5_000000), - }, - ]; - + let (msg, send_funds) = provide_liquidity_msg( + Uint128::new(999_000000), + Uint128::new(1000_000000), + None, + None, + ); app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &send_funds) .unwrap(); + let owner_lp_balance = app + .wrap() + .query_balance(owner.to_string(), &lp_token_address) + .unwrap(); + assert_eq!(owner_lp_balance.amount, Uint128::new(999498874)); + // Check that asset balances changed after providing liqudity app.update_block(|b| b.height += 1); let res: Option = app @@ -1133,13 +1247,13 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test1".to_owned(), + denom: "uluna".to_owned(), }, block_height: app.block_info().height.into(), }, ) .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(5_000000)); + assert_eq!(res.unwrap(), Uint128::new(1000_000000)); let res: Option = app .wrap() @@ -1147,214 +1261,15 @@ fn asset_balances_tracking_works_correctly() { &pair_instance, &QueryMsg::AssetBalanceAt { asset_info: AssetInfo::NativeToken { - denom: "test2".to_owned(), + denom: "uusd".to_owned(), }, block_height: app.block_info().height.into(), }, ) .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(5_000000)); + assert_eq!(res.unwrap(), Uint128::new(999_000000)); - // Instantiate new pair with asset balances tracking starting from instantiation - let msg = FactoryExecuteMsg::CreatePair { - asset_infos: vec![ - AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - ], - pair_type: PairType::Custom(env!("CARGO_PKG_NAME").to_string()), - init_params: Some( - to_json_binary(&SaleTaxInitParams { - track_asset_balances: true, - tax_configs: TaxConfigsUnchecked::new(), - tax_config_admin: "tax_config_admin".to_string(), - }) - .unwrap(), - ), - }; - - app.execute_contract(owner.clone(), factory_instance.clone(), &msg, &[]) - .unwrap(); - - let msg = FactoryQueryMsg::Pair { - asset_infos: vec![ - AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - ], - }; - - let res: PairInfo = app - .wrap() - .query_wasm_smart(&factory_instance, &msg) - .unwrap(); - - let pair_instance = res.contract_addr; - let lp_token_address = res.liquidity_token; - - // Check that asset balances were not tracked before instantiation - // The query AssetBalanceAt returns None for this case - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.is_none()); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.is_none()); - - // Check that enabling asset balances tracking can not be done if it is already enabled - let msg = ExecuteMsg::UpdateConfig { - params: to_json_binary(&SaleTaxConfigUpdates { - track_asset_balances: Some(true), - ..Default::default() - }) - .unwrap(), - }; - assert_eq!( - app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &[]) - .unwrap_err() - .downcast_ref::() - .unwrap(), - &ContractError::AssetBalancesTrackingIsAlreadyEnabled {} - ); - - // Check that asset balances were not tracked before instantiation - // The query AssetBalanceAt returns None for this case - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.is_none()); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.is_none()); - - // Check that asset balances had zero balances before next block upon instantiation - app.update_block(|b| b.height += 1); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.unwrap().is_zero()); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert!(res.unwrap().is_zero()); - - // Provide liquidity - let (msg, send_funds) = provide_liquidity_msg( - Uint128::new(999_000000), - Uint128::new(1000_000000), - None, - None, - ); - app.execute_contract(owner.clone(), pair_instance.clone(), &msg, &send_funds) - .unwrap(); - - let msg = Cw20QueryMsg::Balance { - address: owner.to_string(), - }; - let owner_lp_balance: BalanceResponse = app - .wrap() - .query_wasm_smart(&lp_token_address, &msg) - .unwrap(); - assert_eq!(owner_lp_balance.balance, Uint128::new(999498874)); - - // Check that asset balances changed after providing liqudity - app.update_block(|b| b.height += 1); - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uluna".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(1000_000000)); - - let res: Option = app - .wrap() - .query_wasm_smart( - &pair_instance, - &QueryMsg::AssetBalanceAt { - asset_info: AssetInfo::NativeToken { - denom: "uusd".to_owned(), - }, - block_height: app.block_info().height.into(), - }, - ) - .unwrap(); - assert_eq!(res.unwrap(), Uint128::new(999_000000)); - - // Swap + // Swap let msg = ExecuteMsg::Swap { offer_asset: Asset { @@ -1406,14 +1321,16 @@ fn asset_balances_tracking_works_correctly() { assert_eq!(res.unwrap(), Uint128::new(1000_000000)); // Withdraw liqudity - let msg = Cw20ExecuteMsg::Send { - contract: pair_instance.to_string(), - amount: Uint128::new(500_000000), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { assets: vec![] }).unwrap(), - }; - - app.execute_contract(owner.clone(), lp_token_address, &msg, &[]) - .unwrap(); + app.execute_contract( + owner.clone(), + pair_instance.clone(), + &ExecuteMsg::WithdrawLiquidity { + assets: vec![], + min_assets_to_receive: None, + }, + &[coin(500_000000u128, lp_token_address)], + ) + .unwrap(); // Check that asset balances changed after withdrawing app.update_block(|b| b.height += 1); @@ -1476,6 +1393,10 @@ fn update_pair_config() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: Some(TrackerConfig { + code_id: store_tracker_contract(&mut router), + token_factory_addr: TOKEN_FACTORY_MODULE.to_string(), + }), }; let factory_instance = router @@ -1525,47 +1446,8 @@ fn update_pair_config() { block_time_last: 0, params: Some(to_json_binary(&SaleTaxInitParams::default()).unwrap()), owner: Addr::unchecked("owner"), - factory_addr: Addr::unchecked("contract0") - } - ); - - let msg = ExecuteMsg::UpdateConfig { - params: to_json_binary(&SaleTaxConfigUpdates { - track_asset_balances: Some(true), - ..Default::default() - }) - .unwrap(), - }; - assert_eq!( - router - .execute_contract(Addr::unchecked("not_owner"), pair.clone(), &msg, &[]) - .unwrap_err() - .downcast_ref::() - .unwrap(), - &ContractError::Unauthorized {} - ); - - router - .execute_contract(owner, pair.clone(), &msg, &[]) - .unwrap(); - - let res: ConfigResponse = router - .wrap() - .query_wasm_smart(pair, &QueryMsg::Config {}) - .unwrap(); - assert_eq!( - res, - ConfigResponse { - block_time_last: 0, - params: Some( - to_json_binary(&SaleTaxInitParams { - track_asset_balances: true, - ..Default::default() - }) - .unwrap() - ), - owner: Addr::unchecked("owner"), - factory_addr: Addr::unchecked("contract0") + factory_addr: Addr::unchecked("contract0"), + tracker_addr: None } ); } @@ -1602,6 +1484,7 @@ fn update_tax_configs() { owner: owner.to_string(), whitelist_code_id: 234u64, coin_registry_address: "coin_registry".to_string(), + tracker_config: None, }; let factory_instance = router @@ -1651,7 +1534,8 @@ fn update_tax_configs() { block_time_last: 0, params: Some(to_json_binary(&SaleTaxInitParams::default()).unwrap()), owner: Addr::unchecked("owner"), - factory_addr: Addr::unchecked("contract0") + factory_addr: Addr::unchecked("contract0"), + tracker_addr: None } ); @@ -1692,7 +1576,8 @@ fn update_tax_configs() { .unwrap() ), owner: Addr::unchecked("owner"), - factory_addr: Addr::unchecked("contract0") + factory_addr: Addr::unchecked("contract0"), + tracker_addr: None } ); @@ -1742,13 +1627,14 @@ fn update_tax_configs() { .unwrap() ), owner: Addr::unchecked("owner"), - factory_addr: Addr::unchecked("contract0") + factory_addr: Addr::unchecked("contract0"), + tracker_addr: None } ); } #[test] -fn test_imbalanced_withdraw_is_disabled() { +fn provide_liquidity_with_autostaking_to_generator() { let owner = Addr::unchecked("owner"); let alice_address = Addr::unchecked("alice"); let mut router = mock_app( @@ -1762,6 +1648,10 @@ fn test_imbalanced_withdraw_is_disabled() { denom: "uluna".to_string(), amount: Uint128::new(100_000_000_000u128), }, + Coin { + denom: "cny".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, ], ); @@ -1779,41 +1669,448 @@ fn test_imbalanced_withdraw_is_disabled() { denom: "uluna".to_string(), amount: Uint128::new(2_00_000_000u128), }, + Coin { + denom: "cny".to_string(), + amount: Uint128::from(100_000_000u128), + }, ], ) .unwrap(); - // Init pair - let pair_instance = instantiate_pair(&mut router, &owner); - - let res: PairInfo = router - .wrap() - .query_wasm_smart(pair_instance.to_string(), &QueryMsg::Pair {}) - .unwrap(); - let lp_token = res.liquidity_token; + let token_contract_code_id = store_token_code(&mut router); - assert_eq!( - res.asset_infos, - [ - AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - AssetInfo::NativeToken { - denom: "uluna".to_string(), - }, - ], - ); + let pair_contract_code_id = store_pair_code(&mut router); + let factory_code_id = store_factory_code(&mut router); - // Provide liquidity - let (msg, coins) = provide_liquidity_msg( - Uint128::new(100_000_000), - Uint128::new(100_000_000), - None, - None, - ); - router - .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) - .unwrap(); + let generator_code_id = store_generator_code(&mut router); + + let init_msg = FactoryInstantiateMsg { + fee_address: None, + pair_configs: vec![PairConfig { + code_id: pair_contract_code_id, + maker_fee_bps: 0, + pair_type: PairType::Custom(env!("CARGO_PKG_NAME").to_string()), + total_fee_bps: 0, + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }], + token_code_id: token_contract_code_id, + generator_address: None, + owner: owner.to_string(), + whitelist_code_id: 234u64, + coin_registry_address: "coin_registry".to_string(), + tracker_config: Some(TrackerConfig { + code_id: store_tracker_contract(&mut router), + token_factory_addr: TOKEN_FACTORY_MODULE.to_string(), + }), + }; + + let factory_instance = router + .instantiate_contract( + factory_code_id, + owner.clone(), + &init_msg, + &[], + "FACTORY", + None, + ) + .unwrap(); + + let generator_instance = router + .instantiate_contract( + generator_code_id, + owner.clone(), + &astroport::incentives::InstantiateMsg { + astro_token: native_asset_info("astro".to_string()), + factory: factory_instance.to_string(), + owner: owner.to_string(), + guardian: None, + incentivization_fee_info: None, + vesting_contract: "vesting".to_string(), + }, + &[], + "generator", + None, + ) + .unwrap(); + + router + .execute_contract( + owner.clone(), + factory_instance.clone(), + &astroport::factory::ExecuteMsg::UpdateConfig { + token_code_id: None, + fee_address: None, + generator_address: Some(generator_instance.to_string()), + whitelist_code_id: None, + coin_registry_address: None, + }, + &[], + ) + .unwrap(); + + let msg = FactoryExecuteMsg::CreatePair { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + ], + pair_type: PairType::Custom(env!("CARGO_PKG_NAME").to_string()), + init_params: Some( + to_json_binary(&SaleTaxInitParams { + track_asset_balances: true, + tax_configs: TaxConfigsUnchecked::new(), + tax_config_admin: "tax_config_admin".to_string(), + }) + .unwrap(), + ), + }; + + router + .execute_contract(owner.clone(), factory_instance.clone(), &msg, &[]) + .unwrap(); + + let uusd_amount = Uint128::new(100_000_000); + let uluna_amount = Uint128::new(100_000_000); + + let msg = ExecuteMsg::ProvideLiquidity { + assets: vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: uusd_amount.clone(), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + amount: uluna_amount.clone(), + }, + ], + slippage_tolerance: None, + auto_stake: Some(true), + receiver: None, + min_lp_to_receive: None, + }; + + let coins = [ + Coin { + denom: "uluna".to_string(), + amount: uluna_amount.clone(), + }, + Coin { + denom: "uusd".to_string(), + amount: uusd_amount.clone(), + }, + ]; + + let res: PairInfo = router + .wrap() + .query_wasm_smart( + &factory_instance, + &FactoryQueryMsg::Pair { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + ], + }, + ) + .unwrap(); + + let pair_instance = res.contract_addr; + let lp_token_address = res.liquidity_token; + + router + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) + .unwrap(); + + let amount: Uint128 = router + .wrap() + .query_wasm_smart( + generator_instance.to_string(), + &astroport::incentives::QueryMsg::Deposit { + lp_token: lp_token_address.to_string(), + user: alice_address.to_string(), + }, + ) + .unwrap(); + + assert_eq!(amount, Uint128::new(99999000)); +} + +#[test] +fn test_tracker_contract() { + let owner = Addr::unchecked("owner"); + let alice = Addr::unchecked("alice"); + let mut router = mock_app( + owner.clone(), + vec![ + Coin { + denom: "uusd".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + ], + ); + + let pair_contract_code_id = store_pair_code(&mut router); + let factory_code_id = store_factory_code(&mut router); + + let init_msg = FactoryInstantiateMsg { + fee_address: None, + pair_configs: vec![PairConfig { + code_id: pair_contract_code_id, + maker_fee_bps: 0, + pair_type: PairType::Custom(env!("CARGO_PKG_NAME").to_string()), + total_fee_bps: 0, + is_disabled: false, + is_generator_disabled: false, + permissioned: false, + }], + token_code_id: 0, + generator_address: None, + owner: owner.to_string(), + whitelist_code_id: 234u64, + coin_registry_address: "coin_registry".to_string(), + tracker_config: Some(TrackerConfig { + code_id: store_tracker_contract(&mut router), + token_factory_addr: TOKEN_FACTORY_MODULE.to_string(), + }), + }; + + let factory_instance = router + .instantiate_contract( + factory_code_id, + owner.clone(), + &init_msg, + &[], + "FACTORY", + None, + ) + .unwrap(); + + let msg = FactoryExecuteMsg::CreatePair { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + ], + pair_type: PairType::Custom(env!("CARGO_PKG_NAME").to_string()), + init_params: Some( + to_json_binary(&SaleTaxInitParams { + track_asset_balances: true, + tax_configs: TaxConfigsUnchecked::new(), + tax_config_admin: "tax_config_admin".to_string(), + }) + .unwrap(), + ), + }; + + router + .execute_contract(owner.clone(), factory_instance.clone(), &msg, &[]) + .unwrap(); + + let uusd_amount = Uint128::new(100_000_000); + let uluna_amount = Uint128::new(100_000_000); + + let msg = ExecuteMsg::ProvideLiquidity { + assets: vec![ + Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: uusd_amount.clone(), + }, + Asset { + info: AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + amount: uluna_amount.clone(), + }, + ], + slippage_tolerance: None, + auto_stake: None, + receiver: None, + min_lp_to_receive: None, + }; + + let coins = [ + Coin { + denom: "uluna".to_string(), + amount: uluna_amount.clone(), + }, + Coin { + denom: "uusd".to_string(), + amount: uusd_amount.clone(), + }, + ]; + + let res: PairInfo = router + .wrap() + .query_wasm_smart( + &factory_instance, + &FactoryQueryMsg::Pair { + asset_infos: vec![ + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + ], + }, + ) + .unwrap(); + + let pair_instance = res.contract_addr; + let lp_token = res.liquidity_token; + + router + .execute_contract(owner.clone(), pair_instance.clone(), &msg, &coins) + .unwrap(); + + let owner_lp_funds = router + .wrap() + .query_balance(owner.clone(), lp_token.clone()) + .unwrap(); + + let total_supply = owner_lp_funds.amount + MINIMUM_LIQUIDITY_AMOUNT; + + // Set Alice's balances + router + .send_tokens( + owner.clone(), + alice.clone(), + &[Coin { + denom: lp_token.to_string(), + amount: Uint128::new(100), + }], + ) + .unwrap(); + + let config: ConfigResponse = router + .wrap() + .query_wasm_smart(pair_instance.clone(), &QueryMsg::Config {}) + .unwrap(); + + let tracker_addr = config.tracker_addr.unwrap(); + + let tracker_config: TrackerConfigResponse = router + .wrap() + .query_wasm_smart(tracker_addr.clone(), &TrackerQueryMsg::Config {}) + .unwrap(); + assert_eq!( + tracker_config.token_factory_module, + TOKEN_FACTORY_MODULE.to_string() + ); + assert_eq!(tracker_config.tracked_denom, lp_token.to_string()); + + let tracker_total_supply: Uint128 = router + .wrap() + .query_wasm_smart( + tracker_addr.clone(), + &TrackerQueryMsg::TotalSupplyAt { timestamp: None }, + ) + .unwrap(); + + assert_eq!(total_supply, tracker_total_supply); + + let alice_balance: Uint128 = router + .wrap() + .query_wasm_smart( + tracker_addr, + &TrackerQueryMsg::BalanceAt { + address: alice.to_string(), + timestamp: None, + }, + ) + .unwrap(); + + assert_eq!(alice_balance, Uint128::new(100)); +} + +#[test] +fn test_imbalanced_withdraw_is_disabled() { + let owner = Addr::unchecked("owner"); + let alice_address = Addr::unchecked("alice"); + let mut router = mock_app( + owner.clone(), + vec![ + Coin { + denom: "uusd".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::new(100_000_000_000u128), + }, + ], + ); + + // Set Alice's balances + router + .send_tokens( + owner.clone(), + alice_address.clone(), + &[ + Coin { + denom: "uusd".to_string(), + amount: Uint128::new(233_000_000u128), + }, + Coin { + denom: "uluna".to_string(), + amount: Uint128::new(2_00_000_000u128), + }, + ], + ) + .unwrap(); + + // Init pair + let pair_instance = instantiate_pair(&mut router, &owner); + + let res: PairInfo = router + .wrap() + .query_wasm_smart(pair_instance.to_string(), &QueryMsg::Pair {}) + .unwrap(); + let lp_token = res.liquidity_token; + + assert_eq!( + res.asset_infos, + [ + AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + AssetInfo::NativeToken { + denom: "uluna".to_string(), + }, + ], + ); + + // Provide liquidity + let (msg, coins) = provide_liquidity_msg( + Uint128::new(100_000_000), + Uint128::new(100_000_000), + None, + None, + ); + router + .execute_contract(alice_address.clone(), pair_instance.clone(), &msg, &coins) + .unwrap(); // Provide liquidity for receiver let (msg, coins) = provide_liquidity_msg( @@ -1827,22 +2124,23 @@ fn test_imbalanced_withdraw_is_disabled() { .unwrap(); // Check that imbalanced withdraw is currently disabled - let msg_imbalance = Cw20ExecuteMsg::Send { - contract: pair_instance.to_string(), - amount: Uint128::from(50u8), - msg: to_json_binary(&Cw20HookMsg::WithdrawLiquidity { - assets: vec![Asset { - info: AssetInfo::NativeToken { - denom: "uusd".to_string(), - }, - amount: Uint128::from(100u8), - }], - }) - .unwrap(), + let msg_imbalance = ExecuteMsg::WithdrawLiquidity { + assets: vec![Asset { + info: AssetInfo::NativeToken { + denom: "uusd".to_string(), + }, + amount: Uint128::from(100u8), + }], + min_assets_to_receive: None, }; let err = router - .execute_contract(alice_address.clone(), lp_token.clone(), &msg_imbalance, &[]) + .execute_contract( + alice_address.clone(), + pair_instance.clone(), + &msg_imbalance, + &[coin(100u128, lp_token)], + ) .unwrap_err(); assert_eq!( err.root_cause().to_string(), @@ -1850,6 +2148,7 @@ fn test_imbalanced_withdraw_is_disabled() { ); } +#[ignore] #[test_case("1.3.1"; "v1.3.1")] #[test_case("1.5.0"; "v1.5.0")] fn test_migrate_from_standard_xyk(old_version: &str) { @@ -1899,7 +2198,8 @@ fn test_migrate_from_standard_xyk(old_version: &str) { block_time_last: 0, params: Some(to_json_binary(&SaleTaxInitParams::default()).unwrap()), owner, - factory_addr: config.factory_addr + factory_addr: config.factory_addr, + tracker_addr: None } ) } diff --git a/contracts/periphery/astro_converter/Cargo.toml b/contracts/periphery/astro_converter/Cargo.toml index 3eb917246..c884b5e28 100644 --- a/contracts/periphery/astro_converter/Cargo.toml +++ b/contracts/periphery/astro_converter/Cargo.toml @@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"] library = [] [dependencies] -astroport = { path = "../../../packages/astroport", version = "4" } +astroport = "4" cosmwasm-std = { workspace = true, features = ["stargate"] } cosmwasm-schema.workspace = true cw-storage-plus.workspace = true diff --git a/contracts/periphery/astro_converter_neutron/Cargo.toml b/contracts/periphery/astro_converter_neutron/Cargo.toml index cd713e8e4..5fcff7821 100644 --- a/contracts/periphery/astro_converter_neutron/Cargo.toml +++ b/contracts/periphery/astro_converter_neutron/Cargo.toml @@ -11,7 +11,7 @@ library = [] [dependencies] neutron-sdk = "0.8.0" -astroport = { path = "../../../packages/astroport", version = "4" } +astroport = "4" astro-token-converter = { path = "../astro_converter", version = "1.0", features = ["library"] } cosmwasm-std = "1.5" cw2 = "1.1" diff --git a/contracts/periphery/liquidity_manager/Cargo.toml b/contracts/periphery/liquidity_manager/Cargo.toml deleted file mode 100644 index 6376b5350..000000000 --- a/contracts/periphery/liquidity_manager/Cargo.toml +++ /dev/null @@ -1,38 +0,0 @@ -[package] -name = "astroport-liquidity-manager" -version = "1.2.0" -edition = "2021" -description = "Astroport Liquidity Manager contract" -license = "GPL-3.0-only" -repository = "https://github.com/astroport-fi/astroport" -homepage = "https://astroport.fi" - -[features] -library = [] - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -cosmwasm-std.workspace = true -cosmwasm-schema.workspace = true -cw-storage-plus.workspace = true -cw20 = "1.1" -thiserror.workspace = true -itertools.workspace = true -astroport = { path = "../../../packages/astroport", version = "4" } -cw20-base = { version = "1.1", features = ["library"] } -astroport-pair = { path = "../../pair", features = ["library"], version = "1.5" } -astroport-pair-stable = { path = "../../pair_stable", features = ["library"], version = "3" } -astroport-pair-concentrated = { path = "../../pair_concentrated", features = ["library"], version = "3" } -astroport-pcl-common = { path = "../../../packages/astroport_pcl_common", version = "2" } -astroport-factory = { path = "../../factory", features = ["library"], version = "1" } - -[dev-dependencies] -cw-multi-test = "1.0.0" -astroport-native-coin-registry = { path = "../../periphery/native_coin_registry" } -astroport-incentives = { path = "../../tokenomics/incentives", version = "1" } -serde_json = "1.0.96" -anyhow = "1" -derivative = "2.2" -itertools.workspace = true diff --git a/contracts/periphery/liquidity_manager/README.md b/contracts/periphery/liquidity_manager/README.md deleted file mode 100644 index a7f212fb0..000000000 --- a/contracts/periphery/liquidity_manager/README.md +++ /dev/null @@ -1,159 +0,0 @@ -# Astroport Liquidity Manager - -The Astroport Liquidity Manager is a contract that allows users to provide and withdraw liquidity from the -Astroport xyk and stable pools with additional slippage limit enforcement. This contract is meant to be non-upgradable and -standalone. It depends only on the actual Astroport factory address. Liquidity Manager also exposes provide/withdraw simulation queries -for xyk and stable pools. - ---- - -## InstantiateMsg - -Initializes the contract with the Astroport factory contract address. - -```json -{ - "astroport_factory": "wasm1..." -} -``` - -## ExecuteMsg - -### `receive` - -CW20 receive msg. Handles only withdraw messages which should come from Astroport LP tokens. - -```json -{ - "receive": { - "sender": "wasm...", - "amount": "123", - "msg": "" - } -} -``` - -where is a base64 encoded json string of the following format: - -```json -{ - "withdraw_liquidity": { - "pair_msg": { - "withdraw_liquidity": {} - }, - "min_assets_to_receive": [ - { - "info": { - "native_token": { - "denom": "uusd" - } - }, - "amount": "100000" - }, - { - "info": { - "token": { - "contract_addr": "wasm1...cw20address" - } - }, - "amount": "100000" - } - ] - } -} -``` - -`min_assets_to_receive` enforces after-withdraw check that the user receives at least the specified amount of assets. - -### `provide_liquidity` - -Provides liquidity through Liquidity Manager with slippage limit enforcement. Handles XYK pair imbalanced provide and -returns excess assets to the user. - -```json -{ - "provide_liquidity": { - "pair_addr": "wasm1...", - "pair_msg": { - "provide_liquidity": { - "assets": [ - { - "info": { - "native_token": { - "denom": "uusd" - } - }, - "amount": "100000" - }, - { - "info": { - "token": { - "contract_addr": "wasm1...cw20address" - } - }, - "amount": "100000" - } - ], - "slippage_tolerance": "0.02", - "auto_stake": true, - "receiver": "wasm1...addr" - } - }, - "min_lp_to_receive": "1000" - } -} -``` - -`pair_msg` is equal to original Astroport provide message for all pools. `min_lp_to_receive` enforces after-provide check that the user receives at least the specified amount of LP tokens. - -## QueryMsg - -### `simulate` - -Simulates liquidity provide or withdraw. - -Provide simulation example: - -```json -{ - "simulate_provide": { - "pair_addr": "wasm1...addr", - "pair_msg": { - "provide_liquidity": { - "assets": [ - { - "info": { - "native_token": { - "denom": "uusd" - } - }, - "amount": "100000" - }, - { - "info": { - "token": { - "contract_addr": "wasm1...cw20address" - } - }, - "amount": "100000" - } - ], - "slippage_tolerance": "0.02", - "auto_stake": true, - "receiver": "wasm1...addr" - } - } - } -} -``` - -Withdraw simulation example: - -```json -{ - "simulate_withdraw": { - "pair_addr": "wasm1...addr", - "lp_tokens": "1000" - } -} -``` diff --git a/contracts/periphery/liquidity_manager/examples/liquidity_manager_schema.rs b/contracts/periphery/liquidity_manager/examples/liquidity_manager_schema.rs deleted file mode 100644 index 91b01be4b..000000000 --- a/contracts/periphery/liquidity_manager/examples/liquidity_manager_schema.rs +++ /dev/null @@ -1,11 +0,0 @@ -use cosmwasm_schema::write_api; - -use astroport::liquidity_manager::{ExecuteMsg, InstantiateMsg, QueryMsg}; - -fn main() { - write_api! { - instantiate: InstantiateMsg, - query: QueryMsg, - execute: ExecuteMsg, - } -} diff --git a/contracts/periphery/liquidity_manager/src/contract.rs b/contracts/periphery/liquidity_manager/src/contract.rs deleted file mode 100644 index 9e2c64baa..000000000 --- a/contracts/periphery/liquidity_manager/src/contract.rs +++ /dev/null @@ -1,396 +0,0 @@ -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{ - from_json, to_json_binary, wasm_execute, Addr, DepsMut, Env, MessageInfo, Reply, ReplyOn, - Response, StdError, StdResult, SubMsg, Uint128, -}; -use cw20::{Cw20ExecuteMsg, Expiration}; - -use astroport::asset::{addr_opt_validate, Asset, AssetInfo, AssetInfoExt, PairInfo}; -use astroport::factory::PairType; -use astroport::liquidity_manager::{Cw20HookMsg, ExecuteMsg, InstantiateMsg}; -use astroport::pair::{ - Cw20HookMsg as PairCw20HookMsg, ExecuteMsg as PairExecuteMsg, QueryMsg as PairQueryMsg, - QueryMsg, -}; -use astroport::querier::query_supply; -use astroport_pair::contract::get_share_in_assets; - -use crate::error::ContractError; -use crate::state::{ActionParams, Config, ReplyData, CONFIG, REPLY_DATA}; -use crate::utils::{query_cw20_minter, query_lp_amount, xyk_provide_simulation}; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn instantiate( - deps: DepsMut, - _env: Env, - _info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - CONFIG.save( - deps.storage, - &Config { - factory_addr: deps.api.addr_validate(&msg.astroport_factory)?, - }, - )?; - - Ok(Response::default() - .add_attribute("action", "instantiate") - .add_attribute("contract", "liquidity_manager")) -} - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - msg: ExecuteMsg, -) -> Result { - match msg { - ExecuteMsg::ProvideLiquidity { - pair_addr, - pair_msg: msg, - min_lp_to_receive, - } => { - let pair_addr = deps.api.addr_validate(&pair_addr)?; - provide_liquidity(deps, info, env, pair_addr, msg, min_lp_to_receive) - } - ExecuteMsg::Receive(cw20_msg) => match from_json(&cw20_msg.msg)? { - Cw20HookMsg::WithdrawLiquidity { - pair_msg: msg, - min_assets_to_receive, - } if matches!(&msg, PairCw20HookMsg::WithdrawLiquidity { .. }) => withdraw_liquidity( - deps, - info.sender, - Addr::unchecked(cw20_msg.sender), - cw20_msg.amount, - msg, - min_assets_to_receive, - ), - _ => Err(ContractError::UnsupportedCw20HookMsg {}), - }, - } -} - -const WITHDRAW_LIQUIDITY_REPLY_ID: u64 = 1; -const PROVIDE_LIQUIDITY_REPLY_ID: u64 = 2; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> Result { - match msg.id { - WITHDRAW_LIQUIDITY_REPLY_ID => match REPLY_DATA.load(deps.storage)? { - ReplyData { - receiver, - params: - ActionParams::Withdraw { - pair_addr, - min_assets_to_receive, - }, - } => { - let pair_info: PairInfo = deps - .querier - .query_wasm_smart(pair_addr, &QueryMsg::Pair {})?; - let mut withdrawn_assets = - pair_info.query_pools(&deps.querier, env.contract.address)?; - if withdrawn_assets[0].info.ne(&min_assets_to_receive[0].info) { - withdrawn_assets.swap(0, 1); - } - - let messages = withdrawn_assets - .into_iter() - .zip(min_assets_to_receive.iter()) - .map(|(withdrawn, min_to_receive)| { - if withdrawn.amount < min_to_receive.amount { - Err(ContractError::WithdrawSlippageViolation { - asset_name: withdrawn.info.to_string(), - received: withdrawn.amount, - expected: min_to_receive.amount, - }) - } else { - Ok(withdrawn.into_msg(&receiver)?) - } - }) - .collect::, ContractError>>()?; - - Ok(Response::new() - .add_messages(messages) - .add_attribute("liquidity_manager", "withdraw_check_passed")) - } - _ => Err(ContractError::InvalidReplyData {}), - }, - PROVIDE_LIQUIDITY_REPLY_ID => match REPLY_DATA.load(deps.storage)? { - ReplyData { - receiver, - params: - ActionParams::Provide { - lp_token_addr, - lp_amount_before, - min_lp_to_receive, - staked_in_generator, - }, - } => { - let factory_addr = CONFIG.load(deps.storage)?.factory_addr; - let lp_amount_after = query_lp_amount( - deps.querier, - lp_token_addr, - factory_addr, - staked_in_generator, - &receiver, - )?; - - // allowing 1 to absorb rounding errors - if lp_amount_after - lp_amount_before < min_lp_to_receive { - Err(ContractError::ProvideSlippageViolation( - lp_amount_after - lp_amount_before, - min_lp_to_receive, - )) - } else { - Ok(Response::new().add_attribute("liquidity_manager", "provide_check_passed")) - } - } - _ => Err(ContractError::InvalidReplyData {}), - }, - _ => Err(StdError::generic_err(format!("Unsupported reply id {}", msg.id)).into()), - } -} - -fn provide_liquidity( - deps: DepsMut, - info: MessageInfo, - env: Env, - pair_addr: Addr, - exec_msg: PairExecuteMsg, - min_lp_to_receive: Option, -) -> Result { - if let PairExecuteMsg::ProvideLiquidity { - mut assets, - slippage_tolerance, - auto_stake, - receiver, - } = exec_msg - { - if assets.len() != 2 { - return Err(ContractError::WrongPoolLength {}); - } - - let pair_info: PairInfo = deps - .querier - .query_wasm_smart(&pair_addr, &PairQueryMsg::Pair {})?; - let mut funds = info.funds; - let mut submessages = match &pair_info.pair_type { - // Xyk pair has unfair minting policy; Returning excess assets back to user - PairType::Xyk {} => { - let pools = pair_info.query_pools(&deps.querier, &pair_addr)?; - // Initial provide is always fair because initial LP dictates the price - if !pools[0].amount.is_zero() && !pools[1].amount.is_zero() { - let predicted_lp_amount = xyk_provide_simulation( - deps.querier, - &pools, - &pair_info, - slippage_tolerance, - assets.clone(), - )?; - - if pools[0].info.ne(&assets[0].info) { - assets.swap(0, 1); - } - - // Add user's deposits - let pools = pools - .into_iter() - .zip(assets.iter()) - .map(|(mut pool, asset)| { - pool.amount += asset.amount; - pool - }) - .collect::>(); - let total_share = query_supply(&deps.querier, &pair_info.liquidity_token)?; - let share = get_share_in_assets( - &pools, - predicted_lp_amount, - total_share + predicted_lp_amount, - ); - - assets - .iter_mut() - .zip(share.iter()) - .filter_map(|(asset, share_asset)| { - let maybe_repay = match &asset.info { - AssetInfo::Token { .. } => None, - AssetInfo::NativeToken { denom } => { - let excess_coins = - asset.amount.saturating_sub(share_asset.amount); - - // `xyk_provide_simulation` guarantees that native asset is present in funds - funds - .iter_mut() - .find(|c| &c.denom == denom) - .map(|c| { - c.amount = share_asset.amount; - }) - .unwrap(); - - if !excess_coins.is_zero() { - Some( - asset - .info - .with_balance(excess_coins) - .into_msg(&info.sender) - .map(SubMsg::new), - ) - } else { - None - } - } - }; - - asset.amount = share_asset.amount; - - maybe_repay - }) - .collect::>()? - } else { - vec![] - } - } - _ => vec![], - }; - - // pull cw20 tokens and increase allowance for pair contract - let allowance_submessages = assets - .iter() - .filter_map(|asset| match &asset.info { - AssetInfo::Token { contract_addr } if !asset.amount.is_zero() => { - let transfer_from_msg = wasm_execute( - contract_addr, - &Cw20ExecuteMsg::TransferFrom { - owner: info.sender.to_string(), - recipient: env.contract.address.to_string(), - amount: asset.amount, - }, - vec![], - ); - let increase_allowance_msg = wasm_execute( - contract_addr, - &Cw20ExecuteMsg::IncreaseAllowance { - spender: pair_addr.to_string(), - amount: asset.amount, - expires: Some(Expiration::AtHeight(env.block.height + 1)), - }, - vec![], - ); - Some([transfer_from_msg, increase_allowance_msg]) - } - _ => None, - }) - .flatten() - .map(|v| v.map(SubMsg::new)) - .collect::>>()?; - - submessages.extend(allowance_submessages); - - let receiver = addr_opt_validate(deps.api, &receiver)?.unwrap_or(info.sender); - let tweaked_exec_msg = PairExecuteMsg::ProvideLiquidity { - assets, - slippage_tolerance, - auto_stake, - receiver: Some(receiver.to_string()), - }; - let mut provide_msg = SubMsg::new(wasm_execute(&pair_addr, &tweaked_exec_msg, funds)?); - - if let Some(min_lp_to_receive) = min_lp_to_receive { - let staked_in_generator = auto_stake.unwrap_or(false); - let config = CONFIG.load(deps.storage)?; - let lp_amount_before = query_lp_amount( - deps.querier, - pair_info.liquidity_token.to_string(), - config.factory_addr, - staked_in_generator, - &receiver.to_string(), - )?; - - REPLY_DATA.save( - deps.storage, - &ReplyData { - receiver: receiver.to_string(), - params: ActionParams::Provide { - lp_token_addr: pair_info.liquidity_token.to_string(), - lp_amount_before, - min_lp_to_receive, - staked_in_generator, - }, - }, - )?; - provide_msg.reply_on = ReplyOn::Success; - provide_msg.id = PROVIDE_LIQUIDITY_REPLY_ID; - } else { - // no need to reply as user decided not to enforce minimum LP token amount check - } - submessages.push(provide_msg); - - Ok(Response::new() - .add_submessages(submessages) - .add_attribute("contract", "liquidity_manager") - .add_attribute("action", "provide_liquidity")) - } else { - Err(ContractError::UnsupportedExecuteMsg {}) - } -} - -fn withdraw_liquidity( - deps: DepsMut, - lp_token_addr: Addr, - receiver: Addr, - amount: Uint128, - inner_msg: PairCw20HookMsg, - min_assets_to_receive: Vec, -) -> Result { - let pair_addr = query_cw20_minter(deps.querier, lp_token_addr.clone())?; - let pair_info: PairInfo = deps - .querier - .query_wasm_smart(&pair_addr, &QueryMsg::Pair {})?; - - if pair_info.asset_infos.len() != 2 { - return Err(ContractError::WrongPoolLength {}); - } - - if pair_info.asset_infos.len() != min_assets_to_receive.len() { - return Err(ContractError::WrongAssetLength { - expected: pair_info.asset_infos.len(), - actual: min_assets_to_receive.len(), - }); - } - // Check `min_assets_to_receive` belong to the pair - for asset in &min_assets_to_receive { - if !pair_info.asset_infos.contains(&asset.info) { - return Err(ContractError::AssetNotInPair(asset.info.to_string())); - } - } - - let withdraw_msg = wasm_execute( - lp_token_addr, - &Cw20ExecuteMsg::Send { - contract: pair_addr.to_string(), - amount, - msg: to_json_binary(&inner_msg)?, - }, - vec![], - )?; - let msg_with_reply = SubMsg::reply_on_success(withdraw_msg, WITHDRAW_LIQUIDITY_REPLY_ID); - - REPLY_DATA.save( - deps.storage, - &ReplyData { - receiver: receiver.to_string(), - params: ActionParams::Withdraw { - pair_addr, - min_assets_to_receive, - }, - }, - )?; - - Ok(Response::new() - .add_submessage(msg_with_reply) - .add_attribute("contract", "liquidity_manager") - .add_attribute("action", "withdraw_liquidity")) -} diff --git a/contracts/periphery/liquidity_manager/src/error.rs b/contracts/periphery/liquidity_manager/src/error.rs deleted file mode 100644 index 4e8ae698d..000000000 --- a/contracts/periphery/liquidity_manager/src/error.rs +++ /dev/null @@ -1,41 +0,0 @@ -use astroport_pair::error::ContractError as PairContractError; -use cosmwasm_std::{StdError, Uint128}; - -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum ContractError { - #[error("{0}")] - Std(#[from] StdError), - - #[error("{0}")] - XykPairError(#[from] PairContractError), - - #[error("Unsupported Cw20 hook message")] - UnsupportedCw20HookMsg {}, - - #[error("Unsupported execute message")] - UnsupportedExecuteMsg {}, - - #[error("Invalid reply data")] - InvalidReplyData {}, - - #[error("Slippage is more than expected: received {0}, expected {1} LP tokens")] - ProvideSlippageViolation(Uint128, Uint128), - - #[error("Received {received} {asset_name} but expected {expected}")] - WithdrawSlippageViolation { - asset_name: String, - received: Uint128, - expected: Uint128, - }, - - #[error("Asset {0} is not in the pair")] - AssetNotInPair(String), - - #[error("Wrong asset length: expected {expected}, actual {actual}")] - WrongAssetLength { expected: usize, actual: usize }, - - #[error("Liquidity manager supports only pools with 2 assets")] - WrongPoolLength {}, -} diff --git a/contracts/periphery/liquidity_manager/src/lib.rs b/contracts/periphery/liquidity_manager/src/lib.rs deleted file mode 100644 index 20cb122c6..000000000 --- a/contracts/periphery/liquidity_manager/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod contract; -pub mod error; -pub mod query; -pub mod state; -pub mod utils; diff --git a/contracts/periphery/liquidity_manager/src/query.rs b/contracts/periphery/liquidity_manager/src/query.rs deleted file mode 100644 index 0be7d5939..000000000 --- a/contracts/periphery/liquidity_manager/src/query.rs +++ /dev/null @@ -1,284 +0,0 @@ -use std::collections::HashMap; - -#[cfg(not(feature = "library"))] -use cosmwasm_std::entry_point; -use cosmwasm_std::{to_json_binary, Binary, Deps, Env, StdError, StdResult, Uint128}; - -use astroport::asset::{Asset, PairInfo}; -use astroport::factory::PairType; -use astroport::liquidity_manager::QueryMsg; -use astroport::pair::{ExecuteMsg as PairExecuteMsg, QueryMsg as PairQueryMsg}; -use astroport::querier::query_supply; -use astroport_pair::contract::get_share_in_assets; -use astroport_pcl_common::state::Precisions; - -use crate::error::ContractError; -use crate::utils::{ - convert_pcl_config, convert_stable_config, pcl_provide_simulation, - stableswap_provide_simulation, xyk_provide_simulation, -}; - -#[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::SimulateProvide { - pair_addr, - pair_msg, - } => simulate_provide(deps, env, pair_addr, pair_msg), - QueryMsg::SimulateWithdraw { - pair_addr, - lp_tokens, - } => simulate_withdraw(deps, pair_addr, lp_tokens), - } -} - -fn simulate_provide( - deps: Deps, - env: Env, - pair_addr: String, - msg: PairExecuteMsg, -) -> StdResult { - match msg { - PairExecuteMsg::ProvideLiquidity { - mut assets, - slippage_tolerance, - .. - } => { - if assets.len() != 2 { - return Err(StdError::generic_err(format!( - "{}", - ContractError::WrongPoolLength {} - ))); - } - let pair_addr = deps.api.addr_validate(&pair_addr)?; - let pair_info: PairInfo = deps - .querier - .query_wasm_smart(&pair_addr, &PairQueryMsg::Pair {})?; - match &pair_info.pair_type { - PairType::Xyk {} => { - let pools = pair_info.query_pools(&deps.querier, &pair_addr)?; - - let mut predicted_lp_amount = xyk_provide_simulation( - deps.querier, - &pools, - &pair_info, - slippage_tolerance, - assets.clone(), - ) - .map_err(|err| StdError::generic_err(format!("{err}")))?; - - // Initial provide is always fair because initial LP dictates the price - if !pools[0].amount.is_zero() && !pools[1].amount.is_zero() { - if pools[0].info.ne(&assets[0].info) { - assets.swap(0, 1); - } - - // Add user's deposits - let balances_with_deposit = pools - .clone() - .into_iter() - .zip(assets.iter()) - .map(|(mut pool, asset)| { - pool.amount += asset.amount; - pool - }) - .collect::>(); - let total_share = query_supply(&deps.querier, &pair_info.liquidity_token)?; - let accrued_share = get_share_in_assets( - &balances_with_deposit, - predicted_lp_amount, - total_share + predicted_lp_amount, - ); - - // Simulate provide again without excess tokens - predicted_lp_amount = xyk_provide_simulation( - deps.querier, - &pools, - &pair_info, - slippage_tolerance, - accrued_share, - ) - .map_err(|err| StdError::generic_err(format!("{err}")))?; - } - - to_json_binary(&predicted_lp_amount) - } - PairType::Stable {} => { - let pair_config_data = deps - .querier - .query_wasm_raw(pair_addr, b"config")? - .ok_or_else(|| StdError::generic_err("pair stable config not found"))?; - let pair_config = convert_stable_config(deps.querier, pair_config_data)?; - to_json_binary( - &stableswap_provide_simulation( - deps.querier, - env, - pair_config, - slippage_tolerance, - assets, - ) - .map_err(|err| StdError::generic_err(format!("{err}")))?, - ) - } - PairType::Custom(typ) if typ == "concentrated" => { - let balances = pair_info.query_pools(&deps.querier, &pair_addr)?; - let pcl_config_raw = deps - .querier - .query_wasm_raw(&pair_addr, b"config")? - .ok_or_else(|| StdError::generic_err("PCL config not found"))?; - let pcl_config = convert_pcl_config(pcl_config_raw)?; - let precisions = balances - .iter() - .map(|asset| { - let prec = Precisions::PRECISIONS - .query(&deps.querier, pair_addr.clone(), asset.info.to_string())? - .or_else(|| { - asset - .info - .decimals(&deps.querier, &pcl_config.factory_addr) - .ok() - }) - .ok_or_else(|| { - StdError::generic_err(format!( - "Asset {} precision not found", - &asset.info - )) - })?; - Ok((asset.info.to_string(), prec)) - }) - .collect::>>()?; - let dec_balances = balances - .into_iter() - .map(|asset| { - asset - .to_decimal_asset(*precisions.get(&asset.info.to_string()).unwrap()) - .map_err(Into::into) - }) - .collect::>>()?; - let total_share = query_supply(&deps.querier, &pair_info.liquidity_token)?; - pcl_provide_simulation( - env, - dec_balances, - assets, - total_share, - pcl_config, - precisions, - ) - .map_err(|err| StdError::generic_err(err.to_string())) - .and_then(|res| to_json_binary(&res)) - } - PairType::Custom(_) => unimplemented!("not implemented yet"), - } - } - _ => Err(StdError::generic_err("Invalid simulate message")), - } -} - -fn simulate_withdraw(deps: Deps, pair_addr: String, lp_tokens: Uint128) -> StdResult { - let pair_addr = deps.api.addr_validate(&pair_addr)?; - let assets: Vec = deps - .querier - .query_wasm_smart(pair_addr, &PairQueryMsg::Share { amount: lp_tokens })?; - - if assets.len() != 2 { - return Err(StdError::generic_err(format!( - "{}", - ContractError::WrongPoolLength {} - ))); - } - - to_json_binary(&assets) -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use cosmwasm_std::{Addr, Decimal}; - - use astroport::asset::{native_asset_info, token_asset_info, AssetInfoExt}; - use astroport::liquidity_manager::{Cw20HookMsg, ExecuteMsg}; - use astroport::pair::{Cw20HookMsg as PairCw20HookMsg, ExecuteMsg as PairExecuteMsg}; - - use super::*; - - #[test] - fn generate_query_msg_examples() { - let provide_query_msg = QueryMsg::SimulateProvide { - pair_addr: "wasm1...addr".to_string(), - pair_msg: PairExecuteMsg::ProvideLiquidity { - assets: vec![ - native_asset_info("uusd".to_string()).with_balance(100000u128), - token_asset_info(Addr::unchecked("wasm1...cw20address".to_string())) - .with_balance(100000u128), - ], - slippage_tolerance: Some(Decimal::from_str("0.02").unwrap()), - auto_stake: Some(true), - receiver: Some("wasm1...addr".to_string()), - }, - }; - - println!( - "Provide example query msg: {}", - serde_json::to_string_pretty(&provide_query_msg).unwrap() - ); - - let withdraw_query_msg = QueryMsg::SimulateWithdraw { - pair_addr: "wasm1...addr".to_string(), - lp_tokens: 1000u16.into(), - }; - - println!( - "Withdraw example query msg: {}", - serde_json::to_string_pretty(&withdraw_query_msg).unwrap() - ); - } - - #[test] - fn generate_execute_msg_examples() { - let provide_msg = ExecuteMsg::ProvideLiquidity { - pair_addr: "wasm1...pair address".to_string(), - pair_msg: PairExecuteMsg::ProvideLiquidity { - assets: vec![ - native_asset_info("uusd".to_string()).with_balance(100000u128), - token_asset_info(Addr::unchecked("wasm1...cw20address".to_string())) - .with_balance(100000u128), - ], - slippage_tolerance: Some(Decimal::from_str("0.02").unwrap()), - auto_stake: Some(true), - receiver: Some("wasm1...addr".to_string()), - }, - min_lp_to_receive: Some(100000u128.into()), - }; - - println!( - "Provide example execute msg: {}", - serde_json::to_string_pretty(&provide_msg).unwrap() - ); - - let cw20hook_msg = Cw20HookMsg::WithdrawLiquidity { - pair_msg: PairCw20HookMsg::WithdrawLiquidity { assets: vec![] }, - min_assets_to_receive: vec![ - native_asset_info("uusd".to_string()).with_balance(100000u128), - token_asset_info(Addr::unchecked("wasm1...cw20address".to_string())) - .with_balance(100000u128), - ], - }; - - let withdraw_msg = cw20::Cw20ExecuteMsg::Send { - contract: "wasm1...LP token address".to_string(), - amount: 1000u128.into(), - msg: to_json_binary(&cw20hook_msg).unwrap(), - }; - - println!( - "Withdraw example execute msg: {}", - serde_json::to_string_pretty(&withdraw_msg).unwrap() - ); - - println!( - "Where base64-encoded cw20 hook msg is: {}", - serde_json::to_string_pretty(&cw20hook_msg).unwrap() - ); - } -} diff --git a/contracts/periphery/liquidity_manager/src/state.rs b/contracts/periphery/liquidity_manager/src/state.rs deleted file mode 100644 index a1e408e44..000000000 --- a/contracts/periphery/liquidity_manager/src/state.rs +++ /dev/null @@ -1,34 +0,0 @@ -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{Addr, Uint128}; -use cw_storage_plus::Item; - -use astroport::asset::Asset; - -#[cw_serde] -pub struct Config { - pub factory_addr: Addr, -} - -pub const CONFIG: Item = Item::new("config"); - -#[cw_serde] -pub enum ActionParams { - Provide { - lp_token_addr: String, - lp_amount_before: Uint128, - staked_in_generator: bool, - min_lp_to_receive: Uint128, - }, - Withdraw { - pair_addr: Addr, - min_assets_to_receive: Vec, - }, -} - -#[cw_serde] -pub struct ReplyData { - pub receiver: String, - pub params: ActionParams, -} - -pub const REPLY_DATA: Item = Item::new("reply_data"); diff --git a/contracts/periphery/liquidity_manager/src/utils.rs b/contracts/periphery/liquidity_manager/src/utils.rs index 208b8328f..e69de29bb 100644 --- a/contracts/periphery/liquidity_manager/src/utils.rs +++ b/contracts/periphery/liquidity_manager/src/utils.rs @@ -1,381 +0,0 @@ -use std::collections::HashMap; - -use cosmwasm_schema::cw_serde; -use cosmwasm_std::{ - from_json, Addr, Decimal, Decimal256, Env, QuerierWrapper, StdError, StdResult, Uint128, -}; -use itertools::Itertools; - -use astroport::asset::{ - Asset, AssetInfo, Decimal256Ext, DecimalAsset, PairInfo, MINIMUM_LIQUIDITY_AMOUNT, -}; -use astroport::cosmwasm_ext::IntegerToDecimal; -use astroport::incentives::QueryMsg as GeneratorQueryMsg; -use astroport::liquidity_manager::CompatPairStableConfig; -use astroport::pair::FeeShareConfig; -use astroport::querier::{query_supply, query_token_balance}; -use astroport::U256; -use astroport_pair::{ - contract::assert_slippage_tolerance, error::ContractError as PairContractError, -}; -use astroport_pair_concentrated::error::ContractError as PclContractError; -use astroport_pair_stable::error::ContractError as StableContractError; -use astroport_pair_stable::math::compute_d; -use astroport_pair_stable::state::Config as PairStableConfig; -use astroport_pair_stable::utils::compute_current_amp; -use astroport_pcl_common::state::{Config as PclConfig, PoolParams, PoolState}; -use astroport_pcl_common::utils::calc_provide_fee; -use astroport_pcl_common::{calc_d, get_xcp}; - -/// LP token's precision. -pub const LP_TOKEN_PRECISION: u8 = 6; - -pub fn query_lp_amount( - querier: QuerierWrapper, - lp_token_addr: String, - factory_addr: Addr, - staked_in_generator: bool, - user: &String, -) -> StdResult { - if staked_in_generator { - let maybe_generator = astroport_factory::state::CONFIG - .query(&querier, factory_addr)? - .generator_address; - if let Some(generator_addr) = maybe_generator { - querier.query_wasm_smart( - generator_addr, - &GeneratorQueryMsg::Deposit { - lp_token: lp_token_addr, - user: user.to_string(), - }, - ) - } else { - Err(StdError::generic_err( - "Generator address is not set in factory config", - )) - } - } else { - query_token_balance(&querier, lp_token_addr, user) - } -} - -pub fn query_cw20_minter(querier: QuerierWrapper, lp_token_addr: Addr) -> StdResult { - cw20_base::state::TOKEN_INFO - .query(&querier, lp_token_addr.clone())? - .mint - .map(|info| info.minter) - .ok_or_else(|| StdError::generic_err(format!("Minter for {lp_token_addr} is not set"))) -} - -pub fn xyk_provide_simulation( - querier: QuerierWrapper, - pool_balances: &[Asset], - pair_info: &PairInfo, - slippage_tolerance: Option, - deposits: Vec, -) -> Result { - let deposits = [ - deposits - .iter() - .find(|a| a.info.equal(&pool_balances[0].info)) - .map(|a| a.amount) - .expect("Wrong asset info is given"), - deposits - .iter() - .find(|a| a.info.equal(&pool_balances[1].info)) - .map(|a| a.amount) - .expect("Wrong asset info is given"), - ]; - - if deposits[0].is_zero() || deposits[1].is_zero() { - return Err(StdError::generic_err("Wrong asset info is given").into()); - } - - let total_share = query_supply(&querier, &pair_info.liquidity_token)?; - let share = if total_share.is_zero() { - // Initial share = collateral amount - let share = Uint128::new( - (U256::from(deposits[0].u128()) * U256::from(deposits[1].u128())) - .integer_sqrt() - .as_u128(), - ) - .checked_sub(MINIMUM_LIQUIDITY_AMOUNT) - .map_err(|_| PairContractError::MinimumLiquidityAmountError {})?; - - // share cannot become zero after minimum liquidity subtraction - if share.is_zero() { - return Err(PairContractError::MinimumLiquidityAmountError {}); - } - - share - } else { - // Assert slippage tolerance - assert_slippage_tolerance(slippage_tolerance, &deposits, pool_balances)?; - - // min(1, 2) - // 1. sqrt(deposit_0 * exchange_rate_0_to_1 * deposit_0) * (total_share / sqrt(pool_0 * pool_0)) - // == deposit_0 * total_share / pool_0 - // 2. sqrt(deposit_1 * exchange_rate_1_to_0 * deposit_1) * (total_share / sqrt(pool_1 * pool_1)) - // == deposit_1 * total_share / pool_1 - std::cmp::min( - deposits[0].multiply_ratio(total_share, pool_balances[0].amount), - deposits[1].multiply_ratio(total_share, pool_balances[1].amount), - ) - }; - - Ok(share) -} - -pub fn stableswap_provide_simulation( - querier: QuerierWrapper, - env: Env, - config: PairStableConfig, - _slippage_tolerance: Option, - deposits: Vec, -) -> Result { - if deposits.len() != config.pair_info.asset_infos.len() { - return Err(StableContractError::InvalidNumberOfAssets( - config.pair_info.asset_infos.len(), - )); - } - - let pools: HashMap<_, _> = config - .pair_info - .query_pools(&querier, &config.pair_info.contract_addr)? - .into_iter() - .map(|pool| (pool.info, pool.amount)) - .collect(); - - let mut non_zero_flag = false; - - let mut assets_collection = deposits - .clone() - .into_iter() - .map(|asset| { - // Check that at least one asset is non-zero - if !asset.amount.is_zero() { - non_zero_flag = true; - } - - // Get appropriate pool - let pool = pools - .get(&asset.info) - .copied() - .ok_or_else(|| StableContractError::InvalidAsset(asset.info.to_string()))?; - - Ok((asset, pool)) - }) - .collect::, StableContractError>>()?; - - // If some assets are omitted then add them explicitly with 0 deposit - pools.iter().for_each(|(pool_info, pool_amount)| { - if !deposits.iter().any(|asset| asset.info.eq(pool_info)) { - assets_collection.push(( - Asset { - amount: Uint128::zero(), - info: pool_info.clone(), - }, - *pool_amount, - )); - } - }); - - if !non_zero_flag { - return Err(StableContractError::InvalidZeroAmount {}); - } - - for (deposit, pool) in assets_collection.iter_mut() { - // We cannot put a zero amount into an empty pool. - if deposit.amount.is_zero() && pool.is_zero() { - return Err(StableContractError::InvalidProvideLPsWithSingleToken {}); - } - } - - let assets_collection = assets_collection - .iter() - .cloned() - .map(|(asset, pool)| { - let coin_precision = astroport_pair_stable::state::PRECISIONS - .query( - &querier, - config.pair_info.contract_addr.clone(), - asset.info.to_string(), - )? - .or_else(|| asset.info.decimals(&querier, &config.factory_addr).ok()) - .ok_or_else(|| { - StdError::generic_err(format!("Asset {asset} precision not found")) - })?; - Ok(( - asset.to_decimal_asset(coin_precision)?, - Decimal256::with_precision(pool, coin_precision)?, - )) - }) - .collect::>>()?; - - let amp = compute_current_amp(&config, &env)?; - - // Invariant (D) after deposit added - let new_balances = assets_collection - .iter() - .map(|(deposit, pool)| Ok(pool + deposit.amount)) - .collect::>>()?; - let deposit_d = compute_d(amp, &new_balances)?; - - let total_share = query_supply(&querier, &config.pair_info.liquidity_token)?; - let share = if total_share.is_zero() { - let share = deposit_d - .to_uint128_with_precision(config.greatest_precision)? - .checked_sub(MINIMUM_LIQUIDITY_AMOUNT) - .map_err(|_| StableContractError::MinimumLiquidityAmountError {})?; - - // share cannot become zero after minimum liquidity subtraction - if share.is_zero() { - return Err(StableContractError::MinimumLiquidityAmountError {}); - } - - share - } else { - // Initial invariant (D) - let old_balances = assets_collection - .iter() - .map(|(_, pool)| *pool) - .collect::>(); - let init_d = compute_d(amp, &old_balances)?; - - let share = Decimal256::with_precision(total_share, config.greatest_precision)? - .checked_multiply_ratio(deposit_d.saturating_sub(init_d), init_d)? - .to_uint128_with_precision(config.greatest_precision)?; - - if share.is_zero() { - return Err(StableContractError::LiquidityAmountTooSmall {}); - } - - share - }; - - Ok(share) -} - -pub fn pcl_provide_simulation( - env: Env, - balances: Vec, - deposit: Vec, - total_share: Uint128, - config: PclConfig, - precisions: HashMap, -) -> Result { - let to_dec = |index: usize| -> StdResult { - Decimal256::with_precision( - deposit[index].amount, - *precisions - .get(&deposit[index].info.to_string()) - .ok_or_else(|| { - StdError::generic_err(format!("Invalid asset {}", deposit[index].info)) - })?, - ) - }; - - let deposits = [to_dec(0)?, to_dec(1)?]; - - let mut new_xp = balances - .iter() - .zip(deposits.iter()) - .map(|(balance, deposit)| balance.amount + deposit) - .collect_vec(); - new_xp[1] *= config.pool_state.price_state.price_scale; - - let amp_gamma = config.pool_state.get_amp_gamma(&env); - let new_d = calc_d(&new_xp, &_gamma)?; - - let total_share = total_share.to_decimal256(LP_TOKEN_PRECISION)?; - - if total_share.is_zero() { - let xcp = get_xcp(new_d, config.pool_state.price_state.price_scale) - .to_uint128_with_precision(LP_TOKEN_PRECISION)?; - let mint_amount = xcp.saturating_sub(MINIMUM_LIQUIDITY_AMOUNT); - - // share cannot become zero after minimum liquidity subtraction - if mint_amount.is_zero() { - return Err(PclContractError::MinimumLiquidityAmountError {}); - } - - Ok(mint_amount) - } else { - let mut old_xp = balances.iter().map(|a| a.amount).collect_vec(); - old_xp[1] *= config.pool_state.price_state.price_scale; - let old_d = calc_d(&old_xp, &_gamma)?; - let share = (total_share * new_d / old_d).saturating_sub(total_share); - - let mut ideposits = deposits; - ideposits[1] *= config.pool_state.price_state.price_scale; - - let lp_amount = share - * (Decimal256::one() - calc_provide_fee(&ideposits, &new_xp, &config.pool_params)); - Ok(lp_amount.to_uint128_with_precision(LP_TOKEN_PRECISION)?) - } -} - -pub fn convert_stable_config( - querier: QuerierWrapper, - config_data: Vec, -) -> StdResult { - let compat_config: CompatPairStableConfig = from_json(config_data)?; - - let greatest_precision = if let Some(prec) = compat_config.greatest_precision { - prec - } else { - let mut greatest_precision = 0u8; - for asset_info in &compat_config.pair_info.asset_infos { - let precision = asset_info.decimals(&querier, &compat_config.factory_addr)?; - greatest_precision = greatest_precision.max(precision); - } - greatest_precision - }; - - Ok(PairStableConfig { - owner: compat_config.owner, - pair_info: compat_config.pair_info, - factory_addr: compat_config.factory_addr, - block_time_last: compat_config.block_time_last, - init_amp: compat_config.init_amp, - init_amp_time: compat_config.init_amp_time, - next_amp: compat_config.next_amp, - next_amp_time: compat_config.next_amp_time, - greatest_precision, - cumulative_prices: compat_config.cumulative_prices, - fee_share: None, - }) -} - -#[cw_serde] -pub struct CompatPclConfig { - pub pair_info: PairInfo, - pub factory_addr: Addr, - // Not important for provide simulation - #[serde(default)] - pub block_time_last: u64, - #[serde(default)] - pub cumulative_prices: Vec<(AssetInfo, AssetInfo, Uint128)>, - pub pool_params: PoolParams, - pub pool_state: PoolState, - pub owner: Option, - #[serde(default)] - pub track_asset_balances: bool, - pub fee_share: Option, -} - -pub fn convert_pcl_config(config_data: Vec) -> StdResult { - let compat_config: CompatPclConfig = from_json(config_data)?; - - Ok(PclConfig { - pair_info: compat_config.pair_info, - factory_addr: compat_config.factory_addr, - block_time_last: compat_config.block_time_last, - cumulative_prices: compat_config.cumulative_prices, - pool_params: compat_config.pool_params, - pool_state: compat_config.pool_state, - owner: compat_config.owner, - track_asset_balances: compat_config.track_asset_balances, - fee_share: compat_config.fee_share, - }) -} diff --git a/contracts/periphery/liquidity_manager/tests/helper.rs b/contracts/periphery/liquidity_manager/tests/helper.rs deleted file mode 100644 index 0d1e353b9..000000000 --- a/contracts/periphery/liquidity_manager/tests/helper.rs +++ /dev/null @@ -1,768 +0,0 @@ -#![allow(dead_code)] -#![cfg(not(tarpaulin_include))] - -use std::collections::HashMap; -use std::error::Error; -use std::fmt::Display; -use std::str::FromStr; - -use anyhow::Result as AnyResult; -use cosmwasm_schema::serde::de::DeserializeOwned; -use cosmwasm_std::{ - coin, from_json, to_json_binary, Addr, Coin, Decimal, Empty, StdError, StdResult, Uint128, -}; -use cw20::{BalanceResponse, Cw20Coin, Cw20ExecuteMsg, Cw20QueryMsg}; -use cw_multi_test::{App, AppResponse, Contract, ContractWrapper, Executor}; -use derivative::Derivative; -use itertools::Itertools; - -use astroport::asset::{native_asset_info, token_asset_info, Asset, AssetInfo, PairInfo}; -use astroport::factory::{PairConfig, PairType}; -use astroport::liquidity_manager::{Cw20HookMsg, ExecuteMsg}; -use astroport::liquidity_manager::{InstantiateMsg, QueryMsg}; -use astroport::pair::{Cw20HookMsg as PairCw20HookMsg, ExecuteMsg as PairExecuteMsg}; -use astroport::pair::{ - ReverseSimulationResponse, SimulationResponse, StablePoolParams, XYKPoolParams, -}; -use astroport::pair_concentrated::{ConcentratedPoolParams, QueryMsg as PairQueryMsg}; -use astroport::{factory, incentives}; -use astroport_liquidity_manager::contract::{execute, instantiate, reply}; -use astroport_liquidity_manager::query::query; - -const NATIVE_TOKEN_PRECISION: u8 = 6; - -const INIT_BALANCE: u128 = 1_000_000_000_000; - -pub enum PoolParams { - Constant(XYKPoolParams), - Stable(StablePoolParams), - Concentrated(ConcentratedPoolParams), -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub enum TestCoin { - Cw20(String), - Cw20Precise(String, u8), - Native(String), -} - -impl TestCoin { - pub fn denom(&self) -> Option { - match self { - TestCoin::Native(denom) => Some(denom.clone()), - _ => None, - } - } - - pub fn cw20_init_data(&self) -> Option<(String, u8)> { - match self { - TestCoin::Cw20(name) => Some((name.clone(), 6u8)), - TestCoin::Cw20Precise(name, precision) => Some((name.clone(), *precision)), - _ => None, - } - } - - pub fn native(denom: &str) -> Self { - Self::Native(denom.to_string()) - } - - pub fn cw20(name: &str) -> Self { - Self::Cw20(name.to_string()) - } - - pub fn cw20precise(name: &str, precision: u8) -> Self { - Self::Cw20Precise(name.to_string(), precision) - } -} - -pub fn init_native_coins(test_coins: &[TestCoin]) -> Vec { - let mut test_coins: Vec = test_coins - .iter() - .filter_map(|test_coin| match test_coin { - TestCoin::Native(name) => { - let init_balance = INIT_BALANCE * 10u128.pow(NATIVE_TOKEN_PRECISION as u32); - Some(coin(init_balance, name)) - } - _ => None, - }) - .collect(); - test_coins.push(coin(INIT_BALANCE, "random-coin")); - - test_coins -} - -fn token_contract() -> Box> { - Box::new(ContractWrapper::new_with_empty( - cw20_base::contract::execute, - cw20_base::contract::instantiate, - cw20_base::contract::query, - )) -} - -fn xyk_pair_contract() -> Box> { - Box::new( - ContractWrapper::new_with_empty( - astroport_pair::contract::execute, - astroport_pair::contract::instantiate, - astroport_pair::contract::query, - ) - .with_reply_empty(astroport_pair::contract::reply), - ) -} - -fn stable_pair_contract() -> Box> { - Box::new( - ContractWrapper::new_with_empty( - astroport_pair_stable::contract::execute, - astroport_pair_stable::contract::instantiate, - astroport_pair_stable::contract::query, - ) - .with_reply_empty(astroport_pair_stable::contract::reply), - ) -} - -fn pcl_pair_contract() -> Box> { - Box::new( - ContractWrapper::new_with_empty( - astroport_pair_concentrated::contract::execute, - astroport_pair_concentrated::contract::instantiate, - astroport_pair_concentrated::queries::query, - ) - .with_reply_empty(astroport_pair_concentrated::contract::reply), - ) -} - -fn coin_registry_contract() -> Box> { - Box::new(ContractWrapper::new_with_empty( - astroport_native_coin_registry::contract::execute, - astroport_native_coin_registry::contract::instantiate, - astroport_native_coin_registry::contract::query, - )) -} - -fn factory_contract() -> Box> { - Box::new( - ContractWrapper::new_with_empty( - astroport_factory::contract::execute, - astroport_factory::contract::instantiate, - astroport_factory::contract::query, - ) - .with_reply_empty(astroport_factory::contract::reply), - ) -} - -fn generator_contract() -> Box> { - Box::new( - ContractWrapper::new_with_empty( - astroport_incentives::execute::execute, - astroport_incentives::instantiate::instantiate, - astroport_incentives::query::query, - ) - .with_reply_empty(astroport_incentives::reply::reply), - ) -} - -fn manager_contract() -> Box> { - Box::new(ContractWrapper::new_with_empty(execute, instantiate, query).with_reply_empty(reply)) -} - -#[derive(Derivative)] -#[derivative(Debug)] -pub struct Helper { - #[derivative(Debug = "ignore")] - pub app: App, - pub owner: Addr, - pub assets: HashMap, - pub factory: Addr, - pub pair_addr: Addr, - pub lp_token: Addr, - pub fake_maker: Addr, - pub liquidity_manager: Addr, - pub generator: Addr, -} - -impl Helper { - pub fn new(owner: &Addr, test_coins: Vec, params: PoolParams) -> AnyResult { - let mut app = App::new(|router, _, storage| { - router - .bank - .init_balance(storage, owner, init_native_coins(&test_coins)) - .unwrap() - }); - - let mut asset_infos_vec: Vec<_> = test_coins - .clone() - .into_iter() - .filter_map(|coin| Some((coin.clone(), native_asset_info(coin.denom()?)))) - .collect(); - - let token_code_id = app.store_code(token_contract()); - - test_coins.into_iter().for_each(|coin| { - if let Some((name, decimals)) = coin.cw20_init_data() { - let token_addr = Self::init_token(&mut app, token_code_id, name, decimals, owner); - asset_infos_vec.push((coin, token_asset_info(token_addr))) - } - }); - - let factory_code_id = app.store_code(factory_contract()); - - let (pair_code_id, pair_type, inner_params); - match ¶ms { - PoolParams::Constant(inner) => { - pair_code_id = app.store_code(xyk_pair_contract()); - pair_type = PairType::Xyk {}; - inner_params = to_json_binary(inner).unwrap(); - } - PoolParams::Stable(inner) => { - pair_code_id = app.store_code(stable_pair_contract()); - pair_type = PairType::Stable {}; - inner_params = to_json_binary(inner).unwrap(); - } - PoolParams::Concentrated(inner) => { - pair_code_id = app.store_code(pcl_pair_contract()); - pair_type = PairType::Custom("concentrated".to_owned()); - inner_params = to_json_binary(inner).unwrap(); - } - } - - let fake_maker = Addr::unchecked("fake_maker"); - - let coin_registry_id = app.store_code(coin_registry_contract()); - - let coin_registry_address = app - .instantiate_contract( - coin_registry_id, - owner.clone(), - &astroport::native_coin_registry::InstantiateMsg { - owner: owner.to_string(), - }, - &[], - "Coin registry", - None, - ) - .unwrap(); - - app.execute_contract( - owner.clone(), - coin_registry_address.clone(), - &astroport::native_coin_registry::ExecuteMsg::Add { - native_coins: vec![("uluna".to_owned(), 6), ("uusd".to_owned(), 6)], - }, - &[], - ) - .unwrap(); - let init_msg = astroport::factory::InstantiateMsg { - fee_address: Some(fake_maker.to_string()), - pair_configs: vec![PairConfig { - code_id: pair_code_id, - maker_fee_bps: 5000, - total_fee_bps: 30, - pair_type: pair_type.clone(), - is_disabled: false, - is_generator_disabled: false, - permissioned: false, - }], - token_code_id, - generator_address: None, - owner: owner.to_string(), - whitelist_code_id: 234u64, - coin_registry_address: coin_registry_address.to_string(), - }; - - let factory = app.instantiate_contract( - factory_code_id, - owner.clone(), - &init_msg, - &[], - "FACTORY", - None, - )?; - - let generator_code_id = app.store_code(generator_contract()); - let generator = app - .instantiate_contract( - generator_code_id, - owner.clone(), - &incentives::InstantiateMsg { - owner: owner.to_string(), - factory: factory.to_string(), - guardian: None, - astro_token: native_asset_info("astro".to_string()), - vesting_contract: "vesting".to_string(), - incentivization_fee_info: None, - }, - &[], - "Generator", - None, - ) - .unwrap(); - - app.execute_contract( - owner.clone(), - factory.clone(), - &factory::ExecuteMsg::UpdateConfig { - token_code_id: None, - fee_address: None, - generator_address: Some(generator.to_string()), - whitelist_code_id: None, - coin_registry_address: None, - }, - &[], - ) - .unwrap(); - - let manager_code = app.store_code(manager_contract()); - let liquidity_manager = app - .instantiate_contract( - manager_code, - owner.clone(), - &InstantiateMsg { - astroport_factory: factory.to_string(), - }, - &[], - "Liquidity manager", - None, - ) - .unwrap(); - - let asset_infos = asset_infos_vec - .clone() - .into_iter() - .map(|(_, asset_info)| asset_info) - .collect_vec(); - let init_pair_msg = astroport::factory::ExecuteMsg::CreatePair { - pair_type, - asset_infos: asset_infos.clone(), - init_params: Some(inner_params), - }; - - app.execute_contract(owner.clone(), factory.clone(), &init_pair_msg, &[])?; - - let resp: PairInfo = app.wrap().query_wasm_smart( - &factory, - &astroport::factory::QueryMsg::Pair { asset_infos }, - )?; - - Ok(Self { - app, - owner: owner.clone(), - assets: asset_infos_vec.into_iter().collect(), - factory, - pair_addr: resp.contract_addr, - lp_token: resp.liquidity_token, - fake_maker, - liquidity_manager, - generator, - }) - } - - pub fn simulate_provide( - &self, - slippage_tolerance: Option, - assets: &[Asset], - ) -> AnyResult { - let pair_msg = PairExecuteMsg::ProvideLiquidity { - assets: assets.to_vec(), - slippage_tolerance, - auto_stake: None, - receiver: None, - }; - - self.app - .wrap() - .query_wasm_smart( - &self.liquidity_manager, - &QueryMsg::SimulateProvide { - pair_addr: self.pair_addr.to_string(), - pair_msg, - }, - ) - .map_err(Into::into) - } - - pub fn simulate_withdraw(&self, lp_tokens_amount: impl Into) -> AnyResult> { - self.app - .wrap() - .query_wasm_smart( - &self.liquidity_manager, - &QueryMsg::SimulateWithdraw { - pair_addr: self.pair_addr.to_string(), - lp_tokens: lp_tokens_amount.into(), - }, - ) - .map_err(Into::into) - } - - /// If min_lp_receive is Some provide is done via liquidity manager contract. - pub fn provide_liquidity( - &mut self, - sender: &Addr, - assets: &[Asset], - min_lp_receive: Option, - ) -> AnyResult { - self.provide_liquidity_with_slip_tolerance( - sender, - assets, - Some(f64_to_dec(0.5)), - min_lp_receive, - false, - None, - ) - } - - /// If min_lp_receive is Some provide is done via liquidity manager contract. - pub fn provide_liquidity_with_slip_tolerance( - &mut self, - sender: &Addr, - assets: &[Asset], - slippage_tolerance: Option, - min_lp_receive: Option, - auto_stake: bool, - receiver: Option, - ) -> AnyResult { - let msg = PairExecuteMsg::ProvideLiquidity { - assets: assets.to_vec(), - slippage_tolerance, - auto_stake: Some(auto_stake), - receiver, - }; - - if min_lp_receive.is_some() { - let funds = assets.mock_coins_sent( - &mut self.app, - sender, - &self.liquidity_manager, - SendType::Allowance, - ); - - let manager_msg = ExecuteMsg::ProvideLiquidity { - pair_addr: self.pair_addr.to_string(), - pair_msg: msg, - min_lp_to_receive: min_lp_receive, - }; - self.app.execute_contract( - sender.clone(), - self.liquidity_manager.clone(), - &manager_msg, - &funds, - ) - } else { - let funds = - assets.mock_coins_sent(&mut self.app, sender, &self.pair_addr, SendType::Allowance); - self.app - .execute_contract(sender.clone(), self.pair_addr.clone(), &msg, &funds) - } - } - - pub fn withdraw_liquidity( - &mut self, - sender: &Addr, - amount: u128, - min_assets: Option>, - ) -> AnyResult { - let pair_msg = PairCw20HookMsg::WithdrawLiquidity { assets: vec![] }; - let (contract, msg); - if let Some(min_assets_to_receive) = min_assets { - contract = self.liquidity_manager.to_string(); - msg = to_json_binary(&Cw20HookMsg::WithdrawLiquidity { - pair_msg, - min_assets_to_receive, - }) - .unwrap(); - } else { - contract = self.pair_addr.to_string(); - msg = to_json_binary(&pair_msg).unwrap(); - } - - let msg = Cw20ExecuteMsg::Send { - contract, - amount: Uint128::from(amount), - msg, - }; - - self.app - .execute_contract(sender.clone(), self.lp_token.clone(), &msg, &[]) - } - - pub fn swap( - &mut self, - sender: &Addr, - offer_asset: &Asset, - max_spread: Option, - ) -> AnyResult { - match &offer_asset.info { - AssetInfo::Token { contract_addr } => { - let msg = Cw20ExecuteMsg::Send { - contract: self.pair_addr.to_string(), - amount: offer_asset.amount, - msg: to_json_binary(&PairCw20HookMsg::Swap { - ask_asset_info: None, - belief_price: None, - max_spread, - to: None, - }) - .unwrap(), - }; - - self.app - .execute_contract(sender.clone(), contract_addr.clone(), &msg, &[]) - } - AssetInfo::NativeToken { .. } => { - let funds = offer_asset.mock_coin_sent( - &mut self.app, - sender, - &self.pair_addr, - SendType::None, - ); - - let msg = PairExecuteMsg::Swap { - offer_asset: offer_asset.clone(), - ask_asset_info: None, - belief_price: None, - max_spread, - to: None, - }; - - self.app - .execute_contract(sender.clone(), self.pair_addr.clone(), &msg, &funds) - } - } - } - - pub fn simulate_swap( - &self, - offer_asset: &Asset, - ask_asset_info: Option, - ) -> StdResult { - self.app.wrap().query_wasm_smart( - &self.pair_addr, - &PairQueryMsg::Simulation { - offer_asset: offer_asset.clone(), - ask_asset_info, - }, - ) - } - - pub fn simulate_reverse_swap( - &self, - ask_asset: &Asset, - offer_asset_info: Option, - ) -> StdResult { - self.app.wrap().query_wasm_smart( - &self.pair_addr, - &PairQueryMsg::ReverseSimulation { - ask_asset: ask_asset.clone(), - offer_asset_info, - }, - ) - } - - fn init_token( - app: &mut App, - token_code: u64, - name: String, - decimals: u8, - owner: &Addr, - ) -> Addr { - let init_balance = INIT_BALANCE * 10u128.pow(decimals as u32); - app.instantiate_contract( - token_code, - owner.clone(), - &astroport::token::InstantiateMsg { - symbol: name.to_string(), - name, - decimals, - initial_balances: vec![Cw20Coin { - address: owner.to_string(), - amount: Uint128::from(init_balance), - }], - mint: None, - marketing: None, - }, - &[], - "{name}_token", - None, - ) - .unwrap() - } - - pub fn token_balance(&self, token_addr: &Addr, user: &Addr) -> u128 { - let resp: BalanceResponse = self - .app - .wrap() - .query_wasm_smart( - token_addr, - &Cw20QueryMsg::Balance { - address: user.to_string(), - }, - ) - .unwrap(); - - resp.balance.u128() - } - - pub fn coin_balance(&self, coin: &TestCoin, user: &Addr) -> u128 { - match &self.assets[coin] { - AssetInfo::Token { contract_addr } => self.token_balance(contract_addr, user), - AssetInfo::NativeToken { denom } => self - .app - .wrap() - .query_balance(user, denom) - .unwrap() - .amount - .u128(), - } - } - - pub fn give_me_money(&mut self, assets: &[Asset], recipient: &Addr) { - let funds = - assets.mock_coins_sent(&mut self.app, &self.owner, recipient, SendType::Transfer); - - if !funds.is_empty() { - self.app - .send_tokens(self.owner.clone(), recipient.clone(), &funds) - .unwrap(); - } - } - - pub fn query_config(&self) -> StdResult - where - T: DeserializeOwned, - { - let binary = self - .app - .wrap() - .query_wasm_raw(&self.pair_addr, b"config")? - .ok_or_else(|| StdError::generic_err("Failed to find config in storage"))?; - from_json(&binary) - } - - pub fn query_asset_balance_at( - &self, - asset_info: &AssetInfo, - block_height: u64, - ) -> StdResult> { - self.app.wrap().query_wasm_smart( - &self.pair_addr, - &PairQueryMsg::AssetBalanceAt { - asset_info: asset_info.clone(), - block_height: block_height.into(), - }, - ) - } - - pub fn query_staked_lp(&self, user: &Addr) -> StdResult { - self.app.wrap().query_wasm_smart( - &self.generator, - &incentives::QueryMsg::Deposit { - lp_token: self.lp_token.to_string(), - user: user.to_string(), - }, - ) - } -} - -#[derive(Clone, Copy)] -pub enum SendType { - Allowance, - Transfer, - None, -} - -pub trait AssetExt { - fn mock_coin_sent( - &self, - app: &mut App, - user: &Addr, - spender: &Addr, - typ: SendType, - ) -> Vec; -} - -impl AssetExt for Asset { - fn mock_coin_sent( - &self, - app: &mut App, - user: &Addr, - spender: &Addr, - typ: SendType, - ) -> Vec { - let mut funds = vec![]; - match &self.info { - AssetInfo::Token { contract_addr } if !self.amount.is_zero() => { - let msg = match typ { - SendType::Allowance => Cw20ExecuteMsg::IncreaseAllowance { - spender: spender.to_string(), - amount: self.amount, - expires: None, - }, - SendType::Transfer => Cw20ExecuteMsg::Transfer { - recipient: spender.to_string(), - amount: self.amount, - }, - _ => unimplemented!(), - }; - app.execute_contract(user.clone(), contract_addr.clone(), &msg, &[]) - .unwrap(); - } - AssetInfo::NativeToken { denom } if !self.amount.is_zero() => { - funds = vec![coin(self.amount.u128(), denom)]; - } - _ => {} - } - - funds - } -} - -pub trait AssetsExt { - fn mock_coins_sent( - &self, - app: &mut App, - user: &Addr, - spender: &Addr, - typ: SendType, - ) -> Vec; -} - -impl AssetsExt for &[Asset] { - fn mock_coins_sent( - &self, - app: &mut App, - user: &Addr, - spender: &Addr, - typ: SendType, - ) -> Vec { - let mut funds = vec![]; - for asset in self.iter() { - funds.extend(asset.mock_coin_sent(app, user, spender, typ)); - } - funds - } -} - -pub trait AppExtension { - fn next_block(&mut self, time: u64); -} - -impl AppExtension for App { - fn next_block(&mut self, time: u64) { - self.update_block(|block| { - block.time = block.time.plus_seconds(time); - block.height += 1 - }); - } -} - -pub fn f64_to_dec(val: f64) -> T -where - T: FromStr, - T::Err: Error, -{ - T::from_str(&val.to_string()).unwrap() -} - -pub fn dec_to_f64(val: impl Display) -> f64 { - f64::from_str(&val.to_string()).unwrap() -} diff --git a/contracts/periphery/liquidity_manager/tests/liquidity_manager_integration.rs b/contracts/periphery/liquidity_manager/tests/liquidity_manager_integration.rs deleted file mode 100644 index 8ed10e480..000000000 --- a/contracts/periphery/liquidity_manager/tests/liquidity_manager_integration.rs +++ /dev/null @@ -1,573 +0,0 @@ -#![cfg(not(tarpaulin_include))] - -use cosmwasm_std::{Addr, Decimal, Uint128}; - -use astroport::asset::{native_asset, AssetInfoExt}; -use astroport::pair::{StablePoolParams, XYKPoolParams}; -use astroport::pair_concentrated::ConcentratedPoolParams; -use astroport_liquidity_manager::error::ContractError; - -use crate::helper::{f64_to_dec, Helper, PoolParams, TestCoin}; - -mod helper; - -#[test] -fn test_xyk() { - let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("TEST")]; - let mut helper = Helper::new( - &owner, - test_coins.clone(), - PoolParams::Constant(XYKPoolParams { - track_asset_balances: None, - }), - ) - .unwrap(); - - helper - .provide_liquidity( - &owner, - &[ - helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), - helper.assets[&test_coins[1]].with_balance(100_000_000000_u128), - ], - Some(Uint128::MIN), // setting zero just to make initial provision via manager contract, - ) - .unwrap(); - - let user1 = Addr::unchecked("user1"); - let provide_assets = [ - helper.assets[&test_coins[0]].with_balance(100_000000u128), - helper.assets[&test_coins[1]].with_balance(100_000000u128), - ]; - helper.give_me_money(&provide_assets, &user1); - let sim_lp_amount = helper.simulate_provide(None, &provide_assets).unwrap(); - helper - .provide_liquidity(&user1, &provide_assets, Some(sim_lp_amount)) - .unwrap(); - - // Imagine user3 wants to inject a huge swap which imbalance pool right before victims' (user2) usual provide. - let user3 = Addr::unchecked("user3"); - let swap_asset = helper.assets[&test_coins[1]].with_balance(10_000_000000_u128); - helper.give_me_money(&[swap_asset.clone()], &user3); - helper - .swap(&user3, &swap_asset, Some(f64_to_dec(0.5))) - .unwrap(); - - let user2 = Addr::unchecked("user2"); - helper.give_me_money(&provide_assets, &user2); - // User2 expects that he is making balanced provide (directly in pair contract). Allowing only 2% slippage. - let err = helper - .provide_liquidity_with_slip_tolerance( - &user2, - &provide_assets, - Some(f64_to_dec(0.02)), - None, - false, - None, - ) - .unwrap_err(); - - // However, he is safe because of slippage assertion. - assert_eq!( - astroport_pair::error::ContractError::MaxSlippageAssertion {}, - err.downcast().unwrap() - ); - - // User intentionally allowing 50% slippage. But he is providing via liquidity manager contract. - let slippage_tol = Some(f64_to_dec(0.5)); - let sim_lp_amount = helper - .simulate_provide(slippage_tol, &provide_assets) - .unwrap(); - helper - .provide_liquidity_with_slip_tolerance( - &user2, - &provide_assets, - slippage_tol, - Some(sim_lp_amount), - false, - None, - ) - .unwrap(); - - helper - .withdraw_liquidity(&user2, helper.token_balance(&helper.lp_token, &user2), None) - .unwrap(); - let asset1_bal = helper.coin_balance(&test_coins[0], &user2); - let asset2_bal = helper.coin_balance(&test_coins[1], &user2); - - // After withdraw user2 should have nearly equal amount of assets as before he provided (minus rounding errors). - assert_eq!(asset1_bal, 99_984289); - assert_eq!(asset2_bal, 99_999998); - - // Lets check same scenario but without using liquidity manager contract. - let user4 = Addr::unchecked("user4"); - helper.give_me_money(&provide_assets, &user4); - helper - .provide_liquidity_with_slip_tolerance( - &user4, - &provide_assets, - slippage_tol, - None, - false, - None, - ) - .unwrap(); - - helper - .withdraw_liquidity(&user4, helper.token_balance(&helper.lp_token, &user4), None) - .unwrap(); - let asset1_bal = helper.coin_balance(&test_coins[0], &user4); - let asset2_bal = helper.coin_balance(&test_coins[1], &user4); - - // After withdraw user4 received much less asset2 because of unfair LP minting policy - assert_eq!(asset1_bal, 82_687765); - assert_eq!(asset2_bal, 99_999999); - - // User5 tries to fool liquidity manager out by providing assets with different order in message - let user5 = Addr::unchecked("user5"); - let mut provide_assets_imbalanced = [ - helper.assets[&test_coins[0]].with_balance(10_000000u128), - helper.assets[&test_coins[1]].with_balance(8_000000u128), - ]; - helper.give_me_money(&provide_assets_imbalanced, &user5); - - let sim_lp_amount = helper - .simulate_provide(slippage_tol, &provide_assets_imbalanced) - .unwrap(); - - // Changing order - provide_assets_imbalanced.swap(0, 1); - helper - .provide_liquidity_with_slip_tolerance( - &user5, - &provide_assets_imbalanced, - Some(f64_to_dec(0.5)), - Some(sim_lp_amount), - false, - None, - ) - .unwrap(); - - helper - .withdraw_liquidity(&user5, helper.token_balance(&helper.lp_token, &user5), None) - .unwrap(); - let asset1_bal = helper.coin_balance(&test_coins[0], &user5); - let asset2_bal = helper.coin_balance(&test_coins[1], &user5); - - // However this trick doesn't work with liquidity manager contract. - // User5 received nearly equal amount of assets as provided minus fees charged due to imbalanced provide. - assert_eq!(asset1_bal, 9_999753); - assert_eq!(asset2_bal, 7_999998); -} - -#[test] -fn test_stableswap_without_manager() { - let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uusd"), TestCoin::cw20("UST")]; - let mut helper = Helper::new( - &owner, - test_coins.clone(), - PoolParams::Stable(StablePoolParams { - amp: 40, - owner: None, - }), - ) - .unwrap(); - - helper - .provide_liquidity( - &owner, - &[ - helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), - helper.assets[&test_coins[1]].with_balance(100_000_000000_u128), - ], - None, - ) - .unwrap(); - - let user1 = Addr::unchecked("user1"); - let provide_assets = [ - helper.assets[&test_coins[0]].with_balance(100_000000u128), - helper.assets[&test_coins[1]].with_balance(100_000000u128), - ]; - helper.give_me_money(&provide_assets, &user1); - let sim_lp_amount = helper.simulate_provide(None, &provide_assets).unwrap(); - helper - .provide_liquidity(&user1, &provide_assets, Some(sim_lp_amount)) - .unwrap(); - - // Malicious user3 becomes major LP holder. - let user3 = Addr::unchecked("user3"); - let imbalanced_provide = [ - helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), - helper.assets[&test_coins[1]].with_balance(100_000_000000_u128), - ]; - helper.give_me_money(&imbalanced_provide, &user3); - helper - .provide_liquidity(&user3, &imbalanced_provide, None) - .unwrap(); - // User3 imbalances pool right before the victim's provide. - let swap_asset = helper.assets[&test_coins[0]].with_balance(10_000_000000_u128); - helper.give_me_money(&[swap_asset.clone()], &user3); - helper - .swap(&user3, &swap_asset, Some(f64_to_dec(0.5))) - .unwrap(); - - let user2 = Addr::unchecked("user2"); - helper.give_me_money(&provide_assets, &user2); - // User2 expects that he is making balanced provide (directly in pair contract). However, stableswap doesn't have slippage check. - helper - .provide_liquidity_with_slip_tolerance( - &user2, - &provide_assets, - Some(f64_to_dec(0.02)), - None, - false, - None, - ) - .unwrap(); - - // user2 receives less LP tokens than expected - let user2_lp_bal = helper.token_balance(&helper.lp_token, &user2); - assert!( - user2_lp_bal < sim_lp_amount.u128(), - "user2 lp balance {user2_lp_bal} should be less than simulated: {sim_lp_amount}" - ); -} - -#[test] -fn test_stableswap_with_manager() { - let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uusd"), TestCoin::cw20("UST")]; - let mut helper = Helper::new( - &owner, - test_coins.clone(), - PoolParams::Stable(StablePoolParams { - amp: 40, - owner: None, - }), - ) - .unwrap(); - - helper - .provide_liquidity( - &owner, - &[ - helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), - helper.assets[&test_coins[1]].with_balance(100_000_000000_u128), - ], - None, - ) - .unwrap(); - - // Simulating LP tokens amount before provide - let provide_assets = [ - helper.assets[&test_coins[0]].with_balance(100_000000u128), - helper.assets[&test_coins[1]].with_balance(100_000000u128), - ]; - let sim_lp_amount = helper.simulate_provide(None, &provide_assets).unwrap(); - - // Malicious user3 becomes major LP holder. - let user3 = Addr::unchecked("user3"); - let malicious_provide = [ - helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), - helper.assets[&test_coins[1]].with_balance(100_000_000000_u128), - ]; - helper.give_me_money(&malicious_provide, &user3); - helper - .provide_liquidity(&user3, &malicious_provide, None) - .unwrap(); - // User3 imbalances pool right before the victim's provide. - let swap_asset = helper.assets[&test_coins[0]].with_balance(10_000_000000_u128); - helper.give_me_money(&[swap_asset.clone()], &user3); - helper - .swap(&user3, &swap_asset, Some(f64_to_dec(0.5))) - .unwrap(); - - let user2 = Addr::unchecked("user2"); - // User2 expects that he is making balanced provide and he uses liquidity manager contract. - helper.give_me_money(&provide_assets, &user2); - let err = helper - .provide_liquidity_with_slip_tolerance( - &user2, - &provide_assets, - Some(f64_to_dec(0.02)), - Some(sim_lp_amount), - false, - None, - ) - .unwrap_err(); - - assert_eq!( - ContractError::ProvideSlippageViolation(199_998620u128.into(), 200_000000u128.into()), - err.downcast().unwrap() - ); -} - -#[test] -fn test_pcl_with_manager() { - let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uusd"), TestCoin::cw20("UST")]; - let mut helper = Helper::new( - &owner, - test_coins.clone(), - PoolParams::Concentrated(ConcentratedPoolParams { - amp: f64_to_dec(10f64), - gamma: f64_to_dec(0.000145), - mid_fee: f64_to_dec(0.0026), - out_fee: f64_to_dec(0.0045), - fee_gamma: f64_to_dec(0.00023), - repeg_profit_threshold: f64_to_dec(0.000002), - min_price_scale_delta: f64_to_dec(0.000146), - price_scale: Decimal::one(), - ma_half_time: 600, - track_asset_balances: None, - fee_share: None, - }), - ) - .unwrap(); - - helper - .provide_liquidity( - &owner, - &[ - helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), - helper.assets[&test_coins[1]].with_balance(100_000_000000_u128), - ], - None, - ) - .unwrap(); - - // Simulating LP tokens amount before provide - let provide_assets = [ - helper.assets[&test_coins[0]].with_balance(100_000000u128), - helper.assets[&test_coins[1]].with_balance(100_000000u128), - ]; - let sim_lp_amount = helper.simulate_provide(None, &provide_assets).unwrap(); - - // Provide with zero slippage allowance to ensure simulation gives exact same amount as PCL contract - let user2 = Addr::unchecked("user2"); - helper.give_me_money(&provide_assets, &user2); - helper - .provide_liquidity_with_slip_tolerance( - &user2, - &provide_assets, - Some(Decimal::zero()), - Some(sim_lp_amount), - false, - None, - ) - .unwrap(); -} - -#[test] -fn test_auto_stake_and_receiver() { - let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uusd"), TestCoin::cw20("UST")]; - let mut helper = Helper::new( - &owner, - test_coins.clone(), - PoolParams::Stable(StablePoolParams { - amp: 40, - owner: None, - }), - ) - .unwrap(); - - helper - .provide_liquidity( - &owner, - &[ - helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), - helper.assets[&test_coins[1]].with_balance(100_000_000000_u128), - ], - None, - ) - .unwrap(); - - // Simulating LP tokens amount before provide - let provide_assets = [ - helper.assets[&test_coins[0]].with_balance(100_000000u128), - helper.assets[&test_coins[1]].with_balance(100_000000u128), - ]; - let sim_lp_amount = helper.simulate_provide(None, &provide_assets).unwrap(); - - let user2 = Addr::unchecked("user2"); - helper.give_me_money(&provide_assets, &user2); - // Providing with auto stake - helper - .provide_liquidity_with_slip_tolerance( - &user2, - &provide_assets, - Some(f64_to_dec(0.02)), - Some(sim_lp_amount), - true, - None, - ) - .unwrap(); - - let lp_bal = helper.query_staked_lp(&user2).unwrap(); - assert_eq!(lp_bal, sim_lp_amount); - - let user3 = Addr::unchecked("user3"); - - helper.give_me_money(&provide_assets, &user2); - // Providing with auto stake and different receiver - helper - .provide_liquidity_with_slip_tolerance( - &user2, - &provide_assets, - Some(f64_to_dec(0.02)), - Some(sim_lp_amount), - true, - Some(user3.to_string()), - ) - .unwrap(); - - let lp_bal = helper.query_staked_lp(&user3).unwrap(); - assert_eq!(lp_bal, sim_lp_amount); - - helper.give_me_money(&provide_assets, &user2); - // Providing without auto stake but with different receiver - helper - .provide_liquidity_with_slip_tolerance( - &user2, - &provide_assets, - Some(f64_to_dec(0.02)), - Some(sim_lp_amount), - false, - Some(user3.to_string()), - ) - .unwrap(); - - let lp_bal = helper.token_balance(&helper.lp_token, &user3); - assert_eq!(lp_bal, sim_lp_amount.u128()); -} - -#[test] -fn test_withdraw() { - let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uluna"), TestCoin::cw20("TEST")]; - let mut helper = Helper::new( - &owner, - test_coins.clone(), - PoolParams::Constant(XYKPoolParams { - track_asset_balances: None, - }), - ) - .unwrap(); - - helper - .provide_liquidity( - &owner, - &[ - helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), - helper.assets[&test_coins[1]].with_balance(100_000_000000_u128), - ], - None, - ) - .unwrap(); - - let owner_lp_bal = helper.token_balance(&helper.lp_token, &owner); - let mut sim_withdraw = helper.simulate_withdraw(owner_lp_bal).unwrap(); - - // user makes swap and imbalance pool after owner's withdraw simulation - let user = Addr::unchecked("user"); - let swap_asset = helper.assets[&test_coins[0]].with_balance(1000_000000_u128); - helper.give_me_money(&[swap_asset.clone()], &user); - helper - .swap(&user, &swap_asset, Some(f64_to_dec(0.5))) - .unwrap(); - - let err = helper - .withdraw_liquidity(&owner, owner_lp_bal, Some(sim_withdraw.clone())) - .unwrap_err(); - assert_eq!( - ContractError::WithdrawSlippageViolation { - asset_name: helper.assets[&test_coins[1]].to_string(), - received: 99011_385149u128.into(), - expected: 99999_999000u128.into(), - }, - err.downcast().unwrap() - ); - - // Relaxing slippage tolerance - sim_withdraw[1].amount = 99000_000000u128.into(); - helper - .withdraw_liquidity(&owner, owner_lp_bal, Some(sim_withdraw)) - .unwrap(); - - // Check withdraw with wrong number of assets fails - let provide_assets = [ - helper.assets[&test_coins[0]].with_balance(100_000000u128), - helper.assets[&test_coins[1]].with_balance(100_000000u128), - ]; - helper.give_me_money(&provide_assets, &user); - helper - .provide_liquidity(&user, &provide_assets, None) - .unwrap(); - - let user_lp_bal = helper.token_balance(&helper.lp_token, &user); - let mut sim_withdraw = helper.simulate_withdraw(user_lp_bal).unwrap(); - sim_withdraw.pop(); - let err = helper - .withdraw_liquidity(&user, user_lp_bal, Some(sim_withdraw.clone())) - .unwrap_err(); - assert_eq!( - ContractError::WrongAssetLength { - expected: 2, - actual: 1, - }, - err.downcast().unwrap() - ); - - // Adding random asset which doesn't exist in the pool - sim_withdraw.push(native_asset("random".to_string(), 1000_000000u128.into())); - let err = helper - .withdraw_liquidity(&user, user_lp_bal, Some(sim_withdraw)) - .unwrap_err(); - assert_eq!( - ContractError::AssetNotInPair("random".to_string()), - err.downcast().unwrap() - ); -} - -#[test] -fn test_onesided_provide_stable() { - let owner = Addr::unchecked("owner"); - let test_coins = vec![TestCoin::native("uusd"), TestCoin::cw20("UST")]; - let mut helper = Helper::new( - &owner, - test_coins.clone(), - PoolParams::Stable(StablePoolParams { - amp: 40, - owner: None, - }), - ) - .unwrap(); - - // initial provide must be double-sided - helper - .provide_liquidity( - &owner, - &[ - helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), - helper.assets[&test_coins[1]].with_balance(100_000_000000_u128), - ], - None, - ) - .unwrap(); - - // one-sided provide - helper - .provide_liquidity( - &owner, - &[ - helper.assets[&test_coins[0]].with_balance(100_000_000000_u128), - helper.assets[&test_coins[1]].with_balance(0u8), - ], - None, - ) - .unwrap(); -} diff --git a/contracts/periphery/liquidity_manager/tests/unit.rs b/contracts/periphery/liquidity_manager/tests/unit.rs deleted file mode 100644 index 5113acba3..000000000 --- a/contracts/periphery/liquidity_manager/tests/unit.rs +++ /dev/null @@ -1,50 +0,0 @@ -#![cfg(not(tarpaulin_include))] -use cosmwasm_std::testing::{mock_dependencies, mock_env}; -use cosmwasm_std::{Reply, SubMsgResponse, SubMsgResult}; - -use astroport_liquidity_manager::contract::reply; -use astroport_liquidity_manager::error::ContractError; -use astroport_liquidity_manager::state::{ActionParams, ReplyData, REPLY_DATA}; - -#[test] -fn test_reply() { - let mut deps = mock_dependencies(); - let env = mock_env(); - let msg = Reply { - id: 2000, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }; - - let err = reply(deps.as_mut(), env.clone(), msg).unwrap_err(); - assert_eq!(err.to_string(), "Generic error: Unsupported reply id 2000"); - - let msg = Reply { - id: 1, - result: SubMsgResult::Ok(SubMsgResponse { - events: vec![], - data: None, - }), - }; - - // Storing wrong REPLY_DATA which doesn't match reply.id = 1 - REPLY_DATA - .save( - deps.as_mut().storage, - &ReplyData { - receiver: "".to_string(), - params: ActionParams::Provide { - lp_token_addr: "".to_string(), - lp_amount_before: Default::default(), - staked_in_generator: false, - min_lp_to_receive: Default::default(), - }, - }, - ) - .unwrap(); - - let err = reply(deps.as_mut(), env, msg).unwrap_err(); - assert_eq!(err, ContractError::InvalidReplyData {}); -} diff --git a/contracts/periphery/oracle/Cargo.toml b/contracts/periphery/oracle/Cargo.toml index cf0f086bc..c48a6b390 100644 --- a/contracts/periphery/oracle/Cargo.toml +++ b/contracts/periphery/oracle/Cargo.toml @@ -37,7 +37,7 @@ cw20-base = "1.1" astroport-factory = { path = "../../factory" } astroport-pair = { path = "../../pair" } astroport-pair-stable = { path = "../../pair_stable" } -cw-multi-test = "1.0.0" +astroport-test = { path = "../../../packages/astroport_test"} itertools = "0.10" anyhow = "1.0" astroport-native-coin-registry = { path = "../../periphery/native_coin_registry" } diff --git a/contracts/periphery/oracle/tests/integration.rs b/contracts/periphery/oracle/tests/integration.rs index 8865bb608..54ea53ec6 100644 --- a/contracts/periphery/oracle/tests/integration.rs +++ b/contracts/periphery/oracle/tests/integration.rs @@ -1,12 +1,13 @@ #![cfg(not(tarpaulin_include))] use anyhow::Result; +use astroport_test::cw_multi_test::{AppBuilder, AppResponse, ContractWrapper, Executor}; +use astroport_test::modules::stargate::{MockStargate, StargateApp as App}; use cosmwasm_std::{ attr, to_json_binary, Addr, BlockInfo, Coin, Decimal, QueryRequest, StdResult, Uint128, WasmQuery, }; use cw20::{BalanceResponse, Cw20QueryMsg, MinterResponse}; -use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; use astroport::asset::{Asset, AssetInfo, PairInfo}; use astroport::token::InstantiateMsg as TokenInstantiateMsg; @@ -20,15 +21,19 @@ const OWNER: &str = "owner"; fn mock_app(owner: Option, coins: Option>) -> App { if owner.is_some() && coins.is_some() { - App::new(|router, _, storage| { - // initialization moved to App construction - router - .bank - .init_balance(storage, &owner.unwrap(), coins.unwrap()) - .unwrap() - }) + AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|router, _, storage| { + // initialization moved to App construction + router + .bank + .init_balance(storage, &owner.unwrap(), coins.unwrap()) + .unwrap() + }) } else { - App::default() + AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|_, _, _| {}) } } diff --git a/contracts/periphery/tokenfactory_tracker/Cargo.toml b/contracts/periphery/tokenfactory_tracker/Cargo.toml index 8001b0be8..064583df6 100644 --- a/contracts/periphery/tokenfactory_tracker/Cargo.toml +++ b/contracts/periphery/tokenfactory_tracker/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [features] library = [] +tests-tube = ["test-tube", "neutron-test-tube"] [lib] crate-type = ["cdylib", "rlib"] @@ -15,10 +16,12 @@ cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1"] } cw-storage-plus.workspace = true cosmwasm-schema.workspace = true thiserror.workspace = true -astroport = { path = "../../../packages/astroport", version = "4" } +astroport = "4" + +#Optional dependencies +neutron-test-tube = { git = "https://github.com/j0nl1/neutron-test-tube", optional = true } +test-tube = { git = "https://github.com/j0nl1/neutron-test-tube", optional = true } [dev-dependencies] cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks", features = ["cosmwasm_1_1"] } neutron-sdk = "0.8.0" -neutron-test-tube = { git = "https://github.com/j0nl1/neutron-test-tube" } -test-tube = { git = "https://github.com/j0nl1/neutron-test-tube" } diff --git a/contracts/periphery/tokenfactory_tracker/tests/tube-based-e2e.rs b/contracts/periphery/tokenfactory_tracker/tests/tube-based-e2e.rs index 25c7e5d73..0ae36c56e 100644 --- a/contracts/periphery/tokenfactory_tracker/tests/tube-based-e2e.rs +++ b/contracts/periphery/tokenfactory_tracker/tests/tube-based-e2e.rs @@ -1,3 +1,7 @@ +#![cfg(not(tarpaulin_include))] +#![cfg(feature = "tests-tube")] +#![allow(dead_code)] + use std::collections::HashMap; use cosmwasm_std::{coin, Uint128}; diff --git a/contracts/router/Cargo.toml b/contracts/router/Cargo.toml index f40c30207..a030dd2ad 100644 --- a/contracts/router/Cargo.toml +++ b/contracts/router/Cargo.toml @@ -38,5 +38,5 @@ cosmwasm-schema.workspace = true astroport-factory = { path = "../factory" } cw20-base = "1.1" astroport-pair = { path = "../pair" } +astroport-test= { path = "../../packages/astroport_test" } anyhow = "1.0" -cw-multi-test = "1.0.0" diff --git a/contracts/router/tests/factory_helper.rs b/contracts/router/tests/factory_helper.rs index 261d7900e..9ad6eee6d 100644 --- a/contracts/router/tests/factory_helper.rs +++ b/contracts/router/tests/factory_helper.rs @@ -3,10 +3,11 @@ use anyhow::Result as AnyResult; use cosmwasm_std::{coins, Addr, Binary}; use cw20::MinterResponse; -use cw_multi_test::{App, AppResponse, ContractWrapper, Executor}; use astroport::asset::{AssetInfo, PairInfo}; use astroport::factory::{PairConfig, PairType, QueryMsg}; +use astroport_test::cw_multi_test::{AppResponse, ContractWrapper, Executor}; +use astroport_test::modules::stargate::StargateApp as App; pub struct FactoryHelper { pub owner: Addr, diff --git a/contracts/router/tests/router_integration.rs b/contracts/router/tests/router_integration.rs index 67541b6bf..bce650268 100644 --- a/contracts/router/tests/router_integration.rs +++ b/contracts/router/tests/router_integration.rs @@ -2,12 +2,13 @@ use cosmwasm_std::{coins, from_json, to_json_binary, Addr, Empty, StdError}; use cw20::Cw20ExecuteMsg; -use cw_multi_test::{App, Contract, ContractWrapper, Executor}; use astroport::asset::{native_asset_info, token_asset_info}; use astroport::factory::PairType; use astroport::router::{ExecuteMsg, InstantiateMsg, SwapOperation, SwapResponseData}; use astroport_router::error::ContractError; +use astroport_test::cw_multi_test::{AppBuilder, Contract, ContractWrapper, Executor}; +use astroport_test::modules::stargate::{MockStargate, StargateApp as App}; use crate::factory_helper::{instantiate_token, mint, mint_native, FactoryHelper}; @@ -24,9 +25,15 @@ fn router_contract() -> Box> { ) } +fn mock_app() -> App { + AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|_, _, _| {}) +} + #[test] fn router_does_not_enforce_spread_assertion() { - let mut app = App::default(); + let mut app = mock_app(); let owner = Addr::unchecked("owner"); let mut helper = FactoryHelper::init(&mut app, &owner); @@ -133,7 +140,7 @@ fn router_does_not_enforce_spread_assertion() { #[test] fn route_through_pairs_with_natives() { - let mut app = App::default(); + let mut app = mock_app(); let owner = Addr::unchecked("owner"); let mut helper = FactoryHelper::init(&mut app, &owner); @@ -316,7 +323,7 @@ fn test_swap_route() { }; use cosmwasm_std::{to_json_binary, Addr, Uint128}; use cw20::{BalanceResponse, Cw20ExecuteMsg, Cw20QueryMsg}; - let mut app = App::default(); + let mut app = mock_app(); let owner = Addr::unchecked("owner"); let mut helper = FactoryHelper::init(&mut app, &owner); let astro = instantiate_token(&mut app, helper.cw20_token_code_id, &owner, "astro", None); diff --git a/contracts/tokenomics/incentives/Cargo.toml b/contracts/tokenomics/incentives/Cargo.toml index 76c0cf3f0..ad41b7edb 100644 --- a/contracts/tokenomics/incentives/Cargo.toml +++ b/contracts/tokenomics/incentives/Cargo.toml @@ -21,12 +21,11 @@ cosmwasm-schema.workspace = true cw2.workspace = true cw20 = "1" cw-utils.workspace = true -astroport = { path = "../../../packages/astroport", version = "4" } +astroport = "4" thiserror.workspace = true itertools.workspace = true [dev-dependencies] -cw-multi-test = "1.0.0" astroport-vesting_131 = { package = "astroport-vesting", version = "=1.3.1", features = ["library"] } astro-token-converter = { path = "../../periphery/astro_converter", version = "1.0", features = ["library"] } anyhow = "1" @@ -35,5 +34,6 @@ astroport-pair = { path = "../../pair" } astroport-pair-stable = { path = "../../pair_stable" } astroport-native-coin-registry = { path = "../../periphery/native_coin_registry" } astroport-vesting = { path = "../vesting" } +astroport-test = { path = "../../../packages/astroport_test" } cw20-base = "1" proptest = "1.3" diff --git a/contracts/tokenomics/incentives/src/state.rs b/contracts/tokenomics/incentives/src/state.rs index 6eca11864..bee43b869 100644 --- a/contracts/tokenomics/incentives/src/state.rs +++ b/contracts/tokenomics/incentives/src/state.rs @@ -15,7 +15,7 @@ use crate::error::ContractError; use crate::traits::RewardInfoExt; use crate::utils::asset_info_key; -/// General generator contract settings +/// General Incentives contract settings pub const CONFIG: Item = Item::new("config"); /// Contains a proposal to change contract ownership. diff --git a/contracts/tokenomics/incentives/tests/helper/helper.rs b/contracts/tokenomics/incentives/tests/helper/helper.rs index ad83c91b6..a0af67360 100644 --- a/contracts/tokenomics/incentives/tests/helper/helper.rs +++ b/contracts/tokenomics/incentives/tests/helper/helper.rs @@ -4,17 +4,18 @@ use std::collections::hash_map::Entry; use std::collections::HashMap; use anyhow::Result as AnyResult; +use astroport_test::cw_multi_test::{ + AddressGenerator, App, AppBuilder, AppResponse, BankKeeper, Contract, ContractWrapper, + DistributionKeeper, Executor, FailingModule, StakeKeeper, WasmKeeper, +}; +use astroport_test::modules::stargate::MockStargate; use cosmwasm_std::testing::{mock_env, MockApi, MockStorage}; use cosmwasm_std::{ coin, to_json_binary, Addr, Api, BlockInfo, CanonicalAddr, Coin, Decimal256, Empty, Env, - IbcMsg, IbcQuery, RecoverPubkeyError, StdError, StdResult, Storage, Timestamp, Uint128, + GovMsg, IbcMsg, IbcQuery, RecoverPubkeyError, StdError, StdResult, Storage, Timestamp, Uint128, VerificationError, }; use cw20::MinterResponse; -use cw_multi_test::{ - AddressGenerator, App, AppBuilder, AppResponse, BankKeeper, Contract, ContractWrapper, - DistributionKeeper, Executor, FailingModule, StakeKeeper, WasmKeeper, -}; use itertools::Itertools; use crate::helper::broken_cw20; @@ -242,6 +243,8 @@ pub type TestApp = App< StakeKeeper, DistributionKeeper, FailingModule, + FailingModule, + MockStargate, >; pub struct Helper { @@ -258,6 +261,7 @@ pub struct Helper { impl Helper { pub fn new(owner: &str, astro: &AssetInfo, with_old_vesting: bool) -> AnyResult { let mut app = AppBuilder::new() + .with_stargate(MockStargate::default()) .with_wasm(WasmKeeper::new().with_address_generator(TestAddr)) .with_api(TestApi::new()) .with_block(BlockInfo { diff --git a/contracts/tokenomics/incentives/tests/incentives_integration_tests.rs b/contracts/tokenomics/incentives/tests/incentives_integration_tests.rs index ff22fd678..11af12886 100644 --- a/contracts/tokenomics/incentives/tests/incentives_integration_tests.rs +++ b/contracts/tokenomics/incentives/tests/incentives_integration_tests.rs @@ -1,7 +1,6 @@ use std::str::FromStr; use cosmwasm_std::{coin, coins, Decimal256, Timestamp, Uint128}; -use cw_multi_test::Executor; use astroport::asset::{native_asset_info, AssetInfo, AssetInfoExt}; use astroport::incentives::{ @@ -9,6 +8,7 @@ use astroport::incentives::{ MAX_REWARD_TOKENS, }; use astroport_incentives::error::ContractError; +use astroport_test::cw_multi_test::Executor; use crate::helper::{assert_rewards, dec256_to_u128_floor, Helper, TestAddr}; @@ -18,100 +18,102 @@ mod helper; fn test_stake_unstake() { let astro = native_asset_info("astro".to_string()); let mut helper = Helper::new("owner", &astro, false).unwrap(); + let asset_infos = [AssetInfo::native("foo"), AssetInfo::native("bar")]; + let pair_info = helper.create_pair(&asset_infos).unwrap(); + let lp_token = pair_info.liquidity_token.to_string(); let user = TestAddr::new("user"); - // ##### Check native LPs - // TODO: build token factory based pair and test the lines below - - // let native_lp = native_asset_info("lp_token".to_string()).with_balance(1000u16); - // helper.mint_coins(&user, vec![native_lp.as_coin().unwrap()]); - // - // helper.stake(&user, native_lp).unwrap(); - // - // helper.unstake(&user, "lp_token", 500).unwrap(); - // - // // Unstake more than staked - // let err = helper.unstake(&user, "lp_token", 10000).unwrap_err(); - // assert_eq!( - // err.downcast::().unwrap(), - // ContractError::AmountExceedsBalance { - // available: 500u16.into(), - // withdraw_amount: 10000u16.into() - // } - // ); - // - // // Unstake non-existing LP token - // let err = helper - // .unstake(&user, "non_existing_lp_token", 10000) - // .unwrap_err(); - // assert_eq!( - // err.downcast::().unwrap(), - // ContractError::PositionDoesntExist { - // user: user.to_string(), - // lp_token: "non_existing_lp_token".to_string() - // } - // ); - // - // helper.unstake(&user, "lp_token", 500).unwrap(); - - // ##### Check cw20 LPs + let native_lp = native_asset_info(lp_token.to_string()).with_balance(10000u16); + helper.mint_coin(&user, &native_lp.as_coin().unwrap()); - let asset_infos = [AssetInfo::native("uusd"), AssetInfo::native("ueur")]; - let pair_info = helper.create_pair(&asset_infos).unwrap(); - let provide_assets = [ - asset_infos[0].with_balance(100000u64), - asset_infos[1].with_balance(100000u64), - ]; - helper - .provide_liquidity(&user, &provide_assets, &pair_info.contract_addr, false) - .unwrap(); + helper.stake(&user, native_lp).unwrap(); - let cw20_lp = AssetInfo::cw20(pair_info.liquidity_token.clone()); - let initial_lp_balance = cw20_lp.query_pool(&helper.app.wrap(), &user).unwrap(); helper - .stake(&user, cw20_lp.with_balance(initial_lp_balance)) + .unstake(&user, &lp_token.to_string(), 500u128) .unwrap(); - let lp_balance = cw20_lp.query_pool(&helper.app.wrap(), &user).unwrap(); - assert_eq!(lp_balance.u128(), 0); // Unstake more than staked + let err = helper.unstake(&user, &lp_token, 10000u128).unwrap_err(); + assert_eq!( + err.downcast::().unwrap(), + ContractError::AmountExceedsBalance { + available: 9500u16.into(), + withdraw_amount: 10000u16.into() + } + ); + + // Unstake non-existing LP token let err = helper - .unstake( - &user, - pair_info.liquidity_token.as_str(), - initial_lp_balance + Uint128::one(), - ) + .unstake(&user, "non_existing_lp_token", 10000u128) .unwrap_err(); assert_eq!( err.downcast::().unwrap(), - ContractError::AmountExceedsBalance { - available: initial_lp_balance, - withdraw_amount: initial_lp_balance + Uint128::one() + ContractError::PositionDoesntExist { + user: user.to_string(), + lp_token: "non_existing_lp_token".to_string() } ); - // Unstake half - helper - .unstake( - &user, - pair_info.liquidity_token.as_str(), - initial_lp_balance.u128() / 2, - ) - .unwrap(); - let lp_balance = cw20_lp.query_pool(&helper.app.wrap(), &user).unwrap(); - assert_eq!(lp_balance.u128(), initial_lp_balance.u128() / 2); + helper.unstake(&user, &lp_token, 500u128).unwrap(); - // Unstake the rest - helper - .unstake( - &user, - pair_info.liquidity_token.as_str(), - initial_lp_balance.u128() / 2, - ) - .unwrap(); - let lp_balance = cw20_lp.query_pool(&helper.app.wrap(), &user).unwrap(); - assert_eq!(lp_balance, initial_lp_balance); + // ##### Check cw20 LPs + + // let asset_infos = [AssetInfo::native("uusd"), AssetInfo::native("ueur")]; + // let pair_info = helper.create_pair(&asset_infos).unwrap(); + // let provide_assets = [ + // asset_infos[0].with_balance(100000u64), + // asset_infos[1].with_balance(100000u64), + // ]; + // helper + // .provide_liquidity(&user, &provide_assets, &pair_info.contract_addr, false) + // .unwrap(); + + // let cw20_lp = AssetInfo::cw20(pair_info.liquidity_token.clone()); + // let initial_lp_balance = cw20_lp.query_pool(&helper.app.wrap(), &user).unwrap(); + // helper + // .stake(&user, cw20_lp.with_balance(initial_lp_balance)) + // .unwrap(); + // let lp_balance = cw20_lp.query_pool(&helper.app.wrap(), &user).unwrap(); + // assert_eq!(lp_balance.u128(), 0); + + // // Unstake more than staked + // let err = helper + // .unstake( + // &user, + // pair_info.liquidity_token.as_str(), + // initial_lp_balance + Uint128::one(), + // ) + // .unwrap_err(); + // assert_eq!( + // err.downcast::().unwrap(), + // ContractError::AmountExceedsBalance { + // available: initial_lp_balance, + // withdraw_amount: initial_lp_balance + Uint128::one() + // } + // ); + + // // Unstake half + // helper + // .unstake( + // &user, + // pair_info.liquidity_token.as_str(), + // initial_lp_balance.u128() / 2, + // ) + // .unwrap(); + // let lp_balance = cw20_lp.query_pool(&helper.app.wrap(), &user).unwrap(); + // assert_eq!(lp_balance.u128(), initial_lp_balance.u128() / 2); + + // // Unstake the rest + // helper + // .unstake( + // &user, + // pair_info.liquidity_token.as_str(), + // initial_lp_balance.u128() / 2, + // ) + // .unwrap(); + // let lp_balance = cw20_lp.query_pool(&helper.app.wrap(), &user).unwrap(); + // assert_eq!(lp_balance, initial_lp_balance); } #[test] @@ -144,14 +146,14 @@ fn test_claim_rewards() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); for staker in stakers { let staker_addr = TestAddr::new(staker); - // Pool doesn't exist in Generator yet + // Pool doesn't exist in incentives contract yet let astro_before = astro.query_pool(&helper.app.wrap(), &staker_addr).unwrap(); helper .claim_rewards(&staker_addr, vec![pair_info.liquidity_token.to_string()]) @@ -312,7 +314,7 @@ fn test_incentives() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -447,7 +449,7 @@ fn test_cw20_incentives() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -539,7 +541,7 @@ fn test_large_incentives() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -600,7 +602,7 @@ fn test_multiple_schedules_same_reward() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -711,7 +713,7 @@ fn test_multiple_schedules_different_reward() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -853,7 +855,7 @@ fn test_claim_between_different_periods() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -939,7 +941,7 @@ fn test_astro_external_reward() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -1035,7 +1037,7 @@ fn test_astro_protocol_reward_if_denom_changed() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -1210,8 +1212,8 @@ fn test_blocked_tokens() { ) ); - // Create pair with blocked token 'blk' and stake in Generator. - // Generator should allow it. + // Create pair with blocked token 'blk' and stake in incentives contract. + // Incentives should allow it. let blk_pair_info = helper .create_pair(&[tokens[0].clone(), tokens[2].clone()]) .unwrap(); @@ -1476,7 +1478,7 @@ fn test_remove_rewards() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -1592,7 +1594,7 @@ fn test_long_unclaimed_rewards() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -2002,7 +2004,7 @@ fn test_incentive_without_funds() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); let bank = TestAddr::new("bank"); @@ -2056,13 +2058,13 @@ fn test_claim_excess_rewards() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); for staker in stakers { let staker_addr = TestAddr::new(staker); - // Pool doesn't exist in Generator yet + // Pool doesn't exist in incentives yet let astro_before = astro.query_pool(&helper.app.wrap(), &staker_addr).unwrap(); helper .claim_rewards( @@ -2139,7 +2141,7 @@ fn test_user_claim_less() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -2239,7 +2241,7 @@ fn test_broken_cw20_incentives() { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); diff --git a/contracts/tokenomics/incentives/tests/incentives_simulations.rs b/contracts/tokenomics/incentives/tests/incentives_simulations.rs index c68638973..a687ccb76 100644 --- a/contracts/tokenomics/incentives/tests/incentives_simulations.rs +++ b/contracts/tokenomics/incentives/tests/incentives_simulations.rs @@ -3,7 +3,7 @@ extern crate core; use std::collections::{HashMap, HashSet}; -use cosmwasm_std::{Addr, StdError, Timestamp}; +use cosmwasm_std::{StdError, Timestamp}; use itertools::Itertools; use proptest::prelude::*; @@ -142,7 +142,7 @@ fn simulate_case(events: Vec<(Event, u64)>) { &owner, &provide_assets, &pair_info.contract_addr, - false, // Owner doesn't stake in generator + false, // Owner doesn't stake in incentives ) .unwrap(); @@ -181,7 +181,7 @@ fn simulate_case(events: Vec<(Event, u64)>) { } => { let user = &users[sender_id as usize]; let lp_token = &lp_tokens[lp_token_id as usize]; - let lp_asset_info = AssetInfo::cw20(Addr::unchecked(lp_token)); + let lp_asset_info = AssetInfo::native(lp_token); let total_amount = lp_asset_info.query_pool(&helper.app.wrap(), user).unwrap(); let part = total_amount.u128() * amount as u128 / 100; diff --git a/contracts/tokenomics/maker/Cargo.toml b/contracts/tokenomics/maker/Cargo.toml index 35f18f61a..6f1a4ac5f 100644 --- a/contracts/tokenomics/maker/Cargo.toml +++ b/contracts/tokenomics/maker/Cargo.toml @@ -29,7 +29,7 @@ cosmwasm-std.workspace = true cw2.workspace = true cw20 = "1" cw-storage-plus.workspace = true -astroport = { path = "../../../packages/astroport", version = "4" } +astroport = "4" thiserror.workspace = true cosmwasm-schema.workspace = true astro-satellite-package = { git = "https://github.com/astroport-fi/astroport_ibc", version = "1" } @@ -38,6 +38,6 @@ astro-satellite-package = { git = "https://github.com/astroport-fi/astroport_ibc cw20-base = "1" astroport-factory = { path = "../../factory" } astroport-pair = { path = "../../pair" } -cw-multi-test = "1.0.0" +astroport-test = { path = "../../../packages/astroport_test" } astroport-pair-stable = { path = "../../pair_stable" } astroport-native-coin-registry = { path = "../../periphery/native_coin_registry" } diff --git a/contracts/tokenomics/maker/tests/maker_integration.rs b/contracts/tokenomics/maker/tests/maker_integration.rs index 60d861753..c053da2b6 100644 --- a/contracts/tokenomics/maker/tests/maker_integration.rs +++ b/contracts/tokenomics/maker/tests/maker_integration.rs @@ -2,12 +2,13 @@ use std::str::FromStr; +use astroport_test::cw_multi_test::{next_block, AppBuilder, Contract, ContractWrapper, Executor}; +use astroport_test::modules::stargate::{MockStargate, StargateApp as TestApp}; use cosmwasm_std::{ attr, coin, to_json_binary, Addr, Binary, Coin, Decimal, Deps, DepsMut, Empty, Env, MessageInfo, QueryRequest, Response, StdResult, Uint128, Uint64, WasmQuery, }; use cw20::{BalanceResponse, Cw20QueryMsg, MinterResponse}; -use cw_multi_test::{next_block, App, Contract, ContractWrapper, Executor}; use astroport::asset::{ native_asset, native_asset_info, token_asset, token_asset_info, Asset, AssetInfo, PairInfo, @@ -22,11 +23,13 @@ use cw20_base::msg::InstantiateMsg as TokenInstantiateMsg; const OWNER: &str = "owner"; -fn mock_app(owner: Addr, coins: Vec) -> App { - let mut app = App::new(|router, _, storage| { - // initialization moved to App construction - router.bank.init_balance(storage, &owner, coins).unwrap(); - }); +fn mock_app(owner: Addr, coins: Vec) -> TestApp { + let mut app = AppBuilder::new_custom() + .with_stargate(MockStargate::default()) + .build(|router, _, storage| { + // initialization moved to App construction + router.bank.init_balance(storage, &owner, coins).unwrap(); + }); app.update_block(|bi| { bi.height += 1; @@ -36,7 +39,12 @@ fn mock_app(owner: Addr, coins: Vec) -> App { app } -fn validate_and_send_funds(router: &mut App, sender: &Addr, recipient: &Addr, funds: Vec) { +fn validate_and_send_funds( + router: &mut TestApp, + sender: &Addr, + recipient: &Addr, + funds: Vec, +) { for fund in funds.clone() { // we cannot transfer zero coins if !fund.amount.is_zero() { @@ -47,7 +55,7 @@ fn validate_and_send_funds(router: &mut App, sender: &Addr, recipient: &Addr, fu } } -fn store_coin_registry_code(app: &mut App) -> u64 { +fn store_coin_registry_code(app: &mut TestApp) -> u64 { let coin_registry_contract = Box::new(ContractWrapper::new_with_empty( astroport_native_coin_registry::contract::execute, astroport_native_coin_registry::contract::instantiate, @@ -57,7 +65,7 @@ fn store_coin_registry_code(app: &mut App) -> u64 { app.store_code(coin_registry_contract) } -fn instantiate_coin_registry(mut app: &mut App, coins: Option>) -> Addr { +fn instantiate_coin_registry(mut app: &mut TestApp, coins: Option>) -> Addr { let coin_registry_id = store_coin_registry_code(&mut app); let coin_registry_address = app .instantiate_contract( @@ -104,7 +112,7 @@ fn mock_fee_distributor_contract() -> Box> { } fn instantiate_contracts( - mut router: &mut App, + mut router: &mut TestApp, owner: Addr, staking: Addr, governance_percent: Uint64, @@ -265,7 +273,7 @@ fn instantiate_contracts( ) } -fn instantiate_token(router: &mut App, owner: Addr, name: String, symbol: String) -> Addr { +fn instantiate_token(router: &mut TestApp, owner: Addr, name: String, symbol: String) -> Addr { let token_contract = Box::new(ContractWrapper::new_with_empty( cw20_base::contract::execute, cw20_base::contract::instantiate, @@ -299,7 +307,13 @@ fn instantiate_token(router: &mut App, owner: Addr, name: String, symbol: String token_instance } -fn mint_some_token(router: &mut App, owner: Addr, token_instance: Addr, to: Addr, amount: Uint128) { +fn mint_some_token( + router: &mut TestApp, + owner: Addr, + token_instance: Addr, + to: Addr, + amount: Uint128, +) { let msg = cw20::Cw20ExecuteMsg::Mint { recipient: to.to_string(), amount, @@ -312,7 +326,7 @@ fn mint_some_token(router: &mut App, owner: Addr, token_instance: Addr, to: Addr assert_eq!(res.events[1].attributes[3], attr("amount", amount)); } -fn allowance_token(router: &mut App, owner: Addr, spender: Addr, token: Addr, amount: Uint128) { +fn allowance_token(router: &mut TestApp, owner: Addr, spender: Addr, token: Addr, amount: Uint128) { let msg = cw20::Cw20ExecuteMsg::IncreaseAllowance { spender: spender.to_string(), amount, @@ -336,7 +350,7 @@ fn allowance_token(router: &mut App, owner: Addr, spender: Addr, token: Addr, am assert_eq!(res.events[1].attributes[4], attr("amount", amount)); } -fn check_balance(router: &mut App, user: Addr, token: Addr, expected_amount: Uint128) { +fn check_balance(router: &mut TestApp, user: Addr, token: Addr, expected_amount: Uint128) { let msg = Cw20QueryMsg::Balance { address: user.to_string(), }; @@ -353,7 +367,7 @@ fn check_balance(router: &mut App, user: Addr, token: Addr, expected_amount: Uin } fn create_pair( - mut router: &mut App, + mut router: &mut TestApp, owner: Addr, user: Addr, factory_instance: &Addr, @@ -663,7 +677,7 @@ fn update_config() { } fn test_maker_collect( - mut router: App, + mut router: TestApp, owner: Addr, factory_instance: Addr, maker_instance: Addr, @@ -2075,7 +2089,7 @@ struct CheckDistributedAstro { } impl CheckDistributedAstro { - fn check(&mut self, router: &mut App, distributed_amount: u32) { + fn check(&mut self, router: &mut TestApp, distributed_amount: u32) { let distributed_amount = Uint128::from(distributed_amount as u128); let cur_governance_amount = distributed_amount .multiply_ratio(Uint128::from(self.governance_percent), Uint128::new(100)); diff --git a/contracts/tokenomics/staking/Cargo.toml b/contracts/tokenomics/staking/Cargo.toml index 8cdc4de2a..b9ac4b223 100644 --- a/contracts/tokenomics/staking/Cargo.toml +++ b/contracts/tokenomics/staking/Cargo.toml @@ -28,7 +28,7 @@ cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1"] } cw-storage-plus.workspace = true thiserror.workspace = true cw2.workspace = true -astroport = { path = "../../../packages/astroport", version = "4" } +astroport = "4" cw-utils.workspace = true osmosis-std = "0.21.0" diff --git a/contracts/tokenomics/vesting/Cargo.toml b/contracts/tokenomics/vesting/Cargo.toml index 1abd4e04e..e809aad56 100644 --- a/contracts/tokenomics/vesting/Cargo.toml +++ b/contracts/tokenomics/vesting/Cargo.toml @@ -21,7 +21,7 @@ cw2.workspace = true cw20 = "1.1" cosmwasm-std.workspace = true cw-storage-plus.workspace = true -astroport = { path = "../../../packages/astroport", version = "4" } +astroport = "4" thiserror.workspace = true cw-utils.workspace = true cosmwasm-schema.workspace = true diff --git a/packages/astroport/Cargo.toml b/packages/astroport/Cargo.toml index 6d2988a08..3307c8b52 100644 --- a/packages/astroport/Cargo.toml +++ b/packages/astroport/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "astroport" -version = "4.0.3" +version = "5.0.0" authors = ["Astroport"] edition = "2021" description = "Common Astroport types, queriers and other utils" @@ -15,10 +15,11 @@ homepage = "https://astroport.fi" # for more explicit tests, cargo test --features=backtraces backtraces = ["cosmwasm-std/backtraces"] injective = ["injective-math", "thiserror"] +sei = [] [dependencies] cw20 = "1.1" -cosmwasm-std.workspace = true +cosmwasm-std = { workspace = true, features = ["cosmwasm_1_1", "stargate"] } uint = "0.9" cw-storage-plus.workspace = true itertools.workspace = true @@ -26,6 +27,8 @@ cosmwasm-schema.workspace = true astroport-circular-buffer = { version = "0.2", path = "../circular_buffer" } cw-utils.workspace = true cw-asset = "3.0.0" +prost = "0.11.5" +cosmos-sdk-proto = { version = "0.19.0", default-features = false } # optional injective-math = { version = "0.1", optional = true } diff --git a/packages/astroport/README.md b/packages/astroport/README.md index 461bc7a89..85345a0e1 100644 --- a/packages/astroport/README.md +++ b/packages/astroport/README.md @@ -34,7 +34,7 @@ It is used to represent response data coming from a [Pair-Info-Querier](#Pair-In pub struct PairInfo { pub asset_infos: [AssetInfo; 2], pub contract_addr: Addr, - pub liquidity_token: Addr, + pub liquidity_token: String, pub pair_type: PairType, } ``` diff --git a/packages/astroport/src/asset.rs b/packages/astroport/src/asset.rs index 36b7370d7..c721b1d0f 100644 --- a/packages/astroport/src/asset.rs +++ b/packages/astroport/src/asset.rs @@ -603,8 +603,8 @@ pub struct PairInfo { pub asset_infos: Vec, /// Pair contract address pub contract_addr: Addr, - /// Pair LP token address - pub liquidity_token: Addr, + /// Pair LP token denom + pub liquidity_token: String, /// The pool type (xyk, stableswap etc) available in [`PairType`] pub pair_type: PairType, } diff --git a/packages/astroport/src/factory.rs b/packages/astroport/src/factory.rs index ec36127c0..7d996cbc0 100644 --- a/packages/astroport/src/factory.rs +++ b/packages/astroport/src/factory.rs @@ -14,11 +14,11 @@ pub struct Config { pub owner: Addr, /// CW20 token contract code identifier pub token_code_id: u64, - /// Generator contract address + /// Incentives contract address pub generator_address: Option, /// Contract address to send governance fees to (the Maker contract) pub fee_address: Option, - /// CW1 whitelist contract code id used to store 3rd party generator staking rewards + /// CW1 whitelist contract code id used to store 3rd party incentives staking rewards pub whitelist_code_id: u64, /// The address of the contract that contains the coins with their precision pub coin_registry_address: Addr, @@ -105,6 +105,8 @@ pub struct InstantiateMsg { pub whitelist_code_id: u64, /// The address of the contract that contains the coins and their accuracy pub coin_registry_address: String, + /// Config for the tracking contract + pub tracker_config: Option, } /// This structure describes the execute messages of the contract. @@ -123,6 +125,12 @@ pub enum ExecuteMsg { /// The address of the contract that contains the coins and their accuracy coin_registry_address: Option, }, + UpdateTrackerConfig { + /// Tracking contract code id + tracker_code_id: u64, + /// Token factory module address + token_factory_addr: Option, + }, /// UpdatePairConfig updates the config for a pair type. UpdatePairConfig { /// New [`PairConfig`] settings for a pair type @@ -186,6 +194,13 @@ pub enum QueryMsg { /// Returns a vector that contains blacklisted pair types #[returns(Vec)] BlacklistedPairTypes {}, + #[returns(TrackerConfig)] + TrackerConfig {}, +} + +#[cw_serde] +pub struct MigrateMsg { + pub tracker_config: Option, } /// A custom struct for each query response that returns general contract settings/configs. @@ -233,3 +248,11 @@ pub enum UpdateAddr { /// Removes a contract address. Remove {}, } + +#[cw_serde] +pub struct TrackerConfig { + /// Tracking contract code id + pub code_id: u64, + /// Token factory module address + pub token_factory_addr: String, +} diff --git a/packages/astroport/src/incentives.rs b/packages/astroport/src/incentives.rs index 8805081ca..bf28d2175 100644 --- a/packages/astroport/src/incentives.rs +++ b/packages/astroport/src/incentives.rs @@ -309,7 +309,7 @@ pub struct Config { pub enum RewardType { /// Internal rewards aka ASTRO emissions don't have next_update_ts field and they are paid out from Vesting contract. Int(AssetInfo), - /// External rewards always have corresponding schedules. Reward is paid out from Generator contract balance. + /// External rewards always have corresponding schedules. Reward is paid out from Incentives contract balance. Ext { info: AssetInfo, /// Time when next schedule should start diff --git a/packages/astroport/src/lib.rs b/packages/astroport/src/lib.rs index 8ce3387d6..15c902eef 100644 --- a/packages/astroport/src/lib.rs +++ b/packages/astroport/src/lib.rs @@ -23,6 +23,7 @@ pub mod restricted_vector; pub mod router; pub mod staking; pub mod token; +pub mod token_factory; pub mod tokenfactory_tracker; pub mod vesting; pub mod xastro_token; @@ -32,7 +33,6 @@ mod mock_querier; pub mod astro_converter; pub mod incentives; -pub mod liquidity_manager; #[cfg(test)] mod testing; diff --git a/packages/astroport/src/liquidity_manager.rs b/packages/astroport/src/liquidity_manager.rs deleted file mode 100644 index e2f78f6bc..000000000 --- a/packages/astroport/src/liquidity_manager.rs +++ /dev/null @@ -1,80 +0,0 @@ -use cosmwasm_schema::{cw_serde, QueryResponses}; -use cosmwasm_std::{Addr, Uint128}; -use cw20::Cw20ReceiveMsg; - -use crate::asset::{Asset, AssetInfo, PairInfo}; -use crate::pair::{Cw20HookMsg as PairCw20HookMsg, ExecuteMsg as PairExecuteMsg, FeeShareConfig}; - -#[cw_serde] -pub struct InstantiateMsg { - pub astroport_factory: String, -} - -#[cw_serde] -pub enum ExecuteMsg { - ProvideLiquidity { - pair_addr: String, - pair_msg: PairExecuteMsg, - min_lp_to_receive: Option, - }, - Receive(Cw20ReceiveMsg), -} - -/// This structure describes a CW20 hook message. -#[cw_serde] -pub enum Cw20HookMsg { - WithdrawLiquidity { - pair_msg: PairCw20HookMsg, - #[serde(default)] - min_assets_to_receive: Vec, - }, -} - -#[cw_serde] -#[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(Uint128)] - SimulateProvide { - pair_addr: String, - pair_msg: PairExecuteMsg, - }, - #[returns(Vec)] - SimulateWithdraw { - pair_addr: String, - lp_tokens: Uint128, - }, -} - -/// Stable swap config which is used in raw queries. It's compatible with v1, v2 and v3 stable pair contract. -#[cw_serde] -pub struct CompatPairStableConfig { - /// The contract owner - pub owner: Option, - /// The pair information stored in a [`PairInfo`] struct - pub pair_info: PairInfo, - /// The factory contract address - pub factory_addr: Addr, - /// The last timestamp when the pair contract update the asset cumulative prices - pub block_time_last: u64, - /// This is the current amplification used in the pool - pub init_amp: u64, - /// This is the start time when amplification starts to scale up or down - pub init_amp_time: u64, - /// This is the target amplification to reach at `next_amp_time` - pub next_amp: u64, - /// This is the timestamp when the current pool amplification should be `next_amp` - pub next_amp_time: u64, - - // Fields below are added for compatability with v1 and v2 - /// The greatest precision of assets in the pool - pub greatest_precision: Option, - /// The vector contains cumulative prices for each pair of assets in the pool - #[serde(default)] - pub cumulative_prices: Vec<(AssetInfo, AssetInfo, Uint128)>, - /// The last cumulative price 0 asset in pool - pub price0_cumulative_last: Option, - /// The last cumulative price 1 asset in pool - pub price1_cumulative_last: Option, - // Fee sharing configuration - pub fee_share: Option, -} diff --git a/packages/astroport/src/pair.rs b/packages/astroport/src/pair.rs index 871fbe49e..0369e16ea 100644 --- a/packages/astroport/src/pair.rs +++ b/packages/astroport/src/pair.rs @@ -3,7 +3,7 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; use crate::asset::{Asset, AssetInfo, PairInfo}; -use cosmwasm_std::{Addr, Binary, Decimal, Decimal256, Uint128, Uint64}; +use cosmwasm_std::{Addr, Binary, Decimal, Decimal256, StdError, Uint128, Uint64}; use cw20::Cw20ReceiveMsg; /// The default swap slippage @@ -44,10 +44,17 @@ pub enum ExecuteMsg { assets: Vec, /// The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much slippage_tolerance: Option, - /// Determines whether the LP tokens minted for the user is auto_staked in the Generator contract + /// Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract auto_stake: Option, /// The receiver of LP tokens receiver: Option, + min_lp_to_receive: Option, + }, + /// WithdrawLiquidity allows someone to withdraw liquidity from the pool + WithdrawLiquidity { + #[serde(default)] + assets: Vec, + min_assets_to_receive: Option>, }, /// Swap performs a swap in the pool Swap { @@ -83,11 +90,6 @@ pub enum Cw20HookMsg { max_spread: Option, to: Option, }, - /// Withdraw liquidity from the pool - WithdrawLiquidity { - #[serde(default)] - assets: Vec, - }, } /// This structure describes the query messages available in the contract. @@ -133,6 +135,15 @@ pub enum QueryMsg { /// Query price from observations #[returns(OracleObservation)] Observe { seconds_ago: u64 }, + /// Returns an estimation of assets received for the given amount of LP tokens + #[returns(Vec)] + SimulateWithdraw { lp_amount: Uint128 }, + /// Returns an estimation of shares received for the given amount of assets + #[returns(Uint128)] + SimulateProvide { + assets: Vec, + slippage_tolerance: Option, + }, } /// This struct is used to return a query result with the total amount of LP tokens and assets in a specific pool. @@ -155,6 +166,8 @@ pub struct ConfigResponse { pub owner: Addr, /// The factory contract address pub factory_addr: Addr, + /// Tracker contract address + pub tracker_addr: Option, } /// Holds the configuration for fee sharing @@ -225,8 +238,6 @@ pub struct XYKPoolConfig { /// This enum stores the option available to enable asset balances tracking over blocks. #[cw_serde] pub enum XYKPoolUpdateParams { - /// Enables asset balances tracking over blocks. - EnableAssetBalancesTracking, /// Enables the sharing of swap fees with an external party. EnableFeeShare { /// The fee shared with the fee_share_address @@ -273,6 +284,28 @@ pub enum StablePoolUpdateParams { DisableFeeShare, } +/// A `reply` call code ID used for sub-messages. +#[cw_serde] +pub enum ReplyIds { + CreateDenom = 1, + InstantiateTrackingContract = 2, +} + +impl TryFrom for ReplyIds { + type Error = StdError; + + fn try_from(value: u64) -> Result { + match value { + 1 => Ok(ReplyIds::CreateDenom), + 2 => Ok(ReplyIds::InstantiateTrackingContract), + _ => Err(StdError::ParseErr { + target_type: "ReplyIds".to_string(), + msg: "Failed to parse reply".to_string(), + }), + } + } +} + #[cfg(test)] mod tests { use super::*; @@ -330,10 +363,4 @@ mod tests { let _: ConfigResponse = from_json(&ser_msg).unwrap(); } - - #[test] - fn check_empty_vec_deserialization() { - let variant: Cw20HookMsg = from_json(br#"{"withdraw_liquidity": {} }"#).unwrap(); - assert_eq!(variant, Cw20HookMsg::WithdrawLiquidity { assets: vec![] }); - } } diff --git a/packages/astroport/src/pair_concentrated.rs b/packages/astroport/src/pair_concentrated.rs index 24e4d864e..8419e2f75 100644 --- a/packages/astroport/src/pair_concentrated.rs +++ b/packages/astroport/src/pair_concentrated.rs @@ -68,8 +68,6 @@ pub enum ConcentratedPoolUpdateParams { Promote(PromoteParams), /// Stops Amp and Gamma update and stores current values. StopChangingAmpGamma {}, - /// Enable asset balances tracking - EnableAssetBalancesTracking {}, /// Enables the sharing of swap fees with an external party. EnableFeeShare { /// The fee shared with the fee_share_address @@ -156,6 +154,15 @@ pub enum QueryMsg { /// Query price from observations #[returns(OracleObservation)] Observe { seconds_ago: u64 }, + /// Returns an estimation of shares received for the given amount of assets + #[returns(Uint128)] + SimulateProvide { + assets: Vec, + slippage_tolerance: Option, + }, + /// Returns an estimation of assets received for the given amount of LP tokens + #[returns(Vec)] + SimulateWithdraw { lp_amount: Uint128 }, } #[cw_serde] diff --git a/packages/astroport/src/pair_concentrated_inj.rs b/packages/astroport/src/pair_concentrated_inj.rs index cd6b16b7c..9864f42db 100644 --- a/packages/astroport/src/pair_concentrated_inj.rs +++ b/packages/astroport/src/pair_concentrated_inj.rs @@ -37,7 +37,7 @@ pub enum ExecuteMsg { assets: Vec, /// The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much slippage_tolerance: Option, - /// Determines whether the LP tokens minted for the user is auto_staked in the Generator contract + /// Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract auto_stake: Option, /// The receiver of LP tokens receiver: Option, diff --git a/packages/astroport/src/pair_xyk_sale_tax.rs b/packages/astroport/src/pair_xyk_sale_tax.rs index b75a900a6..1b0dbbb1a 100644 --- a/packages/astroport/src/pair_xyk_sale_tax.rs +++ b/packages/astroport/src/pair_xyk_sale_tax.rs @@ -142,10 +142,6 @@ pub struct SaleTaxConfigUpdates { pub tax_configs: Option, /// The new address that is allowed to updated the tax configs. pub tax_config_admin: Option, - /// Whether asset balances are tracked over blocks or not. - /// They will not be tracked if the parameter is ignored. - /// It can not be disabled later once enabled. - pub track_asset_balances: Option, } /// Extra data embedded in the default pair InstantiateMsg diff --git a/packages/astroport/src/querier.rs b/packages/astroport/src/querier.rs index 5b8bb8f7f..591143431 100644 --- a/packages/astroport/src/querier.rs +++ b/packages/astroport/src/querier.rs @@ -1,6 +1,7 @@ use crate::asset::{Asset, AssetInfo, PairInfo}; use crate::factory::{ Config as FactoryConfig, FeeInfoResponse, PairType, PairsResponse, QueryMsg as FactoryQueryMsg, + TrackerConfig, }; use crate::pair::{QueryMsg as PairQueryMsg, ReverseSimulationResponse, SimulationResponse}; @@ -98,6 +99,19 @@ where Ok(res.total_supply) } +/// Returns the total supply of a native token. +/// +/// * **denom** specifies the denomination used to return the supply (e.g uatom). +pub fn query_native_supply( + querier: &QuerierWrapper, + denom: impl Into, +) -> StdResult +where + C: CustomQuery, +{ + querier.query_supply(denom).map(|res| res.amount) +} + /// Returns the number of decimals that a token has. /// /// * **asset_info** is an object of type [`AssetInfo`] and contains the asset details for a specific token. @@ -151,6 +165,21 @@ where } } +/// Returns the tracker configuration from the factory contract. +pub fn query_tracker_config( + querier: &QuerierWrapper, + factory_contract: impl Into, +) -> StdResult +where + C: CustomQuery, +{ + if let Some(res) = querier.query_wasm_raw(factory_contract, b"tracker_config".as_slice())? { + Ok(from_json(res)?) + } else { + Err(StdError::generic_err("The tracker config not found!")) + } +} + /// This structure holds parameters that describe the fee structure for a pool. pub struct FeeInfo { /// The fee address diff --git a/packages/astroport/src/testing.rs b/packages/astroport/src/testing.rs index fae2577ec..b2bdb1d0c 100644 --- a/packages/astroport/src/testing.rs +++ b/packages/astroport/src/testing.rs @@ -233,7 +233,7 @@ fn query_astroport_pair_contract() { }, ], contract_addr: Addr::unchecked("pair0000"), - liquidity_token: Addr::unchecked("liquidity0000"), + liquidity_token: "liquidity0000".to_owned(), pair_type: PairType::Xyk {}, }, )]); @@ -271,7 +271,7 @@ fn test_format_lp_token_name() { }, ], contract_addr: Addr::unchecked("pair0000"), - liquidity_token: Addr::unchecked("liquidity0000"), + liquidity_token: "liquidity0000".to_owned(), pair_type: PairType::Xyk {}, }, )]); diff --git a/packages/astroport/src/token_factory.rs b/packages/astroport/src/token_factory.rs new file mode 100644 index 000000000..04488f66c --- /dev/null +++ b/packages/astroport/src/token_factory.rs @@ -0,0 +1,355 @@ +pub use cosmos_sdk_proto::cosmos::base::v1beta1::Coin as ProtoCoin; +use cosmwasm_std::{Binary, Coin, CosmosMsg, CustomMsg, StdError}; + +#[cfg(any(feature = "injective", feature = "sei"))] +use cosmwasm_std::BankMsg; + +use prost::Message; + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgCreateDenomResponse { + #[prost(string, tag = "1")] + pub new_token_denom: ::prost::alloc::string::String, +} + +impl MsgCreateDenomResponse { + pub fn to_proto_bytes(&self) -> Vec { + let mut buf = Vec::new(); + self.encode(&mut buf).unwrap(); + buf + } +} + +impl From for Binary { + fn from(msg: MsgCreateDenomResponse) -> Self { + Binary(msg.to_proto_bytes()) + } +} + +impl TryFrom for MsgCreateDenomResponse { + type Error = StdError; + fn try_from(binary: Binary) -> Result { + Self::decode(binary.as_slice()).map_err(|e| { + StdError::generic_err( + format!( + "MsgCreateDenomResponse Unable to decode binary: \n - base64: {}\n - bytes array: {:?}\n\n{:?}", + binary, + binary.to_vec(), + e + ), + ) + }) + } +} + +#[cfg(not(any(feature = "sei")))] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgCreateDenom { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + /// subdenom can be up to 44 "alphanumeric" characters long. + #[prost(string, tag = "2")] + pub subdenom: ::prost::alloc::string::String, +} + +#[cfg(feature = "sei")] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgCreateDenom { + /// subdenom can be up to 44 "alphanumeric" characters long. + #[prost(string, tag = "2")] + pub subdenom: ::prost::alloc::string::String, +} + +impl MsgCreateDenom { + #[cfg(not(any(feature = "injective", feature = "sei")))] + pub const TYPE_URL: &'static str = "/osmosis.tokenfactory.v1beta1.MsgCreateDenom"; + #[cfg(feature = "injective")] + pub const TYPE_URL: &'static str = "/injective.tokenfactory.v1beta1.MsgCreateDenom"; + #[cfg(feature = "sei")] + pub const TYPE_URL: &'static str = "/seiprotocol.seichain.tokenfactory.v1beta1.MsgCreateDenom"; +} + +impl TryFrom for MsgCreateDenom { + type Error = StdError; + fn try_from(binary: Binary) -> Result { + Self::decode(binary.as_slice()).map_err(|e| { + StdError::generic_err(format!( + "MsgCreateDenom Unable to decode binary: \n - base64: {}\n - bytes array: {:?}\n\n{:?}", + binary, + binary.to_vec(), + e + )) + }) + } +} + +#[cfg(not(any(feature = "injective", feature = "sei")))] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgBurn { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, + #[prost(string, tag = "3")] + pub burn_from_address: ::prost::alloc::string::String, +} + +#[cfg(feature = "injective")] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgBurn { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[cfg(feature = "sei")] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgBurn { + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +impl MsgBurn { + #[cfg(not(any(feature = "injective", feature = "sei")))] + pub const TYPE_URL: &'static str = "/osmosis.tokenfactory.v1beta1.MsgBurn"; + #[cfg(feature = "injective")] + pub const TYPE_URL: &'static str = "/injective.tokenfactory.v1beta1.MsgBurn"; + #[cfg(feature = "sei")] + pub const TYPE_URL: &'static str = "/seiprotocol.seichain.tokenfactory.v1beta1.MsgBurn"; +} + +impl TryFrom for MsgBurn { + type Error = StdError; + fn try_from(binary: Binary) -> Result { + Self::decode(binary.as_slice()).map_err(|e| { + StdError::generic_err(format!( + "MsgBurn Unable to decode binary: \n - base64: {}\n - bytes array: {:?}\n\n{:?}", + binary, + binary.to_vec(), + e + )) + }) + } +} + +#[cfg(not(any(feature = "injective", feature = "sei")))] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgMint { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, + #[prost(string, tag = "3")] + pub mint_to_address: ::prost::alloc::string::String, +} + +#[cfg(feature = "injective")] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgMint { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +#[cfg(feature = "sei")] +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgMint { + #[prost(message, optional, tag = "2")] + pub amount: ::core::option::Option, +} + +impl MsgMint { + #[cfg(not(any(feature = "injective", feature = "sei")))] + pub const TYPE_URL: &'static str = "/osmosis.tokenfactory.v1beta1.MsgMint"; + #[cfg(feature = "injective")] + pub const TYPE_URL: &'static str = "/injective.tokenfactory.v1beta1.MsgMint"; + #[cfg(feature = "sei")] + pub const TYPE_URL: &'static str = "/seiprotocol.seichain.tokenfactory.v1beta1.MsgMint"; +} + +impl TryFrom for MsgMint { + type Error = StdError; + fn try_from(binary: Binary) -> Result { + Self::decode(binary.as_slice()).map_err(|e| { + StdError::generic_err(format!( + "MsgMint Unable to decode binary: \n - base64: {}\n - bytes array: {:?}\n\n{:?}", + binary, + binary.to_vec(), + e + )) + }) + } +} + +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct MsgSetBeforeSendHook { + #[prost(string, tag = "1")] + pub sender: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub denom: ::prost::alloc::string::String, + #[prost(string, tag = "3")] + pub cosmwasm_address: ::prost::alloc::string::String, +} + +impl MsgSetBeforeSendHook { + pub const TYPE_URL: &'static str = "/osmosis.tokenfactory.v1beta1.MsgSetBeforeSendHook"; +} + +impl TryFrom for MsgSetBeforeSendHook { + type Error = StdError; + fn try_from(binary: Binary) -> Result { + Self::decode(binary.as_slice()).map_err(|e| { + StdError::generic_err(format!( + "MsgSetBeforeSendHook Unable to decode binary: \n - base64: {}\n - bytes array: {:?}\n\n{:?}", + binary, + binary.to_vec(), + e + )) + }) + } +} + +pub fn tf_create_denom_msg(sender: impl Into, denom: impl Into) -> CosmosMsg +where + T: CustomMsg, +{ + #[cfg(not(any(feature = "sei")))] + let create_denom_msg = MsgCreateDenom { + sender: sender.into(), + subdenom: denom.into(), + }; + + #[cfg(feature = "sei")] + let create_denom_msg = MsgCreateDenom { + subdenom: denom.into(), + }; + + CosmosMsg::Stargate { + type_url: MsgCreateDenom::TYPE_URL.to_string(), + value: Binary::from(create_denom_msg.encode_to_vec()), + } +} + +pub fn tf_mint_msg( + sender: impl Into, + coin: Coin, + receiver: impl Into, +) -> Vec> +where + T: CustomMsg, +{ + let sender_addr: String = sender.into(); + let receiver_addr: String = receiver.into(); + + #[cfg(not(any(feature = "injective", feature = "sei")))] + let mint_msg = MsgMint { + sender: sender_addr.clone(), + amount: Some(ProtoCoin { + denom: coin.denom.to_string(), + amount: coin.amount.to_string(), + }), + mint_to_address: receiver_addr.clone(), + }; + + #[cfg(feature = "injective")] + let mint_msg = MsgMint { + sender: sender_addr.clone(), + amount: Some(ProtoCoin { + denom: coin.denom.to_string(), + amount: coin.amount.to_string(), + }), + }; + + #[cfg(feature = "sei")] + let mint_msg = MsgMint { + amount: Some(ProtoCoin { + denom: coin.denom.to_string(), + amount: coin.amount.to_string(), + }), + }; + + #[cfg(not(any(feature = "injective", feature = "sei")))] + return vec![CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from(mint_msg.encode_to_vec()), + }]; + + #[cfg(any(feature = "injective", feature = "sei"))] + if sender_addr == receiver_addr { + vec![CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from(mint_msg.encode_to_vec()), + }] + } else { + vec![ + CosmosMsg::Stargate { + type_url: MsgMint::TYPE_URL.to_string(), + value: Binary::from(mint_msg.encode_to_vec()), + }, + BankMsg::Send { + to_address: receiver_addr, + amount: vec![coin], + } + .into(), + ] + } +} + +pub fn tf_burn_msg(sender: impl Into, coin: Coin) -> CosmosMsg +where + T: CustomMsg, +{ + #[cfg(not(any(feature = "injective", feature = "sei")))] + let burn_msg = MsgBurn { + sender: sender.into(), + amount: Some(ProtoCoin { + denom: coin.denom, + amount: coin.amount.to_string(), + }), + burn_from_address: "".to_string(), + }; + + #[cfg(feature = "injective")] + let burn_msg = MsgBurn { + sender: sender.into(), + amount: Some(ProtoCoin { + denom: coin.denom, + amount: coin.amount.to_string(), + }), + }; + + #[cfg(feature = "sei")] + let burn_msg = MsgBurn { + amount: Some(ProtoCoin { + denom: coin.denom, + amount: coin.amount.to_string(), + }), + }; + + CosmosMsg::Stargate { + type_url: MsgBurn::TYPE_URL.to_string(), + value: Binary::from(burn_msg.encode_to_vec()), + } +} + +pub fn tf_before_send_hook_msg( + sender: impl Into, + denom: impl Into, + cosmwasm_address: impl Into, +) -> CosmosMsg +where + T: CustomMsg, +{ + let msg = MsgSetBeforeSendHook { + sender: sender.into(), + denom: denom.into(), + cosmwasm_address: cosmwasm_address.into(), + }; + + CosmosMsg::Stargate { + type_url: MsgSetBeforeSendHook::TYPE_URL.to_string(), + value: Binary::from(msg.encode_to_vec()), + } +} diff --git a/packages/astroport_mocks/Cargo.toml b/packages/astroport_mocks/Cargo.toml deleted file mode 100644 index d695f4dac..000000000 --- a/packages/astroport_mocks/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "astroport-mocks" -version = "0.2.0" -authors = ["Astroport"] -edition = "2021" -description = "Mock Astroport contracts used for integration testing" -license = "GPL-3.0-only" -repository = "https://github.com/astroport-fi/astroport-core" -homepage = "https://astroport.fi" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -astroport = { path = "../astroport" } -astroport-factory = { path = "../../contracts/factory" } -astroport-native-coin-registry = { path = "../../contracts/periphery/native_coin_registry" } -astroport-pair = { path = "../../contracts/pair" } -astroport-pair-stable = { path = "../../contracts/pair_stable" } -astroport-pair-concentrated = { path = "../../contracts/pair_concentrated" } -astroport-staking = { path = "../../contracts/tokenomics/staking" } -cw20-base = "1" -astroport-vesting = { path = "../../contracts/tokenomics/vesting" } -astroport-xastro-token = { path = "../../contracts/tokenomics/xastro_token" } -cosmwasm-schema = "1.2.5" -cosmwasm-std = "1.2.5" -cw-multi-test = "1.0.0" -injective-cosmwasm = "0.2" -schemars = "0.8.1" -serde = "1.0" -cw-utils = "1.0" -cw20 = "0.15" -anyhow = "1.0" -cw3 = "1.0" -cw1-whitelist = { version = "1.1.2", features = ["library"] } diff --git a/packages/astroport_mocks/src/coin_registry.rs b/packages/astroport_mocks/src/coin_registry.rs deleted file mode 100644 index 346d3d04c..000000000 --- a/packages/astroport_mocks/src/coin_registry.rs +++ /dev/null @@ -1,127 +0,0 @@ -use astroport::native_coin_registry::{ExecuteMsg, InstantiateMsg}; -use cosmwasm_std::{Addr, Api, CustomMsg, CustomQuery, Storage}; -use cw_multi_test::{ - AppResponse, Bank, ContractWrapper, Distribution, Executor, Gov, Ibc, Module, Staking, Stargate, -}; -use serde::de::DeserializeOwned; - -use crate::{astroport_address, WKApp, ASTROPORT}; - -pub fn store_code(app: &WKApp) -> u64 -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - use astroport_native_coin_registry as cnt; - let contract = Box::new(ContractWrapper::new_with_empty( - cnt::contract::execute, - cnt::contract::instantiate, - cnt::contract::query, - )); - - app.borrow_mut().store_code(contract) -} - -pub struct MockCoinRegistryBuilder { - pub app: WKApp, -} - -impl MockCoinRegistryBuilder -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn new(app: &WKApp) -> Self { - Self { app: app.clone() } - } - pub fn instantiate(self) -> MockCoinRegistry { - let code_id = store_code(&self.app); - let astroport = astroport_address(); - - let address = self - .app - .borrow_mut() - .instantiate_contract( - code_id, - astroport.clone(), - &InstantiateMsg { - owner: ASTROPORT.to_owned(), - }, - &[], - "Astroport Coin Registry", - Some(ASTROPORT.to_owned()), - ) - .unwrap(); - - self.app - .borrow_mut() - .execute_contract( - astroport, - address.clone(), - &ExecuteMsg::Add { - native_coins: vec![("ustake".to_owned(), 6), ("ucosmos".to_owned(), 6)], - }, - &[], - ) - .unwrap(); - - MockCoinRegistry { - app: self.app, - address, - } - } -} - -pub struct MockCoinRegistry { - pub app: WKApp, - pub address: Addr, -} - -impl MockCoinRegistry -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn add(&self, coins: Vec<(String, u8)>) -> AppResponse { - let astroport = astroport_address(); - - self.app - .borrow_mut() - .execute_contract( - astroport, - self.address.clone(), - &ExecuteMsg::Add { - native_coins: coins, - }, - &[], - ) - .unwrap() - } -} diff --git a/packages/astroport_mocks/src/factory.rs b/packages/astroport_mocks/src/factory.rs deleted file mode 100644 index 5d190aeaf..000000000 --- a/packages/astroport_mocks/src/factory.rs +++ /dev/null @@ -1,361 +0,0 @@ -use anyhow::Result as AnyResult; - -use astroport::{ - asset::{AssetInfo, PairInfo}, - factory::{ConfigResponse, ExecuteMsg, InstantiateMsg, PairConfig, PairType, QueryMsg}, - pair::StablePoolParams, - pair_concentrated::ConcentratedPoolParams, -}; -use cosmwasm_std::{to_json_binary, Addr, Api, CustomMsg, CustomQuery, Decimal, Storage}; -use cw_multi_test::{ - AppResponse, Bank, ContractWrapper, Distribution, Executor, Gov, Ibc, Module, Staking, Stargate, -}; -use serde::de::DeserializeOwned; - -use crate::{ - astroport_address, MockCoinRegistry, MockCoinRegistryBuilder, MockConcentratedPair, - MockStablePair, MockXykPair, WKApp, ASTROPORT, -}; - -pub fn store_code(app: &WKApp) -> u64 -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - use astroport_factory as cnt; - let contract = Box::new( - ContractWrapper::new_with_empty( - cnt::contract::execute, - cnt::contract::instantiate, - cnt::contract::query, - ) - .with_reply_empty(cnt::contract::reply), - ); - - app.borrow_mut().store_code(contract) -} - -pub struct MockFactoryBuilder { - pub app: WKApp, -} - -impl MockFactoryBuilder -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn new(app: &WKApp) -> Self { - Self { app: app.clone() } - } - - pub fn instantiate(self) -> MockFactory { - let code_id = store_code(&self.app); - let astroport = astroport_address(); - - let xyk_code_id = crate::pair::store_code(&self.app); - let stable_code_id = crate::pair_stable::store_code(&self.app); - let concentrated_code_id = crate::pair_concentrated::store_code(&self.app); - - let pair_configs = vec![ - PairConfig { - code_id: xyk_code_id, - pair_type: PairType::Xyk {}, - is_disabled: false, - is_generator_disabled: false, - total_fee_bps: 30, - maker_fee_bps: 3333, - permissioned: false, - }, - PairConfig { - code_id: stable_code_id, - pair_type: PairType::Stable {}, - is_disabled: false, - is_generator_disabled: false, - total_fee_bps: 5, - maker_fee_bps: 5000, - permissioned: false, - }, - PairConfig { - code_id: concentrated_code_id, - pair_type: PairType::Custom("concentrated".to_owned()), - is_disabled: false, - is_generator_disabled: false, - total_fee_bps: 30, - maker_fee_bps: 3333, - permissioned: false, - }, - ]; - - let token_code_id = crate::token::store_code(&self.app); - let whitelist_code_id = crate::whitelist::store_code(&self.app); - - let coin_registry = MockCoinRegistryBuilder::new(&self.app).instantiate(); - - let address = self - .app - .borrow_mut() - .instantiate_contract( - code_id, - astroport, - &InstantiateMsg { - owner: ASTROPORT.to_owned(), - fee_address: None, - pair_configs, - token_code_id, - generator_address: None, - whitelist_code_id, - coin_registry_address: coin_registry.address.to_string(), - }, - &[], - "Astroport Factory", - Some(ASTROPORT.to_owned()), - ) - .unwrap(); - - MockFactory { - app: self.app, - address, - } - } -} - -pub struct MockFactory { - pub app: WKApp, - pub address: Addr, -} - -pub type MockFactoryOpt = Option>; - -impl MockFactory -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn whitelist_code_id(&self) -> u64 { - let config: ConfigResponse = self - .app - .borrow() - .wrap() - .query_wasm_smart(self.address.clone(), &QueryMsg::Config {}) - .unwrap(); - - config.whitelist_code_id - } - - pub fn token_code_id(&self) -> u64 { - let config: ConfigResponse = self - .app - .borrow() - .wrap() - .query_wasm_smart(self.address.clone(), &QueryMsg::Config {}) - .unwrap(); - - config.token_code_id - } - - pub fn instantiate_xyk_pair( - &self, - asset_infos: &[AssetInfo], - ) -> MockXykPair { - let astroport = astroport_address(); - - self.app - .borrow_mut() - .execute_contract( - astroport, - self.address.clone(), - &ExecuteMsg::CreatePair { - pair_type: PairType::Xyk {}, - asset_infos: asset_infos.to_vec(), - init_params: None, - }, - &[], - ) - .unwrap(); - - let res: PairInfo = self - .app - .borrow() - .wrap() - .query_wasm_smart( - &self.address, - &QueryMsg::Pair { - asset_infos: asset_infos.to_vec(), - }, - ) - .unwrap(); - - MockXykPair { - app: self.app.clone(), - address: res.contract_addr, - } - } - - /// Set init_params to None to use the defaults - pub fn instantiate_stable_pair( - &self, - asset_infos: &[AssetInfo], - init_params: Option<&StablePoolParams>, - ) -> MockStablePair { - let astroport = astroport_address(); - - let default_params = StablePoolParams { - amp: 100, - owner: Some(astroport.to_string()), - }; - - self.app - .borrow_mut() - .execute_contract( - astroport, - self.address.clone(), - &ExecuteMsg::CreatePair { - pair_type: PairType::Stable {}, - asset_infos: asset_infos.to_vec(), - init_params: Some( - to_json_binary(init_params.unwrap_or(&default_params)).unwrap(), - ), - }, - &[], - ) - .unwrap(); - - let res: PairInfo = self - .app - .borrow() - .wrap() - .query_wasm_smart( - &self.address, - &QueryMsg::Pair { - asset_infos: asset_infos.to_vec(), - }, - ) - .unwrap(); - - MockStablePair { - app: self.app.clone(), - address: res.contract_addr, - } - } - - pub fn coin_registry(&self) -> MockCoinRegistry { - let config: ConfigResponse = self - .app - .borrow() - .wrap() - .query_wasm_smart(self.address.clone(), &QueryMsg::Config {}) - .unwrap(); - - MockCoinRegistry { - app: self.app.clone(), - address: config.coin_registry_address, - } - } - - /// Set init_params to None to use the defaults - pub fn instantiate_concentrated_pair( - &self, - asset_infos: &[AssetInfo], - init_params: Option<&ConcentratedPoolParams>, - ) -> MockConcentratedPair { - let astroport = astroport_address(); - - let default_params = ConcentratedPoolParams { - amp: Decimal::from_ratio(40u128, 1u128), - gamma: Decimal::from_ratio(145u128, 1000000u128), - mid_fee: Decimal::from_ratio(26u128, 10000u128), - out_fee: Decimal::from_ratio(45u128, 10000u128), - fee_gamma: Decimal::from_ratio(23u128, 100000u128), - repeg_profit_threshold: Decimal::from_ratio(2u128, 1000000u128), - min_price_scale_delta: Decimal::from_ratio(146u128, 1000000u128), - price_scale: Decimal::one(), - ma_half_time: 600, - track_asset_balances: None, - fee_share: None, - }; - - self.app - .borrow_mut() - .execute_contract( - astroport, - self.address.clone(), - &ExecuteMsg::CreatePair { - pair_type: PairType::Custom("concentrated".to_owned()), - asset_infos: asset_infos.to_vec(), - init_params: Some( - to_json_binary(init_params.unwrap_or(&default_params)).unwrap(), - ), - }, - &[], - ) - .unwrap(); - - let res: PairInfo = self - .app - .borrow() - .wrap() - .query_wasm_smart( - &self.address, - &QueryMsg::Pair { - asset_infos: asset_infos.to_vec(), - }, - ) - .unwrap(); - - MockConcentratedPair { - app: self.app.clone(), - address: res.contract_addr, - } - } - - pub fn deregister_pair(&self, asset_infos: &[AssetInfo]) -> AnyResult { - let astroport = astroport_address(); - - self.app.borrow_mut().execute_contract( - astroport, - self.address.clone(), - &ExecuteMsg::Deregister { - asset_infos: asset_infos.to_vec(), - }, - &[], - ) - } - - pub fn config(&self) -> ConfigResponse { - let config: ConfigResponse = self - .app - .borrow() - .wrap() - .query_wasm_smart(self.address.clone(), &QueryMsg::Config {}) - .unwrap(); - - config - } -} diff --git a/packages/astroport_mocks/src/lib.rs b/packages/astroport_mocks/src/lib.rs deleted file mode 100644 index 209291772..000000000 --- a/packages/astroport_mocks/src/lib.rs +++ /dev/null @@ -1,40 +0,0 @@ -#![cfg(not(tarpaulin_include))] - -use std::{cell::RefCell, rc::Rc}; - -use cosmwasm_std::Addr; -pub use cw_multi_test; -use cw_multi_test::{App, Module, WasmKeeper}; - -pub use { - coin_registry::{MockCoinRegistry, MockCoinRegistryBuilder}, - factory::{MockFactory, MockFactoryBuilder}, - pair::{MockXykPair, MockXykPairBuilder}, - pair_concentrated::{MockConcentratedPair, MockConcentratedPairBuilder}, - pair_stable::{MockStablePair, MockStablePairBuilder}, - token::{MockToken, MockTokenBuilder}, - vesting::{MockVesting, MockVestingBuilder}, - xastro::{MockXastro, MockXastroBuilder}, -}; - -pub mod coin_registry; -pub mod factory; -pub mod pair; -pub mod pair_concentrated; -pub mod pair_stable; -pub mod token; -pub mod vesting; -pub mod whitelist; -pub mod xastro; - -pub const ASTROPORT: &str = "astroport"; - -pub fn astroport_address() -> Addr { - Addr::unchecked(ASTROPORT) -} - -pub type WKApp = Rc< - RefCell< - App::ExecT, ::QueryT>, X, D, I, G, T>, - >, ->; diff --git a/packages/astroport_mocks/src/pair.rs b/packages/astroport_mocks/src/pair.rs deleted file mode 100644 index e710b37c8..000000000 --- a/packages/astroport_mocks/src/pair.rs +++ /dev/null @@ -1,180 +0,0 @@ -use astroport::{ - asset::{Asset, AssetInfo, PairInfo}, - pair::{ExecuteMsg, QueryMsg}, -}; -use cosmwasm_std::{Addr, Api, Coin, CustomMsg, CustomQuery, Decimal, StdResult, Storage}; -use cw_multi_test::{ - Bank, ContractWrapper, Distribution, Executor, Gov, Ibc, Module, Staking, Stargate, -}; -use serde::de::DeserializeOwned; - -use crate::{ - factory::{MockFactory, MockFactoryOpt}, - MockFactoryBuilder, MockToken, WKApp, -}; - -pub fn store_code(app: &WKApp) -> u64 -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - use astroport_pair as cnt; - let contract = Box::new( - ContractWrapper::new_with_empty( - cnt::contract::execute, - cnt::contract::instantiate, - cnt::contract::query, - ) - .with_reply_empty(cnt::contract::reply), - ); - - app.borrow_mut().store_code(contract) -} -pub struct MockXykPairBuilder { - pub app: WKApp, - pub asset_infos: Vec, - pub factory: MockFactoryOpt, -} - -impl MockXykPairBuilder -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn new(app: &WKApp) -> Self { - Self { - app: app.clone(), - asset_infos: Default::default(), - factory: None, - } - } - - pub fn with_factory(mut self, factory: &MockFactory) -> Self { - self.factory = Some(MockFactory { - app: self.app.clone(), - address: factory.address.clone(), - }); - self - } - - pub fn with_asset(mut self, asset_info: &AssetInfo) -> Self { - self.asset_infos.push(asset_info.clone()); - self - } - - pub fn instantiate(self) -> MockXykPair { - let factory = self - .factory - .unwrap_or_else(|| MockFactoryBuilder::new(&self.app).instantiate()); - - factory.instantiate_xyk_pair(&self.asset_infos) - } -} - -#[derive(Clone)] -pub struct MockXykPair { - pub app: WKApp, - pub address: Addr, -} - -impl MockXykPair -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn lp_token(&self) -> MockToken { - let res: PairInfo = self - .app - .borrow() - .wrap() - .query_wasm_smart(self.address.to_string(), &QueryMsg::Pair {}) - .unwrap(); - MockToken { - app: self.app.clone(), - address: res.liquidity_token, - } - } - - pub fn provide( - &self, - sender: &Addr, - assets: &[Asset], - slippage_tolerance: Option, - auto_stake: bool, - receiver: impl Into>, - ) { - let coins: Vec = assets - .iter() - .filter_map(|a| match &a.info { - AssetInfo::Token { .. } => None, - AssetInfo::NativeToken { denom } => Some(Coin { - denom: denom.clone(), - amount: a.amount, - }), - }) - .collect(); - - self.app - .borrow_mut() - .execute_contract( - sender.clone(), - self.address.clone(), - &ExecuteMsg::ProvideLiquidity { - assets: assets.into(), - slippage_tolerance, - auto_stake: Some(auto_stake), - receiver: receiver.into(), - }, - &coins, - ) - .unwrap(); - } - - pub fn mint_allow_provide_and_stake(&self, sender: &Addr, assets: &[Asset]) { - for asset in assets { - if let AssetInfo::Token { contract_addr } = &asset.info { - let token = MockToken { - app: self.app.clone(), - address: contract_addr.clone(), - }; - token.mint(sender, asset.amount); - token.allow(sender, &self.address, asset.amount); - }; - } - self.provide(sender, assets, None, true, None) - } - - pub fn pair_info(&self) -> StdResult { - self.app - .borrow() - .wrap() - .query_wasm_smart(self.address.clone(), &QueryMsg::Pair {}) - } -} diff --git a/packages/astroport_mocks/src/pair_concentrated.rs b/packages/astroport_mocks/src/pair_concentrated.rs deleted file mode 100644 index 725b57080..000000000 --- a/packages/astroport_mocks/src/pair_concentrated.rs +++ /dev/null @@ -1,161 +0,0 @@ -use astroport::{ - asset::{Asset, AssetInfo, PairInfo}, - pair::QueryMsg, - pair_concentrated::ConcentratedPoolParams, -}; -use cosmwasm_std::{Addr, Api, CustomMsg, CustomQuery, Decimal, Storage}; -use cw_multi_test::{Bank, ContractWrapper, Distribution, Gov, Ibc, Module, Staking, Stargate}; -use serde::de::DeserializeOwned; - -use crate::{ - factory::{MockFactory, MockFactoryOpt}, - MockFactoryBuilder, MockToken, MockXykPair, WKApp, -}; - -pub fn store_code(app: &WKApp) -> u64 -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - use astroport_pair_concentrated as cnt; - let contract = Box::new( - ContractWrapper::new_with_empty( - cnt::contract::execute, - cnt::contract::instantiate, - cnt::queries::query, - ) - .with_reply_empty(cnt::contract::reply), - ); - - app.borrow_mut().store_code(contract) -} - -pub struct MockConcentratedPairBuilder { - pub app: WKApp, - pub asset_infos: Vec, - pub factory: MockFactoryOpt, -} - -impl MockConcentratedPairBuilder -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn new(app: &WKApp) -> Self { - Self { - app: app.clone(), - asset_infos: Default::default(), - factory: None, - } - } - - pub fn with_factory(mut self, factory: &MockFactory) -> Self { - self.factory = Some(MockFactory { - app: self.app.clone(), - address: factory.address.clone(), - }); - self - } - - pub fn with_asset(mut self, asset_info: &AssetInfo) -> Self { - self.asset_infos.push(asset_info.clone()); - self - } - - /// Set init_params to None to use the defaults - pub fn instantiate( - self, - init_params: Option<&ConcentratedPoolParams>, - ) -> MockConcentratedPair { - let factory = self - .factory - .unwrap_or_else(|| MockFactoryBuilder::new(&self.app).instantiate()); - - factory.instantiate_concentrated_pair(&self.asset_infos, init_params) - } -} - -pub struct MockConcentratedPair { - pub app: WKApp, - pub address: Addr, -} - -impl MockConcentratedPair -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn lp_token(&self) -> MockToken { - let res: PairInfo = self - .app - .borrow() - .wrap() - .query_wasm_smart(self.address.to_string(), &QueryMsg::Pair {}) - .unwrap(); - MockToken { - app: self.app.clone(), - address: res.liquidity_token, - } - } - - pub fn provide( - &self, - sender: &Addr, - assets: &[Asset], - slippage_tolerance: Option, - auto_stake: bool, - receiver: impl Into>, - ) { - let xyk = MockXykPair { - app: self.app.clone(), - address: self.address.clone(), - }; - xyk.provide(sender, assets, slippage_tolerance, auto_stake, receiver); - } - - pub fn mint_allow_provide_and_stake(&self, sender: &Addr, assets: &[Asset]) { - let xyk = MockXykPair { - app: self.app.clone(), - address: self.address.clone(), - }; - xyk.mint_allow_provide_and_stake(sender, assets); - } - - pub fn pair_info(&self) -> PairInfo { - let pair_info: PairInfo = self - .app - .borrow() - .wrap() - .query_wasm_smart(self.address.clone(), &QueryMsg::Pair {}) - .unwrap(); - - pair_info - } -} diff --git a/packages/astroport_mocks/src/pair_stable.rs b/packages/astroport_mocks/src/pair_stable.rs deleted file mode 100644 index 2ab146247..000000000 --- a/packages/astroport_mocks/src/pair_stable.rs +++ /dev/null @@ -1,148 +0,0 @@ -use astroport::{ - asset::{Asset, AssetInfo, PairInfo}, - pair::{QueryMsg, StablePoolParams}, -}; -use cosmwasm_std::{Addr, Api, CustomMsg, CustomQuery, Decimal, Storage}; -use cw_multi_test::{Bank, ContractWrapper, Distribution, Gov, Ibc, Module, Staking, Stargate}; -use serde::de::DeserializeOwned; - -use crate::{ - factory::MockFactoryOpt, MockFactory, MockFactoryBuilder, MockToken, MockXykPair, WKApp, -}; - -pub fn store_code(app: &WKApp) -> u64 -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - use astroport_pair_stable as cnt; - let contract = Box::new( - ContractWrapper::new_with_empty( - cnt::contract::execute, - cnt::contract::instantiate, - cnt::contract::query, - ) - .with_reply_empty(cnt::contract::reply), - ); - - app.borrow_mut().store_code(contract) -} - -pub struct MockStablePairBuilder { - pub app: WKApp, - pub asset_infos: Vec, - pub factory: MockFactoryOpt, -} - -impl MockStablePairBuilder -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn new(app: &WKApp) -> Self { - Self { - app: app.clone(), - asset_infos: Default::default(), - factory: None, - } - } - - pub fn with_factory(mut self, factory: &MockFactory) -> Self { - self.factory = Some(MockFactory { - app: self.app.clone(), - address: factory.address.clone(), - }); - self - } - - pub fn with_asset(mut self, asset_info: &AssetInfo) -> Self { - self.asset_infos.push(asset_info.clone()); - self - } - - /// Set init_params to None to use the defaults - pub fn instantiate( - self, - params: Option<&StablePoolParams>, - ) -> MockStablePair { - let factory = self - .factory - .unwrap_or_else(|| MockFactoryBuilder::new(&self.app).instantiate()); - - factory.instantiate_stable_pair(&self.asset_infos, params) - } -} - -pub struct MockStablePair { - pub app: WKApp, - pub address: Addr, -} - -impl MockStablePair -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn lp_token(&self) -> MockToken { - let res: PairInfo = self - .app - .borrow() - .wrap() - .query_wasm_smart(self.address.to_string(), &QueryMsg::Pair {}) - .unwrap(); - MockToken { - app: self.app.clone(), - address: res.liquidity_token, - } - } - - pub fn provide( - &self, - sender: &Addr, - assets: &[Asset], - slippage_tolerance: Option, - auto_stake: bool, - receiver: impl Into>, - ) { - let xyk = MockXykPair { - app: self.app.clone(), - address: self.address.clone(), - }; - xyk.provide(sender, assets, slippage_tolerance, auto_stake, receiver); - } - - pub fn mint_allow_provide_and_stake(&self, sender: &Addr, assets: &[Asset]) { - let xyk = MockXykPair { - app: self.app.clone(), - address: self.address.clone(), - }; - xyk.mint_allow_provide_and_stake(sender, assets); - } -} diff --git a/packages/astroport_mocks/src/token.rs b/packages/astroport_mocks/src/token.rs deleted file mode 100644 index c8cae66d0..000000000 --- a/packages/astroport_mocks/src/token.rs +++ /dev/null @@ -1,211 +0,0 @@ -use astroport::{ - asset::AssetInfo, - token::{BalanceResponse, ExecuteMsg, InstantiateMsg, MinterResponse, QueryMsg}, -}; -use cosmwasm_std::{Addr, Api, CustomMsg, CustomQuery, Storage, Uint128}; -use cw_multi_test::{ - Bank, ContractWrapper, Distribution, Executor, Gov, Ibc, Module, Staking, Stargate, -}; -use serde::de::DeserializeOwned; - -use crate::{astroport_address, WKApp, ASTROPORT}; - -pub fn store_code(app: &WKApp) -> u64 -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - use cw20_base as cnt; - let contract = Box::new(ContractWrapper::new_with_empty( - cnt::contract::execute, - cnt::contract::instantiate, - cnt::contract::query, - )); - - app.borrow_mut().store_code(contract) -} - -pub struct MockTokenBuilder { - pub app: WKApp, - pub symbol: String, -} - -impl MockTokenBuilder -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn new(app: &WKApp, symbol: &str) -> Self { - Self { - app: app.clone(), - symbol: symbol.into(), - } - } - - pub fn instantiate(self) -> MockToken { - let code_id = store_code(&self.app); - let astroport = astroport_address(); - - let address = self - .app - .borrow_mut() - .instantiate_contract( - code_id, - astroport, - &InstantiateMsg { - name: self.symbol.clone(), - mint: Some(MinterResponse { - minter: ASTROPORT.to_owned(), - cap: None, - }), - symbol: self.symbol.clone(), - decimals: 6, - marketing: None, - initial_balances: vec![], - }, - &[], - self.symbol, - Some(ASTROPORT.to_owned()), - ) - .unwrap(); - - MockToken { - app: self.app, - address, - } - } -} - -pub struct MockToken { - pub app: WKApp, - pub address: Addr, -} - -pub type MockTokenOpt = Option>; - -impl MockToken -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn asset_info(&self) -> AssetInfo { - AssetInfo::Token { - contract_addr: self.address.clone(), - } - } - - pub fn mint(&self, recipient: &Addr, amount: Uint128) { - let astroport = astroport_address(); - self.app - .borrow_mut() - .execute_contract( - astroport, - self.address.clone(), - &ExecuteMsg::Mint { - recipient: recipient.into(), - amount, - }, - &[], - ) - .unwrap(); - } - - pub fn balance(&self, address: &Addr) -> Uint128 { - let res: BalanceResponse = self - .app - .borrow() - .wrap() - .query_wasm_smart( - self.address.to_string(), - &QueryMsg::Balance { - address: address.into(), - }, - ) - .unwrap(); - - res.balance - } - - pub fn burn(&self, sender: &Addr, amount: Uint128) { - self.app - .borrow_mut() - .execute_contract( - sender.clone(), - self.address.clone(), - &ExecuteMsg::Burn { amount }, - &[], - ) - .unwrap(); - } - - pub fn allow(&self, sender: &Addr, spender: &Addr, amount: Uint128) { - self.app - .borrow_mut() - .execute_contract( - sender.clone(), - self.address.clone(), - &ExecuteMsg::IncreaseAllowance { - spender: spender.into(), - amount, - expires: None, - }, - &[], - ) - .unwrap(); - } -} -impl TryFrom<(&WKApp, &AssetInfo)> - for MockToken -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - type Error = String; - fn try_from( - value: (&WKApp, &AssetInfo), - ) -> Result, Self::Error> { - match value.1 { - AssetInfo::Token { contract_addr } => Ok(MockToken { - app: value.0.clone(), - address: contract_addr.clone(), - }), - AssetInfo::NativeToken { denom } => Err(format!("{} is native coin!", denom)), - } - } -} diff --git a/packages/astroport_mocks/src/vesting.rs b/packages/astroport_mocks/src/vesting.rs deleted file mode 100644 index 5ed9c735e..000000000 --- a/packages/astroport_mocks/src/vesting.rs +++ /dev/null @@ -1,130 +0,0 @@ -use crate::{astroport_address, MockTokenBuilder, WKApp, ASTROPORT}; -use astroport::{ - asset::AssetInfo, - vesting::QueryMsg, - vesting::{ConfigResponse, InstantiateMsg}, -}; -use cosmwasm_std::{Addr, Api, CustomMsg, CustomQuery, Storage}; -use cw_multi_test::{ - Bank, ContractWrapper, Distribution, Executor, Gov, Ibc, Module, Staking, Stargate, -}; -use serde::de::DeserializeOwned; - -pub fn store_code(app: &WKApp) -> u64 -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - use astroport_vesting as cnt; - let contract = Box::new(ContractWrapper::new_with_empty( - cnt::contract::execute, - cnt::contract::instantiate, - cnt::contract::query, - )); - - app.borrow_mut().store_code(contract) -} - -pub struct MockVestingBuilder { - pub app: WKApp, - pub astro_token: Option, -} - -impl MockVestingBuilder -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn new(app: &WKApp) -> Self { - Self { - app: app.clone(), - astro_token: None, - } - } - - pub fn with_astro_token(mut self, astro_token: &AssetInfo) -> Self { - self.astro_token = Some(astro_token.clone()); - self - } - - pub fn instantiate(self) -> MockVesting { - let code_id = store_code(&self.app); - let astroport = astroport_address(); - - let astro_token = self.astro_token.unwrap_or_else(|| { - MockTokenBuilder::new(&self.app, "ASTRO") - .instantiate() - .asset_info() - }); - - let address = self - .app - .borrow_mut() - .instantiate_contract( - code_id, - astroport, - &InstantiateMsg { - owner: ASTROPORT.to_owned(), - vesting_token: astro_token, - }, - &[], - "Astroport Vesting", - Some(ASTROPORT.to_owned()), - ) - .unwrap(); - - MockVesting { - app: self.app, - address, - } - } -} - -pub struct MockVesting { - pub app: WKApp, - pub address: Addr, -} - -impl MockVesting -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn vesting_token_info(&self) -> AssetInfo { - let res: ConfigResponse = self - .app - .borrow() - .wrap() - .query_wasm_smart(self.address.clone(), &QueryMsg::Config {}) - .unwrap(); - - res.vesting_token - } -} diff --git a/packages/astroport_mocks/src/whitelist.rs b/packages/astroport_mocks/src/whitelist.rs deleted file mode 100644 index f528b256b..000000000 --- a/packages/astroport_mocks/src/whitelist.rs +++ /dev/null @@ -1,29 +0,0 @@ -use cosmwasm_std::{Api, CustomMsg, CustomQuery, Storage}; -use cw_multi_test::{Bank, ContractWrapper, Distribution, Gov, Ibc, Module, Staking, Stargate}; -use serde::de::DeserializeOwned; - -use crate::WKApp; - -pub fn store_code(app: &WKApp) -> u64 -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - use cw1_whitelist as cnt; - let contract = Box::new(ContractWrapper::new_with_empty( - cnt::contract::execute, - cnt::contract::instantiate, - cnt::contract::query, - )); - - app.borrow_mut().store_code(contract) -} diff --git a/packages/astroport_mocks/src/xastro.rs b/packages/astroport_mocks/src/xastro.rs deleted file mode 100644 index aa3a638b7..000000000 --- a/packages/astroport_mocks/src/xastro.rs +++ /dev/null @@ -1,138 +0,0 @@ -use astroport::{ - asset::AssetInfo, - token::{InstantiateMsg, MinterResponse}, -}; -use cosmwasm_std::{Addr, Api, CustomMsg, CustomQuery, Storage}; -use cw_multi_test::{ - Bank, ContractWrapper, Distribution, Executor, Gov, Ibc, Module, Staking, Stargate, -}; -use serde::de::DeserializeOwned; - -use crate::{astroport_address, MockToken, WKApp, ASTROPORT}; - -pub fn store_code(app: &WKApp) -> u64 -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - use astroport_xastro_token as cnt; - let contract = Box::new(ContractWrapper::new_with_empty( - cnt::contract::execute, - cnt::contract::instantiate, - cnt::contract::query, - )); - - app.borrow_mut().store_code(contract) -} - -pub struct MockXastroBuilder { - pub app: WKApp, - pub symbol: String, -} - -impl MockXastroBuilder -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - pub fn new(app: &WKApp, symbol: &str) -> Self { - Self { - app: app.clone(), - symbol: symbol.into(), - } - } - - pub fn instantiate(self) -> MockXastro { - let code_id = store_code(&self.app); - let astroport = astroport_address(); - - let address = self - .app - .borrow_mut() - .instantiate_contract( - code_id, - astroport, - &InstantiateMsg { - name: self.symbol.clone(), - mint: Some(MinterResponse { - minter: ASTROPORT.to_owned(), - cap: None, - }), - symbol: self.symbol.clone(), - decimals: 6, - marketing: None, - initial_balances: vec![], - }, - &[], - self.symbol, - Some(ASTROPORT.to_owned()), - ) - .unwrap(); - - MockXastro { - app: self.app.clone(), - address: address.clone(), - token: MockToken { - app: self.app, - address, - }, - } - } -} - -pub struct MockXastro { - pub app: WKApp, - pub address: Addr, - pub token: MockToken, -} - -impl TryFrom<(WKApp, &AssetInfo)> - for MockXastro -where - B: Bank, - A: Api, - S: Storage, - C: Module, - X: Staking, - D: Distribution, - I: Ibc, - G: Gov, - T: Stargate, - C::ExecT: CustomMsg + DeserializeOwned + 'static, - C::QueryT: CustomQuery + DeserializeOwned + 'static, -{ - type Error = String; - fn try_from( - value: (WKApp, &AssetInfo), - ) -> Result, Self::Error> { - match value.1 { - AssetInfo::Token { contract_addr } => Ok(MockXastro { - app: value.0.clone(), - address: contract_addr.clone(), - token: MockToken { - app: value.0, - address: contract_addr.clone(), - }, - }), - AssetInfo::NativeToken { denom } => Err(format!("{} is native coin!", denom)), - } - } -} diff --git a/packages/astroport_pcl_common/Cargo.toml b/packages/astroport_pcl_common/Cargo.toml index 3f91bcbf8..6cc5d46f9 100644 --- a/packages/astroport_pcl_common/Cargo.toml +++ b/packages/astroport_pcl_common/Cargo.toml @@ -9,15 +9,20 @@ homepage = "https://astroport.fi" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +injective = ["astroport/injective"] +sei = ["astroport/sei"] + [dependencies] cosmwasm-std.workspace = true cosmwasm-schema.workspace = true cw-storage-plus.workspace = true cw20 = "1" thiserror.workspace = true -astroport = { path = "../astroport", version = "4" } +astroport = { path = "../astroport", version = "5" } astroport-factory = { path = "../../contracts/factory", version = "1.5", features = ["library"] } itertools.workspace = true [dev-dependencies] anyhow = "1" +astroport-test = { path = "../astroport_test" } \ No newline at end of file diff --git a/packages/astroport_pcl_common/src/error.rs b/packages/astroport_pcl_common/src/error.rs index fcea9b470..b729606fa 100644 --- a/packages/astroport_pcl_common/src/error.rs +++ b/packages/astroport_pcl_common/src/error.rs @@ -29,7 +29,7 @@ pub enum PclError { #[error("Unauthorized")] Unauthorized {}, - #[error("Generator address is not set in factory. Cannot auto-stake")] + #[error("Incentives address is not set in factory. Cannot auto-stake")] AutoStakeError {}, #[error("Operation exceeds max spread limit")] diff --git a/packages/astroport_pcl_common/src/state.rs b/packages/astroport_pcl_common/src/state.rs index a671fd62f..95d67cd5e 100644 --- a/packages/astroport_pcl_common/src/state.rs +++ b/packages/astroport_pcl_common/src/state.rs @@ -41,6 +41,8 @@ pub struct Config { pub track_asset_balances: bool, /// The config for swap fee sharing pub fee_share: Option, + /// The tracker contract address + pub tracker_addr: Option, } /// This structure stores the pool parameters which may be adjusted via the `update_pool_params`. diff --git a/packages/astroport_pcl_common/src/utils.rs b/packages/astroport_pcl_common/src/utils.rs index 62d5b0dd8..ec9c7b237 100644 --- a/packages/astroport_pcl_common/src/utils.rs +++ b/packages/astroport_pcl_common/src/utils.rs @@ -1,13 +1,14 @@ use cosmwasm_std::{ - to_json_binary, wasm_execute, Addr, Api, CosmosMsg, CustomMsg, CustomQuery, Decimal, - Decimal256, Env, Fraction, QuerierWrapper, StdError, StdResult, Uint128, + coin, wasm_execute, Addr, Api, CosmosMsg, CustomMsg, CustomQuery, Decimal, Decimal256, Env, + Fraction, QuerierWrapper, StdError, StdResult, Uint128, }; -use cw20::Cw20ExecuteMsg; use itertools::Itertools; use astroport::asset::{Asset, AssetInfo, Decimal256Ext, DecimalAsset}; use astroport::cosmwasm_ext::AbsDiff; +use astroport::incentives::ExecuteMsg as IncentiveExecuteMsg; use astroport::querier::query_factory_config; +use astroport::token_factory::tf_mint_msg; use astroport_factory::state::pair_key; use crate::consts::{ @@ -17,6 +18,9 @@ use crate::error::PclError; use crate::state::{Config, PoolParams, PriceState}; use crate::{calc_d, calc_y}; +#[cfg(any(feature = "injective", feature = "sei"))] +use cosmwasm_std::BankMsg; + /// Helper function to check the given asset infos are valid. pub fn check_asset_infos(api: &dyn Api, asset_infos: &[AssetInfo]) -> Result<(), PclError> { if !asset_infos.iter().all_unique() { @@ -49,14 +53,14 @@ pub fn check_cw20_in_pool(config: &Config, cw20_sender: &Addr) -> Result<(), Pcl Err(PclError::Unauthorized {}) } -/// Mint LP tokens for a beneficiary and auto stake the tokens in the Generator contract (if auto staking is specified). +/// Mint LP tokens for a beneficiary and auto stake the tokens in the Incentive contract (if auto staking is specified). /// /// * **recipient** LP token recipient. /// -/// * **amount** amount of LP tokens that will be minted for the recipient. +/// * **coin** denom and amount of LP tokens that will be minted for the recipient. /// /// * **auto_stake** determines whether the newly minted LP tokens will -/// be automatically staked in the Generator on behalf of the recipient. +/// be automatically staked in the Incentives Contract on behalf of the recipient. pub fn mint_liquidity_token_message( querier: QuerierWrapper, config: &Config, @@ -69,48 +73,29 @@ where C: CustomQuery, T: CustomMsg, { - let lp_token = &config.pair_info.liquidity_token; + let coin = coin(amount.into(), config.pair_info.liquidity_token.to_string()); // If no auto-stake - just mint to recipient if !auto_stake { - return Ok(vec![wasm_execute( - lp_token, - &Cw20ExecuteMsg::Mint { - recipient: recipient.to_string(), - amount, - }, - vec![], - )? - .into()]); + return Ok(tf_mint_msg(contract_address, coin, recipient)); } - // Mint for the pair contract and stake into the Generator contract - let generator = query_factory_config(&querier, &config.factory_addr)?.generator_address; + // Mint for the pair contract and stake into the Incentives contract + let incentives_addr = query_factory_config(&querier, &config.factory_addr)?.generator_address; - if let Some(generator) = generator { - Ok(vec![ - wasm_execute( - lp_token, - &Cw20ExecuteMsg::Mint { - recipient: contract_address.to_string(), - amount, - }, - vec![], - )? - .into(), + if let Some(address) = incentives_addr { + let mut msgs = tf_mint_msg(contract_address, coin.clone(), contract_address); + msgs.push( wasm_execute( - lp_token, - &Cw20ExecuteMsg::Send { - contract: generator.to_string(), - amount, - msg: to_json_binary(&astroport::incentives::Cw20Msg::Deposit { - recipient: Some(recipient.to_string()), - })?, + address, + &IncentiveExecuteMsg::Deposit { + recipient: Some(recipient.to_string()), }, - vec![], + vec![coin], )? .into(), - ]) + ); + Ok(msgs) } else { Err(PclError::AutoStakeError {}) } @@ -422,26 +407,11 @@ where #[cfg(test)] mod tests { - use std::error::Error; - use std::fmt::Display; - use std::str::FromStr; - use crate::state::PoolParams; + use astroport_test::convert::{dec_to_f64, f64_to_dec}; use super::*; - pub fn f64_to_dec(val: f64) -> T - where - T: FromStr, - T::Err: Error, - { - T::from_str(&val.to_string()).unwrap() - } - - pub fn dec_to_f64(val: impl Display) -> f64 { - f64::from_str(&val.to_string()).unwrap() - } - #[test] fn test_provide_fees() { let params = PoolParams { diff --git a/packages/astroport_test/Cargo.toml b/packages/astroport_test/Cargo.toml new file mode 100644 index 000000000..08cfbe9cf --- /dev/null +++ b/packages/astroport_test/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "astroport-test" +version = "0.1.0" +authors = ["Astroport"] +edition = "2021" +description = "Astroport Test used for integration testing" +license = "GPL-3.0-only" +repository = "https://github.com/astroport-fi/astroport-core" +homepage = "https://astroport.fi" + +[features] +default = [] +injective = [] +sei = [] +cosmwasm_1_1 = ["cosmwasm-std/cosmwasm_1_1", "cw-multi-test/cosmwasm_1_1"] +cosmwasm_1_2 = ["cosmwasm_1_1", "cosmwasm-std/cosmwasm_1_2", "cw-multi-test/cosmwasm_1_2"] +cosmwasm_1_3 = ["cosmwasm_1_2", "cosmwasm-std/cosmwasm_1_3", "cw-multi-test/cosmwasm_1_3"] +cosmwasm_1_4 = ["cosmwasm_1_3", "cosmwasm-std/cosmwasm_1_4", "cw-multi-test/cosmwasm_1_4"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +astroport = { path = "../astroport" } +cosmwasm-schema = "1.2.5" +cosmwasm-std = "1.2.5" +cw-multi-test = { git = "https://github.com/astroport-fi/cw-multi-test", branch = "feat/bank_with_send_hooks_1_0", features = ["cosmwasm_1_1"] } +serde = "1.0" +schemars = "0.8.1" +anyhow = "1.0" +itertools = { workspace = true } +cw-utils = { workspace = true } +cw-storage-plus = { workspace = true } \ No newline at end of file diff --git a/packages/astroport_test/src/coins.rs b/packages/astroport_test/src/coins.rs new file mode 100644 index 000000000..9b371bde9 --- /dev/null +++ b/packages/astroport_test/src/coins.rs @@ -0,0 +1,35 @@ +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub enum TestCoin { + Cw20(String), + Cw20Precise(String, u8), + Native(String), +} + +impl TestCoin { + pub fn denom(&self) -> Option { + match self { + TestCoin::Native(denom) => Some(denom.clone()), + _ => None, + } + } + + pub fn cw20_init_data(&self) -> Option<(String, u8)> { + match self { + TestCoin::Cw20(name) => Some((name.clone(), 6u8)), + TestCoin::Cw20Precise(name, precision) => Some((name.clone(), *precision)), + _ => None, + } + } + + pub fn native(denom: &str) -> Self { + Self::Native(denom.to_string()) + } + + pub fn cw20(name: &str) -> Self { + Self::Cw20(name.to_string()) + } + + pub fn cw20precise(name: &str, precision: u8) -> Self { + Self::Cw20Precise(name.to_string(), precision) + } +} diff --git a/packages/astroport_test/src/convert.rs b/packages/astroport_test/src/convert.rs new file mode 100644 index 000000000..091b40cff --- /dev/null +++ b/packages/astroport_test/src/convert.rs @@ -0,0 +1,13 @@ +use std::{error::Error, fmt::Display, str::FromStr}; + +pub fn f64_to_dec(val: f64) -> T +where + T: FromStr, + T::Err: Error, +{ + T::from_str(&val.to_string()).unwrap() +} + +pub fn dec_to_f64(val: impl Display) -> f64 { + f64::from_str(&val.to_string()).unwrap() +} diff --git a/packages/astroport_test/src/lib.rs b/packages/astroport_test/src/lib.rs new file mode 100644 index 000000000..c3cadb9df --- /dev/null +++ b/packages/astroport_test/src/lib.rs @@ -0,0 +1,7 @@ +#![cfg(not(tarpaulin_include))] + +pub use cw_multi_test; + +pub mod coins; +pub mod convert; +pub mod modules; diff --git a/packages/astroport_test/src/modules/mod.rs b/packages/astroport_test/src/modules/mod.rs new file mode 100644 index 000000000..570f7f580 --- /dev/null +++ b/packages/astroport_test/src/modules/mod.rs @@ -0,0 +1 @@ +pub mod stargate; diff --git a/packages/astroport_test/src/modules/stargate.rs b/packages/astroport_test/src/modules/stargate.rs new file mode 100644 index 000000000..e68cc16e7 --- /dev/null +++ b/packages/astroport_test/src/modules/stargate.rs @@ -0,0 +1,149 @@ +use cosmwasm_schema::serde::de::DeserializeOwned; +use cosmwasm_std::{ + coins, + testing::{MockApi, MockStorage}, + Addr, Api, BankMsg, Binary, BlockInfo, CustomMsg, CustomQuery, Empty, Querier, Storage, + SubMsgResponse, +}; +use cw_multi_test::{ + App, AppResponse, BankKeeper, BankSudo, CosmosRouter, DistributionKeeper, FailingModule, + GovFailingModule, IbcFailingModule, Module, StakeKeeper, Stargate, StargateMsg, StargateQuery, + SudoMsg, WasmKeeper, +}; + +use anyhow::{Ok, Result as AnyResult}; + +use astroport::token_factory::{ + MsgBurn, MsgCreateDenom, MsgCreateDenomResponse, MsgMint, MsgSetBeforeSendHook, +}; + +pub type StargateApp = App< + BankKeeper, + MockApi, + MockStorage, + FailingModule, + WasmKeeper, + StakeKeeper, + DistributionKeeper, + IbcFailingModule, + GovFailingModule, + MockStargate, +>; + +#[derive(Default)] +pub struct MockStargate {} + +impl Stargate for MockStargate {} + +impl Module for MockStargate { + type ExecT = StargateMsg; + type QueryT = StargateQuery; + type SudoT = Empty; + + fn execute( + &self, + api: &dyn Api, + storage: &mut dyn Storage, + router: &dyn CosmosRouter, + block: &BlockInfo, + sender: Addr, + msg: Self::ExecT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + let StargateMsg { + type_url, value, .. + } = msg; + + match type_url.as_str() { + MsgCreateDenom::TYPE_URL => { + let tf_msg: MsgCreateDenom = value.try_into()?; + #[cfg(not(any(feature = "injective", feature = "sei")))] + let sender_address = tf_msg.sender.to_string(); + #[cfg(any(feature = "injective", feature = "sei"))] + let sender_address = sender.to_string(); + let submsg_response = SubMsgResponse { + events: vec![], + data: Some( + MsgCreateDenomResponse { + new_token_denom: format!( + "factory/{}/{}", + sender_address, tf_msg.subdenom + ), + } + .into(), + ), + }; + Ok(submsg_response.into()) + } + MsgMint::TYPE_URL => { + let tf_msg: MsgMint = value.try_into()?; + let mint_coins = tf_msg + .amount + .expect("Empty amount in tokenfactory MsgMint!"); + #[cfg(not(any(feature = "injective", feature = "sei")))] + let to_address = tf_msg.mint_to_address.to_string(); + #[cfg(any(feature = "injective", feature = "sei"))] + let to_address = sender.to_string(); + let bank_sudo = BankSudo::Mint { + to_address, + amount: coins(mint_coins.amount.parse()?, mint_coins.denom), + }; + router.sudo(api, storage, block, bank_sudo.into()) + } + MsgBurn::TYPE_URL => { + let tf_msg: MsgBurn = value.try_into()?; + let burn_coins = tf_msg + .amount + .expect("Empty amount in tokenfactory MsgBurn!"); + let burn_msg = BankMsg::Burn { + amount: coins(burn_coins.amount.parse()?, burn_coins.denom), + }; + router.execute( + api, + storage, + block, + Addr::unchecked(sender), + burn_msg.into(), + ) + } + MsgSetBeforeSendHook::TYPE_URL => { + let before_hook_msg: MsgSetBeforeSendHook = value.try_into()?; + let msg = BankSudo::SetHook { + contract_addr: before_hook_msg.cosmwasm_address, + denom: before_hook_msg.denom, + }; + router.sudo(api, storage, block, SudoMsg::Bank(msg)) + } + _ => Err(anyhow::anyhow!( + "Unexpected exec msg {type_url} from {sender:?}", + )), + } + } + fn query( + &self, + _api: &dyn Api, + _storage: &dyn Storage, + _querier: &dyn Querier, + _block: &BlockInfo, + _request: Self::QueryT, + ) -> AnyResult { + Ok(Binary::default()) + } + fn sudo( + &self, + _api: &dyn Api, + _storage: &mut dyn Storage, + _router: &dyn CosmosRouter, + _block: &BlockInfo, + _msg: Self::SudoT, + ) -> AnyResult + where + ExecC: CustomMsg + DeserializeOwned + 'static, + QueryC: CustomQuery + DeserializeOwned + 'static, + { + unimplemented!("Sudo not implemented") + } +} diff --git a/schemas/astroport-factory/astroport-factory.json b/schemas/astroport-factory/astroport-factory.json index 8a86721e1..dcdff1928 100644 --- a/schemas/astroport-factory/astroport-factory.json +++ b/schemas/astroport-factory/astroport-factory.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-factory", - "contract_version": "1.7.0", + "contract_version": "1.8.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -50,6 +50,17 @@ "format": "uint64", "minimum": 0.0 }, + "tracker_config": { + "description": "Config for the tracking contract", + "anyOf": [ + { + "$ref": "#/definitions/TrackerConfig" + }, + { + "type": "null" + } + ] + }, "whitelist_code_id": { "description": "CW1 whitelist contract code id used to store 3rd party rewards for staking Astroport LP tokens", "type": "integer", @@ -158,6 +169,26 @@ "additionalProperties": false } ] + }, + "TrackerConfig": { + "type": "object", + "required": [ + "code_id", + "token_factory_addr" + ], + "properties": { + "code_id": { + "description": "Tracking contract code id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_factory_addr": { + "description": "Token factory module address", + "type": "string" + } + }, + "additionalProperties": false } } }, @@ -221,6 +252,37 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "update_tracker_config" + ], + "properties": { + "update_tracker_config": { + "type": "object", + "required": [ + "tracker_code_id" + ], + "properties": { + "token_factory_addr": { + "description": "Token factory module address", + "type": [ + "string", + "null" + ] + }, + "tracker_code_id": { + "description": "Tracking contract code id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "UpdatePairConfig updates the config for a pair type.", "type": "object", @@ -659,6 +721,19 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "tracker_config" + ], + "properties": { + "tracker_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -1071,12 +1146,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", @@ -1288,12 +1359,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", @@ -1353,6 +1420,28 @@ ] } } + }, + "tracker_config": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TrackerConfig", + "type": "object", + "required": [ + "code_id", + "token_factory_addr" + ], + "properties": { + "code_id": { + "description": "Tracking contract code id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_factory_addr": { + "description": "Token factory module address", + "type": "string" + } + }, + "additionalProperties": false } } } diff --git a/schemas/astroport-factory/raw/execute.json b/schemas/astroport-factory/raw/execute.json index da9eb29eb..df0a7f2e9 100644 --- a/schemas/astroport-factory/raw/execute.json +++ b/schemas/astroport-factory/raw/execute.json @@ -58,6 +58,37 @@ }, "additionalProperties": false }, + { + "type": "object", + "required": [ + "update_tracker_config" + ], + "properties": { + "update_tracker_config": { + "type": "object", + "required": [ + "tracker_code_id" + ], + "properties": { + "token_factory_addr": { + "description": "Token factory module address", + "type": [ + "string", + "null" + ] + }, + "tracker_code_id": { + "description": "Tracking contract code id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "UpdatePairConfig updates the config for a pair type.", "type": "object", diff --git a/schemas/astroport-factory/raw/instantiate.json b/schemas/astroport-factory/raw/instantiate.json index db565da79..3c8e5ea01 100644 --- a/schemas/astroport-factory/raw/instantiate.json +++ b/schemas/astroport-factory/raw/instantiate.json @@ -46,6 +46,17 @@ "format": "uint64", "minimum": 0.0 }, + "tracker_config": { + "description": "Config for the tracking contract", + "anyOf": [ + { + "$ref": "#/definitions/TrackerConfig" + }, + { + "type": "null" + } + ] + }, "whitelist_code_id": { "description": "CW1 whitelist contract code id used to store 3rd party rewards for staking Astroport LP tokens", "type": "integer", @@ -154,6 +165,26 @@ "additionalProperties": false } ] + }, + "TrackerConfig": { + "type": "object", + "required": [ + "code_id", + "token_factory_addr" + ], + "properties": { + "code_id": { + "description": "Tracking contract code id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_factory_addr": { + "description": "Token factory module address", + "type": "string" + } + }, + "additionalProperties": false } } } diff --git a/schemas/astroport-factory/raw/query.json b/schemas/astroport-factory/raw/query.json index 4325378e2..d0cc25215 100644 --- a/schemas/astroport-factory/raw/query.json +++ b/schemas/astroport-factory/raw/query.json @@ -118,6 +118,19 @@ } }, "additionalProperties": false + }, + { + "type": "object", + "required": [ + "tracker_config" + ], + "properties": { + "tracker_config": { + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { diff --git a/schemas/astroport-factory/raw/response_to_pair.json b/schemas/astroport-factory/raw/response_to_pair.json index 837f3f8dc..16721f81d 100644 --- a/schemas/astroport-factory/raw/response_to_pair.json +++ b/schemas/astroport-factory/raw/response_to_pair.json @@ -26,12 +26,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", diff --git a/schemas/astroport-factory/raw/response_to_pairs.json b/schemas/astroport-factory/raw/response_to_pairs.json index 2249d13ca..aed0c202b 100644 --- a/schemas/astroport-factory/raw/response_to_pairs.json +++ b/schemas/astroport-factory/raw/response_to_pairs.json @@ -96,12 +96,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", diff --git a/schemas/astroport-factory/raw/response_to_tracker_config.json b/schemas/astroport-factory/raw/response_to_tracker_config.json new file mode 100644 index 000000000..cc7839d9e --- /dev/null +++ b/schemas/astroport-factory/raw/response_to_tracker_config.json @@ -0,0 +1,22 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "TrackerConfig", + "type": "object", + "required": [ + "code_id", + "token_factory_addr" + ], + "properties": { + "code_id": { + "description": "Tracking contract code id", + "type": "integer", + "format": "uint64", + "minimum": 0.0 + }, + "token_factory_addr": { + "description": "Token factory module address", + "type": "string" + } + }, + "additionalProperties": false +} diff --git a/schemas/astroport-liquidity-manager/astroport-liquidity-manager.json b/schemas/astroport-liquidity-manager/astroport-liquidity-manager.json deleted file mode 100644 index 6bfceecb5..000000000 --- a/schemas/astroport-liquidity-manager/astroport-liquidity-manager.json +++ /dev/null @@ -1,876 +0,0 @@ -{ - "contract_name": "astroport-liquidity-manager", - "contract_version": "1.2.0", - "idl_version": "1.0.0", - "instantiate": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "astroport_factory" - ], - "properties": { - "astroport_factory": { - "type": "string" - } - }, - "additionalProperties": false - }, - "execute": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "provide_liquidity" - ], - "properties": { - "provide_liquidity": { - "type": "object", - "required": [ - "pair_addr", - "pair_msg" - ], - "properties": { - "min_lp_to_receive": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "pair_addr": { - "type": "string" - }, - "pair_msg": { - "$ref": "#/definitions/ExecuteMsg" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Asset": { - "description": "This enum describes a Terra asset (native or CW20).", - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "description": "A token amount", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "info": { - "description": "Information about an asset stored in a [`AssetInfo`] struct", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", - "oneOf": [ - { - "description": "Non-native Token", - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Native token", - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "ExecuteMsg": { - "description": "This structure describes the execute messages available in the contract.", - "oneOf": [ - { - "description": "Receives a message of type [`Cw20ReceiveMsg`]", - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "description": "ProvideLiquidity allows someone to provide liquidity in the pool", - "type": "object", - "required": [ - "provide_liquidity" - ], - "properties": { - "provide_liquidity": { - "type": "object", - "required": [ - "assets" - ], - "properties": { - "assets": { - "description": "The assets available in the pool", - "type": "array", - "items": { - "$ref": "#/definitions/Asset" - } - }, - "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", - "type": [ - "boolean", - "null" - ] - }, - "receiver": { - "description": "The receiver of LP tokens", - "type": [ - "string", - "null" - ] - }, - "slippage_tolerance": { - "description": "The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Swap performs a swap in the pool", - "type": "object", - "required": [ - "swap" - ], - "properties": { - "swap": { - "type": "object", - "required": [ - "offer_asset" - ], - "properties": { - "ask_asset_info": { - "anyOf": [ - { - "$ref": "#/definitions/AssetInfo" - }, - { - "type": "null" - } - ] - }, - "belief_price": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "max_spread": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "offer_asset": { - "$ref": "#/definitions/Asset" - }, - "to": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Update the pair configuration", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "required": [ - "params" - ], - "properties": { - "params": { - "$ref": "#/definitions/Binary" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "ProposeNewOwner creates a proposal to change contract ownership. The validity period for the proposal is set in the `expires_in` variable.", - "type": "object", - "required": [ - "propose_new_owner" - ], - "properties": { - "propose_new_owner": { - "type": "object", - "required": [ - "expires_in", - "owner" - ], - "properties": { - "expires_in": { - "description": "The date after which this proposal expires", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Newly proposed contract owner", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "DropOwnershipProposal removes the existing offer to change contract ownership.", - "type": "object", - "required": [ - "drop_ownership_proposal" - ], - "properties": { - "drop_ownership_proposal": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Used to claim contract ownership.", - "type": "object", - "required": [ - "claim_ownership" - ], - "properties": { - "claim_ownership": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "simulate_provide" - ], - "properties": { - "simulate_provide": { - "type": "object", - "required": [ - "pair_addr", - "pair_msg" - ], - "properties": { - "pair_addr": { - "type": "string" - }, - "pair_msg": { - "$ref": "#/definitions/ExecuteMsg" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "simulate_withdraw" - ], - "properties": { - "simulate_withdraw": { - "type": "object", - "required": [ - "lp_tokens", - "pair_addr" - ], - "properties": { - "lp_tokens": { - "$ref": "#/definitions/Uint128" - }, - "pair_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Asset": { - "description": "This enum describes a Terra asset (native or CW20).", - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "description": "A token amount", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "info": { - "description": "Information about an asset stored in a [`AssetInfo`] struct", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", - "oneOf": [ - { - "description": "Non-native Token", - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Native token", - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "ExecuteMsg": { - "description": "This structure describes the execute messages available in the contract.", - "oneOf": [ - { - "description": "Receives a message of type [`Cw20ReceiveMsg`]", - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "description": "ProvideLiquidity allows someone to provide liquidity in the pool", - "type": "object", - "required": [ - "provide_liquidity" - ], - "properties": { - "provide_liquidity": { - "type": "object", - "required": [ - "assets" - ], - "properties": { - "assets": { - "description": "The assets available in the pool", - "type": "array", - "items": { - "$ref": "#/definitions/Asset" - } - }, - "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", - "type": [ - "boolean", - "null" - ] - }, - "receiver": { - "description": "The receiver of LP tokens", - "type": [ - "string", - "null" - ] - }, - "slippage_tolerance": { - "description": "The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Swap performs a swap in the pool", - "type": "object", - "required": [ - "swap" - ], - "properties": { - "swap": { - "type": "object", - "required": [ - "offer_asset" - ], - "properties": { - "ask_asset_info": { - "anyOf": [ - { - "$ref": "#/definitions/AssetInfo" - }, - { - "type": "null" - } - ] - }, - "belief_price": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "max_spread": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "offer_asset": { - "$ref": "#/definitions/Asset" - }, - "to": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Update the pair configuration", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "required": [ - "params" - ], - "properties": { - "params": { - "$ref": "#/definitions/Binary" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "ProposeNewOwner creates a proposal to change contract ownership. The validity period for the proposal is set in the `expires_in` variable.", - "type": "object", - "required": [ - "propose_new_owner" - ], - "properties": { - "propose_new_owner": { - "type": "object", - "required": [ - "expires_in", - "owner" - ], - "properties": { - "expires_in": { - "description": "The date after which this proposal expires", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Newly proposed contract owner", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "DropOwnershipProposal removes the existing offer to change contract ownership.", - "type": "object", - "required": [ - "drop_ownership_proposal" - ], - "properties": { - "drop_ownership_proposal": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Used to claim contract ownership.", - "type": "object", - "required": [ - "claim_ownership" - ], - "properties": { - "claim_ownership": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - }, - "migrate": null, - "sudo": null, - "responses": { - "simulate_provide": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Uint128", - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - }, - "simulate_withdraw": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "Array_of_Asset", - "type": "array", - "items": { - "$ref": "#/definitions/Asset" - }, - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Asset": { - "description": "This enum describes a Terra asset (native or CW20).", - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "description": "A token amount", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "info": { - "description": "Information about an asset stored in a [`AssetInfo`] struct", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", - "oneOf": [ - { - "description": "Non-native Token", - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Native token", - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } - } - } -} diff --git a/schemas/astroport-liquidity-manager/raw/execute.json b/schemas/astroport-liquidity-manager/raw/execute.json deleted file mode 100644 index d5516427f..000000000 --- a/schemas/astroport-liquidity-manager/raw/execute.json +++ /dev/null @@ -1,375 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "provide_liquidity" - ], - "properties": { - "provide_liquidity": { - "type": "object", - "required": [ - "pair_addr", - "pair_msg" - ], - "properties": { - "min_lp_to_receive": { - "anyOf": [ - { - "$ref": "#/definitions/Uint128" - }, - { - "type": "null" - } - ] - }, - "pair_addr": { - "type": "string" - }, - "pair_msg": { - "$ref": "#/definitions/ExecuteMsg" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Asset": { - "description": "This enum describes a Terra asset (native or CW20).", - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "description": "A token amount", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "info": { - "description": "Information about an asset stored in a [`AssetInfo`] struct", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", - "oneOf": [ - { - "description": "Non-native Token", - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Native token", - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "ExecuteMsg": { - "description": "This structure describes the execute messages available in the contract.", - "oneOf": [ - { - "description": "Receives a message of type [`Cw20ReceiveMsg`]", - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "description": "ProvideLiquidity allows someone to provide liquidity in the pool", - "type": "object", - "required": [ - "provide_liquidity" - ], - "properties": { - "provide_liquidity": { - "type": "object", - "required": [ - "assets" - ], - "properties": { - "assets": { - "description": "The assets available in the pool", - "type": "array", - "items": { - "$ref": "#/definitions/Asset" - } - }, - "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", - "type": [ - "boolean", - "null" - ] - }, - "receiver": { - "description": "The receiver of LP tokens", - "type": [ - "string", - "null" - ] - }, - "slippage_tolerance": { - "description": "The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Swap performs a swap in the pool", - "type": "object", - "required": [ - "swap" - ], - "properties": { - "swap": { - "type": "object", - "required": [ - "offer_asset" - ], - "properties": { - "ask_asset_info": { - "anyOf": [ - { - "$ref": "#/definitions/AssetInfo" - }, - { - "type": "null" - } - ] - }, - "belief_price": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "max_spread": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "offer_asset": { - "$ref": "#/definitions/Asset" - }, - "to": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Update the pair configuration", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "required": [ - "params" - ], - "properties": { - "params": { - "$ref": "#/definitions/Binary" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "ProposeNewOwner creates a proposal to change contract ownership. The validity period for the proposal is set in the `expires_in` variable.", - "type": "object", - "required": [ - "propose_new_owner" - ], - "properties": { - "propose_new_owner": { - "type": "object", - "required": [ - "expires_in", - "owner" - ], - "properties": { - "expires_in": { - "description": "The date after which this proposal expires", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Newly proposed contract owner", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "DropOwnershipProposal removes the existing offer to change contract ownership.", - "type": "object", - "required": [ - "drop_ownership_proposal" - ], - "properties": { - "drop_ownership_proposal": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Used to claim contract ownership.", - "type": "object", - "required": [ - "claim_ownership" - ], - "properties": { - "claim_ownership": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/schemas/astroport-liquidity-manager/raw/instantiate.json b/schemas/astroport-liquidity-manager/raw/instantiate.json deleted file mode 100644 index eb48dea5e..000000000 --- a/schemas/astroport-liquidity-manager/raw/instantiate.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "InstantiateMsg", - "type": "object", - "required": [ - "astroport_factory" - ], - "properties": { - "astroport_factory": { - "type": "string" - } - }, - "additionalProperties": false -} diff --git a/schemas/astroport-liquidity-manager/raw/query.json b/schemas/astroport-liquidity-manager/raw/query.json deleted file mode 100644 index a368c40ea..000000000 --- a/schemas/astroport-liquidity-manager/raw/query.json +++ /dev/null @@ -1,378 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ - { - "type": "object", - "required": [ - "simulate_provide" - ], - "properties": { - "simulate_provide": { - "type": "object", - "required": [ - "pair_addr", - "pair_msg" - ], - "properties": { - "pair_addr": { - "type": "string" - }, - "pair_msg": { - "$ref": "#/definitions/ExecuteMsg" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "type": "object", - "required": [ - "simulate_withdraw" - ], - "properties": { - "simulate_withdraw": { - "type": "object", - "required": [ - "lp_tokens", - "pair_addr" - ], - "properties": { - "lp_tokens": { - "$ref": "#/definitions/Uint128" - }, - "pair_addr": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ], - "definitions": { - "Addr": { - "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", - "type": "string" - }, - "Asset": { - "description": "This enum describes a Terra asset (native or CW20).", - "type": "object", - "required": [ - "amount", - "info" - ], - "properties": { - "amount": { - "description": "A token amount", - "allOf": [ - { - "$ref": "#/definitions/Uint128" - } - ] - }, - "info": { - "description": "Information about an asset stored in a [`AssetInfo`] struct", - "allOf": [ - { - "$ref": "#/definitions/AssetInfo" - } - ] - } - }, - "additionalProperties": false - }, - "AssetInfo": { - "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", - "oneOf": [ - { - "description": "Non-native Token", - "type": "object", - "required": [ - "token" - ], - "properties": { - "token": { - "type": "object", - "required": [ - "contract_addr" - ], - "properties": { - "contract_addr": { - "$ref": "#/definitions/Addr" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Native token", - "type": "object", - "required": [ - "native_token" - ], - "properties": { - "native_token": { - "type": "object", - "required": [ - "denom" - ], - "properties": { - "denom": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Binary": { - "description": "Binary is a wrapper around Vec to add base64 de/serialization with serde. It also adds some helper methods to help encode inline.\n\nThis is only needed as serde-json-{core,wasm} has a horrible encoding for Vec. See also .", - "type": "string" - }, - "Cw20ReceiveMsg": { - "description": "Cw20ReceiveMsg should be de/serialized under `Receive()` variant in a ExecuteMsg", - "type": "object", - "required": [ - "amount", - "msg", - "sender" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" - }, - "msg": { - "$ref": "#/definitions/Binary" - }, - "sender": { - "type": "string" - } - }, - "additionalProperties": false - }, - "Decimal": { - "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", - "type": "string" - }, - "ExecuteMsg": { - "description": "This structure describes the execute messages available in the contract.", - "oneOf": [ - { - "description": "Receives a message of type [`Cw20ReceiveMsg`]", - "type": "object", - "required": [ - "receive" - ], - "properties": { - "receive": { - "$ref": "#/definitions/Cw20ReceiveMsg" - } - }, - "additionalProperties": false - }, - { - "description": "ProvideLiquidity allows someone to provide liquidity in the pool", - "type": "object", - "required": [ - "provide_liquidity" - ], - "properties": { - "provide_liquidity": { - "type": "object", - "required": [ - "assets" - ], - "properties": { - "assets": { - "description": "The assets available in the pool", - "type": "array", - "items": { - "$ref": "#/definitions/Asset" - } - }, - "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", - "type": [ - "boolean", - "null" - ] - }, - "receiver": { - "description": "The receiver of LP tokens", - "type": [ - "string", - "null" - ] - }, - "slippage_tolerance": { - "description": "The slippage tolerance that allows liquidity provision only if the price in the pool doesn't move too much", - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Swap performs a swap in the pool", - "type": "object", - "required": [ - "swap" - ], - "properties": { - "swap": { - "type": "object", - "required": [ - "offer_asset" - ], - "properties": { - "ask_asset_info": { - "anyOf": [ - { - "$ref": "#/definitions/AssetInfo" - }, - { - "type": "null" - } - ] - }, - "belief_price": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "max_spread": { - "anyOf": [ - { - "$ref": "#/definitions/Decimal" - }, - { - "type": "null" - } - ] - }, - "offer_asset": { - "$ref": "#/definitions/Asset" - }, - "to": { - "type": [ - "string", - "null" - ] - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Update the pair configuration", - "type": "object", - "required": [ - "update_config" - ], - "properties": { - "update_config": { - "type": "object", - "required": [ - "params" - ], - "properties": { - "params": { - "$ref": "#/definitions/Binary" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "ProposeNewOwner creates a proposal to change contract ownership. The validity period for the proposal is set in the `expires_in` variable.", - "type": "object", - "required": [ - "propose_new_owner" - ], - "properties": { - "propose_new_owner": { - "type": "object", - "required": [ - "expires_in", - "owner" - ], - "properties": { - "expires_in": { - "description": "The date after which this proposal expires", - "type": "integer", - "format": "uint64", - "minimum": 0.0 - }, - "owner": { - "description": "Newly proposed contract owner", - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "DropOwnershipProposal removes the existing offer to change contract ownership.", - "type": "object", - "required": [ - "drop_ownership_proposal" - ], - "properties": { - "drop_ownership_proposal": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - }, - { - "description": "Used to claim contract ownership.", - "type": "object", - "required": [ - "claim_ownership" - ], - "properties": { - "claim_ownership": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" - } - } -} diff --git a/schemas/astroport-pair-concentrated/astroport-pair-concentrated.json b/schemas/astroport-pair-concentrated/astroport-pair-concentrated.json index d1dca0d10..0dd0bc465 100644 --- a/schemas/astroport-pair-concentrated/astroport-pair-concentrated.json +++ b/schemas/astroport-pair-concentrated/astroport-pair-concentrated.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-pair-concentrated", - "contract_version": "3.0.0", + "contract_version": "4.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -142,12 +142,22 @@ } }, "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", + "description": "Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract", "type": [ "boolean", "null" ] }, + "min_lp_to_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "receiver": { "description": "The receiver of LP tokens", "type": [ @@ -172,6 +182,38 @@ }, "additionalProperties": false }, + { + "description": "WithdrawLiquidity allows someone to withdraw liquidity from the pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "assets": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "min_assets_to_receive": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Swap performs a swap in the pool", "type": "object", @@ -651,6 +693,63 @@ } }, "additionalProperties": false + }, + { + "description": "Returns an estimation of shares received for the given amount of assets", + "type": "object", + "required": [ + "simulate_provide" + ], + "properties": { + "simulate_provide": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of assets received for the given amount of LP tokens", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "lp_amount" + ], + "properties": { + "lp_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -734,6 +833,10 @@ } ] }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -820,6 +923,17 @@ "type": "null" } ] + }, + "tracker_addr": { + "description": "Tracker contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -1029,12 +1143,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", @@ -1400,6 +1510,106 @@ } } }, + "simulate_provide": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "simulate_withdraw": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "simulation": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "SimulationResponse", diff --git a/schemas/astroport-pair-concentrated/raw/execute.json b/schemas/astroport-pair-concentrated/raw/execute.json index 179a53617..443466010 100644 --- a/schemas/astroport-pair-concentrated/raw/execute.json +++ b/schemas/astroport-pair-concentrated/raw/execute.json @@ -37,12 +37,22 @@ } }, "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", + "description": "Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract", "type": [ "boolean", "null" ] }, + "min_lp_to_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "receiver": { "description": "The receiver of LP tokens", "type": [ @@ -67,6 +77,38 @@ }, "additionalProperties": false }, + { + "description": "WithdrawLiquidity allows someone to withdraw liquidity from the pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "assets": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "min_assets_to_receive": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Swap performs a swap in the pool", "type": "object", diff --git a/schemas/astroport-pair-concentrated/raw/query.json b/schemas/astroport-pair-concentrated/raw/query.json index 3d9514962..ea222d022 100644 --- a/schemas/astroport-pair-concentrated/raw/query.json +++ b/schemas/astroport-pair-concentrated/raw/query.json @@ -222,6 +222,63 @@ } }, "additionalProperties": false + }, + { + "description": "Returns an estimation of shares received for the given amount of assets", + "type": "object", + "required": [ + "simulate_provide" + ], + "properties": { + "simulate_provide": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of assets received for the given amount of LP tokens", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "lp_amount" + ], + "properties": { + "lp_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -305,6 +362,10 @@ } ] }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair-concentrated/raw/response_to_config.json b/schemas/astroport-pair-concentrated/raw/response_to_config.json index ce805e9c4..953b0adc7 100644 --- a/schemas/astroport-pair-concentrated/raw/response_to_config.json +++ b/schemas/astroport-pair-concentrated/raw/response_to_config.json @@ -41,6 +41,17 @@ "type": "null" } ] + }, + "tracker_addr": { + "description": "Tracker contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, diff --git a/schemas/astroport-pair-concentrated/raw/response_to_pair.json b/schemas/astroport-pair-concentrated/raw/response_to_pair.json index 837f3f8dc..16721f81d 100644 --- a/schemas/astroport-pair-concentrated/raw/response_to_pair.json +++ b/schemas/astroport-pair-concentrated/raw/response_to_pair.json @@ -26,12 +26,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", diff --git a/schemas/astroport-liquidity-manager/raw/response_to_simulate_provide.json b/schemas/astroport-pair-concentrated/raw/response_to_simulate_provide.json similarity index 100% rename from schemas/astroport-liquidity-manager/raw/response_to_simulate_provide.json rename to schemas/astroport-pair-concentrated/raw/response_to_simulate_provide.json diff --git a/schemas/astroport-liquidity-manager/raw/response_to_simulate_withdraw.json b/schemas/astroport-pair-concentrated/raw/response_to_simulate_withdraw.json similarity index 100% rename from schemas/astroport-liquidity-manager/raw/response_to_simulate_withdraw.json rename to schemas/astroport-pair-concentrated/raw/response_to_simulate_withdraw.json diff --git a/schemas/astroport-pair-stable/astroport-pair-stable.json b/schemas/astroport-pair-stable/astroport-pair-stable.json index 398f8cfc9..a61843e34 100644 --- a/schemas/astroport-pair-stable/astroport-pair-stable.json +++ b/schemas/astroport-pair-stable/astroport-pair-stable.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-pair-stable", - "contract_version": "3.5.0", + "contract_version": "4.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -142,12 +142,22 @@ } }, "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", + "description": "Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract", "type": [ "boolean", "null" ] }, + "min_lp_to_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "receiver": { "description": "The receiver of LP tokens", "type": [ @@ -172,6 +182,38 @@ }, "additionalProperties": false }, + { + "description": "WithdrawLiquidity allows someone to withdraw liquidity from the pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "assets": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "min_assets_to_receive": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Swap performs a swap in the pool", "type": "object", @@ -637,6 +679,63 @@ } }, "additionalProperties": false + }, + { + "description": "Returns an estimation of assets received for the given amount of LP tokens", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "lp_amount" + ], + "properties": { + "lp_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of shares received for the given amount of assets", + "type": "object", + "required": [ + "simulate_provide" + ], + "properties": { + "simulate_provide": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -720,6 +819,10 @@ } ] }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -800,6 +903,17 @@ "type": "null" } ] + }, + "tracker_addr": { + "description": "Tracker contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -1003,12 +1117,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", @@ -1380,6 +1490,106 @@ } } }, + "simulate_provide": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "simulate_withdraw": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "simulation": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "SimulationResponse", diff --git a/schemas/astroport-pair-stable/raw/execute.json b/schemas/astroport-pair-stable/raw/execute.json index 179a53617..443466010 100644 --- a/schemas/astroport-pair-stable/raw/execute.json +++ b/schemas/astroport-pair-stable/raw/execute.json @@ -37,12 +37,22 @@ } }, "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", + "description": "Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract", "type": [ "boolean", "null" ] }, + "min_lp_to_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "receiver": { "description": "The receiver of LP tokens", "type": [ @@ -67,6 +77,38 @@ }, "additionalProperties": false }, + { + "description": "WithdrawLiquidity allows someone to withdraw liquidity from the pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "assets": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "min_assets_to_receive": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Swap performs a swap in the pool", "type": "object", diff --git a/schemas/astroport-pair-stable/raw/query.json b/schemas/astroport-pair-stable/raw/query.json index c63191060..b7da792ec 100644 --- a/schemas/astroport-pair-stable/raw/query.json +++ b/schemas/astroport-pair-stable/raw/query.json @@ -208,6 +208,63 @@ } }, "additionalProperties": false + }, + { + "description": "Returns an estimation of assets received for the given amount of LP tokens", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "lp_amount" + ], + "properties": { + "lp_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of shares received for the given amount of assets", + "type": "object", + "required": [ + "simulate_provide" + ], + "properties": { + "simulate_provide": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -291,6 +348,10 @@ } ] }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair-stable/raw/response_to_config.json b/schemas/astroport-pair-stable/raw/response_to_config.json index ce805e9c4..953b0adc7 100644 --- a/schemas/astroport-pair-stable/raw/response_to_config.json +++ b/schemas/astroport-pair-stable/raw/response_to_config.json @@ -41,6 +41,17 @@ "type": "null" } ] + }, + "tracker_addr": { + "description": "Tracker contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, diff --git a/schemas/astroport-pair-stable/raw/response_to_pair.json b/schemas/astroport-pair-stable/raw/response_to_pair.json index 837f3f8dc..16721f81d 100644 --- a/schemas/astroport-pair-stable/raw/response_to_pair.json +++ b/schemas/astroport-pair-stable/raw/response_to_pair.json @@ -26,12 +26,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", diff --git a/schemas/astroport-pair-stable/raw/response_to_simulate_provide.json b/schemas/astroport-pair-stable/raw/response_to_simulate_provide.json new file mode 100644 index 000000000..25b73e8f2 --- /dev/null +++ b/schemas/astroport-pair-stable/raw/response_to_simulate_provide.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" +} diff --git a/schemas/astroport-pair-stable/raw/response_to_simulate_withdraw.json b/schemas/astroport-pair-stable/raw/response_to_simulate_withdraw.json new file mode 100644 index 000000000..8285d4916 --- /dev/null +++ b/schemas/astroport-pair-stable/raw/response_to_simulate_withdraw.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair-xyk-sale-tax/astroport-pair-xyk-sale-tax.json b/schemas/astroport-pair-xyk-sale-tax/astroport-pair-xyk-sale-tax.json index 9b38531b2..f299e47f3 100644 --- a/schemas/astroport-pair-xyk-sale-tax/astroport-pair-xyk-sale-tax.json +++ b/schemas/astroport-pair-xyk-sale-tax/astroport-pair-xyk-sale-tax.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-pair-xyk-sale-tax", - "contract_version": "1.6.0", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -142,12 +142,22 @@ } }, "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", + "description": "Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract", "type": [ "boolean", "null" ] }, + "min_lp_to_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "receiver": { "description": "The receiver of LP tokens", "type": [ @@ -172,6 +182,38 @@ }, "additionalProperties": false }, + { + "description": "WithdrawLiquidity allows someone to withdraw liquidity from the pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "assets": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "min_assets_to_receive": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Swap performs a swap in the pool", "type": "object", @@ -637,6 +679,63 @@ } }, "additionalProperties": false + }, + { + "description": "Returns an estimation of assets received for the given amount of LP tokens", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "lp_amount" + ], + "properties": { + "lp_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of shares received for the given amount of assets", + "type": "object", + "required": [ + "simulate_provide" + ], + "properties": { + "simulate_provide": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -720,6 +819,10 @@ } ] }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -853,6 +956,17 @@ "type": "null" } ] + }, + "tracker_addr": { + "description": "Tracker contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -1056,12 +1170,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", @@ -1433,6 +1543,106 @@ } } }, + "simulate_provide": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "simulate_withdraw": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "simulation": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "SimulationResponse", diff --git a/schemas/astroport-pair-xyk-sale-tax/raw/execute.json b/schemas/astroport-pair-xyk-sale-tax/raw/execute.json index 179a53617..443466010 100644 --- a/schemas/astroport-pair-xyk-sale-tax/raw/execute.json +++ b/schemas/astroport-pair-xyk-sale-tax/raw/execute.json @@ -37,12 +37,22 @@ } }, "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", + "description": "Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract", "type": [ "boolean", "null" ] }, + "min_lp_to_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "receiver": { "description": "The receiver of LP tokens", "type": [ @@ -67,6 +77,38 @@ }, "additionalProperties": false }, + { + "description": "WithdrawLiquidity allows someone to withdraw liquidity from the pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "assets": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "min_assets_to_receive": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Swap performs a swap in the pool", "type": "object", diff --git a/schemas/astroport-pair-xyk-sale-tax/raw/query.json b/schemas/astroport-pair-xyk-sale-tax/raw/query.json index c63191060..b7da792ec 100644 --- a/schemas/astroport-pair-xyk-sale-tax/raw/query.json +++ b/schemas/astroport-pair-xyk-sale-tax/raw/query.json @@ -208,6 +208,63 @@ } }, "additionalProperties": false + }, + { + "description": "Returns an estimation of assets received for the given amount of LP tokens", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "lp_amount" + ], + "properties": { + "lp_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of shares received for the given amount of assets", + "type": "object", + "required": [ + "simulate_provide" + ], + "properties": { + "simulate_provide": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -291,6 +348,10 @@ } ] }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair-xyk-sale-tax/raw/response_to_config.json b/schemas/astroport-pair-xyk-sale-tax/raw/response_to_config.json index ce805e9c4..953b0adc7 100644 --- a/schemas/astroport-pair-xyk-sale-tax/raw/response_to_config.json +++ b/schemas/astroport-pair-xyk-sale-tax/raw/response_to_config.json @@ -41,6 +41,17 @@ "type": "null" } ] + }, + "tracker_addr": { + "description": "Tracker contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, diff --git a/schemas/astroport-pair-xyk-sale-tax/raw/response_to_pair.json b/schemas/astroport-pair-xyk-sale-tax/raw/response_to_pair.json index 837f3f8dc..16721f81d 100644 --- a/schemas/astroport-pair-xyk-sale-tax/raw/response_to_pair.json +++ b/schemas/astroport-pair-xyk-sale-tax/raw/response_to_pair.json @@ -26,12 +26,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", diff --git a/schemas/astroport-pair-xyk-sale-tax/raw/response_to_simulate_provide.json b/schemas/astroport-pair-xyk-sale-tax/raw/response_to_simulate_provide.json new file mode 100644 index 000000000..25b73e8f2 --- /dev/null +++ b/schemas/astroport-pair-xyk-sale-tax/raw/response_to_simulate_provide.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" +} diff --git a/schemas/astroport-pair-xyk-sale-tax/raw/response_to_simulate_withdraw.json b/schemas/astroport-pair-xyk-sale-tax/raw/response_to_simulate_withdraw.json new file mode 100644 index 000000000..8285d4916 --- /dev/null +++ b/schemas/astroport-pair-xyk-sale-tax/raw/response_to_simulate_withdraw.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +} diff --git a/schemas/astroport-pair/astroport-pair.json b/schemas/astroport-pair/astroport-pair.json index 2c291c545..9c84fdee5 100644 --- a/schemas/astroport-pair/astroport-pair.json +++ b/schemas/astroport-pair/astroport-pair.json @@ -1,6 +1,6 @@ { "contract_name": "astroport-pair", - "contract_version": "1.5.1", + "contract_version": "2.0.0", "idl_version": "1.0.0", "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", @@ -142,12 +142,22 @@ } }, "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", + "description": "Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract", "type": [ "boolean", "null" ] }, + "min_lp_to_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "receiver": { "description": "The receiver of LP tokens", "type": [ @@ -172,6 +182,38 @@ }, "additionalProperties": false }, + { + "description": "WithdrawLiquidity allows someone to withdraw liquidity from the pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "assets": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "min_assets_to_receive": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Swap performs a swap in the pool", "type": "object", @@ -637,6 +679,63 @@ } }, "additionalProperties": false + }, + { + "description": "Returns an estimation of assets received for the given amount of LP tokens", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "lp_amount" + ], + "properties": { + "lp_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of shares received for the given amount of assets", + "type": "object", + "required": [ + "simulate_provide" + ], + "properties": { + "simulate_provide": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -720,6 +819,10 @@ } ] }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" @@ -800,6 +903,17 @@ "type": "null" } ] + }, + "tracker_addr": { + "description": "Tracker contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, @@ -1003,12 +1117,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", @@ -1380,6 +1490,106 @@ } } }, + "simulate_provide": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + }, + "simulate_withdraw": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } + }, "simulation": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "SimulationResponse", diff --git a/schemas/astroport-pair/raw/execute.json b/schemas/astroport-pair/raw/execute.json index 179a53617..443466010 100644 --- a/schemas/astroport-pair/raw/execute.json +++ b/schemas/astroport-pair/raw/execute.json @@ -37,12 +37,22 @@ } }, "auto_stake": { - "description": "Determines whether the LP tokens minted for the user is auto_staked in the Generator contract", + "description": "Determines whether the LP tokens minted for the user is auto_staked in the Incentives contract", "type": [ "boolean", "null" ] }, + "min_lp_to_receive": { + "anyOf": [ + { + "$ref": "#/definitions/Uint128" + }, + { + "type": "null" + } + ] + }, "receiver": { "description": "The receiver of LP tokens", "type": [ @@ -67,6 +77,38 @@ }, "additionalProperties": false }, + { + "description": "WithdrawLiquidity allows someone to withdraw liquidity from the pool", + "type": "object", + "required": [ + "withdraw_liquidity" + ], + "properties": { + "withdraw_liquidity": { + "type": "object", + "properties": { + "assets": { + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "min_assets_to_receive": { + "type": [ + "array", + "null" + ], + "items": { + "$ref": "#/definitions/Asset" + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, { "description": "Swap performs a swap in the pool", "type": "object", diff --git a/schemas/astroport-pair/raw/query.json b/schemas/astroport-pair/raw/query.json index c63191060..b7da792ec 100644 --- a/schemas/astroport-pair/raw/query.json +++ b/schemas/astroport-pair/raw/query.json @@ -208,6 +208,63 @@ } }, "additionalProperties": false + }, + { + "description": "Returns an estimation of assets received for the given amount of LP tokens", + "type": "object", + "required": [ + "simulate_withdraw" + ], + "properties": { + "simulate_withdraw": { + "type": "object", + "required": [ + "lp_amount" + ], + "properties": { + "lp_amount": { + "$ref": "#/definitions/Uint128" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns an estimation of shares received for the given amount of assets", + "type": "object", + "required": [ + "simulate_provide" + ], + "properties": { + "simulate_provide": { + "type": "object", + "required": [ + "assets" + ], + "properties": { + "assets": { + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + } + }, + "slippage_tolerance": { + "anyOf": [ + { + "$ref": "#/definitions/Decimal" + }, + { + "type": "null" + } + ] + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false } ], "definitions": { @@ -291,6 +348,10 @@ } ] }, + "Decimal": { + "description": "A fixed-point decimal value with 18 fractional digits, i.e. Decimal(1_000_000_000_000_000_000) == 1.0\n\nThe greatest possible value that can be represented is 340282366920938463463.374607431768211455 (which is (2^128 - 1) / 10^18)", + "type": "string" + }, "Uint128": { "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", "type": "string" diff --git a/schemas/astroport-pair/raw/response_to_config.json b/schemas/astroport-pair/raw/response_to_config.json index ce805e9c4..953b0adc7 100644 --- a/schemas/astroport-pair/raw/response_to_config.json +++ b/schemas/astroport-pair/raw/response_to_config.json @@ -41,6 +41,17 @@ "type": "null" } ] + }, + "tracker_addr": { + "description": "Tracker contract address", + "anyOf": [ + { + "$ref": "#/definitions/Addr" + }, + { + "type": "null" + } + ] } }, "additionalProperties": false, diff --git a/schemas/astroport-pair/raw/response_to_pair.json b/schemas/astroport-pair/raw/response_to_pair.json index 837f3f8dc..16721f81d 100644 --- a/schemas/astroport-pair/raw/response_to_pair.json +++ b/schemas/astroport-pair/raw/response_to_pair.json @@ -26,12 +26,8 @@ ] }, "liquidity_token": { - "description": "Pair LP token address", - "allOf": [ - { - "$ref": "#/definitions/Addr" - } - ] + "description": "Pair LP token denom", + "type": "string" }, "pair_type": { "description": "The pool type (xyk, stableswap etc) available in [`PairType`]", diff --git a/schemas/astroport-pair/raw/response_to_simulate_provide.json b/schemas/astroport-pair/raw/response_to_simulate_provide.json new file mode 100644 index 000000000..25b73e8f2 --- /dev/null +++ b/schemas/astroport-pair/raw/response_to_simulate_provide.json @@ -0,0 +1,6 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Uint128", + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" +} diff --git a/schemas/astroport-pair/raw/response_to_simulate_withdraw.json b/schemas/astroport-pair/raw/response_to_simulate_withdraw.json new file mode 100644 index 000000000..8285d4916 --- /dev/null +++ b/schemas/astroport-pair/raw/response_to_simulate_withdraw.json @@ -0,0 +1,94 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Asset", + "type": "array", + "items": { + "$ref": "#/definitions/Asset" + }, + "definitions": { + "Addr": { + "description": "A human readable address.\n\nIn Cosmos, this is typically bech32 encoded. But for multi-chain smart contracts no assumptions should be made other than being UTF-8 encoded and of reasonable length.\n\nThis type represents a validated address. It can be created in the following ways 1. Use `Addr::unchecked(input)` 2. Use `let checked: Addr = deps.api.addr_validate(input)?` 3. Use `let checked: Addr = deps.api.addr_humanize(canonical_addr)?` 4. Deserialize from JSON. This must only be done from JSON that was validated before such as a contract's state. `Addr` must not be used in messages sent by the user because this would result in unvalidated instances.\n\nThis type is immutable. If you really need to mutate it (Really? Are you sure?), create a mutable copy using `let mut mutable = Addr::to_string()` and operate on that `String` instance.", + "type": "string" + }, + "Asset": { + "description": "This enum describes a Terra asset (native or CW20).", + "type": "object", + "required": [ + "amount", + "info" + ], + "properties": { + "amount": { + "description": "A token amount", + "allOf": [ + { + "$ref": "#/definitions/Uint128" + } + ] + }, + "info": { + "description": "Information about an asset stored in a [`AssetInfo`] struct", + "allOf": [ + { + "$ref": "#/definitions/AssetInfo" + } + ] + } + }, + "additionalProperties": false + }, + "AssetInfo": { + "description": "This enum describes available Token types. ## Examples ``` # use cosmwasm_std::Addr; # use astroport::asset::AssetInfo::{NativeToken, Token}; Token { contract_addr: Addr::unchecked(\"stake...\") }; NativeToken { denom: String::from(\"uluna\") }; ```", + "oneOf": [ + { + "description": "Non-native Token", + "type": "object", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "object", + "required": [ + "contract_addr" + ], + "properties": { + "contract_addr": { + "$ref": "#/definitions/Addr" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Native token", + "type": "object", + "required": [ + "native_token" + ], + "properties": { + "native_token": { + "type": "object", + "required": [ + "denom" + ], + "properties": { + "denom": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + } + ] + }, + "Uint128": { + "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", + "type": "string" + } + } +}