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

feat(smart-contracts): add stableswap support for provide liquidity #351

Merged
merged 52 commits into from
Jun 4, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
84ae06b
feat(smart-contracts): add stableswap support for provide liquidity
nseguias May 6, 2024
4cb71f5
chore: please the lord clippy
nseguias May 6, 2024
c14fb19
feat(smart-contracts): make provide liquidity for stableswap generic …
nseguias May 7, 2024
0176c1a
chore(smart-contracts): please the lord clippy
nseguias May 7, 2024
9b00cc2
fix(smart-contracts): refactor to use generic n_coins using deposits.…
nseguias May 7, 2024
9fe2631
chore(smart-contracts): remvoe unnecessary comments
nseguias May 7, 2024
7f3d4e5
test(smart-contracts): add tests (including proptests) for pool manag…
nseguias May 23, 2024
249d0bc
feat(smart-contracts): test math for pool manager
nseguias May 23, 2024
106de06
feat(smart-contracts): address PR comments
nseguias May 27, 2024
93bfa2d
feat(smart-contracts): address PR comments
nseguias May 27, 2024
ef3f1ca
feat(smart-contracts): address PR comments
nseguias May 27, 2024
45c7cf6
fix(smart-contracts): update slippage tolerance to work with N coins …
nseguias May 28, 2024
0ea5afd
fix(smart-contracts): update slippage tolerance to work with N coins
nseguias May 28, 2024
045a8f1
chore: bump cosmwasm-* versions
kerber0x May 6, 2024
da16403
refactor: remove the temporary FillRewardsCoin msg from the whalelair…
kerber0x May 6, 2024
a4bbed0
refactor: abstract bank burn msg
kerber0x May 6, 2024
6a5522e
refactor: make epoch id u64 on epoch manager
kerber0x May 6, 2024
6c8ea5b
test: use the bonding-manager in pool-manager tests
kerber0x May 6, 2024
db23e50
chore: remove factory flags from token_factory package
kerber0x May 7, 2024
26472df
refactor: bonding-manager
kerber0x May 8, 2024
4a36492
test(bonding-manager): fix instantiate tests
kerber0x May 8, 2024
c364fc1
test(bonding-manager): fix update config tests
kerber0x May 8, 2024
1f3acee
test: fix bond tests
kerber0x May 9, 2024
87aaccb
refactor: update types, change weight calculation from timestamp to e…
kerber0x May 9, 2024
eaaaa4f
refactor: rename bonding-manager.epochs to reward buckets
kerber0x May 9, 2024
7e4b523
chore: start point before major remake of the bonding mechanism
kerber0x May 13, 2024
1f5d722
refactor: move functions to proper folders
kerber0x May 13, 2024
f1b18fb
refactor: using upcoming_reward_bucket when filling rewards
kerber0x May 13, 2024
5de11ef
chore: add last_claimed_epoch when bonding
kerber0x May 14, 2024
76aa0f7
chore: add rewards query
kerber0x May 15, 2024
ff9c23f
test: enable rewards test
kerber0x May 15, 2024
bcb7676
test: add more test coverage, to helpers and bonding
kerber0x May 16, 2024
0e8c4e5
test: add unbond and withdraw tests
kerber0x May 16, 2024
4b8f840
chore: fix broken tests
kerber0x May 16, 2024
9b124e3
refactor: move a few functiosn to helpers.rs
kerber0x May 17, 2024
55990d1
refactor: cleanup state
kerber0x May 17, 2024
01cf973
chore: final refactor
kerber0x May 17, 2024
64176dd
test: use test_case for extract_pool_identifier unit test
kerber0x May 20, 2024
35aaa12
docs: add readme to bonding-manager
kerber0x May 20, 2024
e26f79d
chore: fix PR comments
kerber0x May 20, 2024
39b3bc7
docs: tweak v2 architecture diagram
kerber0x May 20, 2024
ae0591c
docs(epoch-manager): add readme to epoch manager
kerber0x May 20, 2024
52bed91
docs(epoch-manager): add epoch details and hook mechanismm diagram
kerber0x May 21, 2024
15896a2
docs(incentive-manager): add readme to incentive manager
kerber0x May 21, 2024
daed937
docs(vault-manager): add readme for vault manager
kerber0x May 21, 2024
b7ea839
docs(bonding-manager): add diagram of reward distribution (#359)
kerber0x May 21, 2024
2fda9be
docs(pool-manager): add readme to pool manager (#356)
kerber0x May 21, 2024
78ca31f
docs(epopoch-manager): add missing docummentation to structs and enum…
kerber0x May 23, 2024
0975046
fix: Rework all v2 contracts to use bonding-manager not whale-lair (#…
0xFable May 24, 2024
8302636
chore(repo): resolving dependency & package conflicts
nseguias May 31, 2024
750b02a
test: fix min liquidity amount check for stableswap
kerber0x Jun 4, 2024
2f40957
chore: merge branch 'release/v2_contracts' into feat/stableswap-case-…
kerber0x Jun 4, 2024
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
138 changes: 129 additions & 9 deletions contracts/liquidity_hub/pool-manager/src/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@

// todo isn't this for the 3pool? shouldn't it be 3
// the number of assets in the pool
const N_COINS: Uint256 = Uint256::from_u128(2);
pub const N_COINS: u8 = 3;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, was hoping other than some calculation changes it would work like this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

using Uint256::from(pool_info.assets.len() as u128) now


fn calculate_stableswap_d(
offer_pool: Decimal256,
ask_pool: Decimal256,
amp: &u64,
precision: u8,
) -> Result<Decimal256, ContractError> {
let n_coins = Decimal256::from_ratio(N_COINS, Uint256::from_u128(1));
let n_coins = Decimal256::from_ratio(Uint256::from(N_COINS), Uint256::from_u128(1));

let sum_pools = offer_pool.checked_add(ask_pool)?;
if sum_pools.is_zero() {
Expand All @@ -35,7 +35,10 @@
}

// ann = amp * n_coins
let ann = Decimal256::from_ratio(Uint256::from_u128((*amp).into()).checked_mul(N_COINS)?, 1u8);
let ann = Decimal256::from_ratio(
Uint256::from_u128((*amp).into()).checked_mul(Uint256::from(N_COINS))?,
1u8,
);

// perform Newton-Raphson method
let mut current_d = sum_pools;
Expand Down Expand Up @@ -99,7 +102,7 @@
ask_precision: u8,
direction: StableSwapDirection,
) -> Result<Uint128, ContractError> {
let ann = Uint256::from_u128((*amp).into()).checked_mul(N_COINS)?;
let ann = Uint256::from_u128((*amp).into()).checked_mul(Uint256::from(N_COINS))?;

let d = calculate_stableswap_d(offer_pool, ask_pool, amp, ask_precision)?
.to_uint256_with_precision(u32::from(ask_precision))?;
Expand All @@ -111,8 +114,8 @@
.to_uint256_with_precision(u32::from(ask_precision))?;

let c = d
.checked_multiply_ratio(d, pool_sum.checked_mul(N_COINS)?)?
.checked_multiply_ratio(d, ann.checked_mul(N_COINS)?)?;
.checked_multiply_ratio(d, pool_sum.checked_mul(Uint256::from(N_COINS))?)?
.checked_multiply_ratio(d, ann.checked_mul(Uint256::from(N_COINS))?)?;

let b = pool_sum.checked_add(d.checked_div(ann)?)?;

Expand Down Expand Up @@ -474,8 +477,8 @@

pub fn assert_slippage_tolerance(
slippage_tolerance: &Option<Decimal>,
deposits: &[Uint128; 2],
pools: &[Coin; 2],
deposits: &[Coin],
pools: &[Coin],
pool_type: PoolType,
amount: Uint128,
pool_token_supply: Uint128,
Expand All @@ -487,7 +490,7 @@
}

let one_minus_slippage_tolerance = Decimal256::one() - slippage_tolerance;
let deposits: [Uint256; 2] = [deposits[0].into(), deposits[1].into()];
let deposits: [Uint256; 2] = [deposits[0].amount.into(), deposits[1].amount.into()];

Check warning on line 493 in contracts/liquidity_hub/pool-manager/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/helpers.rs#L493

Added line #L493 was not covered by tests
kerber0x marked this conversation as resolved.
Show resolved Hide resolved
let pools: [Uint256; 2] = [pools[0].amount.into(), pools[1].amount.into()];

// Ensure each prices are not dropped as much as slippage tolerance rate
Expand Down Expand Up @@ -662,3 +665,120 @@
ask_decimal,
))
}

// TODO: handle unwraps properly
#[allow(clippy::unwrap_used)]
pub fn compute_d(amp_factor: &u64, deposits: &Vec<Coin>) -> Option<Uint256> {
let n_coins = Uint128::from(deposits.len() as u128);

// sum(x_i), a.k.a S
let sum_x = deposits
.iter()
.fold(Uint128::zero(), |acc, x| acc.checked_add(x.amount).unwrap());

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

Check warning on line 680 in contracts/liquidity_hub/pool-manager/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/helpers.rs#L680

Added line #L680 was not covered by tests
} else {
// do as below but for a generic number of assets
let amount_times_coins: Vec<Uint128> = deposits
.iter()
.map(|coin| coin.amount.checked_mul(n_coins).unwrap())
.collect();

// Newton's method to approximate D
let mut d_prev: Uint256;
let mut d: Uint256 = sum_x.into();
for amount in amount_times_coins.into_iter() {
for _ in 0..256 {
let mut d_prod = d;
d_prod = d_prod
.checked_mul(d)
.unwrap()
.checked_div(amount.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() <= Uint256::one() {

Check warning on line 703 in contracts/liquidity_hub/pool-manager/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/helpers.rs#L703

Added line #L703 was not covered by tests
break;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend adding some unit tests here. Logic looks right but its complicated

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All 3pool tests && proptests have been refactored and integrated into the pool-manager

} else if d_prev.checked_sub(d).unwrap() <= Uint256::one() {
break;
}
}
}
Some(d)
}
}

// TODO: handle unwraps properly
#[allow(clippy::unwrap_used)]
fn compute_next_d(
kerber0x marked this conversation as resolved.
Show resolved Hide resolved
amp_factor: &u64,
d_init: Uint256,
d_prod: Uint256,
sum_x: Uint128,
n_coins: Uint128,
) -> Option<Uint256> {
let ann = amp_factor.checked_mul(n_coins.u128() as u64)?;
let leverage = Uint256::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())
}

/// Computes the amount of pool tokens to mint after a deposit.
#[allow(clippy::unwrap_used, clippy::too_many_arguments)]
pub fn compute_mint_amount_for_deposit(

Check warning on line 750 in contracts/liquidity_hub/pool-manager/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/helpers.rs#L750

Added line #L750 was not covered by tests
amp_factor: &u64,
deposits: &Vec<Coin>,
pool_assets: &[Coin],
pool_token_supply: Uint128,
) -> Option<Uint128> {
// Initial invariant
let d_0 = compute_d(amp_factor, deposits)?;

Check warning on line 757 in contracts/liquidity_hub/pool-manager/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/helpers.rs#L757

Added line #L757 was not covered by tests

let new_balances: Vec<Coin> = pool_assets

Check warning on line 759 in contracts/liquidity_hub/pool-manager/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/helpers.rs#L759

Added line #L759 was not covered by tests
.iter()
.enumerate()
.map(|(i, pool_asset)| {
let deposit_amount = deposits[i].amount;
let new_amount = pool_asset.amount.checked_add(deposit_amount).unwrap();
Coin {
denom: pool_asset.denom.clone(),

Check warning on line 766 in contracts/liquidity_hub/pool-manager/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/helpers.rs#L762-L766

Added lines #L762 - L766 were not covered by tests
amount: new_amount,
}
})
.collect();

// Invariant after change
let d_1 = compute_d(amp_factor, &new_balances)?;
if d_1 <= d_0 {
None

Check warning on line 775 in contracts/liquidity_hub/pool-manager/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/helpers.rs#L773-L775

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

Check warning on line 778 in contracts/liquidity_hub/pool-manager/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/helpers.rs#L777-L778

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

Check warning on line 782 in contracts/liquidity_hub/pool-manager/src/helpers.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/helpers.rs#L782

Added line #L782 was not covered by tests
}
}
58 changes: 40 additions & 18 deletions contracts/liquidity_hub/pool-manager/src/liquidity/commands.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use cosmwasm_std::{
coin, coins, ensure, to_json_binary, wasm_execute, BankMsg, Coin, CosmosMsg, DepsMut, Env,
MessageInfo, Response, StdError, SubMsg,
MessageInfo, Response, SubMsg,
};
use cosmwasm_std::{Decimal, OverflowError, Uint128};

Expand All @@ -23,7 +23,7 @@
// After writing create_pool I see this can get quite verbose so attempting to
// break it down into smaller modules which house some things like swap, liquidity etc
use crate::contract::SINGLE_SIDE_LIQUIDITY_PROVISION_REPLY_ID;
use crate::helpers::aggregate_outgoing_fees;
use crate::helpers::{aggregate_outgoing_fees, compute_d, compute_mint_amount_for_deposit};
use crate::queries::query_simulation;
use crate::state::{
LiquidityProvisionData, SingleSideLiquidityProvisionBuffer,
Expand Down Expand Up @@ -251,22 +251,11 @@
.multiply_ratio(total_share, pool_assets[1].amount),
);

let deposits_as: [Uint128; 2] = deposits
.iter()
.map(|coin| coin.amount)
.collect::<Vec<_>>()
.try_into()
.map_err(|_| StdError::generic_err("Error converting vector to array"))?;
let pools_as: [Coin; 2] = pool_assets
.to_vec()
.try_into()
.map_err(|_| StdError::generic_err("Error converting vector to array"))?;

// assert slippage tolerance
helpers::assert_slippage_tolerance(
&slippage_tolerance,
&deposits_as,
&pools_as,
&deposits,
&pool_assets,
pool.pool_type.clone(),
amount,
total_share,
Expand All @@ -275,10 +264,43 @@
amount
}
}
PoolType::StableSwap { amp: _ } => {
// TODO: Handle stableswap
PoolType::StableSwap { amp: amp_factor } => {
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(3u8);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Beautiful

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually change it to work with N amount of assets let min_lp_token_amount = MINIMUM_LIQUIDITY_AMOUNT * Uint128::from(pool_assets.len() as u128)

kerber0x marked this conversation as resolved.
Show resolved Hide resolved
let share = Uint128::try_from(compute_d(amp_factor, &deposits).unwrap())?
.checked_sub(min_lp_token_amount)
.map_err(|_| {
ContractError::InvalidInitialLiquidityAmount(min_lp_token_amount)

Check warning on line 275 in contracts/liquidity_hub/pool-manager/src/liquidity/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/liquidity/commands.rs#L275

Added line #L275 was not covered by tests
})?;

kerber0x marked this conversation as resolved.
Show resolved Hide resolved
// share should be above zero after subtracting the min_lp_token_amount
if share.is_zero() {
return Err(ContractError::InvalidInitialLiquidityAmount(
min_lp_token_amount,
));
}

Uint128::one()
share
} else {
let amount = compute_mint_amount_for_deposit(
amp_factor,
&deposits,
&pool_assets,

Check warning on line 290 in contracts/liquidity_hub/pool-manager/src/liquidity/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/liquidity/commands.rs#L290

Added line #L290 was not covered by tests
total_share,
)
.unwrap();
helpers::assert_slippage_tolerance(
&slippage_tolerance,
&deposits,
&pool_assets,
pool.pool_type.clone(),

Check warning on line 298 in contracts/liquidity_hub/pool-manager/src/liquidity/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/liquidity/commands.rs#L296-L298

Added lines #L296 - L298 were not covered by tests
amount,
total_share,
)?;
amount

Check warning on line 302 in contracts/liquidity_hub/pool-manager/src/liquidity/commands.rs

View check run for this annotation

Codecov / codecov/patch

contracts/liquidity_hub/pool-manager/src/liquidity/commands.rs#L302

Added line #L302 was not covered by tests
}
}
};

Expand Down
9 changes: 9 additions & 0 deletions contracts/liquidity_hub/pool-manager/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,12 @@ pub const SWAP_ROUTES: Map<(&str, &str), SwapOperations> = Map::new("swap_routes

pub const CONFIG: Item<Config> = Item::new("config");
pub const POOL_COUNTER: Item<u64> = Item::new("pool_count");

pub const STABLE_SWAP_PARAMS: Item<StableSwapParams> = Item::new("stable_swap_params");
#[cw_serde]
pub struct StableSwapParams {
pub initial_amp: u64,
pub future_amp: u64,
pub initial_amp_block: u64,
pub future_amp_block: u64,
}
Loading
Loading