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

chore: add LP mint calculation for stableswap pools #383

Merged
merged 1 commit into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@
#[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 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 @@
};

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_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 @@
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 @@
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"
Loading