Skip to content

Commit

Permalink
Support osmo cosmwasm pool (#367)
Browse files Browse the repository at this point in the history
* Add CosmWasm pool support for Osmosis.

* Swapper migration.

* Update schema.

* Fix deserialization. Update test case.

* 1:1 price for CosmWasmPool (transmuter) when estimating out amount.

* Cover CosmWasm pool usage in estimate.

* Use wasm serde.

* Transmuter V3 support with scaling factors. (#368)

* Transmuter V3 support with scaling factors.

* Ignore audit for test dependency.

* Clean comment.

* Use CalcOutAmtGivenInRequest for CosmWasm pool.
  • Loading branch information
piobab authored Mar 12, 2024
1 parent 163fe7c commit 94bd222
Show file tree
Hide file tree
Showing 21 changed files with 523 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .cargo/audit.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
[advisories]
# Ignore the following advisory IDs.
# Reported vulnerabilities relate to test-tube which is only used for testing.
ignore = ["RUSTSEC-2024-0003", "RUSTSEC-2024-0006"]
ignore = ["RUSTSEC-2024-0003", "RUSTSEC-2024-0006", "RUSTSEC-2024-0019"]
4 changes: 3 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions contracts/oracle/osmosis/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub fn assert_osmosis_pool_assets(
}
Pool::StableSwap(_) => {}
Pool::ConcentratedLiquidity(_) => {}
Pool::CosmWasm(_) => {}
};

Ok(())
Expand All @@ -47,6 +48,11 @@ pub fn assert_osmosis_xyk_lp_pool(pool: &Pool) -> ContractResult<()> {
reason: format!("ConcentratedLiquidity pool not supported. Pool id {}", cl_pool.id),
});
}
Pool::CosmWasm(cw_pool) => {
return Err(ContractError::InvalidPriceSource {
reason: format!("CosmWasm pool not supported. Pool id {}", cw_pool.id),
});
}
};

Ok(())
Expand Down
5 changes: 5 additions & 0 deletions contracts/oracle/osmosis/src/price_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,11 @@ impl OsmosisPriceSourceChecked {
),
})
}
Pool::CosmWasm(pool) => {
return Err(ContractError::InvalidPrice {
reason: format!("CosmWasm pool not supported. Pool id {}", pool.id),
})
}
};

let coin0 = Pool::unwrap_coin(&pool.pool_assets[0].token)?;
Expand Down
34 changes: 32 additions & 2 deletions contracts/oracle/osmosis/tests/tests/helpers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@ use std::{marker::PhantomData, str::FromStr};
use cosmwasm_std::{
coin, from_json,
testing::{mock_env, MockApi, MockQuerier, MockStorage},
Coin, Decimal, Deps, DepsMut, OwnedDeps,
to_json_vec, Coin, Decimal, Deps, DepsMut, OwnedDeps,
};
use mars_oracle_base::ContractError;
use mars_oracle_osmosis::{contract::entry, msg::ExecuteMsg, OsmosisPriceSourceUnchecked};
use mars_osmosis::{BalancerPool, ConcentratedLiquidityPool, StableSwapPool};
use mars_testing::{mock_info, MarsMockQuerier};
use mars_types::oracle::{InstantiateMsg, QueryMsg};
use osmosis_std::types::osmosis::{gamm::v1beta1::PoolAsset, poolmanager::v1beta1::PoolResponse};
use osmosis_std::types::osmosis::{
cosmwasmpool::v1beta1::{CosmWasmPool, InstantiateMsg as CosmwasmPoolInstantiateMsg},
gamm::v1beta1::PoolAsset,
poolmanager::v1beta1::PoolResponse,
};
use pyth_sdk_cw::PriceIdentifier;

pub fn setup_test_with_pools() -> OwnedDeps<MockStorage, MockApi, MarsMockQuerier> {
Expand Down Expand Up @@ -100,6 +104,12 @@ pub fn setup_test_with_pools() -> OwnedDeps<MockStorage, MockApi, MarsMockQuerie
deps.querier
.set_query_pool_response(7777, prepare_query_cl_pool_response(7777, "ujuno", "uosmo"));

// Set CosmWasm pool
deps.querier.set_query_pool_response(
8888,
prepare_query_cosmwasm_pool_response(8888, "uausdc", "unusdc"),
);

deps
}

Expand Down Expand Up @@ -234,6 +244,26 @@ pub fn prepare_query_cl_pool_response(pool_id: u64, token0: &str, token1: &str)
}
}

pub fn prepare_query_cosmwasm_pool_response(
pool_id: u64,
token0: &str,
token1: &str,
) -> PoolResponse {
let msg = CosmwasmPoolInstantiateMsg {
pool_asset_denoms: vec![token0.to_string(), token1.to_string()],
};
let pool = CosmWasmPool {
contract_address: "osmo10c8y69yylnlwrhu32ralf08ekladhfknfqrjsy9yqc9ml8mlxpqq2sttzk"
.to_string(),
pool_id,
code_id: 148,
instantiate_msg: to_json_vec(&msg).unwrap(),
};
PoolResponse {
pool: Some(pool.to_any()),
}
}

pub fn set_pyth_price_source(deps: DepsMut, denom: &str, price_id: PriceIdentifier) {
set_price_source(
deps,
Expand Down
9 changes: 9 additions & 0 deletions contracts/oracle/osmosis/tests/tests/test_set_price_source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1038,6 +1038,15 @@ fn setting_price_source_xyk_lp() {
}
);

// attempting to use CosmWasm pool
let err = set_price_source_xyk_lp("uausdc_unusdc_lp", 8888).unwrap_err();
assert_eq!(
err,
ContractError::InvalidPriceSource {
reason: "CosmWasm pool not supported. Pool id 8888".to_string()
}
);

// properly set xyk lp price source
let res = set_price_source_xyk_lp("uosmo_umars_lp", 89).unwrap();
assert_eq!(res.messages.len(), 0);
Expand Down
3 changes: 2 additions & 1 deletion contracts/swapper/osmosis/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mars-swapper-osmosis"
version = "2.0.1"
version = "2.0.2"
authors = { workspace = true }
license = { workspace = true }
edition = { workspace = true }
Expand Down Expand Up @@ -34,3 +34,4 @@ osmosis-std = { workspace = true }
anyhow = { workspace = true }
cw-it = { workspace = true, features = ["osmosis-test-tube"] }
mars-testing = { workspace = true }
serde = { workspace = true }
2 changes: 1 addition & 1 deletion contracts/swapper/osmosis/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> ContractResult<Binary> {
pub fn migrate(deps: DepsMut, _env: Env, msg: MigrateMsg) -> Result<Response, ContractError> {
match msg {
MigrateMsg::V1_0_0ToV2_0_0 {} => migrations::v2_0_0::migrate(deps),
MigrateMsg::V2_0_0ToV2_0_1 {} => migrations::v2_0_1::migrate(deps),
MigrateMsg::V2_0_1ToV2_0_2 {} => migrations::v2_0_2::migrate(deps),
}
}
2 changes: 1 addition & 1 deletion contracts/swapper/osmosis/src/migrations/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pub mod v2_0_0;
pub mod v2_0_1;
pub mod v2_0_2;
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use mars_swapper_base::ContractError;

use crate::contract::{CONTRACT_NAME, CONTRACT_VERSION};

const FROM_VERSION: &str = "2.0.0";
const FROM_VERSION: &str = "2.0.1";

pub fn migrate(deps: DepsMut) -> Result<Response, ContractError> {
// make sure we're migrating the correct contract and from the correct version
Expand Down
36 changes: 20 additions & 16 deletions contracts/swapper/osmosis/src/route.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::fmt;

use cosmwasm_schema::cw_serde;
use cosmwasm_std::{BlockInfo, CosmosMsg, Decimal, Empty, Env, Fraction, QuerierWrapper, Uint128};
use mars_osmosis::helpers::{query_arithmetic_twap_price, query_pool, CommonPoolData};
use cosmwasm_std::{coin, BlockInfo, CosmosMsg, Decimal, Empty, Env, QuerierWrapper, Uint128};
use mars_osmosis::helpers::{query_arithmetic_twap_price, query_pool, CommonPoolData, Pool};
use mars_swapper_base::{ContractError, ContractResult, Route};
use mars_types::swapper::{EstimateExactInSwapResponse, SwapperRoute};
use osmosis_std::types::osmosis::gamm::v1beta1::MsgSwapExactAmountIn;
Expand Down Expand Up @@ -198,21 +198,25 @@ fn query_out_amount(
) -> ContractResult<Uint128> {
let start_time = block.time.seconds() - TWAP_WINDOW_SIZE_SECONDS;

let mut price = Decimal::one();
let mut denom_in = coin_in.denom.clone();
let mut coin_in = coin_in.clone();
for step in steps {
let step_price = query_arithmetic_twap_price(
querier,
step.pool_id,
&denom_in,
&step.token_out_denom,
start_time,
)?;
price = price.checked_mul(step_price)?;
denom_in = step.token_out_denom.clone();
let pool = query_pool(querier, step.pool_id)?;
let out_amount = if let Pool::CosmWasm(cw_pool) = pool {
// TWAP not supported.
// This is transmuter (https://github.com/osmosis-labs/transmuter) pool.
cw_pool.query_out_amount(querier, step.pool_id, &coin_in, &step.token_out_denom)?
} else {
let price = query_arithmetic_twap_price(
querier,
step.pool_id,
&coin_in.denom,
&step.token_out_denom,
start_time,
)?;
coin_in.amount.checked_mul_floor(price)?
};
coin_in = coin(out_amount.u128(), &step.token_out_denom);
}

let out_amount =
coin_in.amount.checked_multiply_ratio(price.numerator(), price.denominator())?;
Ok(out_amount)
Ok(coin_in.amount)
}
122 changes: 120 additions & 2 deletions contracts/swapper/osmosis/tests/tests/test_estimate.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
use cosmwasm_std::{coin, Uint128};
use std::{marker::PhantomData, str::FromStr};

use cosmwasm_std::{
coin, from_json,
testing::{mock_env, MockApi, MockQuerier, MockStorage},
to_json_vec, Decimal, OwnedDeps, Uint128,
};
use cw_it::osmosis_test_tube::{Gamm, Module, OsmosisTestApp, RunnerResult, Wasm};
use mars_osmosis::ConcentratedLiquidityPool;
use mars_swapper_osmosis::{
config::OsmosisConfig,
contract::{instantiate, query},
route::{OsmosisRoute, SwapAmountInRoute},
};
use mars_testing::{mock_info, MarsMockQuerier};
use mars_types::swapper::{
EstimateExactInSwapResponse, ExecuteMsg, OsmoRoute, OsmoSwap, QueryMsg, SwapperRoute,
EstimateExactInSwapResponse, ExecuteMsg, InstantiateMsg, OsmoRoute, OsmoSwap, QueryMsg,
SwapperRoute,
};
use osmosis_std::types::osmosis::{
cosmwasmpool::v1beta1::{CosmWasmPool, InstantiateMsg as CosmwasmPoolInstantiateMsg},
poolmanager::v1beta1::PoolResponse,
twap::v1beta1::ArithmeticTwapToNowResponse,
};

use super::helpers::{
Expand Down Expand Up @@ -293,3 +308,106 @@ fn estimate_swap_multi_step() {
.unwrap();
assert_eq!(res.amount, expected_output);
}

#[test]
fn estimate_swap_multi_step_with_cosmwasm_pool() {
let mut deps = OwnedDeps::<_, _, _> {
storage: MockStorage::default(),
api: MockApi::default(),
querier: MarsMockQuerier::new(MockQuerier::new(&[])),
custom_query_type: PhantomData,
};

// instantiate the swapper contract
instantiate(
deps.as_mut(),
mock_env(),
mock_info("owner"),
InstantiateMsg {
owner: "owner".to_string(),
},
)
.unwrap();

let atom = "uatom".to_string();
let noble_usdc = "unusdc".to_string();
let axl_usdc = "uausdc".to_string();

// prepare ConcentratedLiquidity pool
let cl_pool_id = 1251;
let pool = ConcentratedLiquidityPool {
address: "osmo126pr9qp44aft4juw7x4ev4s2qdtnwe38jzwunec9pxt5cpzaaphqyagqpu".to_string(),
incentives_address: "osmo1h2mhtj3wmsdt3uacev9pgpg38hkcxhsmyyn9ums0ya6eddrsafjsxs9j03"
.to_string(),
spread_rewards_address: "osmo16j5sssw32xuk8a0kjj8n54g25ye6kr339nz5axf8lzyeajk0k22stsm36c"
.to_string(),
id: cl_pool_id,
current_tick_liquidity: "3820025893854099618.699762490947860933".to_string(),
token0: atom.clone(),
token1: noble_usdc.clone(),
current_sqrt_price: "656651.537483144215151633465586753226461989".to_string(),
current_tick: 102311912,
tick_spacing: 100,
exponent_at_price_one: -6,
spread_factor: "0.002000000000000000".to_string(),
last_liquidity_update: None,
};
let cl_pool = PoolResponse {
pool: Some(pool.to_any()),
};

// prepare CosmWasm (transmuter) pool
let cw_pool_id = 1212;
let msg = CosmwasmPoolInstantiateMsg {
pool_asset_denoms: vec![axl_usdc.clone(), noble_usdc.clone()],
};
let pool = CosmWasmPool {
contract_address: "osmo10c8y69yylnlwrhu32ralf08ekladhfknfqrjsy9yqc9ml8mlxpqq2sttzk"
.to_string(),
pool_id: cw_pool_id,
code_id: 148,
instantiate_msg: to_json_vec(&msg).unwrap(),
};
let cw_pool = PoolResponse {
pool: Some(pool.to_any()),
};

deps.querier.set_query_pool_response(cl_pool_id, cl_pool);
deps.querier.set_query_pool_response(cw_pool_id, cw_pool);

// set arithmetic twap price for the ConcentratedLiquidity pool
deps.querier.set_arithmetic_twap_price(
cl_pool_id,
&atom,
&noble_usdc,
ArithmeticTwapToNowResponse {
arithmetic_twap: Decimal::from_str("11.5").unwrap().to_string(),
},
);

// check the estimate swap output
let res = query(
deps.as_ref(),
mock_env(),
QueryMsg::EstimateExactInSwap {
coin_in: coin(1250, &atom),
denom_out: axl_usdc.clone(),
route: Some(SwapperRoute::Osmo(OsmoRoute {
swaps: vec![
OsmoSwap {
pool_id: cl_pool_id,
to: noble_usdc,
},
OsmoSwap {
pool_id: cw_pool_id,
to: axl_usdc,
},
],
})),
},
)
.unwrap();
let res: EstimateExactInSwapResponse = from_json(res).unwrap();
// 1250 * 11.5 * 1 = 14375
assert_eq!(res.amount, Uint128::from(14375u128));
}
Loading

0 comments on commit 94bd222

Please sign in to comment.