From 7614e24373294826f408de408d214ae2c5a10e98 Mon Sep 17 00:00:00 2001 From: Sturdy <91910406+apollo-sturdy@users.noreply.github.com> Date: Tue, 13 Feb 2024 10:14:39 +0100 Subject: [PATCH 1/4] build: run deny with all features --- Makefile.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.toml b/Makefile.toml index 19d2933..90d450e 100644 --- a/Makefile.toml +++ b/Makefile.toml @@ -54,7 +54,7 @@ args = ["fmt", "--all", "--","--verbose", "--check"] [tasks.deny] command = "cargo" -args = ["deny", "check"] +args = ["deny", "--all-features", "check"] [tasks.check] toolchain = "${RUST_VERSION}" From a65879b545fa06ecae4f0447aa3743849dc972cd Mon Sep 17 00:00:00 2001 From: Sturdy <91910406+apollo-sturdy@users.noreply.github.com> Date: Tue, 13 Feb 2024 12:57:35 +0100 Subject: [PATCH 2/4] feat: split out implementations to separate crates --- Cargo.lock | 51 ++- Cargo.toml | 16 +- cw-dex-astroport/Cargo.toml | 41 ++ .../artifacts/astroport_factory.wasm | Bin .../artifacts/astroport_generator.wasm | Bin .../artifacts/astroport_incentives.wasm | Bin .../artifacts/astroport_liquidity_helper.wasm | Bin .../astroport_liquidity_manager.wasm | Bin .../artifacts/astroport_maker.wasm | Bin .../astroport_native_coin_registry.wasm | Bin .../astroport_native_coin_wrapper.wasm | Bin .../artifacts/astroport_oracle.wasm | Bin .../artifacts/astroport_pair.wasm | Bin .../astroport_pair_astro_xastro.wasm | Bin .../astroport_pair_concentrated.wasm | Bin .../artifacts/astroport_pair_stable.wasm | Bin .../artifacts/astroport_router.wasm | Bin .../artifacts/astroport_staking.wasm | Bin .../artifacts/astroport_token.wasm | Bin .../artifacts/astroport_vault.wasm | Bin .../artifacts/astroport_vesting.wasm | Bin .../artifacts/astroport_whitelist.wasm | Bin .../artifacts/astroport_xastro_token.wasm | Bin .../artifacts/checksums.txt | 0 .../artifacts/checksums_intermediate.txt | 0 .../artifacts/cw_dex_router.wasm | Bin .../artifacts/cw_dex_test_contract.wasm | Bin cw-dex-astroport/src/lib.rs | 9 + cw-dex-astroport/src/pool.rs | 349 +++++++++++++++++ cw-dex-astroport/src/staking.rs | 147 ++++++++ .../tests/astroport_tests.rs | 34 +- cw-dex-osmosis/Cargo.toml | 37 ++ cw-dex-osmosis/src/helpers.rs | 14 + cw-dex-osmosis/src/lib.rs | 8 + cw-dex-osmosis/src/pool.rs | 351 ++++++++++++++++++ cw-dex-osmosis/src/staking.rs | 325 ++++++++++++++++ .../osmosis_proptests.proptest-regressions | 0 .../tests/osmosis_proptests.rs | 0 .../tests/osmosis_tests.rs | 0 cw-dex/Cargo.toml | 4 +- cw-dex/src/implementations/osmosis/pool.rs | 21 -- cw-dex/src/lib.rs | 17 +- cw-dex/tests/configs/terra.yaml | 97 ----- .../astroport-test-contract/Cargo.toml | 5 +- .../astroport-test-contract/src/contract.rs | 4 +- .../astroport-test-contract/src/state.rs | 2 +- .../osmosis-test-contract/Cargo.toml | 5 +- .../osmosis-test-contract/src/contract.rs | 4 +- .../osmosis-test-contract/src/state.rs | 2 +- test-contracts/package/Cargo.toml | 3 +- test-contracts/package/src/msg.rs | 8 +- test-helpers/Cargo.toml | 4 +- 52 files changed, 1389 insertions(+), 169 deletions(-) create mode 100644 cw-dex-astroport/Cargo.toml rename {cw-dex => cw-dex-astroport}/artifacts/astroport_factory.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_generator.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_incentives.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_liquidity_helper.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_liquidity_manager.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_maker.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_native_coin_registry.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_native_coin_wrapper.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_oracle.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_pair.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_pair_astro_xastro.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_pair_concentrated.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_pair_stable.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_router.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_staking.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_token.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_vault.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_vesting.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_whitelist.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/astroport_xastro_token.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/checksums.txt (100%) rename {cw-dex => cw-dex-astroport}/artifacts/checksums_intermediate.txt (100%) rename {cw-dex => cw-dex-astroport}/artifacts/cw_dex_router.wasm (100%) rename {cw-dex => cw-dex-astroport}/artifacts/cw_dex_test_contract.wasm (100%) create mode 100644 cw-dex-astroport/src/lib.rs create mode 100644 cw-dex-astroport/src/pool.rs create mode 100644 cw-dex-astroport/src/staking.rs rename {cw-dex => cw-dex-astroport}/tests/astroport_tests.rs (97%) create mode 100644 cw-dex-osmosis/Cargo.toml create mode 100644 cw-dex-osmosis/src/helpers.rs create mode 100644 cw-dex-osmosis/src/lib.rs create mode 100644 cw-dex-osmosis/src/pool.rs create mode 100644 cw-dex-osmosis/src/staking.rs rename {cw-dex => cw-dex-osmosis}/tests/osmosis_proptests.proptest-regressions (100%) rename {cw-dex => cw-dex-osmosis}/tests/osmosis_proptests.rs (100%) rename {cw-dex => cw-dex-osmosis}/tests/osmosis_tests.rs (100%) delete mode 100644 cw-dex/tests/configs/terra.yaml diff --git a/Cargo.lock b/Cargo.lock index d4aa59f..4e0d104 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -346,12 +346,13 @@ dependencies = [ [[package]] name = "astroport-test-contract" -version = "0.1.0" +version = "0.2.0" dependencies = [ "apollo-cw-asset", "cosmwasm-schema", "cosmwasm-std", "cw-dex", + "cw-dex-astroport", "cw-dex-test-contract", "cw-storage-plus 1.2.0", "thiserror", @@ -886,7 +887,7 @@ dependencies = [ [[package]] name = "cw-dex" -version = "0.6.0-rc.3" +version = "0.5.2" dependencies = [ "apollo-cw-asset", "apollo-utils", @@ -908,13 +909,54 @@ dependencies = [ ] [[package]] -name = "cw-dex-test-contract" +name = "cw-dex-astroport" +version = "0.1.0" +dependencies = [ + "apollo-cw-asset", + "apollo-utils", + "astroport 2.9.0", + "astroport 3.11.1", + "cosmwasm-schema", + "cosmwasm-std", + "cw-dex", + "cw-dex-test-contract", + "cw-dex-test-helpers", + "cw-it", + "cw-utils 1.0.2", + "cw2 1.1.1", + "cw20 1.1.2", + "cw20-base 1.1.1", + "proptest", + "test-case", +] + +[[package]] +name = "cw-dex-osmosis" version = "0.1.0" dependencies = [ "apollo-cw-asset", + "apollo-utils", "cosmwasm-schema", "cosmwasm-std", "cw-dex", + "cw-dex-test-contract", + "cw-dex-test-helpers", + "cw-it", + "cw-utils 1.0.2", + "cw20 1.1.2", + "cw20-base 1.1.1", + "osmosis-std 0.19.2", + "proptest", + "test-case", +] + +[[package]] +name = "cw-dex-test-contract" +version = "0.2.0" +dependencies = [ + "apollo-cw-asset", + "cosmwasm-schema", + "cosmwasm-std", ] [[package]] @@ -2027,12 +2069,13 @@ dependencies = [ [[package]] name = "osmosis-test-contract" -version = "0.1.0" +version = "0.2.0" dependencies = [ "apollo-cw-asset", "cosmwasm-schema", "cosmwasm-std", "cw-dex", + "cw-dex-osmosis", "cw-dex-test-contract", "cw-storage-plus 1.2.0", "thiserror", diff --git a/Cargo.toml b/Cargo.toml index e8d0faf..cf0de53 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["cw-dex", "test-contracts/*", "test-helpers"] +members = ["cw-dex", "cw-dex-astroport", "cw-dex-osmosis", "test-contracts/*", "test-helpers"] resolver = "2" [workspace.package] @@ -27,13 +27,19 @@ thiserror = { version = "1.0.31" } apollo-cw-asset = "0.1.1" osmosis-std = "0.19.2" cw-it = "0.3.0-rc.4" -cw-dex = { path = "cw-dex" } -cw-dex-test-contract = { path = "test-contracts/package" } -astroport-test-contract = { path = "test-contracts/astroport-test-contract" } -cw-dex-test-helpers = { path = "test-helpers" } apollo-utils = "0.1.0" astroport = "=2.9.0" astroport_v3 = { package = "astroport", version = "=3.11.1" } +test-case = "3.0.0" +proptest = "1.0.0" + +# Workspace packages +cw-dex = { path = "cw-dex", version = "0.5.2" } +cw-dex-astroport = { path = "cw-dex-astroport", version = "0.1.0" } +cw-dex-osmosis = { path = "cw-dex-osmosis", version = "0.1.0" } +cw-dex-test-contract = { path = "test-contracts/package" } +astroport-test-contract = { path = "test-contracts/astroport-test-contract" } +cw-dex-test-helpers = { path = "test-helpers" } [profile.release] codegen-units = 1 diff --git a/cw-dex-astroport/Cargo.toml b/cw-dex-astroport/Cargo.toml new file mode 100644 index 0000000..47fa60b --- /dev/null +++ b/cw-dex-astroport/Cargo.toml @@ -0,0 +1,41 @@ +[package] +name = "cw-dex-astroport" +authors = ["Apollo Devs"] +description = "Implementation of the cw-dex API for the Astroport AMM" +edition = "2021" +license = "MPL-2.0" +repository = "https://github.com/apollodao/cw-dex" +version = "0.1.0" +readme = "README.md" + +[features] +default = [] +osmosis-test-tube = ["cw-it/osmosis-test-tube", "cw-dex-test-helpers/osmosis-test-tube"] +# backtraces = ["cosmwasm-std/backtraces", "osmosis-std/backtraces"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +cw-dex = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +apollo-cw-asset = { workspace = true, features = ["astroport"] } +cw-utils = { workspace = true } +cw20 = { workspace = true } +apollo-utils = { workspace = true } + +# Astroport +astroport = { workspace = true } +astroport_v3 = { workspace = true } +cw2 = { workspace = true } + +[dev-dependencies] +cw-it = { workspace = true, features = ["astroport", "multi-test", "astroport-multi-test"] } +test-case = { workspace = true } +cw-dex-test-contract = { workspace = true } +cw-dex-test-helpers = { workspace = true, features = ["astroport"] } +proptest = { workspace = true } +cw20-base = { workspace = true } +cw20 = { workspace = true } diff --git a/cw-dex/artifacts/astroport_factory.wasm b/cw-dex-astroport/artifacts/astroport_factory.wasm similarity index 100% rename from cw-dex/artifacts/astroport_factory.wasm rename to cw-dex-astroport/artifacts/astroport_factory.wasm diff --git a/cw-dex/artifacts/astroport_generator.wasm b/cw-dex-astroport/artifacts/astroport_generator.wasm similarity index 100% rename from cw-dex/artifacts/astroport_generator.wasm rename to cw-dex-astroport/artifacts/astroport_generator.wasm diff --git a/cw-dex/artifacts/astroport_incentives.wasm b/cw-dex-astroport/artifacts/astroport_incentives.wasm similarity index 100% rename from cw-dex/artifacts/astroport_incentives.wasm rename to cw-dex-astroport/artifacts/astroport_incentives.wasm diff --git a/cw-dex/artifacts/astroport_liquidity_helper.wasm b/cw-dex-astroport/artifacts/astroport_liquidity_helper.wasm similarity index 100% rename from cw-dex/artifacts/astroport_liquidity_helper.wasm rename to cw-dex-astroport/artifacts/astroport_liquidity_helper.wasm diff --git a/cw-dex/artifacts/astroport_liquidity_manager.wasm b/cw-dex-astroport/artifacts/astroport_liquidity_manager.wasm similarity index 100% rename from cw-dex/artifacts/astroport_liquidity_manager.wasm rename to cw-dex-astroport/artifacts/astroport_liquidity_manager.wasm diff --git a/cw-dex/artifacts/astroport_maker.wasm b/cw-dex-astroport/artifacts/astroport_maker.wasm similarity index 100% rename from cw-dex/artifacts/astroport_maker.wasm rename to cw-dex-astroport/artifacts/astroport_maker.wasm diff --git a/cw-dex/artifacts/astroport_native_coin_registry.wasm b/cw-dex-astroport/artifacts/astroport_native_coin_registry.wasm similarity index 100% rename from cw-dex/artifacts/astroport_native_coin_registry.wasm rename to cw-dex-astroport/artifacts/astroport_native_coin_registry.wasm diff --git a/cw-dex/artifacts/astroport_native_coin_wrapper.wasm b/cw-dex-astroport/artifacts/astroport_native_coin_wrapper.wasm similarity index 100% rename from cw-dex/artifacts/astroport_native_coin_wrapper.wasm rename to cw-dex-astroport/artifacts/astroport_native_coin_wrapper.wasm diff --git a/cw-dex/artifacts/astroport_oracle.wasm b/cw-dex-astroport/artifacts/astroport_oracle.wasm similarity index 100% rename from cw-dex/artifacts/astroport_oracle.wasm rename to cw-dex-astroport/artifacts/astroport_oracle.wasm diff --git a/cw-dex/artifacts/astroport_pair.wasm b/cw-dex-astroport/artifacts/astroport_pair.wasm similarity index 100% rename from cw-dex/artifacts/astroport_pair.wasm rename to cw-dex-astroport/artifacts/astroport_pair.wasm diff --git a/cw-dex/artifacts/astroport_pair_astro_xastro.wasm b/cw-dex-astroport/artifacts/astroport_pair_astro_xastro.wasm similarity index 100% rename from cw-dex/artifacts/astroport_pair_astro_xastro.wasm rename to cw-dex-astroport/artifacts/astroport_pair_astro_xastro.wasm diff --git a/cw-dex/artifacts/astroport_pair_concentrated.wasm b/cw-dex-astroport/artifacts/astroport_pair_concentrated.wasm similarity index 100% rename from cw-dex/artifacts/astroport_pair_concentrated.wasm rename to cw-dex-astroport/artifacts/astroport_pair_concentrated.wasm diff --git a/cw-dex/artifacts/astroport_pair_stable.wasm b/cw-dex-astroport/artifacts/astroport_pair_stable.wasm similarity index 100% rename from cw-dex/artifacts/astroport_pair_stable.wasm rename to cw-dex-astroport/artifacts/astroport_pair_stable.wasm diff --git a/cw-dex/artifacts/astroport_router.wasm b/cw-dex-astroport/artifacts/astroport_router.wasm similarity index 100% rename from cw-dex/artifacts/astroport_router.wasm rename to cw-dex-astroport/artifacts/astroport_router.wasm diff --git a/cw-dex/artifacts/astroport_staking.wasm b/cw-dex-astroport/artifacts/astroport_staking.wasm similarity index 100% rename from cw-dex/artifacts/astroport_staking.wasm rename to cw-dex-astroport/artifacts/astroport_staking.wasm diff --git a/cw-dex/artifacts/astroport_token.wasm b/cw-dex-astroport/artifacts/astroport_token.wasm similarity index 100% rename from cw-dex/artifacts/astroport_token.wasm rename to cw-dex-astroport/artifacts/astroport_token.wasm diff --git a/cw-dex/artifacts/astroport_vault.wasm b/cw-dex-astroport/artifacts/astroport_vault.wasm similarity index 100% rename from cw-dex/artifacts/astroport_vault.wasm rename to cw-dex-astroport/artifacts/astroport_vault.wasm diff --git a/cw-dex/artifacts/astroport_vesting.wasm b/cw-dex-astroport/artifacts/astroport_vesting.wasm similarity index 100% rename from cw-dex/artifacts/astroport_vesting.wasm rename to cw-dex-astroport/artifacts/astroport_vesting.wasm diff --git a/cw-dex/artifacts/astroport_whitelist.wasm b/cw-dex-astroport/artifacts/astroport_whitelist.wasm similarity index 100% rename from cw-dex/artifacts/astroport_whitelist.wasm rename to cw-dex-astroport/artifacts/astroport_whitelist.wasm diff --git a/cw-dex/artifacts/astroport_xastro_token.wasm b/cw-dex-astroport/artifacts/astroport_xastro_token.wasm similarity index 100% rename from cw-dex/artifacts/astroport_xastro_token.wasm rename to cw-dex-astroport/artifacts/astroport_xastro_token.wasm diff --git a/cw-dex/artifacts/checksums.txt b/cw-dex-astroport/artifacts/checksums.txt similarity index 100% rename from cw-dex/artifacts/checksums.txt rename to cw-dex-astroport/artifacts/checksums.txt diff --git a/cw-dex/artifacts/checksums_intermediate.txt b/cw-dex-astroport/artifacts/checksums_intermediate.txt similarity index 100% rename from cw-dex/artifacts/checksums_intermediate.txt rename to cw-dex-astroport/artifacts/checksums_intermediate.txt diff --git a/cw-dex/artifacts/cw_dex_router.wasm b/cw-dex-astroport/artifacts/cw_dex_router.wasm similarity index 100% rename from cw-dex/artifacts/cw_dex_router.wasm rename to cw-dex-astroport/artifacts/cw_dex_router.wasm diff --git a/cw-dex/artifacts/cw_dex_test_contract.wasm b/cw-dex-astroport/artifacts/cw_dex_test_contract.wasm similarity index 100% rename from cw-dex/artifacts/cw_dex_test_contract.wasm rename to cw-dex-astroport/artifacts/cw_dex_test_contract.wasm diff --git a/cw-dex-astroport/src/lib.rs b/cw-dex-astroport/src/lib.rs new file mode 100644 index 0000000..b48062f --- /dev/null +++ b/cw-dex-astroport/src/lib.rs @@ -0,0 +1,9 @@ +//! Pool and Staking implementations for Astroport + +mod pool; +mod staking; + +pub use pool::AstroportPool; +pub use staking::AstroportStaking; + +pub use astroport; diff --git a/cw-dex-astroport/src/pool.rs b/cw-dex-astroport/src/pool.rs new file mode 100644 index 0000000..de74780 --- /dev/null +++ b/cw-dex-astroport/src/pool.rs @@ -0,0 +1,349 @@ +//! Pool trait implementation for Astroport + +use std::str::FromStr; + +use apollo_cw_asset::{Asset, AssetInfo, AssetInfoBase, AssetList}; +use apollo_utils::iterators::IntoElementwise; +use astroport::liquidity_manager; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + to_json_binary, wasm_execute, Addr, CosmosMsg, Decimal, Deps, Env, Event, QuerierWrapper, + QueryRequest, Response, StdError, StdResult, Uint128, WasmMsg, WasmQuery, +}; +use cw20::Cw20ExecuteMsg; +use cw_utils::Expiration; + +use apollo_utils::assets::separate_natives_and_cw20s; +use astroport::asset::{Asset as AstroAsset, PairInfo}; +use astroport::factory::PairType; +use astroport::pair::{ + Cw20HookMsg as PairCw20HookMsg, ExecuteMsg as PairExecuteMsg, PoolResponse, + QueryMsg as PairQueryMsg, SimulationResponse, MAX_ALLOWED_SLIPPAGE, +}; +use astroport::querier::query_supply; +use cw_dex::traits::Pool; +use cw_dex::CwDexError; + +/// Represents an AMM pool on Astroport +#[cw_serde] +pub struct AstroportPool { + /// The address of the associated pair contract + pub pair_addr: Addr, + /// The address of the associated LP token contract + pub lp_token_addr: Addr, + /// The assets of the pool + pub pool_assets: Vec, + /// The type of pool represented: Constant product (*Xyk*) or *Stableswap* + pub pair_type: PairType, + /// The address of the Astroport liquidity manager contract + pub liquidity_manager: Addr, +} + +impl AstroportPool { + /// Creates a new instance of `AstroportPool` + /// + /// Arguments: + /// - `pair_addr`: The address of the pair contract associated with the pool + pub fn new(deps: Deps, pair_addr: Addr, liquidity_manager: Addr) -> StdResult { + let pair_info = deps + .querier + .query_wasm_smart::(pair_addr.clone(), &PairQueryMsg::Pair {})?; + + // Validate pair type. We only support XYK, stable swap, and PCL pools + match &pair_info.pair_type { + PairType::Custom(t) => match t.as_str() { + "concentrated" => Ok(()), + "astroport-pair-xyk-sale-tax" => Ok(()), + _ => Err(StdError::generic_err("Custom pair type is not supported")), + }, + _ => Ok(()), + }?; + + Ok(Self { + pair_addr, + lp_token_addr: pair_info.liquidity_token, + pool_assets: pair_info.asset_infos.into_elementwise(), + pair_type: pair_info.pair_type, + liquidity_manager, + }) + } + + /// Returns the matching pool given a LP token. + /// + /// Arguments: + /// - `lp_token`: Said LP token + /// - `astroport_liquidity_manager`: The Astroport liquidity manager + /// address. + #[allow(unused_variables)] + #[allow(unreachable_patterns)] + pub fn get_pool_for_lp_token( + deps: Deps, + lp_token: &AssetInfo, + astroport_liquidity_manager: Addr, + ) -> Result { + match lp_token { + AssetInfo::Cw20(address) => { + // To figure out if the CW20 is a LP token, we need to check which address + // instantiated the CW20 and check if that address is an Astroport pair + // contract. + let contract_info = deps.querier.query_wasm_contract_info(address)?; + let creator_addr = deps.api.addr_validate(&contract_info.creator)?; + + // Try to create an `AstroportPool` object with the creator address. This will + // query the contract and assume that it is an Astroport pair + // contract. If it succeeds, the pool object will be returned. + // + // NB: This does NOT validate that the pool is registered with the Astroport + // factory, and that it is an "official" Astroport pool. + let pool = AstroportPool::new(deps, creator_addr, astroport_liquidity_manager)?; + + Ok(pool) + } + _ => Err(CwDexError::NotLpToken {}), + } + } + + /// Returns the total supply of the associated LP token + pub fn query_lp_token_supply(&self, querier: &QuerierWrapper) -> StdResult { + query_supply(querier, self.lp_token_addr.to_owned()) + } + + /// Queries the pair contract for the current pool state + pub fn query_pool_info(&self, querier: &QuerierWrapper) -> StdResult { + querier.query::(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.pair_addr.to_string(), + msg: to_json_binary(&PairQueryMsg::Pool {})?, + })) + } +} + +impl Pool for AstroportPool { + fn provide_liquidity( + &self, + _deps: Deps, + env: &Env, + assets: AssetList, + min_out: Uint128, + ) -> Result { + let (funds, cw20s) = separate_natives_and_cw20s(&assets); + + // Increase allowance on all Cw20s + let allowance_msgs: Vec = cw20s + .into_iter() + .map(|asset| { + Ok(CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: asset.address, + msg: to_json_binary(&Cw20ExecuteMsg::IncreaseAllowance { + spender: self.liquidity_manager.to_string(), + amount: asset.amount, + expires: Some(Expiration::AtHeight(env.block.height + 1)), + })?, + funds: vec![], + })) + }) + .collect::>>()?; + + // Liquidity manager requires assets vec to contain all assets in the pool + let mut assets_vec = assets.to_vec(); + for pool_asset_info in &self.pool_assets { + if !assets_vec.iter().any(|x| &x.info == pool_asset_info) { + assets_vec.push(Asset::new(pool_asset_info.clone(), Uint128::zero())); + } + } + + // Create the provide liquidity message + let provide_liquidity_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.liquidity_manager.to_string(), + msg: to_json_binary(&liquidity_manager::ExecuteMsg::ProvideLiquidity { + pair_addr: self.pair_addr.to_string(), + min_lp_to_receive: Some(min_out), + pair_msg: astroport::pair::ExecuteMsg::ProvideLiquidity { + assets: assets_vec.into_elementwise(), + slippage_tolerance: Some(Decimal::from_str(MAX_ALLOWED_SLIPPAGE)?), + auto_stake: Some(false), + receiver: None, + }, + })?, + funds, + }); + + let event = Event::new("apollo/cw-dex/provide_liquidity") + .add_attribute("pair_addr", &self.pair_addr) + .add_attribute("assets", format!("{:?}", assets)); + + Ok(Response::new() + .add_messages(allowance_msgs) + .add_message(provide_liquidity_msg) + .add_event(event)) + } + + fn withdraw_liquidity( + &self, + _deps: Deps, + _env: &Env, + asset: Asset, + mut min_out: AssetList, + ) -> Result { + if let AssetInfoBase::Cw20(token_addr) = &asset.info { + // Liquidity manager requires min_out to contain all assets in the pool + for asset in &self.pool_assets { + if min_out.find(asset).is_none() { + // Add one unit as AssetList does not allow zero amounts (calls self.purge on + // add) + min_out.add(&Asset::new(asset.clone(), Uint128::one()))?; + } + } + + let withdraw_liquidity = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: token_addr.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Send { + contract: self.liquidity_manager.to_string(), + amount: asset.amount, + msg: to_json_binary(&liquidity_manager::Cw20HookMsg::WithdrawLiquidity { + pair_msg: astroport::pair::Cw20HookMsg::WithdrawLiquidity { + // This field is currently not used... + assets: vec![], + }, + min_assets_to_receive: min_out.to_vec().into_elementwise(), + })?, + })?, + funds: vec![], + }); + + let event = Event::new("apollo/cw-dex/withdraw_liquidity") + .add_attribute("pair_addr", &self.pair_addr) + .add_attribute("asset", format!("{:?}", asset)) + .add_attribute("token_amount", asset.amount); + + Ok(Response::new() + .add_message(withdraw_liquidity) + .add_event(event)) + } else { + Err(CwDexError::InvalidInAsset { a: asset }) + } + } + + fn swap( + &self, + _deps: Deps, + env: &Env, + offer_asset: Asset, + ask_asset_info: AssetInfo, + min_out: Uint128, + ) -> Result { + // Setting belief price to the minimium acceptable return and max spread to zero + // simplifies things Astroport will make the best possible swap that + // returns at least `min_out`. + let belief_price = Some(Decimal::from_ratio(offer_asset.amount, min_out)); + let swap_msg = match &offer_asset.info { + AssetInfo::Native(_) => { + let asset = offer_asset.clone().into(); + wasm_execute( + self.pair_addr.to_string(), + &PairExecuteMsg::Swap { + offer_asset: asset, + belief_price, + max_spread: Some(Decimal::zero()), + to: Some(env.contract.address.to_string()), + ask_asset_info: Some(ask_asset_info.to_owned().into()), + }, + vec![offer_asset.clone().try_into()?], + ) + } + AssetInfo::Cw20(addr) => wasm_execute( + addr.to_string(), + &Cw20ExecuteMsg::Send { + contract: self.pair_addr.to_string(), + amount: offer_asset.amount, + msg: to_json_binary(&PairCw20HookMsg::Swap { + belief_price, + max_spread: Some(Decimal::zero()), + to: Some(env.contract.address.to_string()), + ask_asset_info: Some(ask_asset_info.to_owned().into()), + })?, + }, + vec![], + ), + }?; + let event = Event::new("apollo/cw-dex/swap") + .add_attribute("pair_addr", &self.pair_addr) + .add_attribute("ask_asset", format!("{:?}", ask_asset_info)) + .add_attribute("offer_asset", format!("{:?}", offer_asset.info)) + .add_attribute("minimum_out_amount", min_out); + Ok(Response::new().add_message(swap_msg).add_event(event)) + } + + fn get_pool_liquidity(&self, deps: Deps) -> Result { + let resp = self.query_pool_info(&deps.querier)?; + Ok(resp.assets.to_vec().into()) + } + + fn simulate_provide_liquidity( + &self, + deps: Deps, + _env: &Env, + assets: AssetList, + ) -> Result { + let amount: Uint128 = deps.querier.query_wasm_smart( + self.liquidity_manager.to_string(), + &liquidity_manager::QueryMsg::SimulateProvide { + pair_addr: self.pair_addr.to_string(), + pair_msg: astroport::pair::ExecuteMsg::ProvideLiquidity { + assets: assets.into(), + slippage_tolerance: Some(Decimal::from_str(MAX_ALLOWED_SLIPPAGE)?), + auto_stake: Some(false), + receiver: None, + }, + }, + )?; + + let lp_token = Asset { + info: AssetInfo::Cw20(self.lp_token_addr.clone()), + amount, + }; + + Ok(lp_token) + } + + fn simulate_withdraw_liquidity( + &self, + deps: Deps, + lp_token: &Asset, + ) -> Result { + let assets: Vec = deps.querier.query_wasm_smart( + self.liquidity_manager.to_string(), + &liquidity_manager::QueryMsg::SimulateWithdraw { + pair_addr: self.pair_addr.to_string(), + lp_tokens: lp_token.amount, + }, + )?; + + Ok(assets.into()) + } + + fn simulate_swap( + &self, + deps: Deps, + offer_asset: Asset, + ask_asset_info: AssetInfo, + ) -> StdResult { + Ok(deps + .querier + .query::(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.pair_addr.to_string(), + msg: to_json_binary(&PairQueryMsg::Simulation { + offer_asset: offer_asset.into(), + ask_asset_info: Some(ask_asset_info.into()), + })?, + }))? + .return_amount) + } + + fn lp_token(&self) -> AssetInfo { + AssetInfoBase::Cw20(self.lp_token_addr.clone()) + } + + fn pool_assets(&self, _deps: Deps) -> StdResult> { + Ok(self.pool_assets.clone()) + } +} diff --git a/cw-dex-astroport/src/staking.rs b/cw-dex-astroport/src/staking.rs new file mode 100644 index 0000000..f073938 --- /dev/null +++ b/cw-dex-astroport/src/staking.rs @@ -0,0 +1,147 @@ +//! Staking/rewards traits implementations for Astroport + +use apollo_utils::assets::separate_natives_and_cw20s; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + to_json_binary, Addr, CosmosMsg, Deps, Empty, Env, Event, QuerierWrapper, QueryRequest, + Response, Uint128, WasmMsg, WasmQuery, +}; +use cw20::Cw20ExecuteMsg; + +use apollo_cw_asset::AssetList; +use astroport::asset::Asset as AstroAsset; +use astroport_v3::incentives::{ + Cw20Msg as IncentivesCw20Msg, ExecuteMsg as IncentivesExecuteMsg, + QueryMsg as IncentivesQueryMsg, +}; + +use cw_dex::traits::{Rewards, Stake, Staking, Unstake}; +use cw_dex::CwDexError; + +/// Represents staking of tokens on Astroport +#[cw_serde] +pub struct AstroportStaking { + /// The address of the associated LP token contract + pub lp_token_addr: Addr, + /// The address of the astroport incentives contract + pub incentives: Addr, +} + +impl Staking for AstroportStaking {} + +impl Stake for AstroportStaking { + fn stake(&self, _deps: Deps, _env: &Env, amount: Uint128) -> Result { + let stake_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.lp_token_addr.to_string(), + msg: to_json_binary(&Cw20ExecuteMsg::Send { + contract: self.incentives.to_string(), + amount, + msg: to_json_binary(&IncentivesCw20Msg::Deposit { recipient: None })?, + })?, + funds: vec![], + }); + + let event = Event::new("apollo/cw-dex/stake") + .add_attribute("type", "astroport_staking") + .add_attribute("asset", self.lp_token_addr.to_string()) + .add_attribute("incentives contract address", self.incentives.to_string()); + + Ok(Response::new().add_message(stake_msg).add_event(event)) + } +} + +impl Rewards for AstroportStaking { + fn claim_rewards(&self, deps: Deps, env: &Env) -> Result { + let claimable_rewards: AssetList = + self.query_pending_rewards(&deps.querier, &env.contract.address)?; + + let event = + Event::new("apollo/cw-dex/claim_rewards").add_attribute("type", "astroport_staking"); + + if claimable_rewards.len() == 0 { + return Ok(Response::new().add_event(event)); + } + + let claim_rewards_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.incentives.to_string(), + msg: to_json_binary(&IncentivesExecuteMsg::ClaimRewards { + lp_tokens: vec![self.lp_token_addr.to_string()], + })?, + funds: vec![], + }); + + let mut res = Response::new().add_message(claim_rewards_msg); + + // Astroport generator only supports CW20 tokens as proxy rewards and wraps + // native tokens in their "CW20 wrapper". We need to unwrap them here. + let (_, cw20s) = separate_natives_and_cw20s(&claimable_rewards); + for cw20 in cw20s { + // Query the cw20s creator to get the address of the wrapper contract + let contract_info = deps.querier.query_wasm_contract_info(&cw20.address)?; + let wrapper_contract = deps.api.addr_validate(&contract_info.creator)?; + + // Query the wrapper contract's cw2 info to check if it is a native token + // wrapper, otherwise skip it + let contract_version = cw2::query_contract_info(&deps.querier, &wrapper_contract).ok(); + match contract_version { + Some(contract_version) => { + if &contract_version.contract != "astroport-native-coin-wrapper" { + continue; + } + } + None => continue, + } + + // Unwrap the native token + let unwrap_msg: CosmosMsg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: cw20.address.to_string(), + msg: to_json_binary(&cw20::Cw20ExecuteMsg::Send { + contract: wrapper_contract.to_string(), + amount: cw20.amount, + msg: to_json_binary(&astroport::native_coin_wrapper::Cw20HookMsg::Unwrap {})?, + })?, + funds: vec![], + }); + res = res.add_message(unwrap_msg); + } + + Ok(res.add_event(event)) + } + + fn query_pending_rewards( + &self, + querier: &QuerierWrapper, + user: &Addr, + ) -> Result { + let pending_rewards: Vec = querier + .query::>(&QueryRequest::Wasm(WasmQuery::Smart { + contract_addr: self.incentives.to_string(), + msg: to_json_binary(&IncentivesQueryMsg::PendingRewards { + lp_token: self.lp_token_addr.to_string(), + user: user.to_string(), + })?, + }))? + .into_iter() + .filter(|asset| !asset.amount.is_zero()) //TODO: Is this necessary? + .collect(); + + Ok(pending_rewards.into()) + } +} + +impl Unstake for AstroportStaking { + fn unstake(&self, _deps: Deps, _env: &Env, amount: Uint128) -> Result { + let unstake_msg = CosmosMsg::Wasm(WasmMsg::Execute { + contract_addr: self.incentives.to_string(), + msg: to_json_binary(&IncentivesExecuteMsg::Withdraw { + lp_token: self.lp_token_addr.to_string(), + amount, + })?, + funds: vec![], + }); + + let event = Event::new("apollo/cw-dex/unstake").add_attribute("type", "astroport_staking"); + + Ok(Response::new().add_message(unstake_msg).add_event(event)) + } +} diff --git a/cw-dex/tests/astroport_tests.rs b/cw-dex-astroport/tests/astroport_tests.rs similarity index 97% rename from cw-dex/tests/astroport_tests.rs rename to cw-dex-astroport/tests/astroport_tests.rs index f14d9a2..f155311 100644 --- a/cw-dex/tests/astroport_tests.rs +++ b/cw-dex-astroport/tests/astroport_tests.rs @@ -1,6 +1,4 @@ -#![cfg(feature = "astroport")] mod tests { - use apollo_cw_asset::{Asset, AssetInfo, AssetInfoBase, AssetList}; use apollo_utils::assets::separate_natives_and_cw20s; use apollo_utils::coins::coin_from_str; @@ -9,7 +7,6 @@ mod tests { use astroport_v3::asset::Asset as AstroportAsset; use cosmwasm_std::{assert_approx_eq, coin, coins, Addr, Coin, SubMsgResponse, Uint128}; - use cw_dex::Pool; use cw_dex_test_contract::msg::{AstroportExecuteMsg, ExecuteMsg, QueryMsg}; use cw_dex_test_helpers::astroport::setup_pool_and_test_contract; use cw_dex_test_helpers::{cw20_balance_query, cw20_transfer, query_asset_balance}; @@ -24,6 +21,8 @@ mod tests { use cw_it::{OwnedTestRunner, TestRunner}; use test_case::test_case; + use cw_dex_astroport::AstroportPool; + #[cfg(feature = "osmosis-test-tube")] use cw_it::osmosis_test_tube::OsmosisTestApp; @@ -643,21 +642,18 @@ mod tests { let query = QueryMsg::GetPoolForLpToken { lp_token: AssetInfo::Cw20(Addr::unchecked(lp_token_addr.clone())), }; - let pool = wasm.query::<_, Pool>(&contract_addr, &query).unwrap(); - - match pool { - Pool::Astroport(pool) => { - assert_eq!(pool.lp_token_addr, Addr::unchecked(lp_token_addr)); - assert_eq!(pool.pair_addr, Addr::unchecked(pair_addr)); - assert_eq!( - pool.pool_assets, - asset_list - .into_iter() - .map(|x| x.info.clone()) - .collect::>() - ); - } - _ => panic!("Wrong pool type"), - } + let pool = wasm + .query::<_, AstroportPool>(&contract_addr, &query) + .unwrap(); + + assert_eq!(pool.lp_token_addr, Addr::unchecked(lp_token_addr)); + assert_eq!(pool.pair_addr, Addr::unchecked(pair_addr)); + assert_eq!( + pool.pool_assets, + asset_list + .into_iter() + .map(|x| x.info.clone()) + .collect::>() + ); } } diff --git a/cw-dex-osmosis/Cargo.toml b/cw-dex-osmosis/Cargo.toml new file mode 100644 index 0000000..6625dec --- /dev/null +++ b/cw-dex-osmosis/Cargo.toml @@ -0,0 +1,37 @@ +[package] +name = "cw-dex-osmosis" +authors = ["Apollo Devs"] +description = "Implementation of the cw-dex API for the Osmosis AMM" +edition = "2021" +license = "MPL-2.0" +repository = "https://github.com/apollodao/cw-dex" +version = "0.1.0" +readme = "README.md" + +[features] +default = [] +osmosis-test-tube = ["cw-it/osmosis-test-tube", "cw-dex-test-helpers/osmosis-test-tube"] +# backtraces = ["cosmwasm-std/backtraces", "osmosis-std/backtraces"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] +cw-dex = { workspace = true } +cosmwasm-schema = { workspace = true } +cosmwasm-std = { workspace = true } +apollo-cw-asset = { workspace = true } +cw-utils = { workspace = true } +apollo-utils = { workspace = true } +osmosis-std = { workspace = true } + + +[dev-dependencies] +cw-it = { workspace = true } +test-case = { workspace = true } +cw-dex-test-contract = { workspace = true } +cw-dex-test-helpers = { workspace = true, features = ["osmosis"] } +proptest = { workspace = true } +cw20-base = { workspace = true } +cw20 = { workspace = true } diff --git a/cw-dex-osmosis/src/helpers.rs b/cw-dex-osmosis/src/helpers.rs new file mode 100644 index 0000000..b1c031e --- /dev/null +++ b/cw-dex-osmosis/src/helpers.rs @@ -0,0 +1,14 @@ +use std::time::Duration; + +pub(crate) trait ToProtobufDuration { + fn to_protobuf_duration(&self) -> osmosis_std::shim::Duration; +} + +impl ToProtobufDuration for Duration { + fn to_protobuf_duration(&self) -> osmosis_std::shim::Duration { + osmosis_std::shim::Duration { + seconds: self.as_secs() as i64, + nanos: self.subsec_nanos() as i32, + } + } +} diff --git a/cw-dex-osmosis/src/lib.rs b/cw-dex-osmosis/src/lib.rs new file mode 100644 index 0000000..4274ca8 --- /dev/null +++ b/cw-dex-osmosis/src/lib.rs @@ -0,0 +1,8 @@ +//! Contains cw-dex Pool and Staking implementations for Osmosis + +mod helpers; +mod pool; +mod staking; + +pub use pool::*; +pub use staking::*; diff --git a/cw-dex-osmosis/src/pool.rs b/cw-dex-osmosis/src/pool.rs new file mode 100644 index 0000000..5b2c517 --- /dev/null +++ b/cw-dex-osmosis/src/pool.rs @@ -0,0 +1,351 @@ +//! Pool trait implementation for Osmosis + +use std::ops::Deref; +use std::str::FromStr; + +use apollo_utils::assets::{ + assert_native_asset_info, assert_native_coin, assert_only_native_coins, merge_assets, +}; +use apollo_utils::iterators::{IntoElementwise, TryIntoElementwise}; +use osmosis_std::types::osmosis::gamm::v1beta1::{ + GammQuerier, MsgExitPool, MsgJoinPool, MsgJoinSwapExternAmountIn, MsgSwapExactAmountIn, +}; + +use apollo_cw_asset::{Asset, AssetInfo, AssetList}; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + Coin, CosmosMsg, Deps, Env, Event, QuerierWrapper, Response, StdResult, Uint128, +}; +use osmosis_std::types::osmosis::poolmanager::v1beta1::{PoolmanagerQuerier, SwapAmountInRoute}; + +use cw_dex::traits::Pool; +use cw_dex::CwDexError; + +/// Struct for interacting with Osmosis v1beta1 balancer pools. If `pool_id` +/// maps to another type of pool this will fail. +#[cw_serde] +#[derive(Copy)] +pub struct OsmosisPool { + /// The pool id of the pool to interact with + pool_id: u64, +} + +impl OsmosisPool { + /// Creates a new `OsmosisPool` instance with the given `pool_id` and + /// validates that the pool exists. + pub fn new(pool_id: u64, deps: Deps) -> StdResult { + let pool = Self { pool_id }; + // If this query succeeds then the pool exists + pool.get_pool_liquidity(deps)?; + Ok(pool) + } + + /// Creates an unchecked pool for use in testing. + pub fn unchecked(pool_id: u64) -> Self { + Self { pool_id } + } + + /// Returns the matching pool given a LP token. + /// + /// Arguments: + /// - `lp_token`: Said LP token + pub fn get_pool_for_lp_token(deps: Deps, lp_token: &AssetInfo) -> Result { + match lp_token { + AssetInfo::Native(lp_token_denom) => { + if !lp_token_denom.starts_with("gamm/pool/") { + return Err(CwDexError::NotLpToken {}); + } + + let pool_id_str = lp_token_denom + .strip_prefix("gamm/pool/") + .ok_or(CwDexError::NotLpToken {})?; + + let pool_id = u64::from_str(pool_id_str).map_err(|_| CwDexError::NotLpToken {})?; + + Ok(OsmosisPool::new(pool_id, deps)?) + } + _ => Err(CwDexError::NotLpToken {}), + } + } + + /// Returns the pool id of the pool + pub fn pool_id(&self) -> u64 { + self.pool_id + } + + /// Simulates a single sided join and returns `Uint128` amount of LP tokens + /// returned. A single sided join will use all of the provided asset. + pub fn simulate_single_sided_join( + &self, + querier: &QuerierWrapper, + asset: &Asset, + ) -> StdResult { + let querier = GammQuerier::new(querier); + let share_out_amount = Uint128::from_str( + &querier + .calc_join_pool_shares(self.pool_id, vec![assert_native_coin(asset)?.into()])? + .share_out_amount, + )?; + Ok(share_out_amount) + } + + /// Simulates a liquidity provision with all of the assets of the pool. + /// Returns `(Uint128, AssetList)` amount of LP tokens returned and the + /// tokens used to join the pool. + pub fn simulate_noswap_join( + &self, + querier: &QuerierWrapper, + assets: &AssetList, + ) -> StdResult<(Uint128, AssetList)> { + let querier = GammQuerier::new(querier); + let response = &querier.calc_join_pool_no_swap_shares( + self.pool_id, + assert_only_native_coins(assets)?.into_elementwise(), + )?; + let lp_tokens_returned = Uint128::from_str(&response.shares_out)?; + let tokens_used: Vec = response + .tokens_out + .iter() + .map(|x| { + Ok(Coin { + denom: x.denom.clone(), + amount: Uint128::from_str(&x.amount)?, + }) + }) + .collect::>()?; + + Ok((lp_tokens_returned, AssetList::from(tokens_used))) + } +} + +impl Pool for OsmosisPool { + fn provide_liquidity( + &self, + deps: Deps, + env: &Env, + assets: AssetList, + min_out: Uint128, + ) -> Result { + let mut assets = assets; + + // Remove all zero amount Coins, merge duplicates and assert that all assets are + // native. + let mut assets = assert_only_native_coins(&merge_assets(assets.purge().deref())?)?; + + let expected_shares = self + .simulate_provide_liquidity(deps, env, assets.to_owned().into())? + .amount; + + // Assert slippage tolerance + if min_out > expected_shares { + return Err(CwDexError::MinOutNotReceived { + min_out, + received: expected_shares, + }); + } + + // sort assets + assets.sort_by(|a, b| a.denom.to_string().cmp(&b.denom)); + + let join_pool: CosmosMsg = if assets.len() == 1 { + MsgJoinSwapExternAmountIn { + sender: env.contract.address.to_string(), + pool_id: self.pool_id, + share_out_min_amount: expected_shares.to_string(), + token_in: Some(assets[0].clone().into()), + } + .into() + } else { + MsgJoinPool { + sender: env.contract.address.to_string(), + pool_id: self.pool_id, + share_out_amount: expected_shares.to_string(), + token_in_maxs: assets.into_elementwise(), + } + .into() + }; + + let event = Event::new("apollo/cw-dex/provide_liquidity") + .add_attribute("pool_id", self.pool_id.to_string()) + .add_attribute("min_out", min_out) + .add_attribute("expected_shares", expected_shares); + + Ok(Response::new().add_message(join_pool).add_event(event)) + } + + fn withdraw_liquidity( + &self, + _deps: Deps, + env: &Env, + lp_token: Asset, + min_out: AssetList, + ) -> Result { + let min_out_coins = assert_only_native_coins(&min_out)?.try_into_elementwise()?; + + let exit_msg = MsgExitPool { + sender: env.contract.address.to_string(), + pool_id: self.pool_id, + share_in_amount: lp_token.amount.to_string(), + token_out_mins: min_out_coins, + }; + + let mut event = Event::new("apollo/cw-dex/withdraw_liquidity") + .add_attribute("pool_id", self.pool_id.to_string()) + .add_attribute("shares_in", lp_token.to_string()); + + // We're not allowed to add empty values as attributes. + if !min_out.len() == 0 { + event = event.add_attribute("min_out", min_out.to_string()); + } + + Ok(Response::new().add_message(exit_msg).add_event(event)) + } + + fn swap( + &self, + _deps: Deps, + env: &Env, + offer_asset: Asset, + ask_asset_info: AssetInfo, + min_out: Uint128, + ) -> Result { + let offer = assert_native_coin(&offer_asset)?; + let ask_denom = assert_native_asset_info(&ask_asset_info)?; + + // Min out must be greater than 0 for osmosis. + let min_out = if min_out == Uint128::zero() { + Uint128::one() + } else { + min_out + }; + + let swap_msg = MsgSwapExactAmountIn { + sender: env.contract.address.to_string(), + routes: vec![SwapAmountInRoute { + pool_id: self.pool_id, + token_out_denom: ask_denom.clone(), + }], + token_in: Some(offer.clone().into()), + token_out_min_amount: min_out.to_string(), + }; + + let event = Event::new("apollo/cw-dex/swap") + .add_attribute("pool_id", self.pool_id.to_string()) + .add_attribute("offer", offer.to_string()) + .add_attribute("ask", ask_denom) + .add_attribute("token_out_min_amount", min_out); + + Ok(Response::new().add_message(swap_msg).add_event(event)) + } + + /// Allowing deprecated functions here because + /// `osmosis.gamm.v1beta1.Query/TotalPoolLiquidity` has been deprecated, + /// but `osmosis.poolmanager.v1beta1.Query/TotalPoolLiquidity` has not yet + /// been whitelisted in the stargate queries whitelist. + /// See issue: + #[allow(deprecated)] + fn get_pool_liquidity(&self, deps: Deps) -> Result { + let pool_assets = GammQuerier::new(&deps.querier).total_pool_liquidity(self.pool_id)?; + + let asset_list: AssetList = pool_assets + .liquidity + .into_iter() + .map(|coin| { + Ok(Asset { + info: AssetInfo::Native(coin.denom), + amount: Uint128::from_str(&coin.amount)?, + }) + }) + .collect::>>()? + .into(); + + Ok(asset_list) + } + + fn simulate_provide_liquidity( + &self, + deps: Deps, + _env: &Env, + assets: AssetList, + ) -> Result { + let shares_out_amount: Uint128; + if assets.len() == 1 { + shares_out_amount = + self.simulate_single_sided_join(&deps.querier, &assets.to_vec()[0])?; + } else { + (shares_out_amount, _) = self.simulate_noswap_join(&deps.querier, &assets)?; + } + + Ok(Asset::new(self.lp_token(), shares_out_amount)) + } + + fn simulate_withdraw_liquidity( + &self, + deps: Deps, + lp_token: &Asset, + ) -> Result { + let querier = GammQuerier::new(&deps.querier); + let lp_denom = self.lp_token(); + + if lp_denom != lp_token.info { + return Err(CwDexError::InvalidLpToken {}); + } + + let tokens_out: Vec = querier + .calc_exit_pool_coins_from_shares(self.pool_id, lp_token.amount.to_string())? + .tokens_out + .iter() + .map(|c| { + Ok(Coin { + denom: c.denom.clone(), + amount: Uint128::from_str(&c.amount)?, + }) + }) + .collect::>()?; + + Ok(tokens_out.into()) + } + + fn simulate_swap( + &self, + deps: Deps, + offer: Asset, + ask_asset_info: AssetInfo, + ) -> StdResult { + let offer: Coin = offer.try_into()?; + let swap_response = PoolmanagerQuerier::new(&deps.querier).estimate_swap_exact_amount_in( + self.pool_id, + offer.to_string(), + vec![SwapAmountInRoute { + pool_id: self.pool_id, + token_out_denom: assert_native_asset_info(&ask_asset_info)?, + }], + )?; + Uint128::from_str(swap_response.token_out_amount.as_str()) + } + + fn lp_token(&self) -> AssetInfo { + AssetInfo::Native(format!("gamm/pool/{}", self.pool_id)) + } +} + +#[cfg(test)] +mod tests { + use apollo_cw_asset::AssetInfo; + + use cw_dex::traits::Pool; + + use super::OsmosisPool; + + #[test] + fn test_lp_token() { + let pool = OsmosisPool::unchecked(1337u64); + + let lp_token = pool.lp_token(); + + match lp_token { + AssetInfo::Native(denom) => assert_eq!(denom, format!("gamm/pool/{}", 1337u64)), + AssetInfo::Cw20(_) => panic!("Unexpected cw20 token"), + } + } +} diff --git a/cw-dex-osmosis/src/staking.rs b/cw-dex-osmosis/src/staking.rs new file mode 100644 index 0000000..9442a96 --- /dev/null +++ b/cw-dex-osmosis/src/staking.rs @@ -0,0 +1,325 @@ +//! Staking/rewards traits implementations for Osmosis + +use apollo_cw_asset::AssetList; +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{ + Addr, Coin, Deps, Env, Event, QuerierWrapper, ReplyOn, Response, StdError, StdResult, SubMsg, + Uint128, +}; +use cw_utils::Duration as CwDuration; +use osmosis_std::types::osmosis::lockup::{MsgBeginUnlocking, MsgForceUnlock, MsgLockTokens}; +use osmosis_std::types::osmosis::superfluid::{ + MsgLockAndSuperfluidDelegate, MsgSuperfluidUnbondLock, MsgSuperfluidUndelegate, +}; +use std::time::Duration; + +use cw_dex::traits::{ForceUnlock, LockedStaking, Rewards, Stake, Unlock}; +use cw_dex::CwDexError; + +use super::helpers::ToProtobufDuration; + +/// Implementation of locked staking on osmosis. Using the Staking trait. +/// `lockup_duration` is the duration of the lockup period in nano seconds. +#[cw_serde] +pub struct OsmosisStaking { + /// Lockup duration in nano seconds. Allowed values 1 day, 1 week or 2 + /// weeks. + pub lockup_duration: Duration, + /// ID for the lockup record + pub lock_id: Option, + /// Denomination of the associated LP token + pub lp_token_denom: String, +} + +impl OsmosisStaking { + /// Creates a new OsmosisStaking instance with lock up duration set to + /// `lockup_duration`. + /// + /// Arguments: + /// - `lockup_duration` is the duration of the lockup period in seconds. + /// + /// Returns an error if `lockup_duration` is not one of the allowed values, + /// 86400, 604800 or 1209600, representing 1 day, 1 week or 2 weeks + /// respectively. + pub fn new( + lockup_duration: u64, + lock_id: Option, + lp_token_denom: String, + ) -> StdResult { + if !([86400u64, 604800u64, 1209600u64].contains(&lockup_duration)) { + return Err(StdError::generic_err( + "osmosis error: invalid lockup duration", + )); + } + Ok(Self { + lockup_duration: Duration::from_secs(lockup_duration), + lock_id, + lp_token_denom, + }) + } +} + +/// Reply ID for locking tokens +pub const OSMOSIS_LOCK_TOKENS_REPLY_ID: u64 = 123; +/// Reply ID for unlocking tokens +pub const OSMOSIS_UNLOCK_TOKENS_REPLY_ID: u64 = 124; + +impl Rewards for OsmosisStaking { + fn claim_rewards(&self, _deps: Deps, _env: &Env) -> Result { + // Rewards are automatically distributed to stakers every epoch. + let event = + Event::new("apollo/cw-dex/claim_rewards").add_attribute("type", "osmosis_staking"); + Ok(Response::new().add_event(event)) + } + + fn query_pending_rewards( + &self, + _querier: &QuerierWrapper, + _user: &Addr, + ) -> Result { + // Rewards are automatically distributed to stakers every epoch. + // There is no currently no way to query how many have accumulated since + // last epoch. + Ok(AssetList::new()) + } +} + +impl Stake for OsmosisStaking { + fn stake(&self, _deps: Deps, env: &Env, amount: Uint128) -> Result { + let asset = Coin::new(amount.u128(), self.lp_token_denom.clone()); + + let stake_msg = MsgLockTokens { + owner: env.contract.address.to_string(), + duration: Some(self.lockup_duration.to_protobuf_duration()), + coins: vec![asset.clone().into()], + }; + + let event = Event::new("apollo/cw-dex/stake") + .add_attribute("type", "osmosis_staking") + .add_attribute("asset", asset.to_string()) + .add_attribute( + "lockup_duration_secs", + self.lockup_duration.as_secs().to_string(), + ); + + Ok(Response::new() + .add_submessage(SubMsg { + id: OSMOSIS_LOCK_TOKENS_REPLY_ID, + msg: stake_msg.into(), + gas_limit: None, + reply_on: ReplyOn::Success, + }) + .add_event(event)) + } +} + +impl Unlock for OsmosisStaking { + fn unlock(&self, _deps: Deps, env: &Env, amount: Uint128) -> Result { + let asset = Coin::new(amount.u128(), self.lp_token_denom.clone()); + + let id = self + .lock_id + .ok_or_else(|| StdError::generic_err("osmosis error: lock id not set"))?; + + let unstake_msg = MsgBeginUnlocking { + owner: env.contract.address.to_string(), + id, + coins: vec![asset.clone().into()], + }; + + let event = Event::new("apollo/cw-dex/unstake") + .add_attribute("type", "osmosis_staking") + .add_attribute("asset", asset.to_string()) + .add_attribute( + "lockup_duration_secs", + self.lockup_duration.as_secs().to_string(), + ) + .add_attribute("lock_id", id.to_string()); + + Ok(Response::new() + .add_submessage(SubMsg { + id: OSMOSIS_UNLOCK_TOKENS_REPLY_ID, + msg: unstake_msg.into(), + gas_limit: None, + reply_on: ReplyOn::Success, + }) + .add_event(event)) + } + + fn withdraw_unlocked( + &self, + _deps: Deps, + _env: &Env, + _amount: Uint128, + ) -> Result { + // Osmosis automatically sends the unlocked tokens after the lockup duration + let event = + Event::new("apollo/cw-dex/withdraw_unlocked").add_attribute("type", "osmosis_staking"); + Ok(Response::new().add_event(event)) + } +} + +impl LockedStaking for OsmosisStaking { + fn get_lockup_duration(&self, _deps: Deps) -> Result { + Ok(CwDuration::Time(self.lockup_duration.as_secs())) + } +} + +impl ForceUnlock for OsmosisStaking { + fn force_unlock( + &self, + _deps: Deps, + env: &Env, + lockup_id: Option, + amount: Uint128, + ) -> Result { + let lockup_id = match lockup_id { + Some(id) => Ok(id), + None => self + .lock_id + .ok_or_else(|| StdError::generic_err("osmosis error: lock id not set")), + }?; + + let coin_to_unlock = Coin::new(amount.u128(), self.lp_token_denom.clone()); + + let force_unlock_msg = MsgForceUnlock { + owner: env.contract.address.to_string(), + id: lockup_id, + coins: vec![coin_to_unlock.into()], + }; + + let event = Event::new("apollo/cw-dex/force-unlock") + .add_attribute("type", "osmosis_staking") + .add_attribute("amount", amount) + .add_attribute("lockup_id", lockup_id.to_string()); + + Ok(Response::new() + .add_message(force_unlock_msg) + .add_event(event)) + } +} + +/// Implementation of superfluid staking for osmosis. +#[cw_serde] +pub struct OsmosisSuperfluidStaking { + /// Address of the validator to delegate to. + pub validator_address: Addr, + /// ID of the lockup record. + pub lock_id: Option, + /// Denomination of the associated LP token. + pub lp_token_denom: String, +} + +const TWO_WEEKS_IN_SECS: u64 = 14 * 24 * 60 * 60; + +impl OsmosisSuperfluidStaking { + /// Creates a new instance of `OsmosisSuperfluidStaking`. + /// + /// Arguments: + /// - `validator_address`: Address of the associated validator + /// - `lock_id`: ID of the lockup record + /// - `lp_token_denom`: LP token denomination + pub fn new( + validator_address: Addr, + lock_id: Option, + lp_token_denom: String, + ) -> StdResult { + Ok(Self { + validator_address, + lock_id, + lp_token_denom, + }) + } +} + +impl Rewards for OsmosisSuperfluidStaking { + fn claim_rewards(&self, _deps: Deps, _env: &Env) -> Result { + // Rewards are automatically distributed to stakers every epoch. + let event = Event::new("apollo/cw-dex/claim_rewards") + .add_attribute("type", "osmosis_superfluid_staking"); + Ok(Response::new().add_event(event)) + } + + fn query_pending_rewards( + &self, + _querier: &QuerierWrapper, + _user: &Addr, + ) -> Result { + // Rewards are automatically distributed to stakers every epoch. + // There is no currently no way to query how many have accumulated since + // last epoch. + Ok(AssetList::new()) + } +} + +impl Stake for OsmosisSuperfluidStaking { + fn stake(&self, _deps: Deps, env: &Env, amount: Uint128) -> Result { + let asset = Coin::new(amount.u128(), self.lp_token_denom.clone()); + + let stake_msg = MsgLockAndSuperfluidDelegate { + sender: env.contract.address.to_string(), + coins: vec![asset.clone().into()], + val_addr: self.validator_address.to_string(), + }; + + let event = Event::new("apollo/cw-dex/stake") + .add_attribute("type", "osmosis_superfluid_staking") + .add_attribute("asset", asset.to_string()) + .add_attribute("validator_address", self.validator_address.to_string()); + + Ok(Response::new() + .add_submessage(SubMsg { + id: OSMOSIS_LOCK_TOKENS_REPLY_ID, + msg: stake_msg.into(), + gas_limit: None, + reply_on: ReplyOn::Success, + }) + .add_event(event)) + } +} + +impl Unlock for OsmosisSuperfluidStaking { + fn unlock(&self, _deps: Deps, env: &Env, _amount: Uint128) -> Result { + let lock_id = self + .lock_id + .ok_or_else(|| StdError::generic_err("osmosis error: lock id not set"))?; + + let undelegate_msg = MsgSuperfluidUndelegate { + sender: env.contract.address.to_string(), + lock_id, + }; + let unstake_msg = MsgSuperfluidUnbondLock { + sender: env.contract.address.to_string(), + lock_id, + }; + + let event = Event::new("apollo/cw-dex/unstake") + .add_attribute("type", "osmosis_superfluid_staking") + .add_attribute("validator_address", self.validator_address.to_string()) + .add_attribute("lock_id", lock_id.to_string()); + + Ok(Response::new() + .add_message(undelegate_msg) + .add_message(unstake_msg) + .add_event(event)) + } + + fn withdraw_unlocked( + &self, + _deps: Deps, + _env: &Env, + _amount: Uint128, + ) -> Result { + // Osmosis automatically sends the unlocked tokens after the lockup duration + let event = Event::new("apollo/cw-dex/withdraw_unlocked") + .add_attribute("type", "osmosis_superfluid_staking"); + Ok(Response::new().add_event(event)) + } +} + +impl LockedStaking for OsmosisSuperfluidStaking { + fn get_lockup_duration(&self, _deps: Deps) -> Result { + // Lockup time for superfluid staking is always 14 days. + Ok(CwDuration::Time(TWO_WEEKS_IN_SECS)) + } +} diff --git a/cw-dex/tests/osmosis_proptests.proptest-regressions b/cw-dex-osmosis/tests/osmosis_proptests.proptest-regressions similarity index 100% rename from cw-dex/tests/osmosis_proptests.proptest-regressions rename to cw-dex-osmosis/tests/osmosis_proptests.proptest-regressions diff --git a/cw-dex/tests/osmosis_proptests.rs b/cw-dex-osmosis/tests/osmosis_proptests.rs similarity index 100% rename from cw-dex/tests/osmosis_proptests.rs rename to cw-dex-osmosis/tests/osmosis_proptests.rs diff --git a/cw-dex/tests/osmosis_tests.rs b/cw-dex-osmosis/tests/osmosis_tests.rs similarity index 100% rename from cw-dex/tests/osmosis_tests.rs rename to cw-dex-osmosis/tests/osmosis_tests.rs diff --git a/cw-dex/Cargo.toml b/cw-dex/Cargo.toml index b8f6646..9c1a64a 100644 --- a/cw-dex/Cargo.toml +++ b/cw-dex/Cargo.toml @@ -5,11 +5,11 @@ edition = "2021" license = "MPL-2.0" name = "cw-dex" repository = "https://github.com/apollodao/cw-dex" -version = "0.6.0-rc.3" +version = "0.5.2" readme = "README.md" [features] -default = [] +default = ["astroport", "osmosis", "osmosis-test-tube"] osmosis = ["osmosis-std", "osmosis-test-tube", "cw-it/osmosis", "cw-dex-test-helpers/osmosis"] osmosis-test-tube = ["cw-it/osmosis-test-tube", "cw-dex-test-helpers/osmosis-test-tube"] astroport = ["dep:astroport", "dep:astroport_v3", "apollo-cw-asset/astroport", "dep:cw2", "cw-it/astroport", "cw-it/astroport-multi-test"] diff --git a/cw-dex/src/implementations/osmosis/pool.rs b/cw-dex/src/implementations/osmosis/pool.rs index 1a5dc43..621a896 100644 --- a/cw-dex/src/implementations/osmosis/pool.rs +++ b/cw-dex/src/implementations/osmosis/pool.rs @@ -305,24 +305,3 @@ impl Pool for OsmosisPool { AssetInfo::Native(format!("gamm/pool/{}", self.pool_id)) } } - -#[cfg(test)] -mod tests { - use apollo_cw_asset::AssetInfo; - - use crate::traits::Pool; - - use super::OsmosisPool; - - #[test] - fn test_lp_token() { - let pool = OsmosisPool::unchecked(1337u64); - - let lp_token = pool.lp_token(); - - match lp_token { - AssetInfo::Native(denom) => assert_eq!(denom, format!("gamm/pool/{}", 1337u64)), - AssetInfo::Cw20(_) => panic!("Unexpected cw20 token"), - } - } -} diff --git a/cw-dex/src/lib.rs b/cw-dex/src/lib.rs index c085566..863ff5a 100644 --- a/cw-dex/src/lib.rs +++ b/cw-dex/src/lib.rs @@ -10,15 +10,20 @@ //! interact with any of them. //! //! The currently supported decentralized exchanges are: -//! - [Osmosis](crate::implementations::osmosis) -//! - [Astroport](crate::implementations::astroport) +//! - [Osmosis] +//! - Via crate `cw-dex-osmosis` +//! - [Astroport] +//! - Via crate `cw-dex-astroport` pub mod error; -pub mod implementations; pub mod traits; +#[deprecated( + since = "0.5.2", + note = "Please use separate implementation crates such as `cw-dex-astroport`, and `cw-dex-osmosis` instead" +)] +pub mod implementations; + pub use error::*; +#[allow(deprecated)] pub use implementations::*; - -// #[cfg(test)] -// pub mod tests; diff --git a/cw-dex/tests/configs/terra.yaml b/cw-dex/tests/configs/terra.yaml deleted file mode 100644 index 34c33a9..0000000 --- a/cw-dex/tests/configs/terra.yaml +++ /dev/null @@ -1,97 +0,0 @@ -folder: "./tests/configs" -artifacts_folder: "./artifacts" -chain_config: - name: "terra" - chain_id: "localterra" - prefix: "terra" - denom: "uluna" - gas_price: 1000000 - gas_adjustment: 1.2 - # https://github.com/confio/cosmos-hd-key-derivation-spec#the-cosmos-hub-path - derivation_path: "m/44'/330'/0'/0/0" - - # leave this empty if using test_containers - rpc_endpoint: "" - grpc_endpoint: "" -container: - name: "terramoney/localterra-core" - tag: "2.0.1" - entrypoint: "/entrypoint.sh" - volumes: - [ - ["tests/configs/terra/config", "/root/.terra/config"], - ["tests/configs/terra/entrypoint.sh", "/entrypoint.sh"], - ] - ports: [26657, 1317, 9090, 9091] - -# Chain to download contracts from -contract_chain_download_rpc: "https://terra-rpc.polkachu.com" - -# contracts list to download -# Do not repeat names -contracts: - astroport_liquidity_helper: - url: "https://github.com/apollodao/astroport-liquidity-helper.git" - branch: "master" - artifact: astroport_liquidity_helper.wasm - preferred_source: "url" - - astro_token: - url: "https://github.com/astroport-fi/astroport-core.git" - branch: "c216ecd4f350113316be44d06a95569f451ac681" # TODO: Allow commit instead of branch - artifact: "astro_token.wasm" - preferred_source: "chain" # Where to prefer getting contract, can be either "chain" or "url". - chain_address: "terra1nsuqsk6kh58ulczatwev87ttq2z6r3pusulg9r24mfj2fvtzd4uq3exn26" - - astroport_factory: - artifact: "astroport_factory.wasm" - preferred_source: "chain" - chain_address: "terra14x9fr055x5hvr48hzy2t4q7kvjvfttsvxusa4xsdcy702mnzsvuqprer8r" - - astroport_maker: - artifact: "astroport_maker.wasm" - preferred_source: "chain" - chain_address: "terra1ygcvxp9s054q8u2q4hvl52ke393zvgj0sllahlycm4mj8dm96zjsa45rzk" - - astroport_router: - artifact: "astroport_router.wasm" - preferred_source: "chain" - chain_address: "terra1j8hayvehh3yy02c2vtw5fdhz9f4drhtee8p5n5rguvg3nyd6m83qd2y90a" - - astroport_generator: - artifact: "astroport_generator.wasm" - preferred_source: "chain" - chain_address: "terra1ksvlfex49desf4c452j6dewdjs6c48nafemetuwjyj6yexd7x3wqvwa7j9" - - astroport_pair_stable: - artifact: "astroport_pair_stable.wasm" - preferred_source: "chain" - chain_code_id: 428 - - astroport_pair_xyk: - artifact: "astroport_pair_xyk.wasm" - preferred_source: "chain" - chain_code_id: 392 - - astroport_whitelist: - artifact: "astroport_whitelist.wasm" - preferred_source: "chain" - chain_code_id: 70 - - astroport_staking: - artifact: "astroport_staking.wasm" - preferred_source: "chain" - chain_address: "terra1nyu6sk9rvtvsltm7tjjrp6rlavnm3e4sq03kltde6kesam260f8szar8ze" - - astroport_vesting: - artifact: "astroport_vesting.wasm" - preferred_source: "chain" - chain_address: "terra1qyuarnzcc6uuft9n9mltraprreke4v8gvxd8u3nslngxhflhru9qw34nc3" - - cw_dex_router: - artifact: "cw_dex_router.wasm" - preferred_source: "chain" - - astroport_vault: - artifact: "astroport_vault.wasm" - preferred_source: "chain" diff --git a/test-contracts/astroport-test-contract/Cargo.toml b/test-contracts/astroport-test-contract/Cargo.toml index 0a4028c..027a0ce 100644 --- a/test-contracts/astroport-test-contract/Cargo.toml +++ b/test-contracts/astroport-test-contract/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "astroport-test-contract" description = "Contract to test the cw-dex library" -version = "0.1.0" +version = "0.2.0" authors = ["Pacman "] edition = "2021" license = { workspace = true } @@ -36,5 +36,6 @@ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } thiserror = { workspace = true } apollo-cw-asset = { workspace = true } -cw-dex = { workspace = true, features = ["astroport"] } +cw-dex = { workspace = true } +cw-dex-astroport = { workspace = true } cw-dex-test-contract = { workspace = true } diff --git a/test-contracts/astroport-test-contract/src/contract.rs b/test-contracts/astroport-test-contract/src/contract.rs index 2c8d19d..b2c2a86 100644 --- a/test-contracts/astroport-test-contract/src/contract.rs +++ b/test-contracts/astroport-test-contract/src/contract.rs @@ -7,8 +7,8 @@ use cosmwasm_std::{ to_json_binary, Addr, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdResult, Uint128, }; -use cw_dex::astroport::{AstroportPool, AstroportStaking}; use cw_dex::traits::{Pool, Rewards, Stake, Unstake}; +use cw_dex_astroport::{AstroportPool, AstroportStaking}; use cw_dex_test_contract::msg::{ AstroportContractInstantiateMsg as InstantiateMsg, AstroportExecuteMsg as ExecuteMsg, QueryMsg, }; @@ -145,7 +145,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { ), QueryMsg::SimulateSwap { offer, ask } => query_simulate_swap(deps, offer, ask), QueryMsg::GetPoolForLpToken { lp_token } => to_json_binary( - &cw_dex::Pool::get_pool_for_lp_token(deps, &lp_token, Some(pool.liquidity_manager))?, + &AstroportPool::get_pool_for_lp_token(deps, &lp_token, pool.liquidity_manager)?, ), QueryMsg::PendingRewards {} => { let staking = STAKING.load(deps.storage)?; diff --git a/test-contracts/astroport-test-contract/src/state.rs b/test-contracts/astroport-test-contract/src/state.rs index 7a4ff83..49a2d85 100644 --- a/test-contracts/astroport-test-contract/src/state.rs +++ b/test-contracts/astroport-test-contract/src/state.rs @@ -1,4 +1,4 @@ -use cw_dex::astroport::{AstroportPool, AstroportStaking}; +use cw_dex_astroport::{AstroportPool, AstroportStaking}; use cw_storage_plus::Item; pub const POOL: Item = Item::new("pool"); diff --git a/test-contracts/osmosis-test-contract/Cargo.toml b/test-contracts/osmosis-test-contract/Cargo.toml index 95a9ddb..f078b64 100644 --- a/test-contracts/osmosis-test-contract/Cargo.toml +++ b/test-contracts/osmosis-test-contract/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "osmosis-test-contract" description = "Contract to test the cw-dex library" -version = "0.1.0" +version = "0.2.0" authors = ["Pacman "] edition = "2021" license = { workspace = true } @@ -36,5 +36,6 @@ cosmwasm-std = { workspace = true } cw-storage-plus = { workspace = true } thiserror = { workspace = true } apollo-cw-asset = { workspace = true } -cw-dex = { workspace = true, features = ["osmosis"] } +cw-dex = { workspace = true } +cw-dex-osmosis = { workspace = true } cw-dex-test-contract = { workspace = true } diff --git a/test-contracts/osmosis-test-contract/src/contract.rs b/test-contracts/osmosis-test-contract/src/contract.rs index 77358e7..4fa83aa 100644 --- a/test-contracts/osmosis-test-contract/src/contract.rs +++ b/test-contracts/osmosis-test-contract/src/contract.rs @@ -5,8 +5,8 @@ use cosmwasm_std::{ to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Reply, Response, StdError, StdResult, Uint128, }; -use cw_dex::osmosis::{OsmosisPool, OsmosisStaking, OsmosisSuperfluidStaking}; use cw_dex::traits::{ForceUnlock, Pool, Stake, Unlock}; +use cw_dex_osmosis::{OsmosisPool, OsmosisStaking, OsmosisSuperfluidStaking}; // use cw2::set_contract_version; use crate::error::ContractError; @@ -213,7 +213,7 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult { ), QueryMsg::SimulateSwap { offer, ask } => query_simulate_swap(deps, offer, ask), QueryMsg::GetPoolForLpToken { lp_token } => { - to_json_binary(&cw_dex::Pool::get_pool_for_lp_token(deps, &lp_token, None)?) + to_json_binary(&OsmosisPool::get_pool_for_lp_token(deps, &lp_token)?) } QueryMsg::PendingRewards {} => unimplemented!(), } diff --git a/test-contracts/osmosis-test-contract/src/state.rs b/test-contracts/osmosis-test-contract/src/state.rs index fbbbff1..66c16f9 100644 --- a/test-contracts/osmosis-test-contract/src/state.rs +++ b/test-contracts/osmosis-test-contract/src/state.rs @@ -1,4 +1,4 @@ -use cw_dex::osmosis::{OsmosisPool, OsmosisStaking, OsmosisSuperfluidStaking}; +use cw_dex_osmosis::{OsmosisPool, OsmosisStaking, OsmosisSuperfluidStaking}; use cw_storage_plus::Item; pub const POOL: Item = Item::new("pool"); diff --git a/test-contracts/package/Cargo.toml b/test-contracts/package/Cargo.toml index fc9cc0f..c944f29 100644 --- a/test-contracts/package/Cargo.toml +++ b/test-contracts/package/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "cw-dex-test-contract" description = "Contract to test the cw-dex library" -version = "0.1.0" +version = "0.2.0" authors = ["Pacman "] edition = "2021" license = { workspace = true } @@ -21,4 +21,3 @@ crate-type = ["cdylib", "rlib"] cosmwasm-schema = { workspace = true } cosmwasm-std = { workspace = true } apollo-cw-asset = { workspace = true } -cw-dex = { workspace = true } diff --git a/test-contracts/package/src/msg.rs b/test-contracts/package/src/msg.rs index fb76719..dffec8d 100644 --- a/test-contracts/package/src/msg.rs +++ b/test-contracts/package/src/msg.rs @@ -65,6 +65,12 @@ impl ExecuteMsg { } } +#[cw_serde] +/// Represents an unknown type as the response of a query. +/// This is due to the API being used by different contracts which will return +/// different types. +pub struct Unknown {} + #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { @@ -76,7 +82,7 @@ pub enum QueryMsg { SimulateWithdrawLiquidty { amount: Uint128 }, #[returns(Uint128)] SimulateSwap { offer: Asset, ask: AssetInfo }, - #[returns(cw_dex::Pool)] + #[returns(Unknown)] GetPoolForLpToken { lp_token: AssetInfo }, #[returns(AssetList)] PendingRewards {}, diff --git a/test-helpers/Cargo.toml b/test-helpers/Cargo.toml index 163535b..e6de23e 100644 --- a/test-helpers/Cargo.toml +++ b/test-helpers/Cargo.toml @@ -16,7 +16,7 @@ exclude = [ [features] default = [] osmosis = ["cw-it/osmosis"] -astroport = ["cw-it/astroport", "cw-it/astroport-multi-test", "astroport-test-contract"] +astroport = ["cw-it/astroport", "cw-it/astroport-multi-test", "astroport-test-contract", "apollo-cw-asset/astroport"] osmosis-test-tube = ["cw-it/osmosis-test-tube"] [lib] @@ -31,7 +31,7 @@ optimize = """docker run --rm -v "$(pwd)":/code \ [dependencies] cosmwasm-std = { workspace = true } -apollo-cw-asset = { workspace = true, features = ["astroport"] } +apollo-cw-asset = { workspace = true } apollo-utils = { workspace = true } cw-dex-test-contract = { workspace = true } astroport-test-contract = { workspace = true, optional = true } From c681444f7c186a4675d1e1aa8e15e551d950b5a5 Mon Sep 17 00:00:00 2001 From: Sturdy <91910406+apollo-sturdy@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:26:43 +0100 Subject: [PATCH 3/4] fix: review comments --- Cargo.lock | 2 +- Cargo.toml | 6 +++--- cw-dex-astroport/src/lib.rs | 2 +- cw-dex-astroport/src/pool.rs | 2 -- cw-dex-osmosis/src/lib.rs | 1 + cw-dex-osmosis/src/pool.rs | 15 +++++++-------- cw-dex/Cargo.toml | 14 +++++++------- .../osmosis-test-contract/src/contract.rs | 7 ------- 8 files changed, 20 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e0d104..d22ffaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -945,7 +945,7 @@ dependencies = [ "cw-utils 1.0.2", "cw20 1.1.2", "cw20-base 1.1.1", - "osmosis-std 0.19.2", + "osmosis-std 0.22.0", "proptest", "test-case", ] diff --git a/Cargo.toml b/Cargo.toml index cf0de53..ab15557 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,11 +25,11 @@ schemars = "0.8.10" serde = { version = "1.0.145", default-features = false, features = ["derive"] } thiserror = { version = "1.0.31" } apollo-cw-asset = "0.1.1" -osmosis-std = "0.19.2" +osmosis-std = "0.22.0" cw-it = "0.3.0-rc.4" apollo-utils = "0.1.0" -astroport = "=2.9.0" -astroport_v3 = { package = "astroport", version = "=3.11.1" } +astroport = "2.9.0" +astroport_v3 = { package = "astroport", version = "3.11.1" } test-case = "3.0.0" proptest = "1.0.0" diff --git a/cw-dex-astroport/src/lib.rs b/cw-dex-astroport/src/lib.rs index b48062f..66173e1 100644 --- a/cw-dex-astroport/src/lib.rs +++ b/cw-dex-astroport/src/lib.rs @@ -6,4 +6,4 @@ mod staking; pub use pool::AstroportPool; pub use staking::AstroportStaking; -pub use astroport; +pub use {astroport, astroport_v3}; diff --git a/cw-dex-astroport/src/pool.rs b/cw-dex-astroport/src/pool.rs index de74780..84d255f 100644 --- a/cw-dex-astroport/src/pool.rs +++ b/cw-dex-astroport/src/pool.rs @@ -74,8 +74,6 @@ impl AstroportPool { /// - `lp_token`: Said LP token /// - `astroport_liquidity_manager`: The Astroport liquidity manager /// address. - #[allow(unused_variables)] - #[allow(unreachable_patterns)] pub fn get_pool_for_lp_token( deps: Deps, lp_token: &AssetInfo, diff --git a/cw-dex-osmosis/src/lib.rs b/cw-dex-osmosis/src/lib.rs index 4274ca8..2013d31 100644 --- a/cw-dex-osmosis/src/lib.rs +++ b/cw-dex-osmosis/src/lib.rs @@ -4,5 +4,6 @@ mod helpers; mod pool; mod staking; +pub use osmosis_std; pub use pool::*; pub use staking::*; diff --git a/cw-dex-osmosis/src/pool.rs b/cw-dex-osmosis/src/pool.rs index 5b2c517..e481917 100644 --- a/cw-dex-osmosis/src/pool.rs +++ b/cw-dex-osmosis/src/pool.rs @@ -16,7 +16,9 @@ use cosmwasm_schema::cw_serde; use cosmwasm_std::{ Coin, CosmosMsg, Deps, Env, Event, QuerierWrapper, Response, StdResult, Uint128, }; -use osmosis_std::types::osmosis::poolmanager::v1beta1::{PoolmanagerQuerier, SwapAmountInRoute}; +use osmosis_std::types::osmosis::poolmanager::v1beta1::{ + PoolmanagerQuerier, SwapAmountInRoute, TotalPoolLiquidityRequest, +}; use cw_dex::traits::Pool; use cw_dex::CwDexError; @@ -238,14 +240,11 @@ impl Pool for OsmosisPool { Ok(Response::new().add_message(swap_msg).add_event(event)) } - /// Allowing deprecated functions here because - /// `osmosis.gamm.v1beta1.Query/TotalPoolLiquidity` has been deprecated, - /// but `osmosis.poolmanager.v1beta1.Query/TotalPoolLiquidity` has not yet - /// been whitelisted in the stargate queries whitelist. - /// See issue: - #[allow(deprecated)] fn get_pool_liquidity(&self, deps: Deps) -> Result { - let pool_assets = GammQuerier::new(&deps.querier).total_pool_liquidity(self.pool_id)?; + let pool_assets = TotalPoolLiquidityRequest { + pool_id: self.pool_id, + } + .query(&deps.querier)?; let asset_list: AssetList = pool_assets .liquidity diff --git a/cw-dex/Cargo.toml b/cw-dex/Cargo.toml index 9c1a64a..a445c18 100644 --- a/cw-dex/Cargo.toml +++ b/cw-dex/Cargo.toml @@ -9,10 +9,10 @@ version = "0.5.2" readme = "README.md" [features] -default = ["astroport", "osmosis", "osmosis-test-tube"] +default = [] osmosis = ["osmosis-std", "osmosis-test-tube", "cw-it/osmosis", "cw-dex-test-helpers/osmosis"] osmosis-test-tube = ["cw-it/osmosis-test-tube", "cw-dex-test-helpers/osmosis-test-tube"] -astroport = ["dep:astroport", "dep:astroport_v3", "apollo-cw-asset/astroport", "dep:cw2", "cw-it/astroport", "cw-it/astroport-multi-test"] +astroport = ["dep:astroport", "dep:astroport_v3", "apollo-cw-asset/astroport", "dep:cw2", "cw-it/astroport", "cw-it/astroport-multi-test", "cw-dex-test-helpers/astroport"] # backtraces = ["cosmwasm-std/backtraces", "osmosis-std/backtraces"] [package.metadata.docs.rs] @@ -29,7 +29,7 @@ cw20 = { workspace = true } apollo-utils = { workspace = true } # Osmosis -osmosis-std = { workspace = true, optional = true } +osmosis-std = { version = "0.19.2", optional = true } # Astroport astroport = { workspace = true, optional = true } @@ -37,10 +37,10 @@ astroport_v3 = { workspace = true, optional = true } cw2 = { workspace = true, optional = true } [dev-dependencies] -cw-it = { workspace = true, features = ["astroport", "multi-test", "astroport-multi-test"] } -test-case = "3.0.0" +cw-it = { workspace = true, features = ["multi-test"] } +test-case = { workspace = true } cw-dex-test-contract = { workspace = true } -cw-dex-test-helpers = { workspace = true, features = ["astroport"] } -proptest = "1.0.0" +cw-dex-test-helpers = { workspace = true } +proptest = { workspace = true } cw20-base = { workspace = true } cw20 = { workspace = true } diff --git a/test-contracts/osmosis-test-contract/src/contract.rs b/test-contracts/osmosis-test-contract/src/contract.rs index 4fa83aa..f04594f 100644 --- a/test-contracts/osmosis-test-contract/src/contract.rs +++ b/test-contracts/osmosis-test-contract/src/contract.rs @@ -7,18 +7,11 @@ use cosmwasm_std::{ }; use cw_dex::traits::{ForceUnlock, Pool, Stake, Unlock}; use cw_dex_osmosis::{OsmosisPool, OsmosisStaking, OsmosisSuperfluidStaking}; -// use cw2::set_contract_version; use crate::error::ContractError; use crate::state::{POOL, STAKING, SUPERFLUID}; use cw_dex_test_contract::msg::{ExecuteMsg, OsmosisTestContractInstantiateMsg, QueryMsg}; -/* -// version info for migration info -const CONTRACT_NAME: &str = "crates.io:cw-dex-test-contract"; -const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); -*/ - #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( deps: DepsMut, From 23dc0df93ea7bdbaae6ab81b3b828fc6eb5993eb Mon Sep 17 00:00:00 2001 From: Sturdy <91910406+apollo-sturdy@users.noreply.github.com> Date: Tue, 13 Feb 2024 14:39:41 +0100 Subject: [PATCH 4/4] chore: update changelog --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b383845..9ab5a7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +# [0.5.2] - 2024-02-13 + +### Changed + +- Deprecated `implemenations` module in favor of new crates `cw-dex-astroport` and `cw-dex-osmosis`. + - This is to avoid breaking changes in `cw-dex` when one of the implementations change. + # [0.5.1] - 2024-02-06 ### Changed