From a249ac879b1d7b62327db1b6fe50cf52ff5c7ff7 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Wed, 27 Sep 2023 13:52:08 +1300 Subject: [PATCH 01/13] Added function to set liquidity pool fees. Changing the pool fees will collect all fees and bought amount for LPer and credit them to Lpers' account. --- state-chain/amm/src/common.rs | 9 +- state-chain/amm/src/lib.rs | 19 +- state-chain/amm/src/limit_orders.rs | 13 +- state-chain/amm/src/range_orders.rs | 13 +- state-chain/pallets/cf-pools/src/lib.rs | 59 ++++- state-chain/pallets/cf-pools/src/mock.rs | 46 +++- state-chain/pallets/cf-pools/src/tests.rs | 267 +++++++++++++++----- state-chain/pallets/cf-pools/src/weights.rs | 9 + 8 files changed, 342 insertions(+), 93 deletions(-) diff --git a/state-chain/amm/src/common.rs b/state-chain/amm/src/common.rs index 12e34b492f..25f6711f21 100644 --- a/state-chain/amm/src/common.rs +++ b/state-chain/amm/src/common.rs @@ -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. @@ -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, diff --git a/state-chain/amm/src/lib.rs b/state-chain/amm/src/lib.rs index 0880273742..337f879e79 100644 --- a/state-chain/amm/src/lib.rs +++ b/state-chain/amm/src/lib.rs @@ -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; @@ -343,4 +344,16 @@ impl PoolState { ), }) } + + #[allow(clippy::type_complexity)] + pub fn set_fees( + &mut self, + fee_hundredth_pips: u32, + ) -> Result< + SideMap>, + SetFeesError, + > { + self.range_orders.set_fees(fee_hundredth_pips)?; + self.limit_orders.set_fees(fee_hundredth_pips) + } } diff --git a/state-chain/amm/src/limit_orders.rs b/state-chain/amm/src/limit_orders.rs index 0a2b8306bb..8ecbd642d2 100644 --- a/state-chain/amm/src/limit_orders.rs +++ b/state-chain/amm/src/limit_orders.rs @@ -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 @@ -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 @@ -383,7 +377,7 @@ impl PoolState { /// /// This function never panics. pub(super) fn new(fee_hundredth_pips: u32) -> Result { - (fee_hundredth_pips <= ONE_IN_HUNDREDTH_PIPS / 2) + (fee_hundredth_pips <= MAX_LP_FEE) .then_some(()) .ok_or(NewError::InvalidFeeAmount)?; @@ -400,7 +394,6 @@ impl PoolState { /// /// This function never panics. #[allow(clippy::type_complexity)] - #[allow(dead_code)] pub(super) fn set_fees( &mut self, fee_hundredth_pips: u32, diff --git a/state-chain/amm/src/range_orders.rs b/state-chain/amm/src/range_orders.rs index 5c1e7a11e5..f20bb06be9 100644 --- a/state-chain/amm/src/range_orders.rs +++ b/state-chain/amm/src/range_orders.rs @@ -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 @@ -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 { /// One of the start/end ticks of the range reached its maximum gross liquidity @@ -473,7 +467,6 @@ impl PoolState { /// 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(()) @@ -483,7 +476,7 @@ impl PoolState { } fn validate_fees(fee_hundredth_pips: u32) -> bool { - fee_hundredth_pips <= ONE_IN_HUNDREDTH_PIPS / 2 + fee_hundredth_pips <= MAX_LP_FEE } /// Returns the current sqrt price of the pool. None if the pool has no more liquidity and the diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index 7a2ecbd525..a827ca8e42 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -2,7 +2,7 @@ use core::ops::Range; use cf_amm::{ - common::{Amount, Order, Price, Side, SideMap, Tick}, + common::{Amount, Order, Price, Side, SideMap, Tick, MAX_LP_FEE}, limit_orders, range_orders, range_orders::Liquidity, PoolState, @@ -520,6 +520,11 @@ pub mod pallet { input_amount: AssetAmount, output_amount: AssetAmount, }, + PoolFeeSet { + base_asset: Asset, + pair_asset: Asset, + fee_hundredth_pips: u32, + }, } #[pallet::call] @@ -928,6 +933,58 @@ pub mod pallet { Ok(()) }) } + + /// Sets the Liquidirt Pool fees. The fee must be >= 50%. + /// Requires governance origin. + /// + /// ## Events + /// + /// - [On success](Event::PoolFeeSet) + /// + /// ## Errors + /// + /// - [BadOrigin](frame_system::BadOrigin) + /// - [InvalidFeeAmount](pallet_cf_pools::Error::InvalidFeeAmount) + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::set_pool_fees())] + pub fn set_pool_fees( + origin: OriginFor, + base_asset: Asset, + pair_asset: Asset, + fee_hundredth_pips: u32, + ) -> DispatchResult { + T::EnsureGovernance::ensure_origin(origin)?; + ensure!(fee_hundredth_pips <= MAX_LP_FEE, Error::::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 + .set_fees(fee_hundredth_pips) + .map_err(|_| Error::::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(()) + })?; + + Self::deposit_event(Event::::PoolFeeSet { + base_asset, + pair_asset, + fee_hundredth_pips, + }); + + Ok(()) + } } } diff --git a/state-chain/pallets/cf-pools/src/mock.rs b/state-chain/pallets/cf-pools/src/mock.rs index a6b49b9481..1d6fb46ce2 100644 --- a/state-chain/pallets/cf-pools/src/mock.rs +++ b/state-chain/pallets/cf-pools/src/mock.rs @@ -1,16 +1,21 @@ use crate::{self as pallet_cf_pools, PalletSafeMode}; -use cf_traits::{impl_mock_chainflip, impl_mock_runtime_safe_mode, AccountRoleRegistry}; +use cf_primitives::{Asset, AssetAmount}; +use cf_traits::{ + impl_mock_chainflip, impl_mock_runtime_safe_mode, AccountRoleRegistry, LpBalanceApi, +}; use frame_support::parameter_types; use frame_system as system; use sp_core::H256; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, - Permill, + DispatchResult, Permill, }; type AccountId = u64; pub const ALICE: ::AccountId = 123u64; +pub const BOB: ::AccountId = 124u64; + type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. @@ -57,11 +62,43 @@ impl_mock_chainflip!(Test); parameter_types! { // 20 Basis Points pub static NetworkFee: Permill = Permill::from_perthousand(2); + pub static AliceCollectedEth: AssetAmount = Default::default(); + pub static AliceCollectedUsdc: AssetAmount = Default::default(); + pub static BobCollectedEth: AssetAmount = Default::default(); + pub static BobCollectedUsdc: AssetAmount = Default::default(); } +pub struct MockBalance; +impl LpBalanceApi for MockBalance { + type AccountId = AccountId; + + fn try_credit_account( + who: &Self::AccountId, + asset: cf_primitives::Asset, + amount: cf_primitives::AssetAmount, + ) -> DispatchResult { + match (*who, asset) { + (ALICE, Asset::Eth) => AliceCollectedEth::set(AliceCollectedEth::get() + amount), + (ALICE, Asset::Usdc) => AliceCollectedUsdc::set(AliceCollectedUsdc::get() + amount), + (BOB, Asset::Eth) => BobCollectedEth::set(BobCollectedEth::get() + amount), + (BOB, Asset::Usdc) => BobCollectedUsdc::set(BobCollectedUsdc::get() + amount), + _ => (), + } + Ok(()) + } + + fn try_debit_account( + _who: &Self::AccountId, + _asset: cf_primitives::Asset, + _amount: cf_primitives::AssetAmount, + ) -> sp_runtime::DispatchResult { + Ok(()) + } +} + impl_mock_runtime_safe_mode!(pools: PalletSafeMode); impl pallet_cf_pools::Config for Test { type RuntimeEvent = RuntimeEvent; - type LpBalance = Self; + type LpBalance = MockBalance; type NetworkFee = NetworkFee; type SafeMode = MockRuntimeSafeMode; type WeightInfo = (); @@ -74,5 +111,8 @@ cf_test_utilities::impl_test_helpers! { frame_support::assert_ok!(>::register_as_liquidity_provider( &ALICE, )); + frame_support::assert_ok!(>::register_as_liquidity_provider( + &BOB, + )); } } diff --git a/state-chain/pallets/cf-pools/src/tests.rs b/state-chain/pallets/cf-pools/src/tests.rs index c2dd09f731..89a460878d 100644 --- a/state-chain/pallets/cf-pools/src/tests.rs +++ b/state-chain/pallets/cf-pools/src/tests.rs @@ -1,9 +1,9 @@ use crate::{ - mock::*, utilities, AssetAmounts, CanonicalAssetPair, CollectedNetworkFee, Error, - FlipBuyInterval, FlipToBurn, Pools, RangeOrderSize, STABLE_ASSET, + mock::*, utilities, AssetAmounts, 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}; +use cf_primitives::{chains::assets::any::Asset, AssetAmount, SwapOutput}; use cf_test_utilities::assert_events_match; use frame_support::{assert_noop, assert_ok, traits::Hooks}; use frame_system::pallet_prelude::BlockNumberFor; @@ -273,100 +273,237 @@ fn test_network_fee_calculation() { }); } -/* #[test] -fn can_update_liquidity_fee() { +fn can_update_pool_liquidity_fee() { new_test_ext().execute_with(|| { - let range = -100..100; - let unstable_asset = Asset::Eth; - let default_price = price_at_tick(0).unwrap(); - + let old_fee = 400_000u32; + let new_fee = 100_000u32; // Create a new pool. assert_ok!(LiquidityPools::new_pool( RuntimeOrigin::root(), - unstable_asset, - 500_000u32, - default_price, + Asset::Eth, + STABLE_ASSET, + old_fee, + price_at_tick(0).unwrap(), + )); + assert_eq!( + LiquidityPools::pool_info(Asset::Eth, STABLE_ASSET), + Some(PoolInfo { + limit_order_fee_hundredth_pips: old_fee, + range_order_fee_hundredth_pips: old_fee, + }) + ); + + // 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, )); - assert_ok!(LiquidityPools::collect_and_mint_range_order( + assert_ok!(LiquidityPools::set_limit_order( RuntimeOrigin::signed(ALICE), - unstable_asset, - range, - 1_000_000, + STABLE_ASSET, + Asset::Eth, + 1, + Some(0), + 5_000, )); + assert_ok!(LiquidityPools::set_limit_order( + RuntimeOrigin::signed(BOB), + Asset::Eth, + STABLE_ASSET, + 0, + Some(0), + 10_000, + )); + assert_ok!(LiquidityPools::set_limit_order( + RuntimeOrigin::signed(BOB), + STABLE_ASSET, + Asset::Eth, + 1, + Some(0), + 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![(1, 0, 5000u128.into())] + }, + range_orders: vec![] + }) + ); + assert_eq!( + LiquidityPools::pool_orders(Asset::Eth, STABLE_ASSET, &BOB,), + Some(PoolOrders { + limit_orders: AssetsMap { + base: vec![(0, 0, 10_000u128.into())], + pair: vec![(1, 0, 10_000u128.into())] + }, + range_orders: vec![] + }) + ); - assert_ok!(LiquidityPools::swap(unstable_asset, Asset::Usdc, 1_000)); + // Do some swaps to collect fees. + assert_eq!( + LiquidityPools::swap_with_network_fee(STABLE_ASSET, Asset::Eth, 10_000).unwrap(), + SwapOutput { intermediary: None, output: 5_988u128 } + ); + assert_eq!( + LiquidityPools::swap_with_network_fee(Asset::Eth, STABLE_ASSET, 10_000).unwrap(), + SwapOutput { intermediary: None, output: 5_988u128 } + ); - // Current swap fee is 50% - System::assert_has_event(RuntimeEvent::LiquidityPools(crate::Event::AssetSwapped { - from: Asset::Flip, - to: Asset::Usdc, - input_amount: 1000, - output_amount: 499, + // 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, + new_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!(AliceCollectedUsdc::get(), 3_333u128); + assert_eq!(BobCollectedEth::get(), 6_666u128); + assert_eq!(BobCollectedUsdc::get(), 6_666u128); + + // New pool fee is set and event emitted. + assert_eq!( + LiquidityPools::pool_info(Asset::Eth, STABLE_ASSET), + Some(PoolInfo { + limit_order_fee_hundredth_pips: new_fee, + range_order_fee_hundredth_pips: new_fee, + }) + ); + System::assert_has_event(RuntimeEvent::LiquidityPools(crate::Event::::PoolFeeSet { + base_asset: Asset::Eth, + pair_asset: STABLE_ASSET, + fee_hundredth_pips: new_fee, })); - // Fee must be within the allowable range. - assert_noop!( - LiquidityPools::set_liquidity_fee(RuntimeOrigin::root(), unstable_asset, 500001u32), - Error::::InvalidFeeAmount + // Collected fees and bought amount are reset and position updated. + // Alice's remaining liquidity = 5_000 - 2_000 + // Bob's remaining liquidity = 10_000 - 4_000 + assert_eq!( + LiquidityPools::pool_orders(Asset::Eth, STABLE_ASSET, &ALICE,), + Some(PoolOrders { + limit_orders: AssetsMap { + base: vec![(0, 0, 3_000u128.into())], + pair: vec![(1, 0, 3_000u128.into())] + }, + range_orders: vec![] + }) + ); + assert_eq!( + LiquidityPools::pool_orders(Asset::Eth, STABLE_ASSET, &BOB,), + Some(PoolOrders { + limit_orders: AssetsMap { + base: vec![(0, 0, 6_000u128.into())], + pair: vec![(1, 0, 6_000u128.into())] + }, + range_orders: vec![] + }) ); - // Set the fee to 0% - assert_ok!(LiquidityPools::set_liquidity_fee(RuntimeOrigin::root(), unstable_asset, 0u32)); - System::assert_last_event(RuntimeEvent::LiquidityPools( - crate::Event::LiquidityFeeUpdated { - unstable_asset: Asset::Flip, - fee_hundredth_pips: 0u32, - }, + // Setting the pool fees will collect nothing, since all positions are reset/refreshed. + AliceCollectedEth::set(0u128); + AliceCollectedUsdc::set(0u128); + BobCollectedEth::set(0u128); + BobCollectedUsdc::set(0u128); + assert_ok!(LiquidityPools::set_pool_fees( + RuntimeOrigin::root(), + Asset::Eth, + STABLE_ASSET, + new_fee )); - System::reset_events(); - assert_ok!(LiquidityPools::swap(unstable_asset, Asset::Usdc, 1_000)); - - // Current swap fee is now 0% - System::assert_has_event(RuntimeEvent::LiquidityPools(crate::Event::AssetSwapped { - from: Asset::Flip, - to: Asset::Usdc, - input_amount: 1000, - output_amount: 998, - })); + // No fees are collected. + assert_eq!(AliceCollectedEth::get(), 0u128); + assert_eq!(AliceCollectedUsdc::get(), 0u128); + assert_eq!(BobCollectedEth::get(), 0u128); + assert_eq!(BobCollectedUsdc::get(), 0u128); }); } #[test] -fn can_get_liquidity_and_positions() { +fn setting_the_same_pool_fee_does_nothing() { new_test_ext().execute_with(|| { - let range_1 = -100..100; - let range_2 = -50..200; - let unstable_asset = Asset::Flip; - let default_price = price_at_tick(0).unwrap(); + let fee = 400_000u32; // Create a new pool. assert_ok!(LiquidityPools::new_pool( RuntimeOrigin::root(), - unstable_asset, - 500_000u32, - default_price, + Asset::Eth, + STABLE_ASSET, + 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, + }) + ); - assert_ok!(LiquidityPools::collect_and_mint_range_order( + // Setup liquidity for the pool + assert_ok!(LiquidityPools::set_limit_order( RuntimeOrigin::signed(ALICE), - unstable_asset, - range_1, - 1_000, + Asset::Eth, + STABLE_ASSET, + 0, + Some(0), + 5_000, )); - assert_ok!(LiquidityPools::collect_and_mint_range_order( - RuntimeOrigin::signed(ALICE), - unstable_asset, - range_2, - 2_000, + + assert_eq!( + LiquidityPools::pool_orders(Asset::Eth, STABLE_ASSET, &ALICE,), + Some(PoolOrders { + limit_orders: AssetsMap { base: vec![(0, 0, 5000u128.into())], pair: vec![] }, + range_orders: vec![] + }) + ); + + // 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 } + ); + + // Setting the fee as the same value do nothing. No fee is collected. + assert_ok!(LiquidityPools::set_pool_fees( + RuntimeOrigin::root(), + Asset::Eth, + STABLE_ASSET, + fee )); + 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::minted_positions(&ALICE, &unstable_asset), - vec![(range_1.lower, range_1.upper, 1_000), (range_2.lower, range_2.upper, 2_000),] + 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![] + }) ); - assert_eq!(LiquidityPools::minted_positions(&[1u8; 32].into(), &unstable_asset), vec![]); }); } -*/ diff --git a/state-chain/pallets/cf-pools/src/weights.rs b/state-chain/pallets/cf-pools/src/weights.rs index d906573bd5..e224f3716c 100644 --- a/state-chain/pallets/cf-pools/src/weights.rs +++ b/state-chain/pallets/cf-pools/src/weights.rs @@ -39,6 +39,7 @@ pub trait WeightInfo { fn set_range_order() -> Weight; fn update_limit_order() -> Weight; fn set_limit_order() -> Weight; + fn set_pool_fees() -> Weight; } /// Weights for pallet_cf_pools using the Substrate node and recommended hardware. @@ -144,6 +145,10 @@ impl WeightInfo for PalletWeight { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } + + fn set_pool_fees() -> Weight { + Default::default() + } } // For backwards compatibility and tests @@ -248,4 +253,8 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } + + fn set_pool_fees() -> Weight { + Default::default() + } } From 915ef88e2627db133357f35854683dffd2536eda Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Wed, 27 Sep 2023 14:42:34 +1300 Subject: [PATCH 02/13] Slight improvement --- state-chain/amm/src/lib.rs | 6 ++++++ state-chain/amm/src/limit_orders.rs | 6 +++++- state-chain/amm/src/range_orders.rs | 2 +- state-chain/pallets/cf-pools/src/lib.rs | 4 ++-- 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/state-chain/amm/src/lib.rs b/state-chain/amm/src/lib.rs index 337f879e79..0c1278fd4d 100644 --- a/state-chain/amm/src/lib.rs +++ b/state-chain/amm/src/lib.rs @@ -356,4 +356,10 @@ impl PoolState { 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::::validate_fees(fee_hundredth_pips) && + range_orders::PoolState::::validate_fees(fee_hundredth_pips) + } } diff --git a/state-chain/amm/src/limit_orders.rs b/state-chain/amm/src/limit_orders.rs index 8ecbd642d2..44c52da448 100644 --- a/state-chain/amm/src/limit_orders.rs +++ b/state-chain/amm/src/limit_orders.rs @@ -401,7 +401,7 @@ impl PoolState { SideMap>, SetFeesError, > { - (fee_hundredth_pips <= ONE_IN_HUNDREDTH_PIPS / 2) + Self::validate_fees(fee_hundredth_pips) .then_some(()) .ok_or(SetFeesError::InvalidFeeAmount)?; @@ -847,4 +847,8 @@ impl PoolState { Err(DepthError::InvalidTickRange) } } + + pub fn validate_fees(fee_hundredth_pips: u32) -> bool { + fee_hundredth_pips <= MAX_LP_FEE + } } diff --git a/state-chain/amm/src/range_orders.rs b/state-chain/amm/src/range_orders.rs index f20bb06be9..78c64c2b16 100644 --- a/state-chain/amm/src/range_orders.rs +++ b/state-chain/amm/src/range_orders.rs @@ -475,7 +475,7 @@ impl PoolState { Ok(()) } - fn validate_fees(fee_hundredth_pips: u32) -> bool { + pub fn validate_fees(fee_hundredth_pips: u32) -> bool { fee_hundredth_pips <= MAX_LP_FEE } diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index a827ca8e42..6b6647a3f6 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -2,7 +2,7 @@ use core::ops::Range; use cf_amm::{ - common::{Amount, Order, Price, Side, SideMap, Tick, MAX_LP_FEE}, + common::{Amount, Order, Price, Side, SideMap, Tick}, limit_orders, range_orders, range_orders::Liquidity, PoolState, @@ -954,7 +954,7 @@ pub mod pallet { fee_hundredth_pips: u32, ) -> DispatchResult { T::EnsureGovernance::ensure_origin(origin)?; - ensure!(fee_hundredth_pips <= MAX_LP_FEE, Error::::InvalidFeeAmount); + ensure!(PoolState::<(T::AccountId, OrderId)>::validate_fees(fee_hundredth_pips), Error::::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 From b4d2e54305bde6dd9538d800b7d995a239285eee Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Wed, 27 Sep 2023 14:42:48 +1300 Subject: [PATCH 03/13] cargo fmt --- state-chain/amm/src/lib.rs | 6 +++--- state-chain/pallets/cf-pools/src/lib.rs | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/state-chain/amm/src/lib.rs b/state-chain/amm/src/lib.rs index 0c1278fd4d..07ba80fe15 100644 --- a/state-chain/amm/src/lib.rs +++ b/state-chain/amm/src/lib.rs @@ -356,10 +356,10 @@ impl PoolState { 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::::validate_fees(fee_hundredth_pips) && - range_orders::PoolState::::validate_fees(fee_hundredth_pips) + limit_orders::PoolState::::validate_fees(fee_hundredth_pips) && + range_orders::PoolState::::validate_fees(fee_hundredth_pips) } } diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index 6b6647a3f6..d2aaec1b69 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -954,7 +954,10 @@ pub mod pallet { fee_hundredth_pips: u32, ) -> DispatchResult { T::EnsureGovernance::ensure_origin(origin)?; - ensure!(PoolState::<(T::AccountId, OrderId)>::validate_fees(fee_hundredth_pips), Error::::InvalidFeeAmount); + ensure!( + PoolState::<(T::AccountId, OrderId)>::validate_fees(fee_hundredth_pips), + Error::::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 From 312126fddd8051efad735b8d201f23786789917f Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Wed, 27 Sep 2023 16:28:57 +1300 Subject: [PATCH 04/13] Fixed some typo Added benchmarking for setting pool fees --- state-chain/amm/src/common.rs | 16 +- state-chain/amm/src/limit_orders.rs | 14 +- state-chain/amm/src/range_orders.rs | 6 +- .../pallets/cf-pools/src/benchmarking.rs | 47 ++ state-chain/pallets/cf-pools/src/lib.rs | 11 +- .../pallets/cf-swapping/src/weights.rs | 401 ++++++++++++------ 6 files changed, 332 insertions(+), 163 deletions(-) diff --git a/state-chain/amm/src/common.rs b/state-chain/amm/src/common.rs index 25f6711f21..950c7bea46 100644 --- a/state-chain/amm/src/common.rs +++ b/state-chain/amm/src/common.rs @@ -395,7 +395,7 @@ pub(super) fn tick_at_sqrt_price(sqrt_price: SqrtPriceQ64F96) -> Tick { 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 @@ -403,7 +403,7 @@ pub(super) fn tick_at_sqrt_price(sqrt_price: SqrtPriceQ64F96) -> Tick { 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; } }; @@ -419,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 ) diff --git a/state-chain/amm/src/limit_orders.rs b/state-chain/amm/src/limit_orders.rs index 44c52da448..2f7343f140 100644 --- a/state-chain/amm/src/limit_orders.rs +++ b/state-chain/amm/src/limit_orders.rs @@ -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 }, ) } @@ -349,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, } @@ -377,7 +377,7 @@ impl PoolState { /// /// This function never panics. pub(super) fn new(fee_hundredth_pips: u32) -> Result { - (fee_hundredth_pips <= MAX_LP_FEE) + Self::validate_fees(fee_hundredth_pips) .then_some(()) .ok_or(NewError::InvalidFeeAmount)?; @@ -556,7 +556,7 @@ impl PoolState { // 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 { @@ -742,7 +742,7 @@ impl PoolState { } /// 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( diff --git a/state-chain/amm/src/range_orders.rs b/state-chain/amm/src/range_orders.rs index 78c64c2b16..34891ca806 100644 --- a/state-chain/amm/src/range_orders.rs +++ b/state-chain/amm/src/range_orders.rs @@ -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( @@ -148,7 +148,7 @@ pub struct PoolState { /// 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, @@ -646,7 +646,7 @@ impl PoolState { let (amounts_owed, current_liquidity_delta) = self.inner_liquidity_to_amounts::(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 && diff --git a/state-chain/pallets/cf-pools/src/benchmarking.rs b/state-chain/pallets/cf-pools/src/benchmarking.rs index 56b14cb770..ffe7592fe8 100644 --- a/state-chain/pallets/cf-pools/src/benchmarking.rs +++ b/state-chain/pallets/cf-pools/src/benchmarking.rs @@ -170,6 +170,53 @@ benchmarks! { ) verify {} + set_pool_fees { + let caller = new_lp_account::(); + assert_ok!(Pallet::::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::::set_limit_order( + RawOrigin::Signed(caller.clone()).into(), + Asset::Usdc, + Asset::Eth, + 0, + Some(0), + 10_000, + )); + assert_ok!(Pallet::::set_limit_order( + RawOrigin::Signed(caller.clone()).into(), + Asset::Eth, + Asset::Usdc, + 1, + Some(0), + 10_000, + )); + assert_ok!(Pallet::::swap_with_network_fee(STABLE_ASSET, Asset::Eth, 1_000)); + let fee = 1_000; + let call = Call::::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::::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(), diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index d2aaec1b69..08b1cfe72d 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -294,7 +294,7 @@ pub mod pallet { /// Represents an amount of liquidity, either as an exact amount, or through maximum and minimum /// amounts of both assets. Internally those max/min are converted into exact liquidity amounts, - /// that is if the appropiate asset ratio can be achieved while maintaining the max/min bounds. + /// that is if the appropriate asset ratio can be achieved while maintaining the max/min bounds. #[derive( Copy, Clone, @@ -313,7 +313,7 @@ pub mod pallet { Liquidity { liquidity: Liquidity }, } - /// Indicates if an LP wishs to increase or decreease the size of an order. + /// Indicates if an LP wishes to increase or decrease the size of an order. #[derive( Copy, Clone, @@ -796,7 +796,7 @@ pub mod pallet { } /// Optionally move the order to a different tick and then increase or decrease its amount - /// of liquidity. The appropiate assets will be debited or credited from your balance as + /// of liquidity. The appropriate assets will be debited or credited from your balance as /// needed. If the order_id isn't being used at the moment you must specify a tick, /// otherwise it will not know what tick you want the order to be over. Note limit order /// order_id's are independent of range order order_id's. In addition to that, order_id's @@ -872,7 +872,7 @@ pub mod pallet { } /// Optionally move the order to a different tick and then set its amount of liquidity. The - /// appropiate assets will be debited or credited from your balance as needed. If the + /// appropriate assets will be debited or credited from your balance as needed. If the /// order_id isn't being used at the moment you must specify a tick, otherwise it will not /// know what tick you want the order to be over. Note limit order order_id's are /// independent of range order order_id's. In addition to that, order_id's for buy and sell @@ -934,7 +934,8 @@ pub mod pallet { }) } - /// Sets the Liquidirt Pool fees. The fee must be >= 50%. + /// Sets the Liquidity Pool fees. Also collect earned fees and bought amount for + /// all positions within the fee and accredit them to the liquidity provider. /// Requires governance origin. /// /// ## Events diff --git a/state-chain/pallets/cf-swapping/src/weights.rs b/state-chain/pallets/cf-swapping/src/weights.rs index e3a61e8c1a..2aadd306c5 100644 --- a/state-chain/pallets/cf-swapping/src/weights.rs +++ b/state-chain/pallets/cf-swapping/src/weights.rs @@ -2,9 +2,10 @@ //! Autogenerated weights for pallet_cf_swapping //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-04-21, STEPS: `20`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2023-09-27, STEPS: `20`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `Roys-MacBook-Pro.local`, CPU: `` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 +//! EXECUTION: , WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 // Executed Command: // ./target/release/chainflip-node @@ -24,9 +25,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_cf_swapping. pub trait WeightInfo { @@ -43,169 +45,288 @@ pub trait WeightInfo { /// Weights for pallet_cf_swapping using the Substrate node and recommended hardware. pub struct PalletWeight(PhantomData); impl WeightInfo for PalletWeight { - // Storage: AccountRoles AccountRoles (r:1 w:0) - // Storage: EthereumIngressEgress AddressPool (r:1 w:0) - // Storage: EthereumIngressEgress ChannelIdCounter (r:1 w:1) - // Storage: Environment EthereumVaultAddress (r:1 w:0) - // Storage: Swapping SwapTTL (r:1 w:0) - // Storage: Swapping SwapChannelExpiries (r:1 w:1) - // Storage: EthereumIngressEgress ChannelActions (r:0 w:1) - // Storage: EthereumIngressEgress FetchParamDetails (r:0 w:1) - // Storage: EthereumIngressEgress AddressStatus (r:0 w:1) - // Storage: EthereumIngressEgress DepositAddressDetailsLookup (r:0 w:1) + /// Storage: `Environment::RuntimeSafeMode` (r:1 w:0) + /// Proof: `Environment::RuntimeSafeMode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `AccountRoles::AccountRoles` (r:1 w:0) + /// Proof: `AccountRoles::AccountRoles` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + /// Storage: `Swapping::SwapTTL` (r:1 w:0) + /// Proof: `Swapping::SwapTTL` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::DepositChannelPool` (r:1 w:0) + /// Proof: `EthereumIngressEgress::DepositChannelPool` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::ChannelIdCounter` (r:1 w:1) + /// Proof: `EthereumIngressEgress::ChannelIdCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Environment::EthereumVaultAddress` (r:1 w:0) + /// Proof: `Environment::EthereumVaultAddress` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `EthereumChainTracking::CurrentChainState` (r:1 w:0) + /// Proof: `EthereumChainTracking::CurrentChainState` (`max_values`: Some(1), `max_size`: Some(40), added: 535, mode: `MaxEncodedLen`) + /// Storage: `Swapping::SwapChannelExpiries` (r:1 w:1) + /// Proof: `Swapping::SwapChannelExpiries` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::ChannelActions` (r:0 w:1) + /// Proof: `EthereumIngressEgress::ChannelActions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::DepositChannelLookup` (r:0 w:1) + /// Proof: `EthereumIngressEgress::DepositChannelLookup` (`max_values`: None, `max_size`: None, mode: `Measured`) fn request_swap_deposit_address() -> Weight { - // Minimum execution time: 53_000 nanoseconds. - Weight::from_parts(55_000_000, 0) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `1031` + // Estimated: `4496` + // Minimum execution time: 47_000_000 picoseconds. + Weight::from_parts(48_000_000, 4496) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - - - // Storage: Environment CurrentSystemState (r:1 w:0) - // Storage: AccountRoles AccountRoles (r:1 w:0) - // Storage: Swapping EarnedBrokerFees (r:1 w:1) - // Storage: EthereumIngressEgress EgressIdCounter (r:1 w:1) - // Storage: EthereumIngressEgress ScheduledEgressFetchOrTransfer (r:1 w:1) + /// Storage: `Environment::RuntimeSafeMode` (r:1 w:0) + /// Proof: `Environment::RuntimeSafeMode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `AccountRoles::AccountRoles` (r:1 w:0) + /// Proof: `AccountRoles::AccountRoles` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + /// Storage: `Swapping::EarnedBrokerFees` (r:1 w:1) + /// Proof: `Swapping::EarnedBrokerFees` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::EgressIdCounter` (r:1 w:1) + /// Proof: `EthereumIngressEgress::EgressIdCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::ScheduledEgressFetchOrTransfer` (r:1 w:1) + /// Proof: `EthereumIngressEgress::ScheduledEgressFetchOrTransfer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn withdraw() -> Weight { - // Minimum execution time: 41_000 nanoseconds. - Weight::from_parts(42_000_000, 0) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `904` + // Estimated: `4369` + // Minimum execution time: 33_000_000 picoseconds. + Weight::from_parts(34_000_000, 4369) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Swapping SwapIdCounter (r:1 w:1) - // Storage: Swapping SwapQueue (r:1 w:1) + /// Storage: `Environment::RuntimeSafeMode` (r:1 w:0) + /// Proof: `Environment::RuntimeSafeMode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `AccountRoles::SwappingEnabled` (r:1 w:0) + /// Proof: `AccountRoles::SwappingEnabled` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `AccountRoles::AccountRoles` (r:1 w:1) + /// Proof: `AccountRoles::AccountRoles` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + fn register_as_broker() -> Weight { + // Proof Size summary in bytes: + // Measured: `680` + // Estimated: `3498` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 3498) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Swapping::MinimumSwapAmount` (r:1 w:0) + /// Proof: `Swapping::MinimumSwapAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::SwapIdCounter` (r:1 w:1) + /// Proof: `Swapping::SwapIdCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::SwapQueue` (r:1 w:1) + /// Proof: `Swapping::SwapQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn schedule_swap_from_contract() -> Weight { - // Minimum execution time: 19_000 nanoseconds. - Weight::from_parts(20_000_000, 0) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Swapping CcmIdCounter (r:1 w:1) - // Storage: Swapping SwapIdCounter (r:1 w:1) - // Storage: Swapping SwapQueue (r:1 w:1) - // Storage: Swapping CcmOutputs (r:0 w:1) - // Storage: Swapping PendingCcms (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `208` + // Estimated: `3673` + // Minimum execution time: 17_000_000 picoseconds. + Weight::from_parts(18_000_000, 3673) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: `Swapping::MinimumSwapAmount` (r:1 w:0) + /// Proof: `Swapping::MinimumSwapAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::CcmIdCounter` (r:1 w:1) + /// Proof: `Swapping::CcmIdCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::SwapIdCounter` (r:1 w:1) + /// Proof: `Swapping::SwapIdCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::SwapQueue` (r:1 w:1) + /// Proof: `Swapping::SwapQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::CcmOutputs` (r:0 w:1) + /// Proof: `Swapping::CcmOutputs` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::PendingCcms` (r:0 w:1) + /// Proof: `Swapping::PendingCcms` (`max_values`: None, `max_size`: None, mode: `Measured`) fn ccm_deposit() -> Weight { - // Minimum execution time: 27_000 nanoseconds. - Weight::from_parts(28_000_000, 0) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(5)) - } - // Storage: Swapping SwapChannelExpiries (r:1 w:1) - // Storage: EthereumIngressEgress AddressStatus (r:1 w:0) - // Storage: EthereumIngressEgress DepositAddressDetailsLookup (r:1 w:1) - // Storage: EthereumIngressEgress ChannelActions (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `208` + // Estimated: `3673` + // Minimum execution time: 33_000_000 picoseconds. + Weight::from_parts(34_000_000, 3673) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: `Swapping::SwapChannelExpiries` (r:1 w:1) + /// Proof: `Swapping::SwapChannelExpiries` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::DepositChannelLookup` (r:100 w:0) + /// Proof: `EthereumIngressEgress::DepositChannelLookup` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::ChannelActions` (r:0 w:100) + /// Proof: `EthereumIngressEgress::ChannelActions` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `a` is `[1, 100]`. fn on_initialize(a: u32, ) -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_parts(19_428_056, 0) - // Standard Error: 30_404 - .saturating_add(Weight::from_parts(14_647_491, 0).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(1)) - .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(a.into()))) - } - // Storage: Swapping SwapTTL (r:0 w:1) - fn set_swap_ttl() -> Weight { - // Minimum execution time: 13_000 nanoseconds. - Weight::from_parts(14_000_000, 0) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `241 + a * (105 ±0)` + // Estimated: `3706 + a * (2581 ±0)` + // Minimum execution time: 17_000_000 picoseconds. + Weight::from_parts(14_763_475, 3706) + // Standard Error: 11_169 + .saturating_add(Weight::from_parts(8_725_405, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2581).saturating_mul(a.into())) } - // Storage: AccountRoles SwappingEnabled (r:1 w:0) - // Storage: AccountRoles AccountRoles (r:1 w:1) - fn register_as_broker() -> Weight { - // Minimum execution time: 20_000 nanoseconds. - Weight::from_parts(22_000_000, 0) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) + /// Storage: `Swapping::SwapTTL` (r:0 w:1) + /// Proof: `Swapping::SwapTTL` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_swap_ttl() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(8_000_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - + /// Storage: `Swapping::MinimumSwapAmount` (r:0 w:1) + /// Proof: `Swapping::MinimumSwapAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_minimum_swap_amount() -> Weight { - Weight::from_parts(1_000_000, 0) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: AccountRoles AccountRoles (r:1 w:0) - // Storage: EthereumIngressEgress AddressPool (r:1 w:0) - // Storage: EthereumIngressEgress ChannelIdCounter (r:1 w:1) - // Storage: Environment EthereumVaultAddress (r:1 w:0) - // Storage: Swapping SwapTTL (r:1 w:0) - // Storage: Swapping SwapChannelExpiries (r:1 w:1) - // Storage: EthereumIngressEgress ChannelActions (r:0 w:1) - // Storage: EthereumIngressEgress FetchParamDetails (r:0 w:1) - // Storage: EthereumIngressEgress AddressStatus (r:0 w:1) - // Storage: EthereumIngressEgress DepositAddressDetailsLookup (r:0 w:1) + /// Storage: `Environment::RuntimeSafeMode` (r:1 w:0) + /// Proof: `Environment::RuntimeSafeMode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `AccountRoles::AccountRoles` (r:1 w:0) + /// Proof: `AccountRoles::AccountRoles` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + /// Storage: `Swapping::SwapTTL` (r:1 w:0) + /// Proof: `Swapping::SwapTTL` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::DepositChannelPool` (r:1 w:0) + /// Proof: `EthereumIngressEgress::DepositChannelPool` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::ChannelIdCounter` (r:1 w:1) + /// Proof: `EthereumIngressEgress::ChannelIdCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Environment::EthereumVaultAddress` (r:1 w:0) + /// Proof: `Environment::EthereumVaultAddress` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `EthereumChainTracking::CurrentChainState` (r:1 w:0) + /// Proof: `EthereumChainTracking::CurrentChainState` (`max_values`: Some(1), `max_size`: Some(40), added: 535, mode: `MaxEncodedLen`) + /// Storage: `Swapping::SwapChannelExpiries` (r:1 w:1) + /// Proof: `Swapping::SwapChannelExpiries` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::ChannelActions` (r:0 w:1) + /// Proof: `EthereumIngressEgress::ChannelActions` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::DepositChannelLookup` (r:0 w:1) + /// Proof: `EthereumIngressEgress::DepositChannelLookup` (`max_values`: None, `max_size`: None, mode: `Measured`) fn request_swap_deposit_address() -> Weight { - // Minimum execution time: 53_000 nanoseconds. - Weight::from_parts(55_000_000, 0) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `1031` + // Estimated: `4496` + // Minimum execution time: 47_000_000 picoseconds. + Weight::from_parts(48_000_000, 4496) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - - - // Storage: Environment CurrentSystemState (r:1 w:0) - // Storage: AccountRoles AccountRoles (r:1 w:0) - // Storage: Swapping EarnedBrokerFees (r:1 w:1) - // Storage: EthereumIngressEgress EgressIdCounter (r:1 w:1) - // Storage: EthereumIngressEgress ScheduledEgressFetchOrTransfer (r:1 w:1) + /// Storage: `Environment::RuntimeSafeMode` (r:1 w:0) + /// Proof: `Environment::RuntimeSafeMode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `AccountRoles::AccountRoles` (r:1 w:0) + /// Proof: `AccountRoles::AccountRoles` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + /// Storage: `Swapping::EarnedBrokerFees` (r:1 w:1) + /// Proof: `Swapping::EarnedBrokerFees` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::EgressIdCounter` (r:1 w:1) + /// Proof: `EthereumIngressEgress::EgressIdCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::ScheduledEgressFetchOrTransfer` (r:1 w:1) + /// Proof: `EthereumIngressEgress::ScheduledEgressFetchOrTransfer` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn withdraw() -> Weight { - // Minimum execution time: 41_000 nanoseconds. - Weight::from_parts(42_000_000, 0) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `904` + // Estimated: `4369` + // Minimum execution time: 33_000_000 picoseconds. + Weight::from_parts(34_000_000, 4369) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Swapping SwapIdCounter (r:1 w:1) - // Storage: Swapping SwapQueue (r:1 w:1) + /// Storage: `Environment::RuntimeSafeMode` (r:1 w:0) + /// Proof: `Environment::RuntimeSafeMode` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `AccountRoles::SwappingEnabled` (r:1 w:0) + /// Proof: `AccountRoles::SwappingEnabled` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `AccountRoles::AccountRoles` (r:1 w:1) + /// Proof: `AccountRoles::AccountRoles` (`max_values`: None, `max_size`: Some(33), added: 2508, mode: `MaxEncodedLen`) + fn register_as_broker() -> Weight { + // Proof Size summary in bytes: + // Measured: `680` + // Estimated: `3498` + // Minimum execution time: 18_000_000 picoseconds. + Weight::from_parts(19_000_000, 3498) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `Swapping::MinimumSwapAmount` (r:1 w:0) + /// Proof: `Swapping::MinimumSwapAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::SwapIdCounter` (r:1 w:1) + /// Proof: `Swapping::SwapIdCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::SwapQueue` (r:1 w:1) + /// Proof: `Swapping::SwapQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) fn schedule_swap_from_contract() -> Weight { - // Minimum execution time: 19_000 nanoseconds. - Weight::from_parts(20_000_000, 0) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Swapping CcmIdCounter (r:1 w:1) - // Storage: Swapping SwapIdCounter (r:1 w:1) - // Storage: Swapping SwapQueue (r:1 w:1) - // Storage: Swapping CcmOutputs (r:0 w:1) - // Storage: Swapping PendingCcms (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `208` + // Estimated: `3673` + // Minimum execution time: 17_000_000 picoseconds. + Weight::from_parts(18_000_000, 3673) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: `Swapping::MinimumSwapAmount` (r:1 w:0) + /// Proof: `Swapping::MinimumSwapAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::CcmIdCounter` (r:1 w:1) + /// Proof: `Swapping::CcmIdCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::SwapIdCounter` (r:1 w:1) + /// Proof: `Swapping::SwapIdCounter` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::SwapQueue` (r:1 w:1) + /// Proof: `Swapping::SwapQueue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::CcmOutputs` (r:0 w:1) + /// Proof: `Swapping::CcmOutputs` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `Swapping::PendingCcms` (r:0 w:1) + /// Proof: `Swapping::PendingCcms` (`max_values`: None, `max_size`: None, mode: `Measured`) fn ccm_deposit() -> Weight { - // Minimum execution time: 27_000 nanoseconds. - Weight::from_parts(28_000_000, 0) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(5)) - } - // Storage: Swapping SwapChannelExpiries (r:1 w:1) - // Storage: EthereumIngressEgress AddressStatus (r:1 w:0) - // Storage: EthereumIngressEgress DepositAddressDetailsLookup (r:1 w:1) - // Storage: EthereumIngressEgress ChannelActions (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `208` + // Estimated: `3673` + // Minimum execution time: 33_000_000 picoseconds. + Weight::from_parts(34_000_000, 3673) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: `Swapping::SwapChannelExpiries` (r:1 w:1) + /// Proof: `Swapping::SwapChannelExpiries` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::DepositChannelLookup` (r:100 w:0) + /// Proof: `EthereumIngressEgress::DepositChannelLookup` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `EthereumIngressEgress::ChannelActions` (r:0 w:100) + /// Proof: `EthereumIngressEgress::ChannelActions` (`max_values`: None, `max_size`: None, mode: `Measured`) /// The range of component `a` is `[1, 100]`. fn on_initialize(a: u32, ) -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_parts(19_428_056, 0) - // Standard Error: 30_404 - .saturating_add(Weight::from_parts(14_647_491, 0).saturating_mul(a.into())) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(a.into()))) - .saturating_add(RocksDbWeight::get().writes(1)) - .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(a.into()))) - } - // Storage: Swapping SwapTTL (r:0 w:1) - fn set_swap_ttl() -> Weight { - // Minimum execution time: 13_000 nanoseconds. - Weight::from_parts(14_000_000, 0) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `241 + a * (105 ±0)` + // Estimated: `3706 + a * (2581 ±0)` + // Minimum execution time: 17_000_000 picoseconds. + Weight::from_parts(14_763_475, 3706) + // Standard Error: 11_169 + .saturating_add(Weight::from_parts(8_725_405, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2581).saturating_mul(a.into())) } - // Storage: AccountRoles SwappingEnabled (r:1 w:0) - // Storage: AccountRoles AccountRoles (r:1 w:1) - fn register_as_broker() -> Weight { - // Minimum execution time: 20_000 nanoseconds. - Weight::from_parts(22_000_000, 0) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) + /// Storage: `Swapping::SwapTTL` (r:0 w:1) + /// Proof: `Swapping::SwapTTL` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + fn set_swap_ttl() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(8_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `Swapping::MinimumSwapAmount` (r:0 w:1) + /// Proof: `Swapping::MinimumSwapAmount` (`max_values`: None, `max_size`: None, mode: `Measured`) fn set_minimum_swap_amount() -> Weight { - Weight::from_parts(1_000_000, 0) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } From 6500a70d4b4e4a20da7a7d3192550925c720938c Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Thu, 28 Sep 2023 17:49:08 +1300 Subject: [PATCH 05/13] Corrected a bug where the funds are accredited to the wrong asset Pool's limit orders are updated with the pool's limit order Updated unit to test this logic --- state-chain/amm/src/common.rs | 2 +- state-chain/pallets/cf-pools/src/lib.rs | 80 ++++++++++------- state-chain/pallets/cf-pools/src/tests.rs | 100 ++++++++++++---------- 3 files changed, 104 insertions(+), 78 deletions(-) diff --git a/state-chain/amm/src/common.rs b/state-chain/amm/src/common.rs index 950c7bea46..8a3aae9702 100644 --- a/state-chain/amm/src/common.rs +++ b/state-chain/amm/src/common.rs @@ -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; diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index 08b1cfe72d..62cdc7d397 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -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, @@ -229,6 +229,29 @@ pub mod pallet { pub pool_state: PoolState<(T::AccountId, OrderId)>, } + impl Pool { + 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( @@ -960,25 +983,25 @@ pub mod pallet { Error::::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::::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::::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), + position_info.amount.is_zero(), + ); + } + Result::<(), DispatchError>::Ok(()) + }) })?; Self::deposit_event(Event::::PoolFeeSet { @@ -1146,17 +1169,14 @@ impl Pallet { }, }; - 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)?; diff --git a/state-chain/pallets/cf-pools/src/tests.rs b/state-chain/pallets/cf-pools/src/tests.rs index 89a460878d..a4b63db2c6 100644 --- a/state-chain/pallets/cf-pools/src/tests.rs +++ b/state-chain/pallets/cf-pools/src/tests.rs @@ -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}; @@ -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), @@ -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![] }) @@ -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. @@ -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. @@ -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![] }) @@ -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![] }) @@ -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::::new(Asset::Eth, STABLE_ASSET).unwrap(); // Create a new pool. assert_ok!(LiquidityPools::new_pool( @@ -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::::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::::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)); }); } From 1ec6294cebfd318e986b5932999140bcdcf0121c Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Fri, 29 Sep 2023 12:17:18 +1300 Subject: [PATCH 06/13] Added a new unit test for Range order Fixed some minor typo --- state-chain/amm/src/lib.rs | 6 +- state-chain/amm/src/limit_orders.rs | 12 +-- state-chain/chains/src/lib.rs | 4 +- state-chain/pallets/cf-pools/src/lib.rs | 28 ++---- state-chain/pallets/cf-pools/src/mock.rs | 17 +++- state-chain/pallets/cf-pools/src/tests.rs | 110 +++++++++++++++++++++ state-chain/pallets/cf-swapping/src/lib.rs | 2 +- 7 files changed, 143 insertions(+), 36 deletions(-) diff --git a/state-chain/amm/src/lib.rs b/state-chain/amm/src/lib.rs index 07ba80fe15..29352c28c0 100644 --- a/state-chain/amm/src/lib.rs +++ b/state-chain/amm/src/lib.rs @@ -349,10 +349,8 @@ impl PoolState { pub fn set_fees( &mut self, fee_hundredth_pips: u32, - ) -> Result< - SideMap>, - SetFeesError, - > { + ) -> Result>, SetFeesError> + { self.range_orders.set_fees(fee_hundredth_pips)?; self.limit_orders.set_fees(fee_hundredth_pips) } diff --git a/state-chain/amm/src/limit_orders.rs b/state-chain/amm/src/limit_orders.rs index 2f7343f140..ddfec4b990 100644 --- a/state-chain/amm/src/limit_orders.rs +++ b/state-chain/amm/src/limit_orders.rs @@ -119,7 +119,7 @@ impl FloatBetweenZeroAndOne { // As the denominator <= U256::MAX, this div will not right-shift the mantissa more than // 256 bits, so we maintain at least 256 accurate bits in the result. let (d, div_remainder) = - U512::div_mod(mul_normalised_mantissa, U512::from(denominator)); // Note that d can never be zero as mul_normalised_mantissa always has atleast one bit + U512::div_mod(mul_normalised_mantissa, U512::from(denominator)); // Note that d can never be zero as mul_normalised_mantissa always has at least one bit // set above the 256th bit. let d = if div_remainder.is_zero() { d } else { d + U512::one() }; let normalise_shift = d.leading_zeros(); @@ -397,10 +397,8 @@ impl PoolState { pub(super) fn set_fees( &mut self, fee_hundredth_pips: u32, - ) -> Result< - SideMap>, - SetFeesError, - > { + ) -> Result>, SetFeesError> + { Self::validate_fees(fee_hundredth_pips) .then_some(()) .ok_or(SetFeesError::InvalidFeeAmount)?; @@ -415,7 +413,7 @@ impl PoolState { .into_iter() .map(|(sqrt_price, lp)| { ( - (sqrt_price, lp.clone()), + (tick_at_sqrt_price(sqrt_price), lp.clone()), self.inner_collect::(&lp, sqrt_price).unwrap(), ) }) @@ -427,7 +425,7 @@ impl PoolState { .into_iter() .map(|(sqrt_price, lp)| { ( - (sqrt_price, lp.clone()), + (tick_at_sqrt_price(sqrt_price), lp.clone()), self.inner_collect::(&lp, sqrt_price).unwrap(), ) }) diff --git a/state-chain/chains/src/lib.rs b/state-chain/chains/src/lib.rs index 5f325a99f2..5d58ab9297 100644 --- a/state-chain/chains/src/lib.rs +++ b/state-chain/chains/src/lib.rs @@ -152,7 +152,7 @@ pub trait ChainCrypto { /// Uniquely identifies a transaction on the incoming direction. type TransactionInId: Member + Parameter + Unpin + BenchmarkValue; - /// Uniquely identifies a transaction on the outoing direction. + /// Uniquely identifies a transaction on the outgoing direction. type TransactionOutId: Member + Parameter + Unpin + BenchmarkValue; type GovKey: Member + Parameter + Copy + BenchmarkValue; @@ -384,7 +384,7 @@ pub struct CcmChannelMetadata { pub message: Vec, /// User funds designated to be used for gas. pub gas_budget: AssetAmount, - /// Additonal parameters for the cross chain message. + /// Additional parameters for the cross chain message. #[cfg_attr(feature = "std", serde(with = "hex::serde"))] pub cf_parameters: Vec, } diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index 62cdc7d397..97b98dd9ff 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -2,8 +2,10 @@ use core::ops::Range; use cf_amm::{ - common::{tick_at_sqrt_price, Amount, Order, Price, Side, SideMap, Tick}, - limit_orders, range_orders, + common::{Amount, Order, Price, Side, SideMap, Tick}, + limit_orders, + limit_orders::PositionInfo, + range_orders, range_orders::Liquidity, PoolState, }; @@ -236,10 +238,10 @@ pub mod pallet { side: Side, id: OrderId, tick: cf_amm::common::Tick, - can_remove: bool, + position: &PositionInfo, ) { let limit_orders = &mut self.limit_orders[side]; - if can_remove { + if position.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() { @@ -987,18 +989,12 @@ pub mod pallet { .set_fees(fee_hundredth_pips) .map_err(|_| Error::::InvalidFeeAmount)? .try_map(|side, collected_fees| { - for ((sqrt_price, (lp, order)), (collected, position_info)) in + for ((tick, (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), - position_info.amount.is_zero(), - ); + pool.update_limit_order_storage(&lp, side, order, tick, &position_info); } Result::<(), DispatchError>::Ok(()) }) @@ -1170,13 +1166,7 @@ impl Pallet { }; // Update pool's limit orders - pool.update_limit_order_storage( - lp, - asset_pair.base_side, - id, - tick, - position_info.amount.is_zero(), - ); + pool.update_limit_order_storage(lp, asset_pair.base_side, id, tick, &position_info); let collected_fees = asset_pair.try_credit_asset(lp, !asset_pair.base_side, collected.fees)?; diff --git a/state-chain/pallets/cf-pools/src/mock.rs b/state-chain/pallets/cf-pools/src/mock.rs index 1d6fb46ce2..8242048ea2 100644 --- a/state-chain/pallets/cf-pools/src/mock.rs +++ b/state-chain/pallets/cf-pools/src/mock.rs @@ -66,6 +66,10 @@ parameter_types! { pub static AliceCollectedUsdc: AssetAmount = Default::default(); pub static BobCollectedEth: AssetAmount = Default::default(); pub static BobCollectedUsdc: AssetAmount = Default::default(); + pub static AliceDebitedEth: AssetAmount = Default::default(); + pub static AliceDebitedUsdc: AssetAmount = Default::default(); + pub static BobDebitedEth: AssetAmount = Default::default(); + pub static BobDebitedUsdc: AssetAmount = Default::default(); } pub struct MockBalance; impl LpBalanceApi for MockBalance { @@ -87,10 +91,17 @@ impl LpBalanceApi for MockBalance { } fn try_debit_account( - _who: &Self::AccountId, - _asset: cf_primitives::Asset, - _amount: cf_primitives::AssetAmount, + who: &Self::AccountId, + asset: cf_primitives::Asset, + amount: cf_primitives::AssetAmount, ) -> sp_runtime::DispatchResult { + match (*who, asset) { + (ALICE, Asset::Eth) => AliceDebitedEth::set(AliceDebitedEth::get() + amount), + (ALICE, Asset::Usdc) => AliceDebitedUsdc::set(AliceDebitedUsdc::get() + amount), + (BOB, Asset::Eth) => BobDebitedEth::set(BobDebitedEth::get() + amount), + (BOB, Asset::Usdc) => BobDebitedUsdc::set(BobDebitedUsdc::get() + amount), + _ => (), + } Ok(()) } } diff --git a/state-chain/pallets/cf-pools/src/tests.rs b/state-chain/pallets/cf-pools/src/tests.rs index a4b63db2c6..2fc6b85aac 100644 --- a/state-chain/pallets/cf-pools/src/tests.rs +++ b/state-chain/pallets/cf-pools/src/tests.rs @@ -507,9 +507,119 @@ fn pallet_limit_order_is_in_sync_with_pool() { // 100 swapped + 100 fee. The position is fully consumed. assert_eq!(AliceCollectedUsdc::get(), 200u128); + assert_eq!(AliceDebitedEth::get(), 100u128); let pallet_limit_orders = Pools::::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)); }); } + +#[test] +fn update_pool_liquidity_fee_collects_fees_for_range_order() { + new_test_ext().execute_with(|| { + let range = -100..100; + let old_fee = 400_000u32; + let new_fee = 100_000u32; + // Create a new pool. + assert_ok!(LiquidityPools::new_pool( + RuntimeOrigin::root(), + Asset::Eth, + STABLE_ASSET, + old_fee, + price_at_tick(0).unwrap(), + )); + assert_eq!( + LiquidityPools::pool_info(Asset::Eth, STABLE_ASSET), + Some(PoolInfo { + limit_order_fee_hundredth_pips: old_fee, + range_order_fee_hundredth_pips: old_fee, + }) + ); + + // Setup liquidity for the pool with 2 LPer with range orders + assert_ok!(LiquidityPools::set_range_order( + RuntimeOrigin::signed(ALICE), + Asset::Eth, + STABLE_ASSET, + 0, + Some(range.clone()), + RangeOrderSize::Liquidity { liquidity: 1_000_000 }, + )); + assert_ok!(LiquidityPools::set_range_order( + RuntimeOrigin::signed(BOB), + Asset::Eth, + STABLE_ASSET, + 0, + Some(range.clone()), + RangeOrderSize::Liquidity { liquidity: 1_000_000 }, + )); + + // Do some swaps to collect fees. + assert_eq!( + LiquidityPools::swap_with_network_fee(STABLE_ASSET, Asset::Eth, 5_000).unwrap(), + SwapOutput { intermediary: None, output: 2_989u128 } + ); + assert_eq!( + LiquidityPools::swap_with_network_fee(Asset::Eth, STABLE_ASSET, 5_000).unwrap(), + SwapOutput { intermediary: None, output: 2_998u128 } + ); + + // Updates the fees to the new value. No fee is collected for range orders. + assert_ok!(LiquidityPools::set_pool_fees( + RuntimeOrigin::root(), + Asset::Eth, + STABLE_ASSET, + new_fee + )); + assert_eq!(AliceCollectedEth::get(), 0u128); + assert_eq!(AliceCollectedUsdc::get(), 0u128); + assert_eq!(BobCollectedEth::get(), 0u128); + assert_eq!(BobCollectedUsdc::get(), 0u128); + + assert_eq!( + LiquidityPools::pool_orders(Asset::Eth, STABLE_ASSET, &ALICE,), + Some(PoolOrders { + limit_orders: AssetsMap { base: vec![], pair: vec![] }, + range_orders: vec![(0, range.clone(), 1_000_000)] + }) + ); + assert_eq!( + LiquidityPools::pool_orders(Asset::Eth, STABLE_ASSET, &BOB,), + Some(PoolOrders { + limit_orders: AssetsMap { base: vec![], pair: vec![] }, + range_orders: vec![(0, range.clone(), 1_000_000)] + }) + ); + + // Cash out the liquidity will payout earned fee + assert_ok!(LiquidityPools::set_range_order( + RuntimeOrigin::signed(ALICE), + Asset::Eth, + STABLE_ASSET, + 0, + Some(range.clone()), + RangeOrderSize::Liquidity { liquidity: 0 }, + )); + assert_ok!(LiquidityPools::set_range_order( + RuntimeOrigin::signed(BOB), + Asset::Eth, + STABLE_ASSET, + 0, + Some(range.clone()), + RangeOrderSize::Liquidity { liquidity: 0 }, + )); + + // Earned liquidity pool fees are paid out. + // Total of ~ 4_000 fee were paid, evenly split between Alice and Bob. + assert_eq!(AliceCollectedEth::get(), 5_988u128); + assert_eq!(AliceCollectedUsdc::get(), 5_984u128); + assert_eq!(AliceDebitedEth::get(), 4_988u128); + assert_eq!(AliceDebitedUsdc::get(), 4_988u128); + + assert_eq!(BobCollectedEth::get(), 5_988u128); + assert_eq!(BobCollectedUsdc::get(), 5_984u128); + assert_eq!(BobDebitedEth::get(), 4_988u128); + assert_eq!(BobDebitedUsdc::get(), 4_988u128); + }); +} diff --git a/state-chain/pallets/cf-swapping/src/lib.rs b/state-chain/pallets/cf-swapping/src/lib.rs index 488a68918c..ec4e8ad63f 100644 --- a/state-chain/pallets/cf-swapping/src/lib.rs +++ b/state-chain/pallets/cf-swapping/src/lib.rs @@ -117,7 +117,7 @@ enum BatchExecutionError { DispatchError { error: DispatchError }, } -/// This impl is never used. This is purely used to satisfy trait requirment +/// This impl is never used. This is purely used to satisfy trait requirement impl From for BatchExecutionError { fn from(error: DispatchError) -> Self { Self::DispatchError { error } From 58bb4346c75c9b468ab90f9482a852a5cf47574c Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Fri, 29 Sep 2023 12:25:11 +1300 Subject: [PATCH 07/13] Fixed build --- state-chain/pallets/cf-pools/src/weights.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/state-chain/pallets/cf-pools/src/weights.rs b/state-chain/pallets/cf-pools/src/weights.rs index deb795058c..60d1fbadb1 100644 --- a/state-chain/pallets/cf-pools/src/weights.rs +++ b/state-chain/pallets/cf-pools/src/weights.rs @@ -258,7 +258,7 @@ impl WeightInfo for () { fn set_pool_fees() -> Weight { Weight::from_parts(17_000_000, 3883) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } From 71888601df06021ad5511de198d65958de2443ae Mon Sep 17 00:00:00 2001 From: Alastair Holmes Date: Fri, 29 Sep 2023 11:53:38 +0200 Subject: [PATCH 08/13] naming and extra comments --- state-chain/amm/src/limit_orders.rs | 8 ++- state-chain/custom-rpc/src/lib.rs | 6 +- state-chain/pallets/cf-pools/src/lib.rs | 69 +++++++++++++++-------- state-chain/pallets/cf-pools/src/tests.rs | 4 +- state-chain/runtime/src/lib.rs | 4 +- state-chain/runtime/src/runtime_apis.rs | 4 +- 6 files changed, 60 insertions(+), 35 deletions(-) diff --git a/state-chain/amm/src/limit_orders.rs b/state-chain/amm/src/limit_orders.rs index ddfec4b990..488c16c51e 100644 --- a/state-chain/amm/src/limit_orders.rs +++ b/state-chain/amm/src/limit_orders.rs @@ -362,11 +362,11 @@ pub(super) struct PoolState { /// The ID the next FixedPool that is created will use. next_pool_instance: u128, /// All the FixedPools that have some liquidity. They are grouped into all those that are - /// selling asset `Zero` and all those that are selling asset `one` used the SideMap. + /// selling asset `Zero` and all those that are selling asset `One` used the SideMap. fixed_pools: SideMap>, /// All the Positions that either are providing liquidity currently, or were providing /// liquidity directly after the last time they where updated. They are grouped into all those - /// that are selling asset `Zero` and all those that are selling asset `one` used the SideMap. + /// that are selling asset `Zero` and all those that are selling asset `One` used the SideMap. /// Therefore there can be positions stored here that don't provide any liquidity. positions: SideMap>, } @@ -390,7 +390,9 @@ impl PoolState { } /// Sets the fee for the pool. This will apply to future swaps. The fee may not be set - /// higher than 50%. Also runs collect for all positions in the pool. + /// higher than 50%. Also runs collect for all positions in the pool. Returns a SideMap + /// containing the state and fees collected from every position as part of the set_fees + /// operation. The positions are grouped into a SideMap by the asset they sell. /// /// This function never panics. #[allow(clippy::type_complexity)] diff --git a/state-chain/custom-rpc/src/lib.rs b/state-chain/custom-rpc/src/lib.rs index b3c33561a4..c6ca25fa82 100644 --- a/state-chain/custom-rpc/src/lib.rs +++ b/state-chain/custom-rpc/src/lib.rs @@ -12,7 +12,7 @@ use jsonrpsee::{ SubscriptionSink, }; use pallet_cf_governance::GovCallHash; -use pallet_cf_pools::{AssetsMap, Depth, PoolInfo, PoolLiquidity, PoolOrders}; +use pallet_cf_pools::{AssetsMap, PoolInfo, PoolLiquidity, PoolOrders, UnidirectionalPoolDepth}; use sc_client_api::{BlockchainEvents, HeaderBackend}; use serde::{Deserialize, Serialize}; use sp_api::BlockT; @@ -227,7 +227,7 @@ pub trait CustomApi { pair_asset: Asset, tick_range: Range, at: Option, - ) -> RpcResult>>; + ) -> RpcResult>>; #[method(name = "pool_liquidity")] fn cf_pool_liquidity( &self, @@ -590,7 +590,7 @@ where pair_asset: Asset, tick_range: Range, at: Option, - ) -> RpcResult>> { + ) -> RpcResult>> { self.client .runtime_api() .cf_pool_depth(self.unwrap_or_best(at), base_asset, pair_asset, tick_range) diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index 97b98dd9ff..5805c4ee8e 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -226,8 +226,12 @@ pub mod pallet { #[scale_info(skip_type_params(T))] pub struct Pool { pub enabled: bool, - pub range_orders: BTreeMap>>, - pub limit_orders: SideMap>>, + /// A cache of all the range orders that exist in the pool. This must be kept upto date + /// with the underlying pool. + pub range_orders_cache: BTreeMap>>, + /// A cache of all the limit orders that exist in the pool. This must be kept upto date + /// with the underlying pool. These are grouped by the asset the limit order is selling + pub limit_orders_cache: SideMap>>, pub pool_state: PoolState<(T::AccountId, OrderId)>, } @@ -240,7 +244,7 @@ pub mod pallet { tick: cf_amm::common::Tick, position: &PositionInfo, ) { - let limit_orders = &mut self.limit_orders[side]; + let limit_orders = &mut self.limit_orders_cache[side]; if position.amount.is_zero() { if let Some(lp_limit_orders) = limit_orders.get_mut(lp) { lp_limit_orders.remove(&id); @@ -639,8 +643,8 @@ pub mod pallet { *maybe_pool = Some(Pool { enabled: true, - range_orders: Default::default(), - limit_orders: Default::default(), + range_orders_cache: Default::default(), + limit_orders_cache: Default::default(), pool_state: PoolState::new(fee_hundredth_pips, initial_price).map_err(|e| { match e { NewError::LimitOrders(limit_orders::NewError::InvalidFeeAmount) => @@ -691,7 +695,7 @@ pub mod pallet { let lp = T::AccountRoleRegistry::ensure_liquidity_provider(origin)?; Self::try_mutate_enabled_pool(base_asset, pair_asset, |asset_pair, pool| { let tick_range = match ( - pool.range_orders + pool.range_orders_cache .get(&lp) .and_then(|range_orders| range_orders.get(&id)) .cloned(), @@ -774,7 +778,7 @@ pub mod pallet { let lp = T::AccountRoleRegistry::ensure_liquidity_provider(origin)?; Self::try_mutate_enabled_pool(base_asset, pair_asset, |asset_pair, pool| { let tick_range = match ( - pool.range_orders + pool.range_orders_cache .get(&lp) .and_then(|range_orders| range_orders.get(&id)) .cloned(), @@ -846,7 +850,7 @@ pub mod pallet { let lp = T::AccountRoleRegistry::ensure_liquidity_provider(origin)?; Self::try_mutate_enabled_pool(sell_asset, buy_asset, |asset_pair, pool| { let tick = match ( - pool.limit_orders[asset_pair.base_side] + pool.limit_orders_cache[asset_pair.base_side] .get(&lp) .and_then(|limit_orders| limit_orders.get(&id)) .cloned(), @@ -921,7 +925,7 @@ pub mod pallet { let lp = T::AccountRoleRegistry::ensure_liquidity_provider(origin)?; Self::try_mutate_enabled_pool(sell_asset, buy_asset, |asset_pair, pool| { let tick = match ( - pool.limit_orders[asset_pair.base_side] + pool.limit_orders_cache[asset_pair.base_side] .get(&lp) .and_then(|limit_orders| limit_orders.get(&id)) .cloned(), @@ -1064,32 +1068,50 @@ impl cf_traits::FlipBurnInfo for Pallet { Serialize, )] pub struct PoolInfo { + /// The fee taken, when limit orders are used, from swap inputs that contributes to liquidity + /// provider earnings pub limit_order_fee_hundredth_pips: u32, + /// The fee taken, when range orders are used, from swap inputs that contributes to liquidity + /// provider earnings pub range_order_fee_hundredth_pips: u32, } #[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, Eq, Deserialize, Serialize)] pub struct PoolOrders { + /// Limit orders are groups by which asset they are selling. pub limit_orders: AssetsMap>, + /// Range orders can be both buy and/or sell therefore they not split. The current range order + /// price determines if they are buy and/or sell. pub range_orders: Vec<(OrderId, Range, Liquidity)>, } #[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, Eq, Deserialize, Serialize)] pub struct PoolLiquidity { + /// An ordered lists of the amount of assets available at each tick, if a tick contains zero + /// liquidity it will not be included in the list. Note limit order liquidity is split by which + /// asset the liquidity is "selling". pub limit_orders: AssetsMap>, + /// An ordered list of the amount of range order liquidity available from a tick until the next + /// tick in the list. Note range orders can be both buy and/or sell therefore they not split by + /// sold asset. The current range order price determines if the liquidity can be used for + /// buying and/or selling, pub range_orders: Vec<(Tick, Liquidity)>, } #[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, Eq, Deserialize, Serialize)] -pub struct SingleDepth { +pub struct UnidirectionalSubPoolDepth { + /// The current price in this sub pool, in the given direction of swaps. pub price: Option, + /// The approximate amount of assets available to be sold in the specified price range. pub depth: Amount, } #[derive(Clone, Debug, Encode, Decode, TypeInfo, PartialEq, Eq, Deserialize, Serialize)] -pub struct Depth { - pub limit_orders: SingleDepth, - pub range_orders: SingleDepth, +pub struct UnidirectionalPoolDepth { + /// The depth of the limit order pool. + pub limit_orders: UnidirectionalSubPoolDepth, + /// The depth of the range order pool. + pub range_orders: UnidirectionalSubPoolDepth, } impl Pallet { @@ -1264,14 +1286,14 @@ impl Pallet { let collected_fees = asset_pair.try_credit_assets(lp, collected.fees)?; if position_info.liquidity == 0 { - if let Some(range_orders) = pool.range_orders.get_mut(lp) { + if let Some(range_orders) = pool.range_orders_cache.get_mut(lp) { range_orders.remove(&id); if range_orders.is_empty() { - pool.range_orders.remove(lp); + pool.range_orders_cache.remove(lp); } } } else { - let range_orders = pool.range_orders.entry(lp.clone()).or_default(); + let range_orders = pool.range_orders_cache.entry(lp.clone()).or_default(); range_orders.insert(id, tick_range.clone()); } @@ -1374,7 +1396,7 @@ impl Pallet { base_asset: any::Asset, pair_asset: any::Asset, tick_range: Range, - ) -> Option, DispatchError>> { + ) -> Option, DispatchError>> { let asset_pair = AssetPair::::new(base_asset, pair_asset).ok()?; let mut pool = Pools::::get(asset_pair.canonical_asset_pair)?; @@ -1397,8 +1419,9 @@ impl Pallet { Some(limit_orders.and_then(|limit_orders| { range_orders.map(|range_orders| { asset_pair.side_map_to_assets_map(SideMap::<()>::default().map(|side, ()| { - let to_single_depth = |(price, depth)| SingleDepth { price, depth }; - Depth { + let to_single_depth = + |(price, depth)| UnidirectionalSubPoolDepth { price, depth }; + UnidirectionalPoolDepth { limit_orders: to_single_depth(limit_orders[side]), range_orders: to_single_depth(range_orders[side]), } @@ -1416,7 +1439,7 @@ impl Pallet { Pools::::get(asset_pair.canonical_asset_pair).ok_or(Error::::PoolDoesNotExist)?; Ok(Iterator::chain( - pool.limit_orders.as_ref().into_iter().flat_map(|(side, limit_orders)| { + pool.limit_orders_cache.as_ref().into_iter().flat_map(|(side, limit_orders)| { let pool = &pool; limit_orders .iter() @@ -1433,7 +1456,7 @@ impl Pallet { }) .map(|(lp, _positions)| lp.clone()) }), - pool.range_orders.keys().cloned(), + pool.range_orders_cache.keys().cloned(), ) .collect()) } @@ -1474,7 +1497,7 @@ impl Pallet { let pool = Pools::::get(asset_pair.canonical_asset_pair)?; Some(PoolOrders { limit_orders: AssetsMap::<()>::default().map_with_side(&asset_pair, |side, ()| { - pool.limit_orders[side] + pool.limit_orders_cache[side] .get(lp) .into_iter() .flat_map(|limit_orders| { @@ -1489,7 +1512,7 @@ impl Pallet { .collect() }), range_orders: pool - .range_orders + .range_orders_cache .get(lp) .into_iter() .flat_map(|range_orders| { diff --git a/state-chain/pallets/cf-pools/src/tests.rs b/state-chain/pallets/cf-pools/src/tests.rs index 2fc6b85aac..25fa9695bc 100644 --- a/state-chain/pallets/cf-pools/src/tests.rs +++ b/state-chain/pallets/cf-pools/src/tests.rs @@ -482,7 +482,7 @@ fn pallet_limit_order_is_in_sync_with_pool() { ); let pallet_limit_orders = - Pools::::get(asset_pair.canonical_asset_pair).unwrap().limit_orders; + Pools::::get(asset_pair.canonical_asset_pair).unwrap().limit_orders_cache; 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); @@ -509,7 +509,7 @@ fn pallet_limit_order_is_in_sync_with_pool() { assert_eq!(AliceCollectedUsdc::get(), 200u128); assert_eq!(AliceDebitedEth::get(), 100u128); let pallet_limit_orders = - Pools::::get(asset_pair.canonical_asset_pair).unwrap().limit_orders; + Pools::::get(asset_pair.canonical_asset_pair).unwrap().limit_orders_cache; assert_eq!(pallet_limit_orders.zero.get(&ALICE), None); assert_eq!(pallet_limit_orders.zero.get(&BOB).unwrap().get(&0), Some(&100)); }); diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index 4fd3c82405..7ee7ab7581 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -29,7 +29,7 @@ use cf_chains::{ use core::ops::Range; pub use frame_system::Call as SystemCall; use pallet_cf_governance::GovCallHash; -use pallet_cf_pools::{AssetsMap, Depth, PoolLiquidity}; +use pallet_cf_pools::{AssetsMap, PoolLiquidity, UnidirectionalPoolDepth}; use pallet_cf_reputation::ExclusionList; use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier}; use sp_runtime::DispatchError; @@ -1035,7 +1035,7 @@ impl_runtime_apis! { LiquidityPools::pool_info(base_asset, pair_asset) } - fn cf_pool_depth(base_asset: Asset, pair_asset: Asset, tick_range: Range) -> Option, DispatchError>> { + fn cf_pool_depth(base_asset: Asset, pair_asset: Asset, tick_range: Range) -> Option, DispatchError>> { LiquidityPools::pool_depth(base_asset, pair_asset, tick_range) } diff --git a/state-chain/runtime/src/runtime_apis.rs b/state-chain/runtime/src/runtime_apis.rs index da1779f94a..57f8eab31f 100644 --- a/state-chain/runtime/src/runtime_apis.rs +++ b/state-chain/runtime/src/runtime_apis.rs @@ -9,7 +9,7 @@ use codec::{Decode, Encode}; use core::ops::Range; use frame_support::sp_runtime::AccountId32; use pallet_cf_governance::GovCallHash; -use pallet_cf_pools::{AssetsMap, Depth, PoolInfo, PoolLiquidity, PoolOrders}; +use pallet_cf_pools::{AssetsMap, PoolInfo, PoolLiquidity, PoolOrders, UnidirectionalPoolDepth}; use scale_info::TypeInfo; use serde::{Deserialize, Serialize}; use sp_api::decl_runtime_apis; @@ -118,7 +118,7 @@ decl_runtime_apis!( base_asset: Asset, pair_asset: Asset, tick_range: Range, - ) -> Option, DispatchError>>; + ) -> Option, DispatchError>>; fn cf_pool_liquidity(base_asset: Asset, pair_asset: Asset) -> Option; fn cf_required_asset_ratio_for_range_order( base_asset: Asset, From 36721f290e11df5f76a7a90e8967542c6b6357d3 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Tue, 3 Oct 2023 17:28:40 +1300 Subject: [PATCH 09/13] updating limit order cache also emit events. --- api/lib/src/lp.rs | 9 +-- state-chain/pallets/cf-pools/src/lib.rs | 63 +++++++++++------ state-chain/pallets/cf-pools/src/tests.rs | 82 +++++++++++++++-------- 3 files changed, 102 insertions(+), 52 deletions(-) diff --git a/api/lib/src/lp.rs b/api/lib/src/lp.rs index 5abd815812..a93e60caec 100644 --- a/api/lib/src/lp.rs +++ b/api/lib/src/lp.rs @@ -62,8 +62,7 @@ pub struct LimitOrderReturn { amount_total: AssetAmount, collected_fees: AssetAmount, bought_amount: AssetAmount, - increase_or_decrease: IncreaseOrDecrease, - amount_delta: AssetAmount, + position_delta: Option<(IncreaseOrDecrease, AssetAmount)>, } fn collect_limit_order_returns( @@ -74,8 +73,7 @@ fn collect_limit_order_returns( .filter_map(|event| match event { state_chain_runtime::RuntimeEvent::LiquidityPools( pallet_cf_pools::Event::LimitOrderUpdated { - increase_or_decrease, - amount_delta, + position_delta, amount_total, collected_fees, bought_amount, @@ -87,8 +85,7 @@ fn collect_limit_order_returns( amount_total, collected_fees, bought_amount, - increase_or_decrease, - amount_delta, + position_delta, }), _ => None, }) diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index 5805c4ee8e..297b4164c5 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -236,6 +236,7 @@ pub mod pallet { } impl Pool { + /// Updates the position information in the Pool's cache. pub fn update_limit_order_storage( &mut self, lp: &T::AccountId, @@ -534,8 +535,7 @@ pub mod pallet { buy_asset: Asset, id: OrderId, tick: Tick, - increase_or_decrease: IncreaseOrDecrease, - amount_delta: AssetAmount, + position_delta: Option<(IncreaseOrDecrease, AssetAmount)>, amount_total: AssetAmount, collected_fees: AssetAmount, bought_amount: AssetAmount, @@ -988,21 +988,47 @@ pub mod pallet { PoolState::<(T::AccountId, OrderId)>::validate_fees(fee_hundredth_pips), Error::::InvalidFeeAmount ); - Self::try_mutate_enabled_pool(base_asset, pair_asset, |asset_pair, pool| { - pool.pool_state - .set_fees(fee_hundredth_pips) - .map_err(|_| Error::::InvalidFeeAmount)? - .try_map(|side, collected_fees| { - for ((tick, (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, &position_info); - } - Result::<(), DispatchError>::Ok(()) - }) - })?; + Self::try_mutate_enabled_pool( + base_asset, + pair_asset, + |asset_pair: &AssetPair, pool| { + pool.pool_state + .set_fees(fee_hundredth_pips) + .map_err(|_| Error::::InvalidFeeAmount)? + .try_map(|side, collected_fees| { + for ((tick, (lp, order)), (collected, position_info)) in + collected_fees.into_iter() + { + let collected_fees = + asset_pair.try_credit_asset(&lp, !side, collected.fees)?; + let bought_amount = asset_pair.try_credit_asset( + &lp, + !side, + collected.bought_amount, + )?; + pool.update_limit_order_storage( + &lp, + side, + order, + tick, + &position_info, + ); + Self::deposit_event(Event::::LimitOrderUpdated { + lp: lp.clone(), + sell_asset: asset_pair.canonical_asset_pair.side_to_asset(side), + buy_asset: asset_pair.canonical_asset_pair.side_to_asset(!side), + id: order, + tick, + position_delta: None, + amount_total: position_info.amount.try_into()?, + collected_fees, + bought_amount, + }); + } + Result::<(), DispatchError>::Ok(()) + }) + }, + )?; Self::deposit_event(Event::::PoolFeeSet { base_asset, @@ -1201,8 +1227,7 @@ impl Pallet { buy_asset: asset_pair.canonical_asset_pair.side_to_asset(!asset_pair.base_side), id, tick, - increase_or_decrease, - amount_delta, + position_delta: Some((increase_or_decrease, amount_delta)), amount_total: position_info.amount.try_into()?, collected_fees, bought_amount, diff --git a/state-chain/pallets/cf-pools/src/tests.rs b/state-chain/pallets/cf-pools/src/tests.rs index 25fa9695bc..487817ea9b 100644 --- a/state-chain/pallets/cf-pools/src/tests.rs +++ b/state-chain/pallets/cf-pools/src/tests.rs @@ -1,11 +1,11 @@ use crate::{ mock::*, utilities, AssetAmounts, AssetPair, AssetsMap, CanonicalAssetPair, - CollectedNetworkFee, Error, FlipBuyInterval, FlipToBurn, PoolInfo, PoolOrders, Pools, + CollectedNetworkFee, Error, Event, 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}; -use cf_test_utilities::assert_events_match; +use cf_test_utilities::{assert_events_match, assert_has_event}; use frame_support::{assert_noop, assert_ok, traits::Hooks}; use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::Permill; @@ -40,14 +40,12 @@ fn can_create_new_trading_pool() { 500_000u32, default_price, )); - System::assert_last_event(RuntimeEvent::LiquidityPools( - crate::Event::::NewPoolCreated { - base_asset: unstable_asset, - pair_asset: STABLE_ASSET, - fee_hundredth_pips: 500_000u32, - initial_price: default_price, - }, - )); + System::assert_last_event(RuntimeEvent::LiquidityPools(Event::::NewPoolCreated { + base_asset: unstable_asset, + pair_asset: STABLE_ASSET, + fee_hundredth_pips: 500_000u32, + initial_price: default_price, + })); // Cannot create duplicate pool assert_noop!( @@ -86,13 +84,11 @@ fn can_enable_disable_trading_pool() { STABLE_ASSET, false )); - System::assert_last_event(RuntimeEvent::LiquidityPools( - crate::Event::::PoolStateUpdated { - base_asset: unstable_asset, - pair_asset: STABLE_ASSET, - enabled: false, - }, - )); + System::assert_last_event(RuntimeEvent::LiquidityPools(Event::::PoolStateUpdated { + base_asset: unstable_asset, + pair_asset: STABLE_ASSET, + enabled: false, + })); assert_noop!( LiquidityPools::set_range_order( @@ -113,13 +109,11 @@ fn can_enable_disable_trading_pool() { STABLE_ASSET, true )); - System::assert_last_event(RuntimeEvent::LiquidityPools( - crate::Event::::PoolStateUpdated { - base_asset: unstable_asset, - pair_asset: STABLE_ASSET, - enabled: true, - }, - )); + System::assert_last_event(RuntimeEvent::LiquidityPools(Event::::PoolStateUpdated { + base_asset: unstable_asset, + pair_asset: STABLE_ASSET, + enabled: true, + })); assert_ok!(LiquidityPools::set_range_order( RuntimeOrigin::signed(ALICE), @@ -182,7 +176,7 @@ fn test_buy_back_flip_2() { assert_events_match!( Test, RuntimeEvent::LiquidityPools( - crate::Event::RangeOrderUpdated { + Event::RangeOrderUpdated { .. }, ) => () @@ -275,7 +269,7 @@ fn test_network_fee_calculation() { } #[test] -fn can_update_pool_liquidity_fee() { +fn can_update_pool_liquidity_fee_and_collect_for_limit_order() { new_test_ext().execute_with(|| { let old_fee = 400_000u32; let new_fee = 100_000u32; @@ -382,7 +376,7 @@ fn can_update_pool_liquidity_fee() { range_order_fee_hundredth_pips: new_fee, }) ); - System::assert_has_event(RuntimeEvent::LiquidityPools(crate::Event::::PoolFeeSet { + System::assert_has_event(RuntimeEvent::LiquidityPools(Event::::PoolFeeSet { base_asset: Asset::Eth, pair_asset: STABLE_ASSET, fee_hundredth_pips: new_fee, @@ -512,6 +506,40 @@ fn pallet_limit_order_is_in_sync_with_pool() { Pools::::get(asset_pair.canonical_asset_pair).unwrap().limit_orders_cache; assert_eq!(pallet_limit_orders.zero.get(&ALICE), None); assert_eq!(pallet_limit_orders.zero.get(&BOB).unwrap().get(&0), Some(&100)); + + assert_has_event::(RuntimeEvent::LiquidityPools(Event::::LimitOrderUpdated { + lp: ALICE, + sell_asset: Asset::Eth, + buy_asset: STABLE_ASSET, + id: 0, + tick: 0, + position_delta: None, + amount_total: 0, + collected_fees: 100, + bought_amount: 100, + })); + assert_has_event::(RuntimeEvent::LiquidityPools(Event::::LimitOrderUpdated { + lp: BOB, + sell_asset: Asset::Eth, + buy_asset: STABLE_ASSET, + id: 0, + tick: 100, + position_delta: None, + amount_total: 5, + collected_fees: 100998, + bought_amount: 100998, + })); + assert_has_event::(RuntimeEvent::LiquidityPools(Event::::LimitOrderUpdated { + lp: BOB, + sell_asset: STABLE_ASSET, + buy_asset: Asset::Eth, + id: 1, + tick: 100, + position_delta: None, + amount_total: 910, + collected_fees: 8998, + bought_amount: 8998, + })); }); } From 7acd54b11e5a71457e4e98cd989e9899077e2ff8 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Tue, 3 Oct 2023 19:09:09 +1300 Subject: [PATCH 10/13] Refactored some code out to reduce duplication --- state-chain/pallets/cf-pools/src/lib.rs | 94 ++++++++++++++----------- 1 file changed, 53 insertions(+), 41 deletions(-) diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index 297b4164c5..8a183c9919 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -4,7 +4,7 @@ use core::ops::Range; use cf_amm::{ common::{Amount, Order, Price, Side, SideMap, Tick}, limit_orders, - limit_orders::PositionInfo, + limit_orders::{Collected, PositionInfo}, range_orders, range_orders::Liquidity, PoolState, @@ -226,10 +226,10 @@ pub mod pallet { #[scale_info(skip_type_params(T))] pub struct Pool { pub enabled: bool, - /// A cache of all the range orders that exist in the pool. This must be kept upto date + /// A cache of all the range orders that exist in the pool. This must be kept up to date /// with the underlying pool. pub range_orders_cache: BTreeMap>>, - /// A cache of all the limit orders that exist in the pool. This must be kept upto date + /// A cache of all the limit orders that exist in the pool. This must be kept up to date /// with the underlying pool. These are grouped by the asset the limit order is selling pub limit_orders_cache: SideMap>>, pub pool_state: PoolState<(T::AccountId, OrderId)>, @@ -999,31 +999,17 @@ pub mod pallet { for ((tick, (lp, order)), (collected, position_info)) in collected_fees.into_iter() { - let collected_fees = - asset_pair.try_credit_asset(&lp, !side, collected.fees)?; - let bought_amount = asset_pair.try_credit_asset( - &lp, - !side, - collected.bought_amount, - )?; - pool.update_limit_order_storage( + Self::process_limit_order_update( + pool, + asset_pair, &lp, side, order, tick, - &position_info, - ); - Self::deposit_event(Event::::LimitOrderUpdated { - lp: lp.clone(), - sell_asset: asset_pair.canonical_asset_pair.side_to_asset(side), - buy_asset: asset_pair.canonical_asset_pair.side_to_asset(!side), - id: order, - tick, - position_delta: None, - amount_total: position_info.amount.try_into()?, - collected_fees, - bought_amount, - }); + collected, + position_info, + None, + )?; } Result::<(), DispatchError>::Ok(()) }) @@ -1213,25 +1199,18 @@ impl Pallet { }, }; - // Update pool's limit orders - pool.update_limit_order_storage(lp, asset_pair.base_side, id, tick, &position_info); - - let collected_fees = - asset_pair.try_credit_asset(lp, !asset_pair.base_side, collected.fees)?; - let bought_amount = - asset_pair.try_credit_asset(lp, !asset_pair.base_side, collected.bought_amount)?; - - Self::deposit_event(Event::::LimitOrderUpdated { - lp: lp.clone(), - sell_asset: asset_pair.canonical_asset_pair.side_to_asset(asset_pair.base_side), - buy_asset: asset_pair.canonical_asset_pair.side_to_asset(!asset_pair.base_side), + // Process the update + Self::process_limit_order_update( + pool, + asset_pair, + lp, + asset_pair.base_side, id, tick, - position_delta: Some((increase_or_decrease, amount_delta)), - amount_total: position_info.amount.try_into()?, - collected_fees, - bought_amount, - }); + collected, + position_info, + Some((increase_or_decrease, amount_delta)), + )?; Ok(amount_delta) } @@ -1576,6 +1555,39 @@ impl Pallet { .map(|side_map| asset_pair.side_map_to_assets_map(side_map)), ) } + + /// Process changes to limit order: + /// - Payout collected `fee` and `bought_amount` + /// - Update cache storage for Pool + /// - Deposit the correct event. + #[allow(clippy::too_many_arguments)] + fn process_limit_order_update( + pool: &mut Pool, + asset_pair: &AssetPair, + lp: &T::AccountId, + side: Side, + order: OrderId, + tick: Tick, + collected: Collected, + position_info: PositionInfo, + position_delta: Option<(IncreaseOrDecrease, AssetAmount)>, + ) -> DispatchResult { + let collected_fees = asset_pair.try_credit_asset(lp, !side, collected.fees)?; + let bought_amount = asset_pair.try_credit_asset(lp, !side, collected.bought_amount)?; + pool.update_limit_order_storage(lp, side, order, tick, &position_info); + Self::deposit_event(Event::::LimitOrderUpdated { + lp: lp.clone(), + sell_asset: asset_pair.canonical_asset_pair.side_to_asset(side), + buy_asset: asset_pair.canonical_asset_pair.side_to_asset(!side), + id: order, + tick, + position_delta, + amount_total: position_info.amount.try_into()?, + collected_fees, + bought_amount, + }); + Ok(()) + } } pub mod utilities { From 54f629a9cabf8bb5b7b70b5b02db8be5229d6345 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Thu, 12 Oct 2023 23:04:51 +1300 Subject: [PATCH 11/13] Address some PR comments --- api/lib/src/lp.rs | 3 +- state-chain/pallets/cf-pools/src/lib.rs | 42 ++++++++----------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/api/lib/src/lp.rs b/api/lib/src/lp.rs index a93e60caec..b90444d9e5 100644 --- a/api/lib/src/lp.rs +++ b/api/lib/src/lp.rs @@ -35,8 +35,7 @@ fn collect_range_order_returns( .filter_map(|event| match event { state_chain_runtime::RuntimeEvent::LiquidityPools( pallet_cf_pools::Event::RangeOrderUpdated { - increase_or_decrease, - liquidity_delta, + position_delta: Some((increase_or_decrease, liquidity_delta)), liquidity_total, assets_delta, collected_fees, diff --git a/state-chain/pallets/cf-pools/src/lib.rs b/state-chain/pallets/cf-pools/src/lib.rs index 8a183c9919..58669dedeb 100644 --- a/state-chain/pallets/cf-pools/src/lib.rs +++ b/state-chain/pallets/cf-pools/src/lib.rs @@ -235,30 +235,6 @@ pub mod pallet { pub pool_state: PoolState<(T::AccountId, OrderId)>, } - impl Pool { - /// Updates the position information in the Pool's cache. - pub fn update_limit_order_storage( - &mut self, - lp: &T::AccountId, - side: Side, - id: OrderId, - tick: cf_amm::common::Tick, - position: &PositionInfo, - ) { - let limit_orders = &mut self.limit_orders_cache[side]; - if position.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); - } - } - } - pub type OrderId = u64; #[derive( @@ -520,8 +496,7 @@ pub mod pallet { pair_asset: Asset, id: OrderId, tick_range: core::ops::Range, - increase_or_decrease: IncreaseOrDecrease, - liquidity_delta: Liquidity, + position_delta: Option<(IncreaseOrDecrease, Liquidity)>, liquidity_total: Liquidity, assets_delta: AssetAmounts, collected_fees: AssetAmounts, @@ -1307,8 +1282,7 @@ impl Pallet { pair_asset: asset_pair.canonical_asset_pair.side_to_asset(!asset_pair.base_side), id, tick_range, - increase_or_decrease, - liquidity_delta, + position_delta: Some((increase_or_decrease, liquidity_delta)), liquidity_total: position_info.liquidity, assets_delta, collected_fees, @@ -1574,7 +1548,17 @@ impl Pallet { ) -> DispatchResult { let collected_fees = asset_pair.try_credit_asset(lp, !side, collected.fees)?; let bought_amount = asset_pair.try_credit_asset(lp, !side, collected.bought_amount)?; - pool.update_limit_order_storage(lp, side, order, tick, &position_info); + let limit_orders = &mut pool.limit_orders_cache[side]; + if position_info.amount.is_zero() { + if let Some(lp_limit_orders) = limit_orders.get_mut(lp) { + lp_limit_orders.remove(&order); + if lp_limit_orders.is_empty() { + limit_orders.remove(lp); + } + } + } else { + limit_orders.entry(lp.clone()).or_default().insert(order, tick); + } Self::deposit_event(Event::::LimitOrderUpdated { lp: lp.clone(), sell_asset: asset_pair.canonical_asset_pair.side_to_asset(side), From 707c5488170541b72dee161ba5666339dfca4ce4 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Fri, 13 Oct 2023 09:10:21 +1300 Subject: [PATCH 12/13] Fixed benchmarking --- state-chain/pallets/cf-pools/src/mock.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/state-chain/pallets/cf-pools/src/mock.rs b/state-chain/pallets/cf-pools/src/mock.rs index 33b94c1bac..b7ac55578c 100644 --- a/state-chain/pallets/cf-pools/src/mock.rs +++ b/state-chain/pallets/cf-pools/src/mock.rs @@ -78,7 +78,6 @@ impl LpBalanceApi for MockBalance { #[cfg(feature = "runtime-benchmarks")] fn register_liquidity_refund_address(_who: &Self::AccountId, _address: ForeignChainAddress) { - unimplemented!() } fn ensure_has_refund_address_for_pair( @@ -86,7 +85,7 @@ impl LpBalanceApi for MockBalance { _base_asset: Asset, _pair_asset: Asset, ) -> DispatchResult { - unimplemented!() + Ok(()) } fn try_credit_account( From d006c322c3d87f255305473e88843418470ac181 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Fri, 13 Oct 2023 09:11:51 +1300 Subject: [PATCH 13/13] fmt --- state-chain/pallets/cf-pools/src/mock.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/state-chain/pallets/cf-pools/src/mock.rs b/state-chain/pallets/cf-pools/src/mock.rs index b7ac55578c..bef3b59281 100644 --- a/state-chain/pallets/cf-pools/src/mock.rs +++ b/state-chain/pallets/cf-pools/src/mock.rs @@ -77,8 +77,7 @@ impl LpBalanceApi for MockBalance { type AccountId = AccountId; #[cfg(feature = "runtime-benchmarks")] - fn register_liquidity_refund_address(_who: &Self::AccountId, _address: ForeignChainAddress) { - } + fn register_liquidity_refund_address(_who: &Self::AccountId, _address: ForeignChainAddress) {} fn ensure_has_refund_address_for_pair( _who: &Self::AccountId,