Skip to content

Commit

Permalink
fix(pcl): denormalize spot price (#17)
Browse files Browse the repository at this point in the history
* fix(pcl): denormalize spot price

* fix test

* handle case when denormilized price becomes zero

* apply clippy suggestions
  • Loading branch information
epanchee authored Jun 13, 2024
1 parent b10ab43 commit d4f2f11
Show file tree
Hide file tree
Showing 11 changed files with 140 additions and 21 deletions.
16 changes: 8 additions & 8 deletions 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/factory/tests/querier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ pub struct MockedStargateQuerier<'a> {
pair_infos: HashMap<&'a str, PairInfo>,
}

impl<'a> Default for MockedStargateQuerier<'a> {
fn default() -> Self {
Self::new()
}
}

impl<'a> MockedStargateQuerier<'a> {
pub fn new() -> Self {
Self {
Expand Down
2 changes: 1 addition & 1 deletion contracts/maker/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ pub fn query_out_amount(
for step in steps {
let step_price = query_spot_price(querier, step.pool_id, &denom_in, &step.token_out_denom)?;
price = price.checked_mul(step_price)?;
denom_in = step.token_out_denom.clone();
denom_in.clone_from(&step.token_out_denom);
}

let out_amount = coin_in
Expand Down
2 changes: 1 addition & 1 deletion contracts/maker/tests/common/osmosis_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ impl Stargate for OsmosisStargate {
let contract_address = self.cw_pools.borrow()[&inner.pool_id].clone();
let querier = QuerierWrapper::<Empty>::new(querier);
let spot_price: SpotPriceResponse = querier.query_wasm_smart(
&contract_address,
contract_address,
&QueryMsg::SpotPrice {
quote_asset_denom: inner.quote_asset_denom.to_string(),
base_asset_denom: inner.quote_asset_denom.to_string(),
Expand Down
2 changes: 1 addition & 1 deletion contracts/pair_concentrated/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "astroport-pcl-osmo"
version = "1.0.2"
version = "1.0.3"
authors = ["Astroport"]
edition = "2021"
description = "Astroport passive concentrated pair contract for Osmosis"
Expand Down
2 changes: 1 addition & 1 deletion contracts/pair_concentrated/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -881,7 +881,7 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: Empty) -> Result<Response, Contra

match contract_version.contract.as_ref() {
"astroport-pcl-osmo" => match contract_version.version.as_ref() {
"1.0.0" | "1.0.1" => {}
"1.0.0" | "1.0.1" | "1.0.2" => {}
_ => return Err(ContractError::MigrationError {}),
},
_ => return Err(ContractError::MigrationError {}),
Expand Down
30 changes: 26 additions & 4 deletions contracts/pair_concentrated/src/queries.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use astroport::asset::{native_asset_info, Asset, AssetInfo, AssetInfoExt, Decimal256Ext};
use astroport::cosmwasm_ext::{DecimalToInteger, IntegerToDecimal};
use astroport::observation::{query_observation, try_dec256_into_dec};
use astroport::observation::query_observation;
use astroport::pair::{
ConfigResponse, PoolResponse, ReverseSimulationResponse, SimulationResponse,
};
Expand All @@ -15,8 +15,8 @@ use astroport_pcl_common::{calc_d, get_xcp};
#[cfg(not(feature = "library"))]
use cosmwasm_std::entry_point;
use cosmwasm_std::{
ensure, to_json_binary, Binary, Decimal, Decimal256, Deps, Env, StdError, StdResult, Uint128,
Uint64,
ensure, to_json_binary, Binary, Decimal, Decimal256, DecimalRangeExceeded, Deps, Env, StdError,
StdResult, Uint128, Uint64,
};
use itertools::Itertools;

Expand Down Expand Up @@ -194,8 +194,30 @@ pub fn query(deps: Deps, env: Env, msg: QueryMsg) -> StdResult<Binary> {
// Calculate average from buy and sell prices
let spot_price = (get_spot_price(0)? + get_spot_price(1)?) / TWO;

// Denormalize the price
let quote_precision = precisions
.get_precision(&AssetInfo::native(&quote_asset_denom))
.map_err(|err| StdError::generic_err(err.to_string()))?;
let base_precision = precisions
.get_precision(&AssetInfo::native(&base_asset_denom))
.map_err(|err| StdError::generic_err(err.to_string()))?;
let denorm_price = spot_price
* Decimal256::from_ratio(
10u128.pow(quote_precision.into()),
10u128.pow(base_precision.into()),
);

ensure!(
spot_price.is_zero() || !denorm_price.is_zero(),
StdError::generic_err(format!(
"Normalized price {spot_price} became zero after denormalization"
))
);

to_json_binary(&SpotPriceResponse {
spot_price: try_dec256_into_dec(spot_price)?,
spot_price: denorm_price
.try_into()
.map_err(|err: DecimalRangeExceeded| StdError::generic_err(err.to_string()))?,
})
}
// Osmosis confirmed we can safely set 0% here.
Expand Down
3 changes: 2 additions & 1 deletion contracts/pair_concentrated/tests/common/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ pub fn init_native_coins(test_coins: &[TestCoin]) -> Vec<Coin> {
.iter()
.filter_map(|test_coin| match test_coin {
TestCoin::Native(name) => {
let init_balance = INIT_BALANCE * 10u128.pow(NATIVE_TOKEN_PRECISION as u32);
let init_balance = u128::MAX / 2;
Some(coin(init_balance, name))
}
_ => None,
Expand Down Expand Up @@ -271,6 +271,7 @@ impl Helper {
("uusd".to_owned(), 6),
("rc".to_owned(), 6),
("foo".to_owned(), 5),
("eth".to_owned(), 18),
],
},
&[],
Expand Down
94 changes: 92 additions & 2 deletions contracts/pair_concentrated/tests/pair_concentrated_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -861,8 +861,7 @@ fn check_prices() {

let helper = Helper::new(&owner, test_coins, common_pcl_params()).unwrap();
let err = helper.query_prices().unwrap_err();
assert_eq!(StdError::generic_err("Querier contract error: Generic error: Not implemented.Use { \"observe\" : { \"seconds_ago\" : ... } } instead.")
, err);
assert!(err.to_string().contains("Not implemented.Use"));
}

#[test]
Expand Down Expand Up @@ -1554,6 +1553,97 @@ fn test_osmosis_specific_queries() {
);
}

#[test]
fn test_spot_price_diff_decimals() {
let owner = Addr::unchecked("owner");

let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("eth")];

let mut helper = Helper::new(
&owner,
test_coins.clone(),
ConcentratedPoolParams {
price_scale: Decimal::from_ratio(3000u16, 1u8),
..common_pcl_params()
},
)
.unwrap();

let provide_assets = [
helper.assets[&test_coins[0]].with_balance(3_000_000 * 1e6 as u128),
helper.assets[&test_coins[1]].with_balance(1_000 * 1e18 as u128),
];
helper.give_me_money(&provide_assets, &owner);
helper.provide_liquidity(&owner, &provide_assets).unwrap();

let resp = helper
.app
.wrap()
.query_wasm_smart::<SpotPriceResponse>(
&helper.pair_addr,
&QueryMsg::SpotPrice {
quote_asset_denom: helper.assets[&test_coins[0]].to_string(),
base_asset_denom: helper.assets[&test_coins[1]].to_string(),
},
)
.unwrap();
assert_eq!(resp.spot_price.to_string(), "0.000000003000010176");

// query inverted price
let price = helper
.app
.wrap()
.query_wasm_smart::<SpotPriceResponse>(
&helper.pair_addr,
&QueryMsg::SpotPrice {
quote_asset_denom: helper.assets[&test_coins[1]].to_string(),
base_asset_denom: helper.assets[&test_coins[0]].to_string(),
},
)
.unwrap();
assert_eq!(price.spot_price.to_string(), "333334464.080626");
}

#[test]
fn test_truncated_spot_price() {
let owner = Addr::unchecked("owner");

let test_coins = vec![TestCoin::native("uusd"), TestCoin::native("eth")];

let mut helper = Helper::new(
&owner,
test_coins.clone(),
ConcentratedPoolParams {
price_scale: Decimal::from_ratio(1u128, 10000u128),
..common_pcl_params()
},
)
.unwrap();

let provide_assets = [
helper.assets[&test_coins[0]].with_balance(1e2 as u128),
helper.assets[&test_coins[1]].with_balance(1_000 * 1e18 as u128),
];
helper.give_me_money(&provide_assets, &owner);
helper.provide_liquidity(&owner, &provide_assets).unwrap();

let err = helper
.app
.wrap()
.query_wasm_smart::<SpotPriceResponse>(
&helper.pair_addr,
&QueryMsg::SpotPrice {
quote_asset_denom: helper.assets[&test_coins[0]].to_string(),
base_asset_denom: helper.assets[&test_coins[1]].to_string(),
},
)
.unwrap_err();
assert_eq!(
err.to_string(),
"Generic error: Querier contract error: Generic error: Normalized price 0.000000100003868184 became zero after denormalization"
);
}

#[test]
fn check_reverse_swaps() {
let owner = Addr::unchecked("owner");
Expand Down
2 changes: 1 addition & 1 deletion e2e_tests/src/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ impl<'a> TestAppWrapper<'a> {
&[],
)
.unwrap();
helper.coin_registry = coin_registry_address.clone();
helper.coin_registry.clone_from(&coin_registry_address);

helper.astro_denom = helper.register_and_mint("astro", 1_000_000_000_000, 6, None);

Expand Down
2 changes: 1 addition & 1 deletion packages/astroport_on_osmosis/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "astroport-on-osmosis"
version = "1.1.1"
version = "1.1.2"
authors = ["Astroport"]
edition = "2021"
description = "External API of Astroport contracts on Osmosis"
Expand Down

0 comments on commit d4f2f11

Please sign in to comment.