Skip to content

Commit

Permalink
Merge pull request #372 from astroport-fi/merge/fixes_080823
Browse files Browse the repository at this point in the history
Release v3.3.1
  • Loading branch information
epanchee authored Aug 8, 2023
2 parents e53c4d1 + 0506a5a commit b14d7b9
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 58 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

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

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-pair-concentrated"
version = "2.0.4"
version = "2.0.5"
authors = ["Astroport"]
edition = "2021"
description = "The Astroport concentrated liquidity pair"
Expand Down
30 changes: 14 additions & 16 deletions contracts/pair_concentrated/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ use crate::state::{
};
use crate::utils::{
accumulate_swap_sizes, assert_max_spread, assert_slippage_tolerance, before_swap_check,
calc_last_prices, calc_provide_fee, check_asset_infos, check_assets, check_cw20_in_pool,
check_pair_registered, compute_swap, get_share_in_assets, mint_liquidity_token_message,
query_pools,
calc_provide_fee, check_asset_infos, check_assets, check_cw20_in_pool, check_pair_registered,
compute_swap, get_share_in_assets, mint_liquidity_token_message, query_pools,
};

/// Contract name that is used for migration.
Expand Down Expand Up @@ -398,7 +397,7 @@ pub fn provide_liquidity(
.find_position(|pool| pool.equal(&assets[0].info))
.ok_or_else(|| ContractError::InvalidAsset(assets[0].info.to_string()))?;
assets.push(Asset {
info: config.pair_info.asset_infos[1 - given_ind].clone(),
info: config.pair_info.asset_infos[1 ^ given_ind].clone(),
amount: Uint128::zero(),
});
}
Expand Down Expand Up @@ -471,7 +470,6 @@ pub fn provide_liquidity(

let amp_gamma = config.pool_state.get_amp_gamma(&env);
let new_d = calc_d(&new_xp, &amp_gamma)?;
let mut old_price = config.pool_state.price_state.last_price;

let share = if total_share.is_zero() {
let xcp = get_xcp(new_d, config.pool_state.price_state.price_scale);
Expand Down Expand Up @@ -499,7 +497,6 @@ pub fn provide_liquidity(
mint_amount
} else {
let mut old_xp = pools.iter().map(|a| a.amount).collect_vec();
old_price = calc_last_prices(&old_xp, &config, &env)?;
old_xp[1] *= config.pool_state.price_state.price_scale;
let old_d = calc_d(&old_xp, &amp_gamma)?;
let share = (total_share * new_d / old_d).saturating_sub(total_share);
Expand All @@ -521,18 +518,18 @@ pub fn provide_liquidity(
deposits[1].diff(balanced_share[1]),
];

let tmp_xp = vec![
new_xp[0],
new_xp[1] / config.pool_state.price_state.price_scale,
];
let new_price = calc_last_prices(&tmp_xp, &config, &env)?;
let mut slippage = Decimal256::zero();

// if assets_diff[1] is zero then deposits are balanced thus no need to update price
// 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() {
let last_price = assets_diff[0] / assets_diff[1];

assert_slippage_tolerance(old_price, new_price, slippage_tolerance)?;
slippage = assert_slippage_tolerance(
&deposits,
share,
&config.pool_state.price_state,
slippage_tolerance,
)?;

let last_price = assets_diff[0] / assets_diff[1];
config.pool_state.update_price(
&config.pool_params,
&env,
Expand Down Expand Up @@ -578,6 +575,7 @@ pub fn provide_liquidity(
attr("receiver", receiver),
attr("assets", format!("{}, {}", &assets[0], &assets[1])),
attr("share", share_uint128),
attr("slippage", slippage.to_string()),
];

Ok(Response::new().add_messages(messages).add_attributes(attrs))
Expand Down Expand Up @@ -894,7 +892,7 @@ pub fn migrate(deps: DepsMut, _env: Env, _msg: MigrateMsg) -> Result<Response, C
"1.2.4" => {
BufferManager::init(deps.storage, OBSERVATIONS, OBSERVATIONS_SIZE)?;
}
"2.0.3" => {}
"2.0.3" | "2.0.4" => {}
_ => return Err(ContractError::MigrationError {}),
},
_ => return Err(ContractError::MigrationError {}),
Expand Down
30 changes: 19 additions & 11 deletions contracts/pair_concentrated/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ use astroport_circular_buffer::error::BufferResult;
use astroport_circular_buffer::BufferManager;
use astroport_factory::state::pair_key;

use crate::consts::{DEFAULT_SLIPPAGE, MAX_ALLOWED_SLIPPAGE, N, OFFER_PERCENT};
use crate::consts::{DEFAULT_SLIPPAGE, MAX_ALLOWED_SLIPPAGE, N, OFFER_PERCENT, TWO};
use crate::error::ContractError;
use crate::math::{calc_d, calc_y};
use crate::state::{Config, PoolParams, Precisions, OBSERVATIONS};
use crate::state::{Config, PoolParams, Precisions, PriceState, OBSERVATIONS};

/// Helper function to check the given asset infos are valid.
pub(crate) fn check_asset_infos(
Expand Down Expand Up @@ -373,25 +373,31 @@ pub fn calc_provide_fee(
deposits[0].diff(avg) * params.fee(xp) / sum
}

/// This is an internal function that enforces slippage tolerance for swaps.
/// This is an internal function that enforces slippage tolerance for provides. Returns actual slippage.
pub fn assert_slippage_tolerance(
old_price: Decimal256,
new_price: Decimal256,
deposits: &[Decimal256],
actual_share: Decimal256,
price_state: &PriceState,
slippage_tolerance: Option<Decimal>,
) -> Result<(), ContractError> {
) -> Result<Decimal256, ContractError> {
let slippage_tolerance = slippage_tolerance
.map(Into::into)
.unwrap_or(DEFAULT_SLIPPAGE);
if slippage_tolerance > MAX_ALLOWED_SLIPPAGE {
return Err(ContractError::AllowedSpreadAssertion {});
}

// Ensure price was not changed more than the slippage tolerance allows
if Decimal256::one().diff(new_price / old_price) > slippage_tolerance {
let deposit_value = deposits[0] + deposits[1] * price_state.price_scale;
let lp_expected = (deposit_value / TWO * deposit_value / (TWO * price_state.price_scale))
.sqrt()
/ price_state.xcp_profit_real;
let slippage = lp_expected.saturating_sub(actual_share) / lp_expected;

if slippage > slippage_tolerance {
return Err(ContractError::MaxSpreadAssertion {});
}

Ok(())
Ok(slippage)
}

// Checks whether the pair is registered in the factory or not.
Expand Down Expand Up @@ -481,12 +487,14 @@ pub fn accumulate_swap_sizes(

#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{mock_env, MockStorage};
use std::error::Error;
use std::fmt::Display;
use std::str::FromStr;

use cosmwasm_std::testing::{mock_env, MockStorage};

use super::*;

pub fn f64_to_dec<T>(val: f64) -> T
where
T: FromStr,
Expand Down
64 changes: 64 additions & 0 deletions contracts/pair_concentrated/tests/pair_concentrated_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1475,3 +1475,67 @@ fn provide_withdraw_provide() {
.provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.5)))
.unwrap();
}

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

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

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

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

// Fully balanced provide
let assets = vec![
helper.assets[&test_coins[0]].with_balance(10_000000u128),
helper.assets[&test_coins[1]].with_balance(1_000000u128),
];
helper
.provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.02)))
.unwrap();

// Imbalanced provide. Slippage is more than 2% while we enforce 2% max slippage
let assets = vec![
helper.assets[&test_coins[0]].with_balance(5_000000u128),
helper.assets[&test_coins[1]].with_balance(1_000000u128),
];
let err = helper
.provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.02)))
.unwrap_err();
assert_eq!(
ContractError::MaxSpreadAssertion {},
err.downcast().unwrap(),
);
// With 3% slippage it should work
helper
.provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.03)))
.unwrap();

// Provide with a huge imbalance. Slippage is ~42.2%
let assets = vec![
helper.assets[&test_coins[0]].with_balance(1000_000000u128),
helper.assets[&test_coins[1]].with_balance(1000_000000u128),
];
let err = helper
.provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.02)))
.unwrap_err();
assert_eq!(
ContractError::MaxSpreadAssertion {},
err.downcast().unwrap(),
);
helper
.provide_liquidity_with_slip_tolerance(&owner, &assets, Some(f64_to_dec(0.5)))
.unwrap();
}
2 changes: 1 addition & 1 deletion contracts/pair_concentrated_inj/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "astroport-pair-concentrated-injective"
version = "2.0.4"
version = "2.0.5"
authors = ["Astroport"]
edition = "2021"
description = "The Astroport concentrated liquidity pair which supports Injective orderbook integration"
Expand Down
26 changes: 12 additions & 14 deletions contracts/pair_concentrated_inj/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,8 @@ use crate::state::{
};
use crate::utils::{
accumulate_swap_sizes, assert_max_spread, assert_slippage_tolerance, before_swap_check,
calc_last_prices, calc_provide_fee, check_asset_infos, check_assets, check_pair_registered,
compute_swap, get_share_in_assets, mint_liquidity_token_message, query_contract_balances,
query_pools,
calc_provide_fee, check_asset_infos, check_assets, check_pair_registered, compute_swap,
get_share_in_assets, mint_liquidity_token_message, query_contract_balances, query_pools,
};

/// Contract name that is used for migration.
Expand Down Expand Up @@ -479,7 +478,6 @@ where

let amp_gamma = config.pool_state.get_amp_gamma(&env);
let new_d = calc_d(&new_xp, &amp_gamma)?;
let mut old_price = config.pool_state.price_state.last_price;

let share = if total_share.is_zero() {
let xcp = get_xcp(new_d, config.pool_state.price_state.price_scale);
Expand Down Expand Up @@ -507,7 +505,6 @@ where
mint_amount
} else {
let mut old_xp = xs.clone();
old_price = calc_last_prices(&old_xp, &config, &env)?;
old_xp[1] *= config.pool_state.price_state.price_scale;
let old_d = calc_d(&old_xp, &amp_gamma)?;
let share = (total_share * new_d / old_d).saturating_sub(total_share);
Expand All @@ -529,18 +526,18 @@ where
deposits[1].diff(balanced_share[1]),
];

let mut slippage = Decimal256::zero();

// if assets_diff[1] is zero then deposits are balanced thus no need to update price
if !assets_diff[1].is_zero() {
let last_price = assets_diff[0] / assets_diff[1];

let tmp_xp = vec![
new_xp[0],
new_xp[1] / config.pool_state.price_state.price_scale,
];
let new_price = calc_last_prices(&tmp_xp, &config, &env)?;

assert_slippage_tolerance(old_price, new_price, slippage_tolerance)?;
slippage = assert_slippage_tolerance(
&deposits,
share,
&config.pool_state.price_state,
slippage_tolerance,
)?;

let last_price = assets_diff[0] / assets_diff[1];
config.pool_state.update_price(
&config.pool_params,
&env,
Expand Down Expand Up @@ -574,6 +571,7 @@ where
attr("receiver", receiver),
attr("assets", format!("{}, {}", &assets[0], &assets[1])),
attr("share", share_uint128),
attr("slippage", slippage.to_string()),
];

Ok(Response::new().add_messages(messages).add_attributes(attrs))
Expand Down
4 changes: 2 additions & 2 deletions contracts/pair_concentrated_inj/src/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use astroport_pair_concentrated::state::Config as CLConfig;
use crate::state::{AmpGamma, Config, PoolParams, PoolState, PriceState, CONFIG};

const MIGRATE_FROM: &str = "astroport-pair-concentrated";
const MIGRATION_VERSION: &str = "2.0.4";
const MIGRATION_VERSION: &str = "2.0.5";

/// Manages the contract migration.
#[cfg_attr(not(feature = "library"), entry_point)]
Expand Down Expand Up @@ -57,7 +57,7 @@ pub fn migrate(
let contract_info = cw2::get_contract_version(deps.storage)?;
match contract_info.contract.as_str() {
CONTRACT_NAME => match contract_info.version.as_str() {
"2.0.3" => {}
"2.0.3" | "2.0.4" => {}
_ => {
return Err(StdError::generic_err(format!(
"Can't migrate from {} {}",
Expand Down
24 changes: 15 additions & 9 deletions contracts/pair_concentrated_inj/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ use astroport_circular_buffer::error::BufferResult;
use astroport_circular_buffer::BufferManager;
use astroport_factory::state::pair_key;

use crate::consts::{DEFAULT_SLIPPAGE, MAX_ALLOWED_SLIPPAGE, N, OFFER_PERCENT};
use crate::consts::{DEFAULT_SLIPPAGE, MAX_ALLOWED_SLIPPAGE, N, OFFER_PERCENT, TWO};
use crate::error::ContractError;
use crate::math::{calc_d, calc_y};
use crate::orderbook::state::OrderbookState;
use crate::orderbook::utils::get_subaccount_balances_dec;
use crate::state::{Config, PoolParams, Precisions, OBSERVATIONS};
use crate::state::{Config, PoolParams, Precisions, PriceState, OBSERVATIONS};

/// Helper function to check the given asset infos are valid.
pub(crate) fn check_asset_infos(asset_infos: &[AssetInfo]) -> Result<(), ContractError> {
Expand Down Expand Up @@ -475,25 +475,31 @@ pub fn calc_provide_fee(
deviation * params.fee(xp) / (sum * N)
}

/// This is an internal function that enforces slippage tolerance for swaps.
/// This is an internal function that enforces slippage tolerance for provides. Returns actual slippage.
pub fn assert_slippage_tolerance(
old_price: Decimal256,
new_price: Decimal256,
deposits: &[Decimal256],
actual_share: Decimal256,
price_state: &PriceState,
slippage_tolerance: Option<Decimal>,
) -> Result<(), ContractError> {
) -> Result<Decimal256, ContractError> {
let slippage_tolerance = slippage_tolerance
.map(Into::into)
.unwrap_or(DEFAULT_SLIPPAGE);
if slippage_tolerance > MAX_ALLOWED_SLIPPAGE {
return Err(ContractError::AllowedSpreadAssertion {});
}

// Ensure price was not changed more than the slippage tolerance allows
if Decimal256::one().diff(new_price / old_price) > slippage_tolerance {
let deposit_value = deposits[0] + deposits[1] * price_state.price_scale;
let lp_expected = (deposit_value / TWO * deposit_value / (TWO * price_state.price_scale))
.sqrt()
/ price_state.xcp_profit_real;
let slippage = lp_expected.saturating_sub(actual_share) / lp_expected;

if slippage > slippage_tolerance {
return Err(ContractError::MaxSpreadAssertion {});
}

Ok(())
Ok(slippage)
}

/// Checks whether the pair is registered in the factory or not.
Expand Down
Loading

0 comments on commit b14d7b9

Please sign in to comment.