Skip to content

Commit

Permalink
chore: add LP mint calculation for stableswap pools
Browse files Browse the repository at this point in the history
  • Loading branch information
kerber0x authored Dec 13, 2024
1 parent 06cca15 commit b8358ed
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 56 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.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "terraswap-pair"
version = "1.3.7"
version = "1.3.8"
authors = [
"Terraform Labs, PTE.",
"DELIGHT LABS",
Expand Down
159 changes: 109 additions & 50 deletions contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use cw20::{Cw20ExecuteMsg, Cw20ReceiveMsg};
#[cfg(any(feature = "osmosis_token_factory", feature = "injective"))]
use white_whale_std::pool_network::asset::is_factory_token;
use white_whale_std::pool_network::asset::{
get_total_share, Asset, AssetInfo, AssetInfoRaw, PairInfoRaw, MINIMUM_LIQUIDITY_AMOUNT,
get_total_share, Asset, AssetInfo, AssetInfoRaw, PairInfoRaw, PairType,
MINIMUM_LIQUIDITY_AMOUNT,
};
#[cfg(feature = "injective")]
use white_whale_std::pool_network::denom_injective::{Coin, MsgBurn, MsgMint};
Expand All @@ -22,7 +23,9 @@ use white_whale_std::pool_network::{swap, U256};

use crate::error::ContractError;
use crate::helpers;
use crate::helpers::get_protocol_fee_for_asset;
use crate::helpers::{
compute_d, compute_lp_mint_amount_for_stableswap_deposit, get_protocol_fee_for_asset,
};
use crate::state::{
store_fee, ALL_TIME_BURNED_FEES, ALL_TIME_COLLECTED_PROTOCOL_FEES, COLLECTED_PROTOCOL_FEES,
CONFIG, PAIR_INFO,
Expand Down Expand Up @@ -194,56 +197,112 @@ pub fn provide_liquidity(
};

let total_share = get_total_share(&deps.as_ref(), liquidity_token.clone())?;
let share = if total_share == Uint128::zero() {
// Make sure at least MINIMUM_LIQUIDITY_AMOUNT is deposited to mitigate the risk of the first
// depositor preventing small liquidity providers from joining the pool
let share = Uint128::new(
(U256::from(deposits[0].u128())
.checked_mul(U256::from(deposits[1].u128()))
.ok_or::<ContractError>(ContractError::LiquidityShareComputation {}))?
.integer_sqrt()
.as_u128(),
)
.checked_sub(MINIMUM_LIQUIDITY_AMOUNT)
.map_err(|_| ContractError::InvalidInitialLiquidityAmount(MINIMUM_LIQUIDITY_AMOUNT))?;

messages.append(&mut mint_lp_token_msg(
liquidity_token.clone(),
env.contract.address.to_string(),
env.contract.address.to_string(),
MINIMUM_LIQUIDITY_AMOUNT,
)?);

// share should be above zero after subtracting the MINIMUM_LIQUIDITY_AMOUNT
if share.is_zero() {
return Err(ContractError::InvalidInitialLiquidityAmount(
MINIMUM_LIQUIDITY_AMOUNT,
));
}

share
} else {
// min(1, 2)
// 1. sqrt(deposit_0 * exchange_rate_0_to_1 * deposit_0) * (total_share / sqrt(pool_0 * pool_1))
// == deposit_0 * total_share / pool_0
// 2. sqrt(deposit_1 * exchange_rate_1_to_0 * deposit_1) * (total_share / sqrt(pool_1 * pool_1))
// == deposit_1 * total_share / pool_1
let amount = std::cmp::min(
deposits[0].multiply_ratio(total_share, pools[0].amount),
deposits[1].multiply_ratio(total_share, pools[1].amount),
);

// assert slippage tolerance
helpers::assert_slippage_tolerance(
&slippage_tolerance,
&deposits,
&pools,
pair_info.pair_type,
amount,
total_share,
)?;
let share = match &pair_info.pair_type {
PairType::StableSwap { amp } => {
if total_share == Uint128::zero() {
// Make sure at least MINIMUM_LIQUIDITY_AMOUNT is deposited to mitigate the risk of the first
// depositor preventing small liquidity providers from joining the pool
let min_lp_token_amount = MINIMUM_LIQUIDITY_AMOUNT * Uint128::from(2u8);

let share = Uint128::try_from(compute_d(amp, deposits[0], deposits[1]).unwrap())?
.saturating_sub(min_lp_token_amount);

messages.append(&mut mint_lp_token_msg(
liquidity_token.clone(),
env.contract.address.to_string(),
env.contract.address.to_string(),
min_lp_token_amount,
)?);

// share should be above zero after subtracting the min_lp_token_amount
if share.is_zero() {
return Err(ContractError::InvalidInitialLiquidityAmount(
min_lp_token_amount,
));
}

amount
share
} else {
let amount = compute_lp_mint_amount_for_stableswap_deposit(
amp,
deposits[0],
deposits[1],
pools[0].amount,
pools[1].amount,
total_share,
)
.unwrap();

// assert slippage tolerance
helpers::assert_slippage_tolerance(
&slippage_tolerance,
&deposits,
&pools,
pair_info.pair_type,
amount,
total_share,
)?;

amount
}
}
PairType::ConstantProduct => {
if total_share == Uint128::zero() {
// Make sure at least MINIMUM_LIQUIDITY_AMOUNT is deposited to mitigate the risk of the first
// depositor preventing small liquidity providers from joining the pool
let share = Uint128::new(
(U256::from(deposits[0].u128())
.checked_mul(U256::from(deposits[1].u128()))
.ok_or::<ContractError>(ContractError::LiquidityShareComputation {}))?
.integer_sqrt()
.as_u128(),
)
.checked_sub(MINIMUM_LIQUIDITY_AMOUNT)
.map_err(|_| {
ContractError::InvalidInitialLiquidityAmount(MINIMUM_LIQUIDITY_AMOUNT)
})?;

messages.append(&mut mint_lp_token_msg(
liquidity_token.clone(),
env.contract.address.to_string(),
env.contract.address.to_string(),
MINIMUM_LIQUIDITY_AMOUNT,
)?);

// share should be above zero after subtracting the MINIMUM_LIQUIDITY_AMOUNT
if share.is_zero() {
return Err(ContractError::InvalidInitialLiquidityAmount(
MINIMUM_LIQUIDITY_AMOUNT,
));
}

share
} else {
// min(1, 2)
// 1. sqrt(deposit_0 * exchange_rate_0_to_1 * deposit_0) * (total_share / sqrt(pool_0 * pool_1))
// == deposit_0 * total_share / pool_0
// 2. sqrt(deposit_1 * exchange_rate_1_to_0 * deposit_1) * (total_share / sqrt(pool_1 * pool_1))
// == deposit_1 * total_share / pool_1
//todo fix the index stuff here
let amount = std::cmp::min(
deposits[0].multiply_ratio(total_share, pools[0].amount),
deposits[1].multiply_ratio(total_share, pools[1].amount),
);

// assert slippage tolerance
helpers::assert_slippage_tolerance(
&slippage_tolerance,
&deposits,
&pools,
pair_info.pair_type,
amount,
total_share,
)?;

amount
}
}
};

// mint LP token to sender
Expand Down
121 changes: 120 additions & 1 deletion contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use cosmwasm_schema::cw_serde;
use cosmwasm_std::CosmosMsg;
use cosmwasm_std::{
to_json_binary, Decimal, Decimal256, DepsMut, Env, ReplyOn, Response, StdError, StdResult,
Storage, SubMsg, Uint128, Uint256, WasmMsg,
Storage, SubMsg, Uint128, Uint256, Uint512, WasmMsg,
};
use cw20::MinterResponse;
use cw_storage_plus::Item;
Expand Down Expand Up @@ -99,6 +99,95 @@ pub enum StableSwapDirection {
ReverseSimulate,
}

/// Computes the Stable Swap invariant (D).
///
/// The invariant is defined as follows:
///
/// ```text
/// A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i))
/// ```
///
/// # Arguments
///
/// - `amount_a` - The amount of token A owned by the LP pool. (i.e. token A reserves)
/// - `amount_b` - The amount of token B owned by the LP pool. (i.e. token B reserves)
///
#[allow(clippy::unwrap_used)]
pub fn compute_d(amp_factor: &u64, amount_a: Uint128, amount_b: Uint128) -> Option<Uint512> {
let sum_x = amount_a.checked_add(amount_b).unwrap(); // sum(x_i), a.k.a S

// a and b
let n_coins = Uint128::new(2);

if sum_x == Uint128::zero() {
Some(Uint512::zero())
} else {
let amount_a_times_coins = amount_a.checked_mul(n_coins).unwrap();
let amount_b_times_coins = amount_b.checked_mul(n_coins).unwrap();

// Newton's method to approximate D
let mut d_prev: Uint512;
let mut d: Uint512 = sum_x.into();
for _ in 0..256 {
let mut d_prod = d;
d_prod = d_prod
.checked_mul(d)
.unwrap()
.checked_div(amount_a_times_coins.into())
.unwrap();
d_prod = d_prod
.checked_mul(d)
.unwrap()
.checked_div(amount_b_times_coins.into())
.unwrap();
d_prev = d;
d = compute_next_d(amp_factor, d, d_prod, sum_x, n_coins).unwrap();
// Equality with the precision of 1
if d > d_prev {
if d.checked_sub(d_prev).unwrap() <= Uint512::one() {
break;
}
} else if d_prev.checked_sub(d).unwrap() <= Uint512::one() {
break;
}
}

Some(d)
}
}

#[allow(clippy::unwrap_used)]
fn compute_next_d(
amp_factor: &u64,
d_init: Uint512,
d_prod: Uint512,
sum_x: Uint128,
n_coins: Uint128,
) -> Option<Uint512> {
let ann = amp_factor.checked_mul(n_coins.u128() as u64)?;
let leverage = Uint512::from(sum_x).checked_mul(ann.into()).unwrap();
// d = (ann * sum_x + d_prod * n_coins) * d / ((ann - 1) * d + (n_coins + 1) * d_prod)
let numerator = d_init
.checked_mul(
d_prod
.checked_mul(n_coins.into())
.unwrap()
.checked_add(leverage)
.unwrap(),
)
.unwrap();
let denominator = d_init
.checked_mul(ann.checked_sub(1)?.into())
.unwrap()
.checked_add(
d_prod
.checked_mul((n_coins.checked_add(1u128.into()).unwrap()).into())
.unwrap(),
)
.unwrap();
Some(numerator.checked_div(denominator).unwrap())
}

/// Calculates the new pool amount given the current pools and swap size.
pub fn calculate_stableswap_y(
offer_pool: Decimal256,
Expand Down Expand Up @@ -151,6 +240,36 @@ pub fn calculate_stableswap_y(
Err(ContractError::ConvergeError {})
}

/// Computes the amount of pool tokens to mint after a deposit.
#[allow(clippy::unwrap_used, clippy::too_many_arguments)]
pub fn compute_lp_mint_amount_for_stableswap_deposit(
amp_factor: &u64,
deposit_amount_a: Uint128,
deposit_amount_b: Uint128,
swap_amount_a: Uint128,
swap_amount_b: Uint128,
pool_token_supply: Uint128,
) -> Option<Uint128> {
// Initial invariant
let d_0 = compute_d(amp_factor, swap_amount_a, swap_amount_b)?;
let new_balances = [
swap_amount_a.checked_add(deposit_amount_a).unwrap(),
swap_amount_b.checked_add(deposit_amount_b).unwrap(),
];
// Invariant after change
let d_1 = compute_d(amp_factor, new_balances[0], new_balances[1])?;
if d_1 <= d_0 {
None
} else {
let amount = Uint512::from(pool_token_supply)
.checked_mul(d_1.checked_sub(d_0).unwrap())
.unwrap()
.checked_div(d_0)
.unwrap();
Some(Uint128::try_from(amount).unwrap())
}
}

pub fn compute_swap(
offer_pool: Uint128,
ask_pool: Uint128,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ pub mod state;
mod error;
mod helpers;
mod math;
mod migrations;
mod queries;
mod response;

mod migrations;
#[cfg(test)]
#[cfg(not(target_arch = "wasm32"))]
pub mod tests;
2 changes: 1 addition & 1 deletion scripts/deployment/deploy_env/mainnets/terra.env
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export CHAIN_ID="phoenix-1"
export DENOM="uluna"
export BINARY="terrad"
export RPC="https://ww-terra-rpc.polkachu.com:443"
export RPC="https://rpc.lavenderfive.com:443/terra2"

0 comments on commit b8358ed

Please sign in to comment.