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: Set pool fees #4050

Merged
merged 16 commits into from
Oct 12, 2023
2 changes: 1 addition & 1 deletion state-chain/amm/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ pub(super) fn sqrt_price_at_tick(tick: Tick) -> SqrtPriceQ64F96 {
}

/// Calculates the greatest tick value such that `sqrt_price_at_tick(tick) <= sqrt_price`
pub(super) fn tick_at_sqrt_price(sqrt_price: SqrtPriceQ64F96) -> Tick {
pub fn tick_at_sqrt_price(sqrt_price: SqrtPriceQ64F96) -> Tick {
assert!(is_sqrt_price_valid(sqrt_price));

let sqrt_price_q64f128 = sqrt_price << 32u128;
Expand Down
80 changes: 50 additions & 30 deletions state-chain/pallets/cf-pools/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
use core::ops::Range;

use cf_amm::{
common::{Amount, Order, Price, Side, SideMap, Tick},
common::{tick_at_sqrt_price, Amount, Order, Price, Side, SideMap, Tick},
limit_orders, range_orders,
range_orders::Liquidity,
PoolState,
Expand Down Expand Up @@ -229,6 +229,29 @@ pub mod pallet {
pub pool_state: PoolState<(T::AccountId, OrderId)>,
}

impl<T: Config> Pool<T> {
pub fn update_limit_order_storage(
&mut self,
lp: &T::AccountId,
side: Side,
id: OrderId,
tick: cf_amm::common::Tick,
can_remove: bool,
) {
let limit_orders = &mut self.limit_orders[side];
if can_remove {
if let Some(lp_limit_orders) = limit_orders.get_mut(lp) {
lp_limit_orders.remove(&id);
if lp_limit_orders.is_empty() {
limit_orders.remove(lp);
}
}
} else {
limit_orders.entry(lp.clone()).or_default().insert(id, tick);
}
}
}

pub type OrderId = u64;

#[derive(
Expand Down Expand Up @@ -960,25 +983,25 @@ pub mod pallet {
Error::<T>::InvalidFeeAmount
);
Self::try_mutate_enabled_pool(base_asset, pair_asset, |asset_pair, pool| {
if pool.pool_state.limit_order_fee() == fee_hundredth_pips &&
pool.pool_state.range_order_fee() == fee_hundredth_pips
{
return Ok(())
}

let SideMap { zero, one } = pool
.pool_state
pool.pool_state
.set_fees(fee_hundredth_pips)
.map_err(|_| Error::<T>::InvalidFeeAmount)?;
for (collected_fees, side) in [(zero, Side::Zero), (one, Side::One)].into_iter() {
for ((_, (lp, _order)), (collected, _position_info)) in
collected_fees.into_iter()
{
asset_pair.try_credit_asset(&lp, side, collected.fees)?;
asset_pair.try_credit_asset(&lp, side, collected.bought_amount)?;
}
}
Result::<(), DispatchError>::Ok(())
.map_err(|_| Error::<T>::InvalidFeeAmount)?
.try_map(|side, collected_fees| {
for ((sqrt_price, (lp, order)), (collected, position_info)) in
collected_fees.into_iter()
{
asset_pair.try_credit_asset(&lp, !side, collected.fees)?;
asset_pair.try_credit_asset(&lp, !side, collected.bought_amount)?;
pool.update_limit_order_storage(
&lp,
side,
order,
tick_at_sqrt_price(sqrt_price),
syan095 marked this conversation as resolved.
Show resolved Hide resolved
position_info.amount.is_zero(),
syan095 marked this conversation as resolved.
Show resolved Hide resolved
);
}
Result::<(), DispatchError>::Ok(())
})
})?;

Self::deposit_event(Event::<T>::PoolFeeSet {
Expand Down Expand Up @@ -1146,17 +1169,14 @@ impl<T: Config> Pallet<T> {
},
};

let limit_orders = &mut pool.limit_orders[asset_pair.base_side];
if position_info.amount.is_zero() {
if let Some(lp_limit_orders) = limit_orders.get_mut(lp) {
lp_limit_orders.remove(&id);
if lp_limit_orders.is_empty() {
limit_orders.remove(lp);
}
}
} else {
limit_orders.entry(lp.clone()).or_default().insert(id, tick);
}
// Update pool's limit orders
pool.update_limit_order_storage(
lp,
asset_pair.base_side,
id,
tick,
position_info.amount.is_zero(),
);

let collected_fees =
asset_pair.try_credit_asset(lp, !asset_pair.base_side, collected.fees)?;
Expand Down
100 changes: 53 additions & 47 deletions state-chain/pallets/cf-pools/src/tests.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use crate::{
mock::*, utilities, AssetAmounts, AssetsMap, CanonicalAssetPair, CollectedNetworkFee, Error,
FlipBuyInterval, FlipToBurn, PoolInfo, PoolOrders, Pools, RangeOrderSize, STABLE_ASSET,
mock::*, utilities, AssetAmounts, AssetPair, AssetsMap, CanonicalAssetPair,
CollectedNetworkFee, Error, FlipBuyInterval, FlipToBurn, PoolInfo, PoolOrders, Pools,
RangeOrderSize, STABLE_ASSET,
};
use cf_amm::common::{price_at_tick, Tick};
use cf_primitives::{chains::assets::any::Asset, AssetAmount, SwapOutput};
Expand Down Expand Up @@ -309,7 +310,7 @@ fn can_update_pool_liquidity_fee() {
Asset::Eth,
1,
Some(0),
5_000,
1_000,
));
assert_ok!(LiquidityPools::set_limit_order(
RuntimeOrigin::signed(BOB),
Expand All @@ -332,7 +333,7 @@ fn can_update_pool_liquidity_fee() {
Some(PoolOrders {
limit_orders: AssetsMap {
base: vec![(0, 0, 5000u128.into())],
pair: vec![(1, 0, 5000u128.into())]
pair: vec![(1, 0, 1000u128.into())]
},
range_orders: vec![]
})
Expand All @@ -355,7 +356,7 @@ fn can_update_pool_liquidity_fee() {
);
assert_eq!(
LiquidityPools::swap_with_network_fee(Asset::Eth, STABLE_ASSET, 10_000).unwrap(),
SwapOutput { intermediary: None, output: 5_988u128 }
SwapOutput { intermediary: None, output: 5_987u128 }
);

// Updates the fees to the new value and collect any fees on current positions.
Expand All @@ -368,11 +369,9 @@ fn can_update_pool_liquidity_fee() {

// All Lpers' fees and bought amount are Collected and accredited.
// Fee and swaps are calculated proportional to the liquidity amount.
// Alice's amount = 4000 * 1/3 + 6000 * 1/3
// Bob's amount = 4000 * 2/3 + 6000 * 2/3
assert_eq!(AliceCollectedEth::get(), 3_333u128);
assert_eq!(AliceCollectedEth::get(), 908u128);
assert_eq!(AliceCollectedUsdc::get(), 3_333u128);
assert_eq!(BobCollectedEth::get(), 6_666u128);
assert_eq!(BobCollectedEth::get(), 9090u128);
assert_eq!(BobCollectedUsdc::get(), 6_666u128);

// New pool fee is set and event emitted.
Expand All @@ -397,7 +396,7 @@ fn can_update_pool_liquidity_fee() {
Some(PoolOrders {
limit_orders: AssetsMap {
base: vec![(0, 0, 3_000u128.into())],
pair: vec![(1, 0, 3_000u128.into())]
pair: vec![(1, 0, 454u128.into())]
},
range_orders: vec![]
})
Expand All @@ -407,7 +406,7 @@ fn can_update_pool_liquidity_fee() {
Some(PoolOrders {
limit_orders: AssetsMap {
base: vec![(0, 0, 6_000u128.into())],
pair: vec![(1, 0, 6_000u128.into())]
pair: vec![(1, 0, 4_545u128.into())]
},
range_orders: vec![]
})
Expand All @@ -434,9 +433,11 @@ fn can_update_pool_liquidity_fee() {
}

#[test]
fn setting_the_same_pool_fee_does_nothing() {
fn pallet_limit_order_is_in_sync_with_pool() {
new_test_ext().execute_with(|| {
let fee = 400_000u32;
let fee = 500_000u32;
let tick = 100;
let asset_pair = AssetPair::<Test>::new(Asset::Eth, STABLE_ASSET).unwrap();

// Create a new pool.
assert_ok!(LiquidityPools::new_pool(
Expand All @@ -446,64 +447,69 @@ fn setting_the_same_pool_fee_does_nothing() {
fee,
price_at_tick(0).unwrap(),
));
assert_eq!(
LiquidityPools::pool_info(Asset::Eth, STABLE_ASSET),
Some(PoolInfo {
limit_order_fee_hundredth_pips: fee,
range_order_fee_hundredth_pips: fee,
})
);

// Setup liquidity for the pool
// Setup liquidity for the pool with 2 LPer
assert_ok!(LiquidityPools::set_limit_order(
RuntimeOrigin::signed(ALICE),
Asset::Eth,
STABLE_ASSET,
0,
Some(0),
5_000,
100,
));
assert_ok!(LiquidityPools::set_limit_order(
RuntimeOrigin::signed(BOB),
Asset::Eth,
STABLE_ASSET,
0,
Some(tick),
100_000,
));
assert_ok!(LiquidityPools::set_limit_order(
RuntimeOrigin::signed(BOB),
STABLE_ASSET,
Asset::Eth,
1,
Some(tick),
10_000,
));

assert_eq!(
LiquidityPools::pool_orders(Asset::Eth, STABLE_ASSET, &ALICE,),
Some(PoolOrders {
limit_orders: AssetsMap { base: vec![(0, 0, 5000u128.into())], pair: vec![] },
limit_orders: AssetsMap { base: vec![(0, 0, 100u128.into())], pair: vec![] },
range_orders: vec![]
})
);

let pallet_limit_orders =
Pools::<Test>::get(asset_pair.canonical_asset_pair).unwrap().limit_orders;
assert_eq!(pallet_limit_orders.zero[&ALICE][&0], 0);
assert_eq!(pallet_limit_orders.zero[&BOB][&0], tick);
assert_eq!(pallet_limit_orders.one[&BOB][&1], tick);

// Do some swaps to collect fees.
assert_eq!(
LiquidityPools::swap_with_network_fee(STABLE_ASSET, Asset::Eth, 1_000).unwrap(),
SwapOutput { intermediary: None, output: 598u128 }
LiquidityPools::swap_with_network_fee(STABLE_ASSET, Asset::Eth, 202_200).unwrap(),
SwapOutput { intermediary: None, output: 99_894u128 }
);
assert_eq!(
LiquidityPools::swap_with_network_fee(Asset::Eth, STABLE_ASSET, 18_000).unwrap(),
SwapOutput { intermediary: None, output: 9_071 }
);

// Setting the fee as the same value do nothing. No fee is collected.
// Updates the fees to the new value and collect any fees on current positions.
assert_ok!(LiquidityPools::set_pool_fees(
RuntimeOrigin::root(),
Asset::Eth,
STABLE_ASSET,
fee
0u32
));
assert_eq!(AliceCollectedEth::get(), 0u128);
assert_eq!(AliceCollectedUsdc::get(), 0u128);
assert_eq!(BobCollectedEth::get(), 0u128);
assert_eq!(BobCollectedUsdc::get(), 0u128);

assert_eq!(
LiquidityPools::pool_info(Asset::Eth, STABLE_ASSET),
Some(PoolInfo {
limit_order_fee_hundredth_pips: fee,
range_order_fee_hundredth_pips: fee,
})
);

assert_eq!(
LiquidityPools::pool_orders(Asset::Eth, STABLE_ASSET, &ALICE,),
Some(PoolOrders {
limit_orders: AssetsMap { base: vec![(0, 0, 4_400u128.into())], pair: vec![] },
range_orders: vec![]
})
);
// 100 swapped + 100 fee. The position is fully consumed.
assert_eq!(AliceCollectedUsdc::get(), 200u128);
let pallet_limit_orders =
Pools::<Test>::get(asset_pair.canonical_asset_pair).unwrap().limit_orders;
assert_eq!(pallet_limit_orders.zero.get(&ALICE), None);
assert_eq!(pallet_limit_orders.zero.get(&BOB).unwrap().get(&0), Some(&100));
});
}