Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimum reserves check #19

Closed
wants to merge 11 commits into from
24 changes: 23 additions & 1 deletion amm/contracts/stable_pool/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ mod token_rate;
pub mod stable_pool {
use crate::token_rate::TokenRate;
use amm_helpers::{
constants::stable_pool::{MAX_AMP, MIN_AMP, RATE_PRECISION, TOKEN_TARGET_DECIMALS},
constants::stable_pool::{
MAX_AMP, MIN_AMP, MIN_RESERVE, RATE_PRECISION, TOKEN_TARGET_DECIMALS,
},
ensure,
stable_swap_math::{self as math, fees::Fees},
};
Expand Down Expand Up @@ -380,6 +382,7 @@ pub mod stable_pool {
self.pool.reserves[token_id] = self.pool.reserves[token_id]
.checked_sub(amount)
.ok_or(MathError::SubUnderflow(101))?;
self.check_min_reserve(token_id, self.pool.reserves[token_id])?;
Ok(())
}

Expand Down Expand Up @@ -556,6 +559,17 @@ pub mod stable_pool {
ensure!(amount > 0, StablePoolError::InsufficientInputAmount);
Ok(amount)
}

fn check_min_reserve(&self, token_id: usize, reserve: u128) -> Result<(), StablePoolError> {
if self.pool.precisions[token_id]
.checked_mul(reserve)
.ok_or(MathError::MulOverflow(115))?
< MIN_RESERVE
{
return Err(StablePoolError::MinReserve);
}
Ok(())
}
}

impl StablePool for StablePoolContract {
Expand All @@ -566,6 +580,14 @@ pub mod stable_pool {
amounts: Vec<u128>,
to: AccountId,
) -> Result<(u128, u128), StablePoolError> {
//check amounts if it is initial liquidity supply
if self.total_supply() == 0 {
amounts
.iter()
.enumerate()
.try_for_each(|(id, &amount)| self.check_min_reserve(id, amount))?;
deuszx marked this conversation as resolved.
Show resolved Hide resolved
}

// calc lp tokens (shares_to_mint, fee)
let (shares, fee_part) = self.get_mint_liquidity_for_amounts(amounts.clone())?;

Expand Down
1 change: 1 addition & 0 deletions amm/traits/stable_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ pub enum StablePoolError {
IdenticalTokenId,
IncorrectAmountsCount,
InvalidAmpCoef,
MinReserve,
InsufficientLiquidityMinted,
InsufficientLiquidityBurned,
InsufficientOutputAmount,
Expand Down
3 changes: 3 additions & 0 deletions helpers/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@ pub mod stable_pool {
pub const MIN_AMP: u128 = 1;
/// Max amplification coefficient.
pub const MAX_AMP: u128 = 1_000_000;

/// Min reserve
pub const MIN_RESERVE: u128 = TOKEN_TARGET_PRECISION;
}
160 changes: 74 additions & 86 deletions helpers/stable_swap_math/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,101 +341,89 @@ pub fn rated_swap_from(
/// Compute the amount of LP tokens to mint after a deposit
/// return <lp_amount_to_mint, lp_fees_part>
fn compute_lp_amount_for_deposit(
deposit_amounts: &Vec<u128>,
deposit_amounts: &[u128],
old_reserves: &Vec<u128>,
pool_token_supply: u128,
fees: Option<&Fees>,
amp_coef: u128,
) -> Result<(u128, u128), MathError> {
if pool_token_supply == 0 {
if deposit_amounts.contains(&0) {
return Err(MathError::DivByZero(8));
// Initial invariant
let d_0 = compute_d(old_reserves, amp_coef)?;
let n_coins = old_reserves.len() as u16;
let mut new_reserves = old_reserves
.iter()
.zip(deposit_amounts.iter())
.map(|(reserve, &amount)| {
reserve
.checked_add(amount)
.ok_or(MathError::AddOverflow(14))
})
.collect::<Result<Vec<u128>, MathError>>()?;
// Invariant after change
let d_1 = compute_d(&new_reserves, amp_coef)?;
if let Some(_fees) = fees {
// Recalculate the invariant accounting for fees
for i in 0..new_reserves.len() {
let ideal_reserve: u128 = d_1
.checked_mul(old_reserves[i].into())
.ok_or(MathError::MulOverflow(17))?
.checked_div(d_0)
.ok_or(MathError::DivByZero(9))?
.try_into()
.map_err(|_| MathError::CastOverflow(2))?;
let difference = if ideal_reserve > new_reserves[i] {
ideal_reserve
.checked_sub(new_reserves[i])
.ok_or(MathError::SubUnderflow(16))?
} else {
new_reserves[i]
.checked_sub(ideal_reserve)
.ok_or(MathError::SubUnderflow(17))?
};
let fee = _fees.normalized_trade_fee(n_coins, difference)?;
new_reserves[i] = new_reserves[i]
.checked_sub(fee)
.ok_or(MathError::SubUnderflow(18))?;
}
let d_2: U256 = compute_d(&new_reserves, amp_coef)?;
let mint_shares: u128 = U256::from(pool_token_supply)
.checked_mul(d_2.checked_sub(d_0).ok_or(MathError::SubUnderflow(19))?)
.ok_or(MathError::MulOverflow(18))?
.checked_div(d_0)
.ok_or(MathError::DivByZero(10))?
.try_into()
.map_err(|_| MathError::CastOverflow(3))?;

let diff_shares: u128 = U256::from(pool_token_supply)
.checked_mul(d_1.checked_sub(d_0).ok_or(MathError::SubUnderflow(20))?)
.ok_or(MathError::MulOverflow(19))?
.checked_div(d_0)
.ok_or(MathError::DivByZero(11))?
.try_into()
.map_err(|_| MathError::CastOverflow(4))?;
// d1 > d2 > d0,
// (d2-d0) => mint_shares (charged fee),
// (d1-d0) => diff_shares (without fee),
// (d1-d2) => fee part,
// diff_shares = mint_shares + fee part
Ok((
compute_d(deposit_amounts, amp_coef)?
.try_into()
.map_err(|_| MathError::CastOverflow(1))?,
0,
mint_shares,
diff_shares
.checked_sub(mint_shares)
.ok_or(MathError::SubUnderflow(21))?,
))
} else {
// Initial invariant
let d_0 = compute_d(old_reserves, amp_coef)?;
let n_coins = old_reserves.len() as u16;
let mut new_reserves = old_reserves
.iter()
.zip(deposit_amounts.iter())
.map(|(reserve, &amount)| {
reserve
.checked_add(amount)
.ok_or(MathError::AddOverflow(14))
})
.collect::<Result<Vec<u128>, MathError>>()?;
// Invariant after change
let d_1 = compute_d(&new_reserves, amp_coef)?;
if let Some(_fees) = fees {
// Recalculate the invariant accounting for fees
for i in 0..new_reserves.len() {
let ideal_reserve: u128 = d_1
.checked_mul(old_reserves[i].into())
.ok_or(MathError::MulOverflow(17))?
.checked_div(d_0)
.ok_or(MathError::DivByZero(9))?
.try_into()
.map_err(|_| MathError::CastOverflow(2))?;
let difference = if ideal_reserve > new_reserves[i] {
ideal_reserve
.checked_sub(new_reserves[i])
.ok_or(MathError::SubUnderflow(16))?
} else {
new_reserves[i]
.checked_sub(ideal_reserve)
.ok_or(MathError::SubUnderflow(17))?
};
let fee = _fees.normalized_trade_fee(n_coins, difference)?;
new_reserves[i] = new_reserves[i]
.checked_sub(fee)
.ok_or(MathError::SubUnderflow(18))?;
}
let d_2: U256 = compute_d(&new_reserves, amp_coef)?;
let mint_shares: u128 = U256::from(pool_token_supply)
.checked_mul(d_2.checked_sub(d_0).ok_or(MathError::SubUnderflow(19))?)
.ok_or(MathError::MulOverflow(18))?
.checked_div(d_0)
.ok_or(MathError::DivByZero(10))?
.try_into()
.map_err(|_| MathError::CastOverflow(3))?;

let diff_shares: u128 = U256::from(pool_token_supply)
.checked_mul(d_1.checked_sub(d_0).ok_or(MathError::SubUnderflow(20))?)
.ok_or(MathError::MulOverflow(19))?
.checked_div(d_0)
.ok_or(MathError::DivByZero(11))?
.try_into()
.map_err(|_| MathError::CastOverflow(4))?;
// d1 > d2 > d0,
// (d2-d0) => mint_shares (charged fee),
// (d1-d0) => diff_shares (without fee),
// (d1-d2) => fee part,
// diff_shares = mint_shares + fee part
Ok((
mint_shares,
diff_shares
.checked_sub(mint_shares)
.ok_or(MathError::SubUnderflow(21))?,
))
} else {
// Calc without fees
let mint_shares: u128 = U256::from(pool_token_supply)
.checked_mul(d_1.checked_sub(d_0).ok_or(MathError::SubUnderflow(22))?)
.ok_or(MathError::MulOverflow(20))?
.checked_div(d_0)
.ok_or(MathError::DivByZero(12))?
.try_into()
.map_err(|_| MathError::CastOverflow(5))?;
// d1 > d0,
// (d1-d0) => mint_shares
Ok((mint_shares, 0))
}
// Calc without fees
let mint_shares: u128 = U256::from(pool_token_supply)
.checked_mul(d_1.checked_sub(d_0).ok_or(MathError::SubUnderflow(22))?)
.ok_or(MathError::MulOverflow(20))?
.checked_div(d_0)
.ok_or(MathError::DivByZero(12))?
.try_into()
.map_err(|_| MathError::CastOverflow(5))?;
// d1 > d0,
// (d1-d0) => mint_shares
Ok((mint_shares, 0))
}
}

Expand Down
Loading