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 committed Dec 12, 2024
1 parent 06cca15 commit 595ed83
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() {

Check warning on line 203 in contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs#L202-L203

Added lines #L202 - L203 were not covered by tests
// 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);

Check warning on line 206 in contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs#L206

Added line #L206 was not covered by tests

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

Check warning on line 208 in contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs#L208

Added line #L208 was not covered by tests
.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(),

Check warning on line 214 in contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs#L211-L214

Added lines #L211 - L214 were not covered by tests
min_lp_token_amount,
)?);

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

Check warning on line 220 in contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs#L219-L220

Added lines #L219 - L220 were not covered by tests
min_lp_token_amount,
));
}

amount
share

Check warning on line 225 in contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs#L225

Added line #L225 was not covered by tests
} else {
let amount = compute_lp_mint_amount_for_stableswap_deposit(
amp,
deposits[0],
deposits[1],
pools[0].amount,
pools[1].amount,

Check warning on line 232 in contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs#L229-L232

Added lines #L229 - L232 were not covered by tests
total_share,
)
.unwrap();

// assert slippage tolerance
helpers::assert_slippage_tolerance(
&slippage_tolerance,
&deposits,
&pools,
pair_info.pair_type,

Check warning on line 242 in contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs#L242

Added line #L242 was not covered by tests
amount,
total_share,
)?;

amount

Check warning on line 247 in contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/commands.rs#L247

Added line #L247 was not covered by tests
}
}
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

Check warning on line 117 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L116-L117

Added lines #L116 - L117 were not covered by tests

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

Check warning on line 120 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L120

Added line #L120 was not covered by tests

if sum_x == Uint128::zero() {
Some(Uint512::zero())

Check warning on line 123 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L122-L123

Added lines #L122 - L123 were not covered by tests
} 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();

Check warning on line 126 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L125-L126

Added lines #L125 - L126 were not covered by tests

// 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)

Check warning on line 134 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L130-L134

Added lines #L130 - L134 were not covered by tests
.unwrap()
.checked_div(amount_a_times_coins.into())

Check warning on line 136 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L136

Added line #L136 was not covered by tests
.unwrap();
d_prod = d_prod
.checked_mul(d)

Check warning on line 139 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L138-L139

Added lines #L138 - L139 were not covered by tests
.unwrap()
.checked_div(amount_b_times_coins.into())

Check warning on line 141 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L141

Added line #L141 was not covered by tests
.unwrap();
d_prev = d;
d = compute_next_d(amp_factor, d, d_prod, sum_x, n_coins).unwrap();

Check warning on line 144 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L143-L144

Added lines #L143 - L144 were not covered by tests
// Equality with the precision of 1
if d > d_prev {
if d.checked_sub(d_prev).unwrap() <= Uint512::one() {

Check warning on line 147 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L146-L147

Added lines #L146 - L147 were not covered by tests
break;
}
} else if d_prev.checked_sub(d).unwrap() <= Uint512::one() {

Check warning on line 150 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L150

Added line #L150 was not covered by tests
break;
}
}

Some(d)

Check warning on line 155 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L155

Added line #L155 was not covered by tests
}
}

#[allow(clippy::unwrap_used)]
fn compute_next_d(

Check warning on line 160 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L160

Added line #L160 was not covered by tests
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();

Check warning on line 168 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L167-L168

Added lines #L167 - L168 were not covered by tests
// d = (ann * sum_x + d_prod * n_coins) * d / ((ann - 1) * d + (n_coins + 1) * d_prod)
let numerator = d_init

Check warning on line 170 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L170

Added line #L170 was not covered by tests
.checked_mul(
d_prod
.checked_mul(n_coins.into())

Check warning on line 173 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L172-L173

Added lines #L172 - L173 were not covered by tests
.unwrap()
.checked_add(leverage)

Check warning on line 175 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L175

Added line #L175 was not covered by tests
.unwrap(),
)
.unwrap();
let denominator = d_init
.checked_mul(ann.checked_sub(1)?.into())

Check warning on line 180 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L179-L180

Added lines #L179 - L180 were not covered by tests
.unwrap()
.checked_add(
d_prod
.checked_mul((n_coins.checked_add(1u128.into()).unwrap()).into())

Check warning on line 184 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L183-L184

Added lines #L183 - L184 were not covered by tests
.unwrap(),
)
.unwrap();
Some(numerator.checked_div(denominator).unwrap())

Check warning on line 188 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L188

Added line #L188 was not covered by tests
}

/// 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(

Check warning on line 245 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L245

Added line #L245 was not covered by tests
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(),

Check warning on line 257 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L254-L257

Added lines #L254 - L257 were not covered by tests
];
// Invariant after change
let d_1 = compute_d(amp_factor, new_balances[0], new_balances[1])?;
if d_1 <= d_0 {
None

Check warning on line 262 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L260-L262

Added lines #L260 - L262 were not covered by tests
} else {
let amount = Uint512::from(pool_token_supply)
.checked_mul(d_1.checked_sub(d_0).unwrap())

Check warning on line 265 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L264-L265

Added lines #L264 - L265 were not covered by tests
.unwrap()
.checked_div(d_0)
.unwrap();
Some(Uint128::try_from(amount).unwrap())

Check warning on line 269 in contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-network/terraswap_pair/src/helpers.rs#L269

Added line #L269 was not covered by tests
}
}

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 595ed83

Please sign in to comment.