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
27 changes: 17 additions & 10 deletions state-chain/amm/src/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_core::{U256, U512};

pub const ONE_IN_HUNDREDTH_PIPS: u32 = 1000000;
pub const ONE_IN_HUNDREDTH_PIPS: u32 = 1_000_000;
pub const MAX_LP_FEE: u32 = ONE_IN_HUNDREDTH_PIPS / 2;

/// Represents an amount of an asset, in its smallest unit i.e. Ethereum has 10^-18 precision, and
/// therefore an `Amount` with the literal value of `1` would represent 10^-18 Ethereum.
Expand All @@ -18,6 +19,12 @@ pub type SqrtPriceQ64F96 = U256;
/// The number of fractional bits used by `SqrtPriceQ64F96`.
pub const SQRT_PRICE_FRACTIONAL_BITS: u32 = 96;

#[derive(Debug)]
pub enum SetFeesError {
/// Fee must be between 0 - 50%
InvalidFeeAmount,
}

#[derive(
Debug,
Clone,
Expand Down Expand Up @@ -381,22 +388,22 @@ 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;

let (integer_log_2, mantissa) = {
let mut _bits_remaining = sqrt_price_q64f128;
let mut most_signifcant_bit = 0u8;
let mut most_significant_bit = 0u8;

// rustfmt chokes when formatting this macro.
// See: https://github.com/rust-lang/rustfmt/issues/5404
#[rustfmt::skip]
macro_rules! add_integer_bit {
($bit:literal, $lower_bits_mask:literal) => {
if _bits_remaining > U256::from($lower_bits_mask) {
most_signifcant_bit |= $bit;
most_significant_bit |= $bit;
_bits_remaining >>= $bit;
}
};
Expand All @@ -412,17 +419,17 @@ pub(super) fn tick_at_sqrt_price(sqrt_price: SqrtPriceQ64F96) -> Tick {
add_integer_bit!(1u8, 0x1u128);

(
// most_signifcant_bit is the log2 of sqrt_price_q64f128 as an integer. This
// converts most_signifcant_bit to the integer log2 of sqrt_price_q64f128 as an
// most_significant_bit is the log2 of sqrt_price_q64f128 as an integer. This
// converts most_significant_bit to the integer log2 of sqrt_price_q64f128 as an
// q64f128
((most_signifcant_bit as i16) + (-128i16)) as i8,
((most_significant_bit as i16) + (-128i16)) as i8,
// Calculate mantissa of sqrt_price_q64f128.
if most_signifcant_bit >= 128u8 {
if most_significant_bit >= 128u8 {
// The bits we possibly drop when right shifting don't contribute to the log2
// above the 14th fractional bit.
sqrt_price_q64f128 >> (most_signifcant_bit - 127u8)
sqrt_price_q64f128 >> (most_significant_bit - 127u8)
} else {
sqrt_price_q64f128 << (127u8 - most_signifcant_bit)
sqrt_price_q64f128 << (127u8 - most_significant_bit)
}
.as_u128(), // Conversion to u128 is safe as top 128 bits are always zero
)
Expand Down
25 changes: 22 additions & 3 deletions state-chain/amm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ use core::convert::Infallible;

use codec::{Decode, Encode};
use common::{
price_to_sqrt_price, sqrt_price_to_price, Amount, OneToZero, Order, Price, Side, SideMap,
SqrtPriceQ64F96, Tick, ZeroToOne,
price_to_sqrt_price, sqrt_price_to_price, Amount, OneToZero, Order, Price, SetFeesError, Side,
SideMap, SqrtPriceQ64F96, Tick, ZeroToOne,
};
use limit_orders::{Collected, PositionInfo};
use range_orders::Liquidity;
use scale_info::TypeInfo;
use sp_std::vec::Vec;
use sp_std::{collections::btree_map::BTreeMap, vec::Vec};

pub mod common;
pub mod limit_orders;
Expand Down Expand Up @@ -343,4 +344,22 @@ impl<LiquidityProvider: Clone + Ord> PoolState<LiquidityProvider> {
),
})
}

#[allow(clippy::type_complexity)]
pub fn set_fees(
&mut self,
fee_hundredth_pips: u32,
) -> Result<
SideMap<BTreeMap<(SqrtPriceQ64F96, LiquidityProvider), (Collected, PositionInfo)>>,
SetFeesError,
> {
self.range_orders.set_fees(fee_hundredth_pips)?;
self.limit_orders.set_fees(fee_hundredth_pips)
}

// Returns if the pool fee is valid.
pub fn validate_fees(fee_hundredth_pips: u32) -> bool {
limit_orders::PoolState::<LiquidityProvider>::validate_fees(fee_hundredth_pips) &&
range_orders::PoolState::<LiquidityProvider>::validate_fees(fee_hundredth_pips)
}
}
31 changes: 14 additions & 17 deletions state-chain/amm/src/limit_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ use sp_std::vec::Vec;

use crate::common::{
is_tick_valid, mul_div_ceil, mul_div_floor, sqrt_price_at_tick, sqrt_price_to_price,
tick_at_sqrt_price, Amount, OneToZero, Price, SideMap, SqrtPriceQ64F96, Tick, ZeroToOne,
ONE_IN_HUNDREDTH_PIPS, PRICE_FRACTIONAL_BITS,
tick_at_sqrt_price, Amount, OneToZero, Price, SetFeesError, SideMap, SqrtPriceQ64F96, Tick,
ZeroToOne, MAX_LP_FEE, ONE_IN_HUNDREDTH_PIPS, PRICE_FRACTIONAL_BITS,
};

// This is the maximum liquidity/amount of an asset that can be sold at a single tick/price. If an
Expand Down Expand Up @@ -170,14 +170,14 @@ impl FloatBetweenZeroAndOne {

let (y_floor, shift_remainder) = Self::right_shift_mod(y_shifted_floor, negative_exponent);

let y_floor = y_floor.try_into().unwrap(); // Unwrap safe as numerator <= demoninator and therefore y cannot be greater than x
let y_floor = y_floor.try_into().unwrap(); // Unwrap safe as numerator <= denominator and therefore y cannot be greater than x

(
y_floor,
if div_remainder.is_zero() && shift_remainder.is_zero() {
y_floor
} else {
y_floor + 1 // Safe as for there to be a remainder y_floor must be atleast 1 less than x
y_floor + 1 // Safe as for there to be a remainder y_floor must be at least 1 less than x
},
)
}
Expand Down Expand Up @@ -247,12 +247,6 @@ pub enum NewError {
InvalidFeeAmount,
}

#[derive(Debug)]
pub enum SetFeesError {
/// Fee must be between 0 - 50%
InvalidFeeAmount,
}

#[derive(Debug)]
pub enum DepthError {
/// Invalid Price
Expand Down Expand Up @@ -355,8 +349,8 @@ pub(super) struct FixedPool {
/// associated position but have some liquidity available, but this would likely be a very
/// small amount.
available: Amount,
/// This is the big product of all `1.0 - percent_used_by_swap` for all swaps that have occured
/// since this FixedPool instance was created and used liquidity from it.
/// This is the big product of all `1.0 - percent_used_by_swap` for all swaps that have
/// occurred since this FixedPool instance was created and used liquidity from it.
percent_remaining: FloatBetweenZeroAndOne,
}

Expand All @@ -383,7 +377,7 @@ impl<LiquidityProvider: Clone + Ord> PoolState<LiquidityProvider> {
///
/// This function never panics.
pub(super) fn new(fee_hundredth_pips: u32) -> Result<Self, NewError> {
(fee_hundredth_pips <= ONE_IN_HUNDREDTH_PIPS / 2)
Self::validate_fees(fee_hundredth_pips)
.then_some(())
.ok_or(NewError::InvalidFeeAmount)?;

Expand All @@ -400,15 +394,14 @@ impl<LiquidityProvider: Clone + Ord> PoolState<LiquidityProvider> {
///
/// This function never panics.
#[allow(clippy::type_complexity)]
#[allow(dead_code)]
pub(super) fn set_fees(
&mut self,
fee_hundredth_pips: u32,
) -> Result<
SideMap<BTreeMap<(SqrtPriceQ64F96, LiquidityProvider), (Collected, PositionInfo)>>,
SetFeesError,
> {
(fee_hundredth_pips <= ONE_IN_HUNDREDTH_PIPS / 2)
Self::validate_fees(fee_hundredth_pips)
.then_some(())
.ok_or(SetFeesError::InvalidFeeAmount)?;

Expand Down Expand Up @@ -563,7 +556,7 @@ impl<LiquidityProvider: Clone + Ord> PoolState<LiquidityProvider> {
// bought_amount and fees than may exist in the pool
position.amount - remaining_amount_ceil,
// We under-estimate remaining liquidity so that lp's cannot burn more liquidity
// than truely exists in the pool
// than truly exists in the pool
if remaining_amount_floor.is_zero() {
None
} else {
Expand Down Expand Up @@ -749,7 +742,7 @@ impl<LiquidityProvider: Clone + Ord> PoolState<LiquidityProvider> {
}

/// Collects any earnings from the specified position. The SwapDirection determines which
/// direction of swaps the liquidity/position you're refering to is for.
/// direction of swaps the liquidity/position you're referring to is for.
///
/// This function never panics.
pub(super) fn collect<SD: SwapDirection>(
Expand Down Expand Up @@ -854,4 +847,8 @@ impl<LiquidityProvider: Clone + Ord> PoolState<LiquidityProvider> {
Err(DepthError::InvalidTickRange)
}
}

pub fn validate_fees(fee_hundredth_pips: u32) -> bool {
fee_hundredth_pips <= MAX_LP_FEE
}
}
21 changes: 7 additions & 14 deletions state-chain/amm/src/range_orders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ use sp_core::{U256, U512};

use crate::common::{
is_sqrt_price_valid, is_tick_valid, mul_div_ceil, mul_div_floor, sqrt_price_at_tick,
tick_at_sqrt_price, Amount, OneToZero, Side, SideMap, SqrtPriceQ64F96, Tick, ZeroToOne,
MAX_TICK, MIN_TICK, ONE_IN_HUNDREDTH_PIPS, SQRT_PRICE_FRACTIONAL_BITS,
tick_at_sqrt_price, Amount, OneToZero, SetFeesError, Side, SideMap, SqrtPriceQ64F96, Tick,
ZeroToOne, MAX_LP_FEE, MAX_TICK, MIN_TICK, ONE_IN_HUNDREDTH_PIPS, SQRT_PRICE_FRACTIONAL_BITS,
};

/// This is the invariant wrt xy = k. It represents / is proportional to the depth of the
Expand Down Expand Up @@ -87,7 +87,7 @@ impl Position {

/*
Proof that `mul_div_floor` does not overflow:
Note position.liqiudity: u128
Note position.liquidity: u128
U512::one() << 128 > u128::MAX
*/
mul_div_floor(
Expand Down Expand Up @@ -148,7 +148,7 @@ pub struct PoolState<LiquidityProvider> {
/// This is the highest tick that represents a strictly lower price than the
/// current_sqrt_price. `current_tick` is the tick that when you swap ZeroToOne the
/// `current_sqrt_price` is moving towards (going down in literal value), and will cross when
/// `current_sqrt_price` reachs it. `current_tick + 1` is the tick the price is moving towards
/// `current_sqrt_price` reaches it. `current_tick + 1` is the tick the price is moving towards
/// (going up in literal value) when you swap OneToZero and will cross when
/// `current_sqrt_price` reaches it,
current_tick: Tick,
Expand Down Expand Up @@ -338,12 +338,6 @@ pub enum NewError {
InvalidInitialPrice,
}

#[derive(Debug)]
pub enum SetFeesError {
/// Fee must be between 0 - 50%
InvalidFeeAmount,
}

#[derive(Debug)]
pub enum MintError<E> {
/// One of the start/end ticks of the range reached its maximum gross liquidity
Expand Down Expand Up @@ -473,7 +467,6 @@ impl<LiquidityProvider: Clone + Ord> PoolState<LiquidityProvider> {
/// fee is greater than 50%.
///
/// This function never panics
#[allow(dead_code)]
pub(super) fn set_fees(&mut self, fee_hundredth_pips: u32) -> Result<(), SetFeesError> {
Self::validate_fees(fee_hundredth_pips)
.then_some(())
Expand All @@ -482,8 +475,8 @@ impl<LiquidityProvider: Clone + Ord> PoolState<LiquidityProvider> {
Ok(())
}

fn validate_fees(fee_hundredth_pips: u32) -> bool {
fee_hundredth_pips <= ONE_IN_HUNDREDTH_PIPS / 2
pub fn validate_fees(fee_hundredth_pips: u32) -> bool {
fee_hundredth_pips <= MAX_LP_FEE
}

/// Returns the current sqrt price of the pool. None if the pool has no more liquidity and the
Expand Down Expand Up @@ -653,7 +646,7 @@ impl<LiquidityProvider: Clone + Ord> PoolState<LiquidityProvider> {
let (amounts_owed, current_liquidity_delta) =
self.inner_liquidity_to_amounts::<false>(burnt_liquidity, lower_tick, upper_tick);
// Will not underflow as current_liquidity_delta must have previously been added to
// current_liquidity for it to need to be substrated now
// current_liquidity for it to need to be subtracted now
self.current_liquidity -= current_liquidity_delta;

if lower_delta.liquidity_gross == 0 &&
Expand Down
47 changes: 47 additions & 0 deletions state-chain/pallets/cf-pools/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,53 @@ benchmarks! {
)
verify {}

set_pool_fees {
let caller = new_lp_account::<T>();
assert_ok!(Pallet::<T>::new_pool(T::EnsureGovernance::try_successful_origin().unwrap(), Asset::Eth, Asset::Usdc, 0, price_at_tick(0).unwrap()));
assert_ok!(T::LpBalance::try_credit_account(
&caller,
Asset::Eth,
1_000_000,
));
assert_ok!(T::LpBalance::try_credit_account(
&caller,
Asset::Usdc,
1_000_000,
));
assert_ok!(Pallet::<T>::set_limit_order(
RawOrigin::Signed(caller.clone()).into(),
Asset::Usdc,
Asset::Eth,
0,
Some(0),
10_000,
));
assert_ok!(Pallet::<T>::set_limit_order(
RawOrigin::Signed(caller.clone()).into(),
Asset::Eth,
Asset::Usdc,
1,
Some(0),
10_000,
));
assert_ok!(Pallet::<T>::swap_with_network_fee(STABLE_ASSET, Asset::Eth, 1_000));
let fee = 1_000;
let call = Call::<T>::set_pool_fees {
base_asset: Asset::Eth,
pair_asset: Asset::Usdc,
fee_hundredth_pips: fee,
};
}: { let _ = call.dispatch_bypass_filter(T::EnsureGovernance::try_successful_origin().unwrap()); }
verify {
assert_eq!(
Pallet::<T>::pool_info(Asset::Eth, STABLE_ASSET),
Some(PoolInfo {
limit_order_fee_hundredth_pips: fee,
range_order_fee_hundredth_pips: fee,
})
);
}

impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(),
Expand Down
Loading