Skip to content

Commit

Permalink
fix(PCL): backporting bugfix from 66d00d7
Browse files Browse the repository at this point in the history
  • Loading branch information
epanchee committed Sep 27, 2023
1 parent 706caab commit e81b62b
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

31 changes: 19 additions & 12 deletions contracts/pair_concentrated/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use astroport::asset::{
use astroport::common::{claim_ownership, drop_ownership_proposal, propose_new_owner};
use astroport::cosmwasm_ext::{AbsDiff, DecimalToInteger, IntegerToDecimal};
use astroport::factory::PairType;
use astroport::pair::{Cw20HookMsg, ExecuteMsg, InstantiateMsg};
use astroport::pair::{Cw20HookMsg, ExecuteMsg, InstantiateMsg, MIN_TRADE_SIZE};
use astroport::pair_concentrated::{
ConcentratedPoolParams, ConcentratedPoolUpdateParams, MigrateMsg, UpdatePoolParams,
};
Expand Down Expand Up @@ -527,8 +527,8 @@ pub fn provide_liquidity(

let mut slippage = Decimal256::zero();

// if assets_diff[1] is zero then deposits are balanced thus no need to update price and check slippage
if !assets_diff[1].is_zero() {
// If deposit doesn't diverge too much from the balanced share, we don't update the price
if assets_diff[0] >= MIN_TRADE_SIZE && assets_diff[1] >= MIN_TRADE_SIZE {
slippage = assert_slippage_tolerance(
&deposits,
share,
Expand Down Expand Up @@ -776,13 +776,19 @@ fn swap(
let total_share = query_supply(&deps.querier, &config.pair_info.liquidity_token)?
.to_decimal256(LP_TOKEN_PRECISION)?;

let last_price = swap_result.calc_last_prices(offer_asset_dec.amount, offer_ind);

// update_price() works only with internal representation
xs[1] *= config.pool_state.price_state.price_scale;
config
.pool_state
.update_price(&config.pool_params, &env, total_share, &xs, last_price)?;
// Skip very small trade sizes which could significantly mess up the price due to rounding errors,
// especially if token precisions are 18.
if (swap_result.dy + swap_result.maker_fee) >= MIN_TRADE_SIZE
&& offer_asset_dec.amount >= MIN_TRADE_SIZE
{
let last_price = swap_result.calc_last_prices(offer_asset_dec.amount, offer_ind);

// update_price() works only with internal representation
xs[1] *= config.pool_state.price_state.price_scale;
config
.pool_state
.update_price(&config.pool_params, &env, total_share, &xs, last_price)?;
}

let receiver = to.unwrap_or_else(|| sender.clone());

Expand All @@ -792,10 +798,11 @@ fn swap(
}
.into_msg(&receiver)?];

// Send the maker fee
let mut maker_fee = Uint128::zero();
if let Some(fee_address) = fee_info.fee_address {
if !swap_result.maker_fee.is_zero() {
maker_fee = swap_result.maker_fee.to_uint(ask_asset_prec)?;
maker_fee = swap_result.maker_fee.to_uint(ask_asset_prec)?;
if !maker_fee.is_zero() {
let fee = Asset {
info: pools[ask_ind].info.clone(),
amount: maker_fee,
Expand Down
24 changes: 20 additions & 4 deletions contracts/pair_concentrated/tests/helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use anyhow::Result as AnyResult;
use astroport::asset::{native_asset_info, token_asset_info, Asset, AssetInfo, PairInfo};
use astroport::factory::{PairConfig, PairType};
use astroport::pair::{
ConfigResponse, CumulativePricesResponse, Cw20HookMsg, ExecuteMsg, ReverseSimulationResponse,
SimulationResponse,
ConfigResponse, CumulativePricesResponse, Cw20HookMsg, ExecuteMsg, PoolResponse,
ReverseSimulationResponse, SimulationResponse,
};
use astroport::pair_concentrated::{
ConcentratedPoolParams, ConcentratedPoolUpdateParams, QueryMsg,
Expand Down Expand Up @@ -304,6 +304,16 @@ impl Helper {
sender: &Addr,
offer_asset: &Asset,
max_spread: Option<Decimal>,
) -> AnyResult<AppResponse> {
self.swap_full_params(sender, offer_asset, max_spread, None)
}

pub fn swap_full_params(
&mut self,
sender: &Addr,
offer_asset: &Asset,
max_spread: Option<Decimal>,
belief_price: Option<Decimal>,
) -> AnyResult<AppResponse> {
match &offer_asset.info {
AssetInfo::Token { contract_addr } => {
Expand All @@ -312,7 +322,7 @@ impl Helper {
amount: offer_asset.amount,
msg: to_binary(&Cw20HookMsg::Swap {
ask_asset_info: None,
belief_price: None,
belief_price,
max_spread,
to: None,
})
Expand All @@ -333,7 +343,7 @@ impl Helper {
let msg = ExecuteMsg::Swap {
offer_asset: offer_asset.clone(),
ask_asset_info: None,
belief_price: None,
belief_price,
max_spread,
to: None,
};
Expand Down Expand Up @@ -455,6 +465,12 @@ impl Helper {
from_slice(&binary)
}

pub fn query_pool(&self) -> StdResult<PoolResponse> {
self.app
.wrap()
.query_wasm_smart(&self.pair_addr, &QueryMsg::Pool {})
}

pub fn query_lp_price(&self) -> StdResult<Decimal256> {
self.app
.wrap()
Expand Down
181 changes: 177 additions & 4 deletions contracts/pair_concentrated/tests/pair_concentrated_integration.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use cosmwasm_std::{Addr, Decimal, Uint128};

use cosmwasm_std::{Addr, Decimal, Decimal256, Uint128};
use cw_multi_test::{next_block, Executor};
use itertools::Itertools;
use itertools::{max, Itertools};

use astroport::asset::{
native_asset_info, Asset, AssetInfo, AssetInfoExt, MINIMUM_LIQUIDITY_AMOUNT,
};

use astroport::cosmwasm_ext::IntegerToDecimal;
use astroport::pair::{ExecuteMsg, PoolResponse};
use astroport::pair_concentrated::{
ConcentratedPoolParams, ConcentratedPoolUpdateParams, PromoteParams, QueryMsg, UpdatePoolParams,
Expand Down Expand Up @@ -1401,3 +1400,177 @@ fn provide_withdraw_slippage() {
.provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.5)))
.unwrap();
}

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

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

let params = ConcentratedPoolParams {
price_scale: f64_to_dec(4.360000915600192),
amp: f64_to_dec(10f64),
gamma: f64_to_dec(0.000145),
mid_fee: f64_to_dec(0.0026),
out_fee: f64_to_dec(0.0045),
fee_gamma: f64_to_dec(0.00023),
repeg_profit_threshold: f64_to_dec(0.000002),
min_price_scale_delta: f64_to_dec(0.000146),
ma_half_time: 600,
track_asset_balances: None,
};

let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap();

// Fully balanced but small provide
let assets = vec![
helper.assets[&test_coins[0]].with_balance(8_000000u128),
helper.assets[&test_coins[1]].with_balance(1_834862u128),
];
helper.provide_liquidity(&owner, &assets).unwrap();

// Trying to mess the last price with lowest possible swap
for _ in 0..1000 {
helper.app.next_block(30);
let offer_asset = helper.assets[&test_coins[1]].with_balance(1u8);
helper
.swap_full_params(&owner, &offer_asset, None, Some(Decimal::MAX))
.unwrap();
}

// Check that after price scale adjustments (even they are small) internal value is still nearly balanced
let config = helper.query_config().unwrap();
let pool = helper
.query_pool()
.unwrap()
.assets
.into_iter()
.map(|asset| asset.amount.to_decimal256(6u8).unwrap())
.collect_vec();

let ixs = [pool[0], pool[1] * config.pool_state.price_state.price_scale];
let relative_diff = ixs[0].abs_diff(ixs[1]) / max(&ixs).unwrap();

assert!(
relative_diff < Decimal256::percent(3),
"Internal PCL value is off. Relative_diff: {}",
relative_diff
);

// Trying to mess the last price with lowest possible provide
for _ in 0..1000 {
helper.app.next_block(30);
let assets = vec![helper.assets[&test_coins[1]].with_balance(1u8)];
helper
.provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.5)))
.unwrap();
}

// Check that after price scale adjustments (even they are small) internal value is still nearly balanced
let config = helper.query_config().unwrap();
let pool = helper
.query_pool()
.unwrap()
.assets
.into_iter()
.map(|asset| asset.amount.to_decimal256(6u8).unwrap())
.collect_vec();

let ixs = [pool[0], pool[1] * config.pool_state.price_state.price_scale];
let relative_diff = ixs[0].abs_diff(ixs[1]) / max(&ixs).unwrap();

assert!(
relative_diff < Decimal256::percent(3),
"Internal PCL value is off. Relative_diff: {}",
relative_diff
);
}

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

let test_coins = vec![
TestCoin::cw20precise("ETH", 18),
TestCoin::cw20precise("USD", 18),
];

let params = ConcentratedPoolParams {
price_scale: f64_to_dec(4.360000915600192),
amp: f64_to_dec(10f64),
gamma: f64_to_dec(0.000145),
mid_fee: f64_to_dec(0.0026),
out_fee: f64_to_dec(0.0045),
fee_gamma: f64_to_dec(0.00023),
repeg_profit_threshold: f64_to_dec(0.000002),
min_price_scale_delta: f64_to_dec(0.000146),
ma_half_time: 600,
track_asset_balances: None,
};

let mut helper = Helper::new(&owner, test_coins.clone(), params).unwrap();

// Fully balanced but small provide
let assets = vec![
helper.assets[&test_coins[0]].with_balance(8e18 as u128),
helper.assets[&test_coins[1]].with_balance(1_834862000000000000u128),
];
helper.provide_liquidity(&owner, &assets).unwrap();

// Trying to mess the last price with lowest possible swap
for _ in 0..1000 {
helper.app.next_block(30);
let offer_asset = helper.assets[&test_coins[1]].with_balance(1u8);
helper
.swap_full_params(&owner, &offer_asset, None, Some(Decimal::MAX))
.unwrap();
}

// Check that after price scale adjustments (even they are small) internal value is still nearly balanced
let config = helper.query_config().unwrap();
let pool = helper
.query_pool()
.unwrap()
.assets
.into_iter()
.map(|asset| asset.amount.to_decimal256(6u8).unwrap())
.collect_vec();

let ixs = [pool[0], pool[1] * config.pool_state.price_state.price_scale];
let relative_diff = ixs[0].abs_diff(ixs[1]) / max(&ixs).unwrap();

assert!(
relative_diff < Decimal256::percent(3),
"Internal PCL value is off. Relative_diff: {}",
relative_diff
);

// Trying to mess the last price with lowest possible provide
for _ in 0..1000 {
helper.app.next_block(30);
// 0.000001 USD. minimum provide is limited to LP token precision which is 6 decimals.
let assets = vec![helper.assets[&test_coins[1]].with_balance(1000000000000u128)];
helper
.provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.5)))
.unwrap();
}

// Check that after price scale adjustments (even they are small) internal value is still nearly balanced
let config = helper.query_config().unwrap();
let pool = helper
.query_pool()
.unwrap()
.assets
.into_iter()
.map(|asset| asset.amount.to_decimal256(6u8).unwrap())
.collect_vec();

let ixs = [pool[0], pool[1] * config.pool_state.price_state.price_scale];
let relative_diff = ixs[0].abs_diff(ixs[1]) / max(&ixs).unwrap();

assert!(
relative_diff < Decimal256::percent(3),
"Internal PCL value is off. Relative_diff: {}",
relative_diff
);
}
2 changes: 1 addition & 1 deletion packages/astroport/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "astroport"
version = "2.8.5"
version = "2.8.6"
authors = ["Astroport"]
edition = "2021"
description = "Common Astroport types, queriers and other utils"
Expand Down
11 changes: 8 additions & 3 deletions packages/astroport/src/pair.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use cosmwasm_schema::{cw_serde, QueryResponses};
use cosmwasm_std::{
from_slice, Addr, Binary, Decimal, Decimal256, QuerierWrapper, StdResult, Uint128, Uint64,
};
use cw20::Cw20ReceiveMsg;

use crate::asset::{Asset, AssetInfo, PairInfo};

use cosmwasm_std::{from_slice, Addr, Binary, Decimal, QuerierWrapper, StdResult, Uint128, Uint64};
use cw20::Cw20ReceiveMsg;

/// The default swap slippage
pub const DEFAULT_SLIPPAGE: &str = "0.005";
/// The maximum allowed swap slippage
Expand All @@ -13,6 +14,10 @@ pub const MAX_ALLOWED_SLIPPAGE: &str = "0.5";
/// Decimal precision for TWAP results
pub const TWAP_PRECISION: u8 = 6;

/// Min safe trading size (0.00001) to calculate a price. This value considers
/// amount in decimal form with respective token precision.
pub const MIN_TRADE_SIZE: Decimal256 = Decimal256::raw(10000000000000);

/// This structure describes the parameters used for creating a contract.
#[cw_serde]
pub struct InstantiateMsg {
Expand Down

0 comments on commit e81b62b

Please sign in to comment.