diff --git a/src/chain/evm.rs b/src/chain/evm.rs index 18de439..ef226d6 100644 --- a/src/chain/evm.rs +++ b/src/chain/evm.rs @@ -11,10 +11,6 @@ type U96 = ruint::Uint<96, 2>; #[cfg(feature = "evm-alloy-1")] use crate::alloy_primitives::aliases::U96; use crate::private; -use crate::quoting::pools::base::{ - BasePool, BasePoolConfig, BasePoolConstructionError, BasePoolKey, BasePoolQuoteError, - BasePoolResources, BasePoolState, BasePoolTypeConfig, TickSpacing, -}; use crate::quoting::pools::boosted_fees::concentrated::{ BoostedFeesConcentratedPool, BoostedFeesConcentratedPoolConfig, BoostedFeesConcentratedPoolConstructionError, BoostedFeesConcentratedPoolKey, @@ -34,6 +30,11 @@ use crate::quoting::pools::boosted_fees::stableswap::{ BoostedFeesStableswapPoolQuoteError, BoostedFeesStableswapPoolResources, BoostedFeesStableswapPoolState, BoostedFeesStableswapPoolTypeConfig, }; +use crate::quoting::pools::concentrated::{ + ConcentratedPool, ConcentratedPoolConfig, ConcentratedPoolConstructionError, + ConcentratedPoolKey, ConcentratedPoolQuoteError, ConcentratedPoolResources, + ConcentratedPoolState, ConcentratedPoolTypeConfig, TickSpacing, +}; use crate::quoting::pools::full_range::{ FullRangePool, FullRangePoolConfig, FullRangePoolConstructionError, FullRangePoolKey, FullRangePoolQuoteError, FullRangePoolResources, FullRangePoolState, FullRangePoolTypeConfig, @@ -59,14 +60,14 @@ use crate::quoting::pools::twamm::{ use crate::quoting::types::{PoolConfig, PoolKey, TokenAmount}; // Re-export pool types for ergonomic, chain-scoped usage. -pub type EvmBasePool = BasePool; -pub type EvmBasePoolConstructionError = BasePoolConstructionError; -pub type EvmBasePoolConfig = BasePoolConfig; -pub type EvmBasePoolKey = BasePoolKey; -pub type EvmBasePoolQuoteError = BasePoolQuoteError; -pub type EvmBasePoolResources = BasePoolResources; -pub type EvmBasePoolState = BasePoolState; -pub type EvmBasePoolTypeConfig = BasePoolTypeConfig; +pub type EvmConcentratedPool = ConcentratedPool; +pub type EvmConcentratedPoolConstructionError = ConcentratedPoolConstructionError; +pub type EvmConcentratedPoolConfig = ConcentratedPoolConfig; +pub type EvmConcentratedPoolKey = ConcentratedPoolKey; +pub type EvmConcentratedPoolQuoteError = ConcentratedPoolQuoteError; +pub type EvmConcentratedPoolResources = ConcentratedPoolResources; +pub type EvmConcentratedPoolState = ConcentratedPoolState; +pub type EvmConcentratedPoolTypeConfig = ConcentratedPoolTypeConfig; pub type EvmFullRangePool = FullRangePool; pub type EvmFullRangePoolConstructionError = FullRangePoolConstructionError; @@ -90,7 +91,7 @@ pub type EvmMevCapturePool = MevCapturePool; pub type EvmMevCapturePoolConstructionError = MevCapturePoolConstructionError; pub type EvmMevCapturePoolConfig = MevCapturePoolConfig; pub type EvmMevCapturePoolKey = MevCapturePoolKey; -pub type EvmMevCapturePoolQuoteError = BasePoolQuoteError; +pub type EvmMevCapturePoolQuoteError = ConcentratedPoolQuoteError; pub type EvmMevCapturePoolResources = MevCapturePoolResources; pub type EvmMevCaptureStandalonePoolResources = MevCaptureStandalonePoolResources; pub type EvmMevCapturePoolState = MevCapturePoolState; @@ -190,7 +191,7 @@ pub enum EvmPoolTypeConfig { /// Stableswap config for pegged assets. Stableswap(StableswapPoolTypeConfig), /// Concentrated liquidity config (tick spacing). - Concentrated(BasePoolTypeConfig), + Concentrated(ConcentratedPoolTypeConfig), } #[cfg(feature = "evm-alloy-1")] diff --git a/src/chain/mod.rs b/src/chain/mod.rs index 3322ad5..393f8cc 100644 --- a/src/chain/mod.rs +++ b/src/chain/mod.rs @@ -1,6 +1,6 @@ use crate::{ private, - quoting::{pools::base::TickSpacing, types::Pool}, + quoting::{pools::concentrated::TickSpacing, types::Pool}, }; use core::hash::Hash; use core::{ diff --git a/src/chain/starknet.rs b/src/chain/starknet.rs index d37b287..b8f5a3f 100644 --- a/src/chain/starknet.rs +++ b/src/chain/starknet.rs @@ -6,9 +6,10 @@ use thiserror::Error; use crate::chain::Chain; use crate::private; -use crate::quoting::pools::base::{ - BasePool, BasePoolConfig, BasePoolConstructionError, BasePoolKey, BasePoolQuoteError, - BasePoolResources, BasePoolState, BasePoolTypeConfig, TickSpacing, +use crate::quoting::pools::concentrated::{ + ConcentratedPool, ConcentratedPoolConfig, ConcentratedPoolConstructionError, + ConcentratedPoolKey, ConcentratedPoolQuoteError, ConcentratedPoolResources, + ConcentratedPoolState, ConcentratedPoolTypeConfig, TickSpacing, }; use crate::quoting::pools::limit_order::{ LimitOrderPool, LimitOrderPoolConfig, LimitOrderPoolConstructionError, LimitOrderPoolKey, @@ -29,20 +30,20 @@ use crate::quoting::pools::twamm::{ use crate::quoting::types::{PoolConfig, PoolKey, Tick, TokenAmount}; // Re-export pool types for ergonomic, chain-scoped usage. -pub type StarknetBasePool = BasePool; -pub type StarknetBasePoolConstructionError = BasePoolConstructionError; -pub type StarknetBasePoolConfig = BasePoolConfig; -pub type StarknetBasePoolKey = BasePoolKey; -pub type StarknetBasePoolQuoteError = BasePoolQuoteError; -pub type StarknetBasePoolResources = BasePoolResources; -pub type StarknetBasePoolState = BasePoolState; -pub type StarknetBasePoolTypeConfig = BasePoolTypeConfig; +pub type StarknetConcentratedPool = ConcentratedPool; +pub type StarknetConcentratedPoolConstructionError = ConcentratedPoolConstructionError; +pub type StarknetConcentratedPoolConfig = ConcentratedPoolConfig; +pub type StarknetConcentratedPoolKey = ConcentratedPoolKey; +pub type StarknetConcentratedPoolQuoteError = ConcentratedPoolQuoteError; +pub type StarknetConcentratedPoolResources = ConcentratedPoolResources; +pub type StarknetConcentratedPoolState = ConcentratedPoolState; +pub type StarknetConcentratedPoolTypeConfig = ConcentratedPoolTypeConfig; pub type StarknetLimitOrderPool = LimitOrderPool; pub type StarknetLimitOrderPoolConstructionError = LimitOrderPoolConstructionError; pub type StarknetLimitOrderPoolConfig = LimitOrderPoolConfig; pub type StarknetLimitOrderPoolKey = LimitOrderPoolKey; -pub type StarknetLimitOrderPoolQuoteError = BasePoolQuoteError; +pub type StarknetLimitOrderPoolQuoteError = ConcentratedPoolQuoteError; pub type StarknetLimitOrderPoolResources = LimitOrderPoolResources; pub type StarknetLimitOrderStandalonePoolResources = LimitOrderStandalonePoolResources; pub type StarknetLimitOrderPoolState = LimitOrderPoolState; @@ -52,14 +53,14 @@ pub type StarknetOraclePoolConstructionError = OraclePoolConstructionError; pub type StarknetOraclePoolConfig = OraclePoolConfig; pub type StarknetOraclePoolKey = OraclePoolKey; -pub type StarknetOraclePoolQuoteError = BasePoolQuoteError; -pub type StarknetOraclePoolResources = OraclePoolResources; +pub type StarknetOraclePoolQuoteError = ConcentratedPoolQuoteError; +pub type StarknetOraclePoolResources = OraclePoolResources; pub type StarknetOracleStandalonePoolResources = OracleStandalonePoolResources; -pub type StarknetOraclePoolState = OraclePoolState; +pub type StarknetOraclePoolState = OraclePoolState; pub type StarknetOraclePoolTypeConfig = OraclePoolTypeConfig; pub type StarknetSplinePool = SplinePool; -pub type StarknetSplinePoolConstructionError = BasePoolConstructionError; +pub type StarknetSplinePoolConstructionError = ConcentratedPoolConstructionError; pub type StarknetSplinePoolConfig = SplinePoolConfig; pub type StarknetSplinePoolKey = SplinePoolKey; pub type StarknetSplinePoolResources = SplinePoolResources; @@ -71,10 +72,10 @@ pub type StarknetTwammPoolConstructionError = TwammPoolConstructionError; pub type StarknetTwammPoolConfig = TwammPoolConfig; pub type StarknetTwammPoolKey = TwammPoolKey; -pub type StarknetTwammPoolQuoteError = TwammPoolQuoteError; -pub type StarknetTwammPoolResources = TwammPoolResources; +pub type StarknetTwammPoolQuoteError = TwammPoolQuoteError; +pub type StarknetTwammPoolResources = TwammPoolResources; pub type StarknetTwammStandalonePoolResources = TwammStandalonePoolResources; -pub type StarknetTwammPoolState = TwammPoolState; +pub type StarknetTwammPoolState = TwammPoolState; pub type StarknetTwammPoolTypeConfig = TwammPoolTypeConfig; pub const STARKNET_MAX_TICK_SPACING: TickSpacing = TickSpacing(354892); @@ -121,8 +122,8 @@ pub type StarknetTokenAmount = TokenAmount<::Address>; /// Errors constructing a Starknet full range pool from inputs. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Error)] pub enum FullRangePoolConstructionError { - #[error("base pool error")] - BasePoolConstructionError(#[from] BasePoolConstructionError), + #[error("concentrated pool error")] + ConcentratedPoolConstructionError(#[from] ConcentratedPoolConstructionError), #[error("active liquidity does not fit into signed integer")] ActiveLiquidityDoesNotFitSignedInteger, } @@ -130,7 +131,7 @@ pub enum FullRangePoolConstructionError { impl Chain for Starknet { type Fee = u128; - type FullRangePool = BasePool; + type FullRangePool = ConcentratedPool; type FullRangePoolConstructionError = FullRangePoolConstructionError; fn max_tick_spacing() -> TickSpacing { @@ -218,20 +219,20 @@ impl Chain for Starknet { extension, }; - BasePool::new( + ConcentratedPool::new( PoolKey { token0, token1, config, }, - BasePoolState { + ConcentratedPoolState { sqrt_ratio, liquidity, active_tick_index, }, sorted_ticks, ) - .map_err(FullRangePoolConstructionError::BasePoolConstructionError) + .map_err(FullRangePoolConstructionError::ConcentratedPoolConstructionError) } type Address = Felt; diff --git a/src/math/facade.rs b/src/math/facade.rs index 17f1f1b..e2fa916 100644 --- a/src/math/facade.rs +++ b/src/math/facade.rs @@ -1,16 +1,5 @@ //! Thin facades over std/libm so float operations work in `no_std` mode. use ruint::aliases::U256; -#[inline] -pub fn ln(value: f64) -> f64 { - #[cfg(feature = "std")] - { - value.ln() - } - #[cfg(feature = "no_std")] - { - libm::log(value) - } -} #[inline] pub fn log_base(value: f64, base: f64) -> f64 { @@ -20,7 +9,7 @@ pub fn log_base(value: f64, base: f64) -> f64 { } #[cfg(feature = "no_std")] { - ln(value) / ln(base) + libm::log(value) / libm::log(base) } } diff --git a/src/math/uint.rs b/src/math/uint.rs index 51550a1..5edef06 100644 --- a/src/math/uint.rs +++ b/src/math/uint.rs @@ -8,3 +8,37 @@ pub fn u256_to_float_base_x128(x128: U256) -> f64 { + l2 as f64 + (l3 as f64 * 18446744073709551616f64) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn u256_to_fraction() { + assert_eq!( + u256_to_float_base_x128(U256::from_limbs([ + 16403144882676588163, + 1525053501570699700, + 35, + 0 + ])), + 35.08267331597798 + ); + assert_eq!( + u256_to_float_base_x128(U256::from_limbs([123456, 0, 0, 0])), + 3.628045764377908e-34 + ); + assert_eq!( + u256_to_float_base_x128(U256::from_limbs([0, 123456, 0, 0])), + 6.692563170318522e-15 + ); + assert_eq!( + u256_to_float_base_x128(U256::from_limbs([0, 0, 123456, 0])), + 123456.0 + ); + assert_eq!( + u256_to_float_base_x128(U256::from_limbs([0, 0, 0, 123456])), + 2.2773612363638864e24 + ); + } +} diff --git a/src/quoting/pools/boosted_fees/concentrated.rs b/src/quoting/pools/boosted_fees/concentrated.rs index 321d155..cd8c868 100644 --- a/src/quoting/pools/boosted_fees/concentrated.rs +++ b/src/quoting/pools/boosted_fees/concentrated.rs @@ -2,7 +2,7 @@ use crate::chain::Chain; use crate::quoting::types::{ BlockTimestamp, LastTimeInfo, Pool, PoolConfig, PoolKey, Quote, QuoteParams, TimeRateDelta, }; -use crate::quoting::util::real_last_time; +use crate::quoting::util::{approximate_extra_distinct_time_bitmap_lookups, real_last_time}; use crate::{private, quoting::types::PoolState}; use alloc::vec::Vec; @@ -12,26 +12,26 @@ use ruint::aliases::U256; use thiserror::Error; use crate::chain::evm::{ - Evm, EvmBasePoolKey, EvmBasePoolQuoteError, EvmBasePoolResources, EvmBasePoolState, - EvmBasePoolTypeConfig, + Evm, EvmConcentratedPoolKey, EvmConcentratedPoolQuoteError, EvmConcentratedPoolResources, + EvmConcentratedPoolState, EvmConcentratedPoolTypeConfig, }; -use crate::quoting::pools::base::BasePool; +use crate::quoting::pools::concentrated::ConcentratedPool; /// Boosted fees pool that wraps an underlying pool and tracks donation deltas. #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BoostedFeesConcentratedPool { - underlying_pool: BasePool, + underlying_pool: ConcentratedPool, last_donate_time: u32, donate_rate0: u128, donate_rate1: u128, donate_rate_deltas: Vec, } -pub type BoostedFeesConcentratedPoolTypeConfig = as Pool>::PoolTypeConfig; +pub type BoostedFeesConcentratedPoolTypeConfig = as Pool>::PoolTypeConfig; pub type BoostedFeesConcentratedPoolKey = PoolKey< ::Address, - as Pool>::Fee, + as Pool>::Fee, BoostedFeesConcentratedPoolTypeConfig, >; pub type BoostedFeesConcentratedPoolConfig

= @@ -40,7 +40,7 @@ pub type BoostedFeesConcentratedPoolConfig

= #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BoostedFeesConcentratedPoolState { - pub base_pool_state: EvmBasePoolState, + pub concentrated_pool_state: EvmConcentratedPoolState, pub last_donate_time: u32, pub donate_rate0: u128, pub donate_rate1: u128, @@ -49,8 +49,8 @@ pub struct BoostedFeesConcentratedPoolState { #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Add, AddAssign, Sub, SubAssign)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BoostedFeesConcentratedStandalonePoolResources { - /// The number of seconds that passed since the last donation accumulation - pub virtual_donate_seconds_executed: u32, + /// Number of additional distinct time bitmap lookups (besides the mandatory one). + pub extra_distinct_bitmap_lookups: u32, /// The amount of donate rate updates that were applied pub virtual_donate_delta_times_crossed: u32, /// Whether the donations were executed or not (for a single swap, 1 or 0) @@ -61,7 +61,7 @@ pub struct BoostedFeesConcentratedStandalonePoolResources { #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash, Add, AddAssign, Sub, SubAssign)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BoostedFeesConcentratedPoolResources { - pub base: EvmBasePoolResources, + pub concentrated: EvmConcentratedPoolResources, pub boosted_fees: BoostedFeesConcentratedStandalonePoolResources, } @@ -79,7 +79,7 @@ pub enum BoostedFeesConcentratedPoolConstructionError { impl BoostedFeesConcentratedPool { pub fn new( - underlying_pool: BasePool, + underlying_pool: ConcentratedPool, last_donate_time_info: LastTimeInfo, donate_rate0: u128, donate_rate1: u128, @@ -125,25 +125,25 @@ impl BoostedFeesConcentratedPool { #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Error)] pub enum BoostedFeesConcentratedPoolQuoteError { #[error("underlying pool quote error")] - UnderlyingPoolQuoteError(EvmBasePoolQuoteError), + UnderlyingPoolQuoteError(EvmConcentratedPoolQuoteError), } impl Pool for BoostedFeesConcentratedPool { type Address = ::Address; type Fee = ::Fee; - type PoolTypeConfig = EvmBasePoolTypeConfig; + type PoolTypeConfig = EvmConcentratedPoolTypeConfig; type Resources = BoostedFeesConcentratedPoolResources; type State = BoostedFeesConcentratedPoolState; type QuoteError = BoostedFeesConcentratedPoolQuoteError; type Meta = BlockTimestamp; - fn key(&self) -> EvmBasePoolKey { + fn key(&self) -> EvmConcentratedPoolKey { self.underlying_pool.key() } fn state(&self) -> Self::State { BoostedFeesConcentratedPoolState { - base_pool_state: self.underlying_pool.state(), + concentrated_pool_state: self.underlying_pool.state(), last_donate_time: self.last_donate_time, donate_rate0: self.donate_rate0, donate_rate1: self.donate_rate1, @@ -160,7 +160,7 @@ impl Pool for BoostedFeesConcentratedPool { }: QuoteParams, ) -> Result, Self::QuoteError> { let BoostedFeesConcentratedPoolState { - base_pool_state: underlying_pool_state, + concentrated_pool_state: underlying_pool_state, last_donate_time, mut donate_rate0, mut donate_rate1, @@ -171,7 +171,7 @@ impl Pool for BoostedFeesConcentratedPool { let real_last_donate_time; if current_time as u32 != last_donate_time { - real_last_donate_time = real_last_time(last_donate_time, current_time); + real_last_donate_time = real_last_time(current_time, last_donate_time); let mut time = real_last_donate_time; for delta in self @@ -216,16 +216,19 @@ impl Pool for BoostedFeesConcentratedPool { calculated_amount, fees_paid, execution_resources: BoostedFeesConcentratedPoolResources { - base: underlying_execution_resources, + concentrated: underlying_execution_resources, boosted_fees: BoostedFeesConcentratedStandalonePoolResources { - virtual_donate_seconds_executed: (current_time - real_last_donate_time) as u32, + extra_distinct_bitmap_lookups: approximate_extra_distinct_time_bitmap_lookups( + real_last_donate_time, + current_time, + ), virtual_donate_delta_times_crossed, virtual_donations_executed: u32::from(current_time != real_last_donate_time), fees_accumulated: u32::from(fees_accumulated), }, }, state_after: BoostedFeesConcentratedPoolState { - base_pool_state: underlying_state_after, + concentrated_pool_state: underlying_state_after, last_donate_time: current_time as u32, donate_rate0, donate_rate1, @@ -252,11 +255,11 @@ impl Pool for BoostedFeesConcentratedPool { impl PoolState for BoostedFeesConcentratedPoolState { fn sqrt_ratio(&self) -> U256 { - self.base_pool_state.sqrt_ratio() + self.concentrated_pool_state.sqrt_ratio() } fn liquidity(&self) -> u128 { - self.base_pool_state.liquidity() + self.concentrated_pool_state.liquidity() } } @@ -270,14 +273,14 @@ mod tests { chain::{evm::Evm, tests::ChainTest as _}, math::tick::to_sqrt_ratio, quoting::{ - pools::base::{BasePool, BasePoolState, TickSpacing}, + pools::concentrated::{ConcentratedPool, ConcentratedPoolState, TickSpacing}, types::{Pool, PoolConfig, PoolKey, Quote, QuoteParams, Tick, TokenAmount}, }, }; use alloc::vec; - fn base_pool(liquidity: u128) -> BasePool { - BasePool::new( + fn concentrated_pool(liquidity: u128) -> ConcentratedPool { + ConcentratedPool::new( PoolKey { token0: Evm::zero_address(), token1: Evm::one_address(), @@ -287,7 +290,7 @@ mod tests { extension: Evm::zero_address(), }, }, - BasePoolState { + ConcentratedPoolState { sqrt_ratio: to_sqrt_ratio::(0).expect("valid sqrt ratio"), liquidity, active_tick_index: Some(0), @@ -303,7 +306,7 @@ mod tests { }, ], ) - .expect("base pool construction succeeds") + .expect("concentrated pool construction succeeds") } fn boosted_pool( @@ -313,7 +316,7 @@ mod tests { donate_rate_deltas: Vec, ) -> BoostedFeesConcentratedPool { BoostedFeesConcentratedPool::new( - base_pool(1_000_000), + concentrated_pool(1_000_000), last_donated_time_info, donate_rate0, donate_rate1, @@ -345,13 +348,13 @@ mod tests { let resources = quote.execution_resources.boosted_fees; - assert_eq!(resources.virtual_donate_seconds_executed, 0); + assert_eq!(resources.extra_distinct_bitmap_lookups, 0); assert_eq!(resources.virtual_donate_delta_times_crossed, 0); assert_eq!(resources.virtual_donations_executed, 0); assert_eq!(resources.fees_accumulated, 0); let BoostedFeesConcentratedPoolState { - base_pool_state: _, + concentrated_pool_state: _, last_donate_time, donate_rate0, donate_rate1, @@ -369,13 +372,13 @@ mod tests { let resources = quote.execution_resources.boosted_fees; - assert_eq!(resources.virtual_donate_seconds_executed, 50); + assert_eq!(resources.extra_distinct_bitmap_lookups, 0); assert_eq!(resources.virtual_donate_delta_times_crossed, 0); assert_eq!(resources.virtual_donations_executed, 1); assert_eq!(resources.fees_accumulated, 0); let BoostedFeesConcentratedPoolState { - base_pool_state: _, + concentrated_pool_state: _, last_donate_time, donate_rate0, donate_rate1, @@ -404,13 +407,13 @@ mod tests { let resources = quote.execution_resources.boosted_fees; - assert_eq!(resources.virtual_donate_seconds_executed, 300); + assert_eq!(resources.extra_distinct_bitmap_lookups, 0); assert_eq!(resources.virtual_donate_delta_times_crossed, 1); assert_eq!(resources.virtual_donations_executed, 1); assert_eq!(resources.fees_accumulated, 1); let BoostedFeesConcentratedPoolState { - base_pool_state: _, + concentrated_pool_state: _, last_donate_time, donate_rate0, donate_rate1, @@ -446,13 +449,13 @@ mod tests { let resources = quote.execution_resources.boosted_fees; - assert_eq!(resources.virtual_donate_seconds_executed, 50); + assert_eq!(resources.extra_distinct_bitmap_lookups, 0); assert_eq!(resources.virtual_donate_delta_times_crossed, 0); assert_eq!(resources.virtual_donations_executed, 1); assert_eq!(resources.fees_accumulated, 0); let BoostedFeesConcentratedPoolState { - base_pool_state: _, + concentrated_pool_state: _, last_donate_time, donate_rate0, donate_rate1, diff --git a/src/quoting/pools/base.rs b/src/quoting/pools/concentrated.rs similarity index 82% rename from src/quoting/pools/base.rs rename to src/quoting/pools/concentrated.rs index 0b89e92..651a2e2 100644 --- a/src/quoting/pools/base.rs +++ b/src/quoting/pools/concentrated.rs @@ -4,7 +4,8 @@ use crate::quoting::pools::{ }; use crate::quoting::types::{Pool, PoolConfig, PoolKey, Quote, QuoteParams, Tick}; use crate::quoting::util::{ - approximate_number_of_tick_spacings_crossed, construct_sorted_ticks, ConstructSortedTicksError, + approximate_extra_distinct_tick_bitmap_lookups, construct_sorted_ticks, + ConstructSortedTicksError, }; use crate::{ chain::Chain, @@ -27,32 +28,33 @@ use thiserror::Error; deserialize = "C::Fee: serde::Deserialize<'de>, C::Address: serde::Deserialize<'de>" )) )] -pub struct BasePool { +pub struct ConcentratedPool { /// Immutable pool key identifying tokens and fee config. - key: BasePoolKey, + key: ConcentratedPoolKey, /// Current pool state (price, liquidity, active tick index). - state: BasePoolState, + state: ConcentratedPoolState, /// Sorted ticks defining liquidity changes across price ranges. sorted_ticks: Vec, } -/// Unique identifier for a [`BasePool`]. -pub type BasePoolKey = PoolKey<::Address, ::Fee, BasePoolTypeConfig>; -/// Pool configuration for a [`BasePool`]. -pub type BasePoolConfig = - PoolConfig<::Address, ::Fee, BasePoolTypeConfig>; -/// Type configuration for a [`BasePool`], representing the tick spacing. -pub type BasePoolTypeConfig = TickSpacing; +/// Unique identifier for a [`ConcentratedPool`]. +pub type ConcentratedPoolKey = + PoolKey<::Address, ::Fee, ConcentratedPoolTypeConfig>; +/// Pool configuration for a [`ConcentratedPool`]. +pub type ConcentratedPoolConfig = + PoolConfig<::Address, ::Fee, ConcentratedPoolTypeConfig>; +/// Type configuration for a [`ConcentratedPool`], representing the tick spacing. +pub type ConcentratedPoolTypeConfig = TickSpacing; /// Tick spacing for a concentrated pool. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TickSpacing(pub u32); -/// Price/liquidity state for a [`BasePool`]. +/// Price/liquidity state for a [`ConcentratedPool`]. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct BasePoolState { +pub struct ConcentratedPoolState { /// Current square root price ratio. pub sqrt_ratio: U256, /// Active liquidity at the current price. @@ -64,18 +66,18 @@ pub struct BasePoolState { /// Resources consumed during swap execution #[derive(Clone, Copy, Default, Debug, PartialEq, Hash, Eq, Add, AddAssign, Sub, SubAssign)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct BasePoolResources { +pub struct ConcentratedPoolResources { /// Whether price changed when no override was provided. pub no_override_price_change: u32, /// Count of initialized ticks crossed during the quote. pub initialized_ticks_crossed: u32, - /// Count of tick spacings crossed during the quote. - pub tick_spacings_crossed: u32, + /// Number of additional distinct tick bitmap lookups (besides the mandatory one). + pub extra_distinct_bitmap_lookups: u32, } -/// Errors that can occur when constructing a `BasePool`. +/// Errors that can occur when constructing a `ConcentratedPool`. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Error)] -pub enum BasePoolConstructionError { +pub enum ConcentratedPoolConstructionError { #[error(transparent)] Common(#[from] CommonPoolConstructionError), #[error("constructing ticks from partial data")] @@ -116,7 +118,7 @@ pub enum BasePoolConstructionError { } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Error)] -pub enum BasePoolQuoteError { +pub enum ConcentratedPoolQuoteError { #[error(transparent)] Common(#[from] CommonPoolQuoteError), #[error("invalid price limit")] @@ -127,11 +129,11 @@ pub enum BasePoolQuoteError { FailedComputeSwapStep(#[from] ComputeStepError), } -impl BasePool { - /// Creates a `BasePool` from partial tick data retrieved from a quote data fetcher lens contract. +impl ConcentratedPool { + /// Creates a `ConcentratedPool` from partial tick data retrieved from a quote data fetcher lens contract. /// /// This helper constructor takes partial tick data along with min/max tick boundaries and constructs - /// a valid `BasePool` instance with properly balanced liquidity deltas. + /// a valid `ConcentratedPool` instance with properly balanced liquidity deltas. /// /// # Arguments /// @@ -145,16 +147,16 @@ impl BasePool { /// /// # Returns /// - /// * `Result` - A new `BasePool` instance or an error + /// * `Result` - A new `ConcentratedPool` instance or an error pub fn from_partial_data( - key: BasePoolKey, + key: ConcentratedPoolKey, sqrt_ratio: U256, partial_ticks: Vec, min_tick_searched: i32, max_tick_searched: i32, liquidity: u128, current_tick: i32, - ) -> Result { + ) -> Result { let tick_spacing = key.config.pool_type_config; let spacing_i32 = tick_spacing.0 as i32; let tick_spacing = tick_spacing.0; @@ -168,12 +170,12 @@ impl BasePool { liquidity, current_tick, ) - .map_err(BasePoolConstructionError::ConstructSortedTicksFromPartialDataError)?; + .map_err(ConcentratedPoolConstructionError::ConstructSortedTicksFromPartialDataError)?; // Ensure all ticks are multiples of tick spacing for tick in &sorted_ticks { if tick.index % spacing_i32 != 0 { - return Err(BasePoolConstructionError::TickNotMultipleOfSpacing); + return Err(ConcentratedPoolConstructionError::TickNotMultipleOfSpacing); } } @@ -187,8 +189,8 @@ impl BasePool { } } - // Create the BasePoolState with the provided sqrt_ratio, liquidity, and computed active_tick_index - let state = BasePoolState { + // Create the ConcentratedPoolState with the provided sqrt_ratio, liquidity, and computed active_tick_index + let state = ConcentratedPoolState { sqrt_ratio, liquidity, active_tick_index, @@ -199,20 +201,20 @@ impl BasePool { } pub fn new( - key: BasePoolKey, - state: BasePoolState, + key: ConcentratedPoolKey, + state: ConcentratedPoolState, sorted_ticks: Vec, - ) -> Result { + ) -> Result { ensure_valid_token_order(&key)?; let tick_spacing = key.config.pool_type_config; if tick_spacing > C::max_tick_spacing() { - return Err(BasePoolConstructionError::TickSpacingTooLarge); + return Err(ConcentratedPoolConstructionError::TickSpacingTooLarge); } if tick_spacing.0.is_zero() { - return Err(BasePoolConstructionError::TickSpacingCannotBeZero); + return Err(ConcentratedPoolConstructionError::TickSpacingCannotBeZero); } // Check ticks are sorted in linear time @@ -225,13 +227,13 @@ impl BasePool { // Verify ticks are sorted if let Some(last) = last_tick { if tick.index <= last { - return Err(BasePoolConstructionError::TicksNotSorted); + return Err(ConcentratedPoolConstructionError::TicksNotSorted); } } // Verify ticks are multiples of tick_spacing if !(tick.index % spacing_i32).is_zero() { - return Err(BasePoolConstructionError::TickNotMultipleOfSpacing); + return Err(ConcentratedPoolConstructionError::TickNotMultipleOfSpacing); } last_tick = Some(tick.index); @@ -242,7 +244,7 @@ impl BasePool { } else { total_liquidity.checked_add(tick.liquidity_delta.unsigned_abs()) } - .ok_or(BasePoolConstructionError::ActiveLiquidityOverflow)?; + .ok_or(ConcentratedPoolConstructionError::ActiveLiquidityOverflow)?; // Calculate active liquidity if let Some(active_index) = state.active_tick_index { @@ -252,39 +254,41 @@ impl BasePool { } else { active_liquidity.checked_sub(tick.liquidity_delta.unsigned_abs()) } - .ok_or(BasePoolConstructionError::ActiveLiquidityOverflow)?; + .ok_or(ConcentratedPoolConstructionError::ActiveLiquidityOverflow)?; } } } // Verify total liquidity is zero if !total_liquidity.is_zero() { - return Err(BasePoolConstructionError::TotalLiquidityNotZero); + return Err(ConcentratedPoolConstructionError::TotalLiquidityNotZero); } // Verify active liquidity matches state liquidity if active_liquidity != state.liquidity { - return Err(BasePoolConstructionError::ActiveLiquidityMismatch); + return Err(ConcentratedPoolConstructionError::ActiveLiquidityMismatch); } // Validate sqrt ratio against active or first tick if let Some(active) = state.active_tick_index { let tick = sorted_ticks .get(active) - .ok_or(BasePoolConstructionError::ActiveTickIndexOutOfBounds)?; + .ok_or(ConcentratedPoolConstructionError::ActiveTickIndexOutOfBounds)?; - let active_tick_sqrt_ratio = to_sqrt_ratio::(tick.index) - .ok_or(BasePoolConstructionError::InvalidTickIndex(tick.index))?; + let active_tick_sqrt_ratio = to_sqrt_ratio::(tick.index).ok_or( + ConcentratedPoolConstructionError::InvalidTickIndex(tick.index), + )?; if active_tick_sqrt_ratio > state.sqrt_ratio { - return Err(BasePoolConstructionError::ActiveTickSqrtRatioInvalid); + return Err(ConcentratedPoolConstructionError::ActiveTickSqrtRatioInvalid); } } else if let Some(first) = sorted_ticks.first() { - let first_tick_sqrt_ratio = to_sqrt_ratio::(first.index) - .ok_or(BasePoolConstructionError::InvalidTickIndex(first.index))?; + let first_tick_sqrt_ratio = to_sqrt_ratio::(first.index).ok_or( + ConcentratedPoolConstructionError::InvalidTickIndex(first.index), + )?; if state.sqrt_ratio > first_tick_sqrt_ratio { - return Err(BasePoolConstructionError::SqrtRatioTooHighWithNoActiveTick); + return Err(ConcentratedPoolConstructionError::SqrtRatioTooHighWithNoActiveTick); } } @@ -300,7 +304,7 @@ impl BasePool { } } -impl PoolState for BasePoolState { +impl PoolState for ConcentratedPoolState { fn sqrt_ratio(&self) -> U256 { self.sqrt_ratio } @@ -310,14 +314,14 @@ impl PoolState for BasePoolState { } } -impl Pool for BasePool { +impl Pool for ConcentratedPool { type Address = C::Address; type Fee = C::Fee; - type Resources = BasePoolResources; - type State = BasePoolState; - type QuoteError = BasePoolQuoteError; + type Resources = ConcentratedPoolResources; + type State = ConcentratedPoolState; + type QuoteError = ConcentratedPoolQuoteError; type Meta = (); - type PoolTypeConfig = BasePoolTypeConfig; + type PoolTypeConfig = ConcentratedPoolTypeConfig; fn key(&self) -> PoolKey { self.key @@ -342,7 +346,7 @@ impl Pool for BasePool { is_price_increasing: is_token1, consumed_amount: 0, calculated_amount: 0, - execution_resources: BasePoolResources::default(), + execution_resources: ConcentratedPoolResources::default(), state_after: state, fees_paid: 0, }); @@ -355,13 +359,13 @@ impl Pool for BasePool { let sqrt_ratio_limit = if let Some(limit) = params.sqrt_ratio_limit { if is_increasing && limit < sqrt_ratio { - return Err(BasePoolQuoteError::InvalidSqrtRatioLimit); + return Err(ConcentratedPoolQuoteError::InvalidSqrtRatioLimit); } if !is_increasing && limit > sqrt_ratio { - return Err(BasePoolQuoteError::InvalidSqrtRatioLimit); + return Err(ConcentratedPoolQuoteError::InvalidSqrtRatioLimit); } if limit < C::min_sqrt_ratio() || limit > C::max_sqrt_ratio() { - return Err(BasePoolQuoteError::InvalidSqrtRatioLimit); + return Err(ConcentratedPoolQuoteError::InvalidSqrtRatioLimit); } limit } else if is_increasing { @@ -381,14 +385,14 @@ impl Pool for BasePool { if let Some(index) = active_tick_index { if let Some(tick) = self.sorted_ticks.get(index + 1) { let ratio = to_sqrt_ratio::(tick.index) - .ok_or(BasePoolQuoteError::InvalidTick(tick.index))?; + .ok_or(ConcentratedPoolQuoteError::InvalidTick(tick.index))?; Some((index + 1, tick, ratio)) } else { None } } else if let Some(tick) = self.sorted_ticks.first() { let ratio = to_sqrt_ratio::(tick.index) - .ok_or(BasePoolQuoteError::InvalidTick(tick.index))?; + .ok_or(ConcentratedPoolQuoteError::InvalidTick(tick.index))?; Some((0, tick, ratio)) } else { None @@ -396,7 +400,7 @@ impl Pool for BasePool { } else if let Some(index) = active_tick_index { if let Some(tick) = self.sorted_ticks.get(index) { let ratio = to_sqrt_ratio::(tick.index) - .ok_or(BasePoolQuoteError::InvalidTick(tick.index))?; + .ok_or(ConcentratedPoolQuoteError::InvalidTick(tick.index))?; Some((index, tick, ratio)) } else { None @@ -424,7 +428,7 @@ impl Pool for BasePool { is_token1, self.key.config.fee, ) - .map_err(BasePoolQuoteError::FailedComputeSwapStep)?; + .map_err(ConcentratedPoolQuoteError::FailedComputeSwapStep)?; amount_remaining -= step.consumed_amount; calculated_amount += step.calculated_amount; @@ -456,19 +460,19 @@ impl Pool for BasePool { } } - let resources = BasePoolResources { + let resources = ConcentratedPoolResources { no_override_price_change: u32::from( starting_sqrt_ratio == self.state.sqrt_ratio && starting_sqrt_ratio != sqrt_ratio, ), initialized_ticks_crossed, - tick_spacings_crossed: approximate_number_of_tick_spacings_crossed( + extra_distinct_bitmap_lookups: approximate_extra_distinct_tick_bitmap_lookups( starting_sqrt_ratio, sqrt_ratio, self.key.config.pool_type_config.0, ), }; - let state_after = BasePoolState { + let state_after = ConcentratedPoolState { sqrt_ratio, liquidity, active_tick_index, @@ -501,8 +505,8 @@ impl Pool for BasePool { } } -impl private::Sealed for BasePool {} -impl private::Sealed for BasePoolState {} +impl private::Sealed for ConcentratedPool {} +impl private::Sealed for ConcentratedPoolState {} #[cfg(test)] mod tests { @@ -518,7 +522,7 @@ mod tests { use alloc::vec; use num_traits::Zero; - fn pool_key(tick_spacing: u32, fee: C::Fee) -> BasePoolKey { + fn pool_key(tick_spacing: u32, fee: C::Fee) -> ConcentratedPoolKey { PoolKey { token0: C::zero_address(), token1: C::one_address(), @@ -540,8 +544,12 @@ mod tests { .collect() } - fn pool_state(sqrt_ratio: U256, liquidity: u128, active: Option) -> BasePoolState { - BasePoolState { + fn pool_state( + sqrt_ratio: U256, + liquidity: u128, + active: Option, + ) -> ConcentratedPoolState { + ConcentratedPoolState { sqrt_ratio, liquidity, active_tick_index: active, @@ -562,7 +570,7 @@ mod tests { #[test] fn token0_must_be_less_than_token1() { run_for_all_chains!(ChainTy, _chain => { - let result = BasePool::::new( + let result = ConcentratedPool::::new( PoolKey { token0: ChainTy::zero_address(), token1: ChainTy::zero_address(), @@ -577,7 +585,7 @@ mod tests { ); assert_eq!( result.unwrap_err(), - BasePoolConstructionError::Common( + ConcentratedPoolConstructionError::Common( CommonPoolConstructionError::TokenOrderInvalid ) ); @@ -587,14 +595,14 @@ mod tests { #[test] fn tick_spacing_zero_reverts() { run_for_all_chains!(ChainTy, _chain => { - let result = BasePool::::new( + let result = ConcentratedPool::::new( pool_key::(0, zero_fee::()), pool_state(SQRT_RATIO_ONE, 0, None), vec![], ); assert_eq!( result.unwrap_err(), - BasePoolConstructionError::TickSpacingCannotBeZero + ConcentratedPoolConstructionError::TickSpacingCannotBeZero ); }); } @@ -603,14 +611,14 @@ mod tests { fn tick_spacing_cannot_exceed_max() { run_for_all_chains!(ChainTy, _chain => { if let Some(invalid) = ChainTy::max_tick_spacing().0.checked_add(1) { - let result = BasePool::::new( + let result = ConcentratedPool::::new( pool_key::(invalid, zero_fee::()), pool_state(SQRT_RATIO_ONE, 0, None), vec![], ); assert_eq!( result.unwrap_err(), - BasePoolConstructionError::TickSpacingTooLarge + ConcentratedPoolConstructionError::TickSpacingTooLarge ); } }); @@ -619,14 +627,14 @@ mod tests { #[test] fn ticks_must_be_sorted() { run_for_all_chains!(ChainTy, _chain => { - let result = BasePool::::new( + let result = ConcentratedPool::::new( pool_key::(1, zero_fee::()), pool_state(sqrt_ratio::(0), 1, Some(0)), ticks(&[(ChainTy::max_tick(), 0), (0, 0)]), ); assert_eq!( result.unwrap_err(), - BasePoolConstructionError::TicksNotSorted + ConcentratedPoolConstructionError::TicksNotSorted ); }); } @@ -635,14 +643,14 @@ mod tests { fn ticks_must_align_with_spacing() { run_for_all_chains!(ChainTy, _chain => { let spacing = ChainTy::max_tick_spacing(); - let result = BasePool::::new( + let result = ConcentratedPool::::new( pool_key::(spacing.0, zero_fee::()), pool_state(sqrt_ratio::(0), 1, Some(0)), ticks(&[(-1, 1), (ChainTy::max_tick() - 1, -1)]), ); assert_eq!( result.unwrap_err(), - BasePoolConstructionError::TickNotMultipleOfSpacing + ConcentratedPoolConstructionError::TickNotMultipleOfSpacing ); }); } @@ -650,14 +658,14 @@ mod tests { #[test] fn total_liquidity_must_sum_to_zero() { run_for_all_chains!(ChainTy, _chain => { - let result = BasePool::::new( + let result = ConcentratedPool::::new( pool_key::(1, zero_fee::()), pool_state(sqrt_ratio::(0), 1, Some(0)), ticks(&[(0, 2), (ChainTy::max_tick(), -1)]), ); assert_eq!( result.unwrap_err(), - BasePoolConstructionError::TotalLiquidityNotZero + ConcentratedPoolConstructionError::TotalLiquidityNotZero ); }); } @@ -665,14 +673,14 @@ mod tests { #[test] fn active_tick_index_within_bounds() { run_for_all_chains!(ChainTy, _chain => { - let result = BasePool::::new( + let result = ConcentratedPool::::new( pool_key::(1, zero_fee::()), pool_state(sqrt_ratio::(0), 0, Some(2)), ticks(&[(0, 2), (ChainTy::max_tick(), -2)]), ); assert_eq!( result.unwrap_err(), - BasePoolConstructionError::ActiveTickIndexOutOfBounds + ConcentratedPoolConstructionError::ActiveTickIndexOutOfBounds ); }); } @@ -680,14 +688,14 @@ mod tests { #[test] fn active_liquidity_must_match_sum_before_active_tick() { run_for_all_chains!(ChainTy, _chain => { - let result = BasePool::::new( + let result = ConcentratedPool::::new( pool_key::(1, zero_fee::()), pool_state(SQRT_RATIO_ONE, 0, Some(0)), ticks(&[(0, 2), (ChainTy::max_tick(), -2)]), ); assert_eq!( result.unwrap_err(), - BasePoolConstructionError::ActiveLiquidityMismatch + ConcentratedPoolConstructionError::ActiveLiquidityMismatch ); }); } @@ -695,14 +703,14 @@ mod tests { #[test] fn active_tick_sqrt_ratio_cannot_exceed_state() { run_for_all_chains!(ChainTy, _chain => { - let result = BasePool::::new( + let result = ConcentratedPool::::new( pool_key::(1, zero_fee::()), pool_state(SQRT_RATIO_ONE - U256::ONE, 2, Some(0)), ticks(&[(0, 2), (ChainTy::max_tick(), -2)]), ); assert_eq!( result.unwrap_err(), - BasePoolConstructionError::ActiveTickSqrtRatioInvalid + ConcentratedPoolConstructionError::ActiveTickSqrtRatioInvalid ); }); } @@ -710,14 +718,14 @@ mod tests { #[test] fn sqrt_ratio_must_be_below_first_tick_when_no_active_tick() { run_for_all_chains!(ChainTy, _chain => { - let result = BasePool::::new( + let result = ConcentratedPool::::new( pool_key::(1, zero_fee::()), pool_state(SQRT_RATIO_ONE + U256::ONE, 0, None), ticks(&[(0, 2), (ChainTy::max_tick(), -2)]), ); assert_eq!( result.unwrap_err(), - BasePoolConstructionError::SqrtRatioTooHighWithNoActiveTick + ConcentratedPoolConstructionError::SqrtRatioTooHighWithNoActiveTick ); }); } @@ -729,7 +737,7 @@ mod tests { #[test] fn zero_liquidity_token1_input() { run_for_all_chains!(ChainTy, _chain => { - let pool = BasePool::::new( + let pool = ConcentratedPool::::new( pool_key::(1, zero_fee::()), pool_state(SQRT_RATIO_ONE, 0, None), vec![], @@ -758,7 +766,7 @@ mod tests { #[test] fn zero_liquidity_token0_input() { run_for_all_chains!(ChainTy, _chain => { - let pool = BasePool::::new( + let pool = ConcentratedPool::::new( pool_key::(1, zero_fee::()), pool_state(SQRT_RATIO_ONE, 0, None), vec![], @@ -787,7 +795,7 @@ mod tests { #[test] fn liquidity_token1_input() { run_for_all_chains!(ChainTy, _chain => { - let pool = BasePool::::new( + let pool = ConcentratedPool::::new( pool_key::(1, zero_fee::()), pool_state(SQRT_RATIO_ONE, 1_000_000_000, Some(0)), ticks(&[(0, 1_000_000_000), (1, -1_000_000_000)]), @@ -816,7 +824,7 @@ mod tests { #[test] fn liquidity_token0_input() { run_for_all_chains!(ChainTy, _chain => { - let pool = BasePool::::new( + let pool = ConcentratedPool::::new( pool_key::(1, zero_fee::()), pool_state(sqrt_ratio::(1), 0, Some(1)), ticks(&[(0, 1_000_000_000), (1, -1_000_000_000)]), @@ -844,9 +852,9 @@ mod tests { #[test] fn example_failing_quote_starknet_only() { - let pool = BasePool::::new( + let pool = ConcentratedPool::::new( pool_key::(100, 17014118346046923988514818429550592u128), - BasePoolState { + ConcentratedPoolState { sqrt_ratio: U256::from_limbs([16035209758820767612, 757181812420893, 0, 0]), liquidity: 99999, active_tick_index: Some(16), diff --git a/src/quoting/pools/limit_order.rs b/src/quoting/pools/limit_order.rs index 51cd1ed..2dd44a8 100644 --- a/src/quoting/pools/limit_order.rs +++ b/src/quoting/pools/limit_order.rs @@ -1,19 +1,19 @@ use crate::quoting::{ - pools::base::{BasePoolConstructionError, BasePoolQuoteError}, + pools::concentrated::{ConcentratedPoolConstructionError, ConcentratedPoolQuoteError}, types::TokenAmount, - util::{approximate_number_of_tick_spacings_crossed, find_nearest_initialized_tick_index}, + util::{approximate_extra_distinct_tick_bitmap_lookups, find_nearest_initialized_tick_index}, }; use crate::quoting::{ - pools::base::{BasePoolResources, BasePoolTypeConfig}, + pools::concentrated::{ConcentratedPoolResources, ConcentratedPoolTypeConfig}, types::PoolState, }; use crate::{chain::starknet::Starknet, math::swap::is_price_increasing}; -use crate::{chain::Chain, quoting::pools::base::BasePoolState}; +use crate::{chain::Chain, quoting::pools::concentrated::ConcentratedPoolState}; use crate::{math::tick::to_sqrt_ratio, quoting::types::PoolConfig}; use crate::{ private, quoting::{ - pools::base::{BasePool, TickSpacing}, + pools::concentrated::{ConcentratedPool, TickSpacing}, types::{Pool, PoolKey, Quote, QuoteParams, Tick}, }, }; @@ -28,18 +28,18 @@ use thiserror::Error; #[derive(Clone, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct LimitOrderPool { - /// Underlying base pool. - base_pool: BasePool, + /// Underlying concentrated pool. + concentrated_pool: ConcentratedPool, } /// Unique identifier for a [`LimitOrderPool`]. pub type LimitOrderPoolKey = - PoolKey<::Address, ::Fee, BasePoolTypeConfig>; + PoolKey<::Address, ::Fee, ConcentratedPoolTypeConfig>; /// Pool configuration for a [`LimitOrderPool`]. pub type LimitOrderPoolConfig = - PoolConfig<::Address, ::Fee, BasePoolTypeConfig>; -/// [`BasePoolQuoteError`] re-exported with a pool-specific name. -pub type LimitOrderPoolQuoteError = BasePoolQuoteError; + PoolConfig<::Address, ::Fee, ConcentratedPoolTypeConfig>; +/// [`ConcentratedPoolQuoteError`] re-exported with a pool-specific name. +pub type LimitOrderPoolQuoteError = ConcentratedPoolQuoteError; /// Tick spacing required for limit order pools. pub const LIMIT_ORDER_TICK_SPACING: i32 = 128; @@ -50,8 +50,8 @@ pub const DOUBLE_LIMIT_ORDER_TICK_SPACING: i32 = 2i32 * LIMIT_ORDER_TICK_SPACING #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct LimitOrderPoolState { - /// State of the underlying base pool. - pub base_pool_state: BasePoolState, + /// State of the underlying concentrated pool. + pub concentrated_pool_state: ConcentratedPoolState, /// Minimum and maximum active tick indices observed since the last tick refresh. pub tick_indices_reached: Option<(Option, Option)>, } @@ -68,8 +68,8 @@ pub struct LimitOrderStandalonePoolResources { #[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, Add, AddAssign, Sub, SubAssign)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct LimitOrderPoolResources { - /// Resources consumed by the underlying base pool. - pub base: BasePoolResources, + /// Resources consumed by the underlying concentrated pool. + pub concentrated: ConcentratedPoolResources, /// Resources added by the limit order wrapper. pub limit_order: LimitOrderStandalonePoolResources, } @@ -77,8 +77,8 @@ pub struct LimitOrderPoolResources { /// Errors that can occur when constructing a [`LimitOrderPool`]. #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Error)] pub enum LimitOrderPoolConstructionError { - #[error("base pool error")] - BasePoolConstructionError(#[from] BasePoolConstructionError), + #[error("concentrated pool error")] + ConcentratedPoolConstructionError(#[from] ConcentratedPoolConstructionError), #[error("all ticks must have at least one neighbor")] AllTicksMustHaveNeighbor, #[error("last tick has no neighbor")] @@ -117,7 +117,7 @@ impl LimitOrderPool { } Ok(LimitOrderPool { - base_pool: BasePool::new( + concentrated_pool: ConcentratedPool::new( PoolKey { token0, token1, @@ -127,14 +127,14 @@ impl LimitOrderPool { extension, }, }, - BasePoolState { + ConcentratedPoolState { sqrt_ratio, liquidity, active_tick_index, }, sorted_ticks, ) - .map_err(LimitOrderPoolConstructionError::BasePoolConstructionError)?, + .map_err(LimitOrderPoolConstructionError::ConcentratedPoolConstructionError)?, }) } } @@ -146,15 +146,15 @@ impl Pool for LimitOrderPool { type State = LimitOrderPoolState; type QuoteError = LimitOrderPoolQuoteError; type Meta = (); - type PoolTypeConfig = BasePoolTypeConfig; + type PoolTypeConfig = ConcentratedPoolTypeConfig; fn key(&self) -> LimitOrderPoolKey { - self.base_pool.key() + self.concentrated_pool.key() } fn state(&self) -> Self::State { LimitOrderPoolState { - base_pool_state: self.base_pool.state(), + concentrated_pool_state: self.concentrated_pool.state(), tick_indices_reached: None, } } @@ -164,7 +164,7 @@ impl Pool for LimitOrderPool { params: QuoteParams, ) -> Result, Self::QuoteError> { let initial_state = params.override_state.unwrap_or_else(|| self.state()); - let sorted_ticks = self.base_pool.ticks(); + let sorted_ticks = self.concentrated_pool.ticks(); let is_increasing = is_price_increasing( params.token_amount.amount, @@ -174,10 +174,10 @@ impl Pool for LimitOrderPool { let mut calculated_amount = 0; let mut consumed_amount = 0; let mut fees_paid = 0; - let mut base_pool_resources = BasePoolResources::default(); - let mut base_pool_state = initial_state.base_pool_state; + let mut concentrated_pool_resources = ConcentratedPoolResources::default(); + let mut concentrated_pool_state = initial_state.concentrated_pool_state; - let active_tick_index = base_pool_state.active_tick_index; + let active_tick_index = concentrated_pool_state.active_tick_index; // if we need to skip one or more already pulled orders, this contains the tick index of the next unpulled order let next_unpulled_order_tick_index_after_skip = initial_state @@ -221,13 +221,13 @@ impl Pool for LimitOrderPool { .map_or_else(|| sorted_ticks.first(), |idx| sorted_ticks.get(idx + 1)) .map_or(Ok(Starknet::max_sqrt_ratio()), |next| { to_sqrt_ratio::(next.index) - .ok_or(BasePoolQuoteError::InvalidTick(next.index)) + .ok_or(ConcentratedPoolQuoteError::InvalidTick(next.index)) }) } else { active_tick_index.map_or(Ok(Starknet::min_sqrt_ratio()), |idx| { let tick = sorted_ticks[idx]; // is always valid to_sqrt_ratio::(tick.index) - .ok_or(BasePoolQuoteError::InvalidTick(tick.index)) + .ok_or(ConcentratedPoolQuoteError::InvalidTick(tick.index)) }) }?; @@ -244,19 +244,19 @@ impl Pool for LimitOrderPool { }; // swap to (at most) the boundary of the current active tick - let quote_to_active_tick_boundary = self.base_pool.quote(QuoteParams { + let quote_to_active_tick_boundary = self.concentrated_pool.quote(QuoteParams { sqrt_ratio_limit: Some(active_tick_boundary_sqrt_ratio), token_amount: params.token_amount, - override_state: Some(base_pool_state), + override_state: Some(concentrated_pool_state), meta: (), })?; calculated_amount += quote_to_active_tick_boundary.calculated_amount; consumed_amount += quote_to_active_tick_boundary.consumed_amount; fees_paid += quote_to_active_tick_boundary.fees_paid; - base_pool_resources += quote_to_active_tick_boundary.execution_resources; + concentrated_pool_resources += quote_to_active_tick_boundary.execution_resources; - base_pool_state = quote_to_active_tick_boundary.state_after; + concentrated_pool_state = quote_to_active_tick_boundary.state_after; let amount_remaining = params.token_amount.amount - consumed_amount; @@ -269,7 +269,7 @@ impl Pool for LimitOrderPool { |idx| { let tick_index = sorted_ticks[idx].index; to_sqrt_ratio::(tick_index) - .ok_or(BasePoolQuoteError::InvalidTick(tick_index)) + .ok_or(ConcentratedPoolQuoteError::InvalidTick(tick_index)) }, )? .min(params_sqrt_ratio_limit) @@ -278,22 +278,22 @@ impl Pool for LimitOrderPool { .map_or_else(|| sorted_ticks.first(), |idx| sorted_ticks.get(idx + 1)) .map_or(Ok(Starknet::min_sqrt_ratio()), |tick| { to_sqrt_ratio::(tick.index) - .ok_or(BasePoolQuoteError::InvalidTick(tick.index)) + .ok_or(ConcentratedPoolQuoteError::InvalidTick(tick.index)) })? .max(params_sqrt_ratio_limit) }; - // account for the tick spacings of the uninitialized ticks that we will skip next - base_pool_resources.tick_spacings_crossed += - approximate_number_of_tick_spacings_crossed( - base_pool_state.sqrt_ratio, + // account for the uninitialized ticks that we will skip next + concentrated_pool_resources.extra_distinct_bitmap_lookups += + approximate_extra_distinct_tick_bitmap_lookups( + concentrated_pool_state.sqrt_ratio, skip_starting_sqrt_ratio, LIMIT_ORDER_TICK_SPACING.unsigned_abs(), ); let liquidity_at_next_unpulled_order_tick_index = { - let mut current_liquidity = base_pool_state.liquidity; - let mut current_active_tick_index = base_pool_state.active_tick_index; + let mut current_liquidity = concentrated_pool_state.liquidity; + let mut current_active_tick_index = concentrated_pool_state.active_tick_index; // apply all liquidity_deltas in between the current active tick and the next unpulled order loop { @@ -334,13 +334,13 @@ impl Pool for LimitOrderPool { current_liquidity }; - let quote_from_next_unpulled_order = self.base_pool.quote(QuoteParams { + let quote_from_next_unpulled_order = self.concentrated_pool.quote(QuoteParams { sqrt_ratio_limit: params.sqrt_ratio_limit, token_amount: TokenAmount { amount: amount_remaining, token: params.token_amount.token, }, - override_state: Some(BasePoolState { + override_state: Some(ConcentratedPoolState { active_tick_index: next_unpulled_order_tick_index, sqrt_ratio: skip_starting_sqrt_ratio, liquidity: liquidity_at_next_unpulled_order_tick_index, @@ -351,14 +351,14 @@ impl Pool for LimitOrderPool { calculated_amount += quote_from_next_unpulled_order.calculated_amount; consumed_amount += quote_from_next_unpulled_order.consumed_amount; fees_paid += quote_from_next_unpulled_order.fees_paid; - base_pool_resources += quote_from_next_unpulled_order.execution_resources; + concentrated_pool_resources += quote_from_next_unpulled_order.execution_resources; - base_pool_state = quote_from_next_unpulled_order.state_after; + concentrated_pool_state = quote_from_next_unpulled_order.state_after; } } else { - let quote_simple = self.base_pool.quote(QuoteParams { + let quote_simple = self.concentrated_pool.quote(QuoteParams { sqrt_ratio_limit: params.sqrt_ratio_limit, - override_state: Some(initial_state.base_pool_state), + override_state: Some(initial_state.concentrated_pool_state), token_amount: params.token_amount, meta: (), })?; @@ -366,14 +366,14 @@ impl Pool for LimitOrderPool { calculated_amount += quote_simple.calculated_amount; consumed_amount += quote_simple.consumed_amount; fees_paid += quote_simple.fees_paid; - base_pool_resources += quote_simple.execution_resources; + concentrated_pool_resources += quote_simple.execution_resources; - base_pool_state = quote_simple.state_after; + concentrated_pool_state = quote_simple.state_after; } let (tick_index_before, tick_index_after) = ( - initial_state.base_pool_state.active_tick_index, - base_pool_state.active_tick_index, + initial_state.concentrated_pool_state.active_tick_index, + concentrated_pool_state.active_tick_index, ); let new_tick_indices_reached = if is_increasing { @@ -391,7 +391,7 @@ impl Pool for LimitOrderPool { }; let from_tick_index = next_unpulled_order_tick_index_after_skip - .unwrap_or(initial_state.base_pool_state.active_tick_index); + .unwrap_or(initial_state.concentrated_pool_state.active_tick_index); let to_tick_index = if is_increasing { Ord::max(from_tick_index, tick_index_after) } else { @@ -405,28 +405,28 @@ impl Pool for LimitOrderPool { calculated_amount, consumed_amount, execution_resources: LimitOrderPoolResources { - base: base_pool_resources, + concentrated: concentrated_pool_resources, limit_order: LimitOrderStandalonePoolResources { orders_pulled }, }, fees_paid, is_price_increasing: is_increasing, state_after: LimitOrderPoolState { - base_pool_state, + concentrated_pool_state, tick_indices_reached: Some(new_tick_indices_reached), }, }) } fn has_liquidity(&self) -> bool { - self.base_pool.has_liquidity() + self.concentrated_pool.has_liquidity() } fn max_tick_with_liquidity(&self) -> Option { - self.base_pool.max_tick_with_liquidity() + self.concentrated_pool.max_tick_with_liquidity() } fn min_tick_with_liquidity(&self) -> Option { - self.base_pool.min_tick_with_liquidity() + self.concentrated_pool.min_tick_with_liquidity() } fn is_path_dependent(&self) -> bool { @@ -436,11 +436,11 @@ impl Pool for LimitOrderPool { impl PoolState for LimitOrderPoolState { fn sqrt_ratio(&self) -> U256 { - self.base_pool_state.sqrt_ratio() + self.concentrated_pool_state.sqrt_ratio() } fn liquidity(&self) -> u128 { - self.base_pool_state.liquidity() + self.concentrated_pool_state.liquidity() } } @@ -645,11 +645,20 @@ mod tests { ); assert_eq!( ( - quote.execution_resources.base.initialized_ticks_crossed, - quote.execution_resources.base.no_override_price_change, - quote.execution_resources.base.tick_spacings_crossed, + quote + .execution_resources + .concentrated + .initialized_ticks_crossed, + quote + .execution_resources + .concentrated + .no_override_price_change, + quote + .execution_resources + .concentrated + .extra_distinct_bitmap_lookups, ), - (2, 1, 2) + (2, 1, 0) ); } @@ -669,7 +678,7 @@ mod tests { ); assert_eq!( ( - quote0.state_after.base_pool_state.active_tick_index, + quote0.state_after.concentrated_pool_state.active_tick_index, quote0.state_after.tick_indices_reached, quote0.execution_resources.limit_order.orders_pulled, ), @@ -712,7 +721,10 @@ mod tests { to_sqrt_ratio::(LIMIT_ORDER_TICK_SPACING), None, ); - assert_eq!(quote0.state_after.base_pool_state.active_tick_index, None); + assert_eq!( + quote0.state_after.concentrated_pool_state.active_tick_index, + None + ); assert_eq!( quote0.state_after.tick_indices_reached, Some((None, Some(1))) @@ -733,7 +745,7 @@ mod tests { Some(quote1.state_after), ); assert_eq!((quote1.consumed_amount, quote2.consumed_amount), (0, 0)); - assert_eq!(quote1.state_after.base_pool_state.liquidity, 0); + assert_eq!(quote1.state_after.concentrated_pool_state.liquidity, 0); } fn complex_pool() -> LimitOrderPool { @@ -760,7 +772,7 @@ mod tests { None, ); assert_eq!( - quote0.state_after.base_pool_state.active_tick_index, + quote0.state_after.concentrated_pool_state.active_tick_index, Some(5) ); assert_eq!( @@ -788,7 +800,7 @@ mod tests { (1_282, 1_278, Some((Some(0), Some(5)))) ); assert_eq!( - quote1.state_after.base_pool_state.sqrt_ratio, + quote1.state_after.concentrated_pool_state.sqrt_ratio, to_sqrt_ratio::(LIMIT_ORDER_TICK_SPACING * -5 / 2).unwrap() ); @@ -856,7 +868,7 @@ mod tests { ( quote2.consumed_amount, quote2.calculated_amount, - quote2.state_after.base_pool_state.active_tick_index, + quote2.state_after.concentrated_pool_state.active_tick_index, ), (641, 639, Some(0)) ); diff --git a/src/quoting/pools/mev_capture.rs b/src/quoting/pools/mev_capture.rs index c5272f6..ee5e128 100644 --- a/src/quoting/pools/mev_capture.rs +++ b/src/quoting/pools/mev_capture.rs @@ -10,9 +10,9 @@ use crate::{ swap::{amount_before_fee, compute_fee}, }, private, - quoting::pools::base::{ - BasePool, BasePoolQuoteError, BasePoolResources, BasePoolState, BasePoolTypeConfig, - TickSpacing, + quoting::pools::concentrated::{ + ConcentratedPool, ConcentratedPoolQuoteError, ConcentratedPoolResources, + ConcentratedPoolState, ConcentratedPoolTypeConfig, TickSpacing, }, }; use crate::{ @@ -26,7 +26,7 @@ use crate::{math::tick::approximate_sqrt_ratio_to_tick, quoting::types::PoolStat #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct MevCapturePool { /// Underlying concentrated liquidity pool. - base_pool: BasePool, + concentrated_pool: ConcentratedPool, /// Last update timestamp. last_update_time: u32, /// Current tick used for fixed-point fee calculation. @@ -41,7 +41,7 @@ pub type MevCapturePoolConfig = PoolConfig<::Address, ::Fee, MevCapturePoolTypeConfig>; /// Type config for a [`MevCapturePool`]. -pub type MevCapturePoolTypeConfig = BasePoolTypeConfig; +pub type MevCapturePoolTypeConfig = ConcentratedPoolTypeConfig; /// State snapshot for a [`MevCapturePool`]. #[derive(Clone, Debug, PartialEq, Eq, Copy, Hash)] @@ -49,8 +49,8 @@ pub type MevCapturePoolTypeConfig = BasePoolTypeConfig; pub struct MevCapturePoolState { /// Last update timestamp. pub last_update_time: u32, - /// State of the underlying base pool. - pub base_pool_state: BasePoolState, + /// State of the underlying concentrated pool. + pub concentrated_pool_state: ConcentratedPoolState, } /// Resources consumed during MEV-capture quote execution. @@ -65,8 +65,8 @@ pub struct MevCaptureStandalonePoolResources { #[derive(Clone, Copy, Default, Debug, PartialEq, Eq, Hash, Add, AddAssign, Sub, SubAssign)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct MevCapturePoolResources { - /// Resources consumed by the underlying base pool. - pub base: BasePoolResources, + /// Resources consumed by the underlying concentrated pool. + pub concentrated: ConcentratedPoolResources, /// Resources added by the MEV-capture wrapper. pub mev_capture: MevCaptureStandalonePoolResources, } @@ -85,9 +85,9 @@ pub enum MevCapturePoolConstructionError { } impl MevCapturePool { - // An MEV resist pool just wraps a base pool with some additional logic + // An MEV resist pool just wraps a concentrated pool with some additional logic pub fn new( - base_pool: BasePool, + concentrated_pool: ConcentratedPool, last_update_time: u32, tick: i32, ) -> Result { @@ -95,7 +95,7 @@ impl MevCapturePool { fee, pool_type_config: TickSpacing(tick_spacing), extension, - } = base_pool.key().config; + } = concentrated_pool.key().config; if fee.is_zero() { return Err(MevCapturePoolConstructionError::FeeMustBeGreaterThanZero); @@ -108,8 +108,8 @@ impl MevCapturePool { } // validates that the current tick is between the active tick and the active tick index + 1 - if let Some(i) = base_pool.state().active_tick_index { - let sorted_ticks = base_pool.ticks(); + if let Some(i) = concentrated_pool.state().active_tick_index { + let sorted_ticks = concentrated_pool.ticks(); if let Some(t) = sorted_ticks.get(i) { if t.index > tick { return Err(MevCapturePoolConstructionError::InvalidCurrentTick); @@ -120,14 +120,14 @@ impl MevCapturePool { return Err(MevCapturePoolConstructionError::InvalidCurrentTick); } } - } else if let Some(t) = base_pool.ticks().first() { + } else if let Some(t) = concentrated_pool.ticks().first() { if t.index <= tick { return Err(MevCapturePoolConstructionError::InvalidCurrentTick); } } Ok(Self { - base_pool, + concentrated_pool, last_update_time, tick, }) @@ -139,17 +139,17 @@ impl Pool for MevCapturePool { type Fee = ::Fee; type Resources = MevCapturePoolResources; type State = MevCapturePoolState; - type QuoteError = BasePoolQuoteError; + type QuoteError = ConcentratedPoolQuoteError; type Meta = BlockTimestamp; type PoolTypeConfig = MevCapturePoolTypeConfig; fn key(&self) -> MevCapturePoolKey { - self.base_pool.key() + self.concentrated_pool.key() } fn state(&self) -> Self::State { MevCapturePoolState { - base_pool_state: self.base_pool.state(), + concentrated_pool_state: self.concentrated_pool.state(), last_update_time: self.last_update_time, } } @@ -158,10 +158,10 @@ impl Pool for MevCapturePool { &self, params: QuoteParams, ) -> Result, Self::QuoteError> { - match self.base_pool.quote(QuoteParams { + match self.concentrated_pool.quote(QuoteParams { token_amount: params.token_amount, sqrt_ratio_limit: params.sqrt_ratio_limit, - override_state: params.override_state.map(|o| o.base_pool_state), + override_state: params.override_state.map(|o| o.concentrated_pool_state), meta: (), }) { Ok(quote) => { @@ -169,7 +169,7 @@ impl Pool for MevCapturePool { let tick_after_swap = approximate_sqrt_ratio_to_tick(quote.state_after.sqrt_ratio); - let pool_config = self.base_pool.key().config; + let pool_config = self.concentrated_pool.key().config; let approximate_fee_multiplier = f64::from((tick_after_swap - self.tick).abs() + 1) / f64::from(pool_config.pool_type_config.0); @@ -203,7 +203,7 @@ impl Pool for MevCapturePool { // exact output, compute the additional fee for the output calculated_amount += fee; } else { - return Err(BasePoolQuoteError::FailedComputeSwapStep( + return Err(ConcentratedPoolQuoteError::FailedComputeSwapStep( crate::math::swap::ComputeStepError::AmountBeforeFeeOverflow, )); } @@ -213,14 +213,14 @@ impl Pool for MevCapturePool { calculated_amount, consumed_amount: quote.consumed_amount, execution_resources: MevCapturePoolResources { - base: quote.execution_resources, + concentrated: quote.execution_resources, mev_capture: MevCaptureStandalonePoolResources { state_update_count }, }, fees_paid: quote.fees_paid, is_price_increasing: quote.is_price_increasing, state_after: MevCapturePoolState { last_update_time: current_time, - base_pool_state: quote.state_after, + concentrated_pool_state: quote.state_after, }, }) } @@ -229,15 +229,15 @@ impl Pool for MevCapturePool { } fn has_liquidity(&self) -> bool { - self.base_pool.has_liquidity() + self.concentrated_pool.has_liquidity() } fn max_tick_with_liquidity(&self) -> Option { - self.base_pool.max_tick_with_liquidity() + self.concentrated_pool.max_tick_with_liquidity() } fn min_tick_with_liquidity(&self) -> Option { - self.base_pool.min_tick_with_liquidity() + self.concentrated_pool.min_tick_with_liquidity() } fn is_path_dependent(&self) -> bool { @@ -247,11 +247,11 @@ impl Pool for MevCapturePool { impl PoolState for MevCapturePoolState { fn sqrt_ratio(&self) -> U256 { - self.base_pool_state.sqrt_ratio() + self.concentrated_pool_state.sqrt_ratio() } fn liquidity(&self) -> u128 { - self.base_pool_state.liquidity() + self.concentrated_pool_state.liquidity() } } @@ -295,7 +295,7 @@ mod tests { tick_entries: &[(i32, i128)], ) -> MevCapturePool { MevCapturePool::new( - BasePool::new( + ConcentratedPool::new( PoolKey { token0, token1, @@ -305,7 +305,7 @@ mod tests { extension: Evm::one_address(), }, }, - BasePoolState { + ConcentratedPoolState { active_tick_index: Some(0), liquidity: liquidity as u128, sqrt_ratio, diff --git a/src/quoting/pools/mod.rs b/src/quoting/pools/mod.rs index aff1a12..2d40db0 100644 --- a/src/quoting/pools/mod.rs +++ b/src/quoting/pools/mod.rs @@ -1,6 +1,6 @@ -pub mod base; #[cfg(any(feature = "evm", feature = "evm-alloy-0_6", feature = "evm-alloy-1"))] pub mod boosted_fees; +pub mod concentrated; #[cfg(any(feature = "evm", feature = "evm-alloy-0_6", feature = "evm-alloy-1"))] pub mod full_range; #[cfg(feature = "starknet")] diff --git a/src/quoting/pools/spline.rs b/src/quoting/pools/spline.rs index a29406e..b7d50c2 100644 --- a/src/quoting/pools/spline.rs +++ b/src/quoting/pools/spline.rs @@ -1,17 +1,18 @@ use crate::{ chain::{starknet::Starknet, Chain}, - quoting::pools::base::{ - BasePool, BasePoolConstructionError, BasePoolKey, BasePoolQuoteError, BasePoolResources, - BasePoolState, BasePoolTypeConfig, + quoting::pools::concentrated::{ + ConcentratedPool, ConcentratedPoolConstructionError, ConcentratedPoolKey, + ConcentratedPoolQuoteError, ConcentratedPoolResources, ConcentratedPoolState, + ConcentratedPoolTypeConfig, }, quoting::types::PoolConfig, }; -pub type SplinePool = BasePool; +pub type SplinePool = ConcentratedPool; pub type SplinePoolConfig = - PoolConfig<::Address, ::Fee, BasePoolTypeConfig>; -pub type SplinePoolConstructionError = BasePoolConstructionError; -pub type SplinePoolKey = BasePoolKey; -pub type SplinePoolState = BasePoolState; -pub type SplinePoolResources = BasePoolResources; -pub type SplinePoolQuoteError = BasePoolQuoteError; + PoolConfig<::Address, ::Fee, ConcentratedPoolTypeConfig>; +pub type SplinePoolConstructionError = ConcentratedPoolConstructionError; +pub type SplinePoolKey = ConcentratedPoolKey; +pub type SplinePoolState = ConcentratedPoolState; +pub type SplinePoolResources = ConcentratedPoolResources; +pub type SplinePoolQuoteError = ConcentratedPoolQuoteError; diff --git a/src/quoting/pools/stableswap.rs b/src/quoting/pools/stableswap.rs index e2e1ca5..bfb18bc 100644 --- a/src/quoting/pools/stableswap.rs +++ b/src/quoting/pools/stableswap.rs @@ -46,8 +46,6 @@ pub struct StableswapPool { pub struct StableswapPoolResources { /// Whether price changed when no override was provided. pub no_override_price_change: u32, - /// Count of initialized ticks crossed. - pub initialized_ticks_crossed: u32, } /// Unique identifier for a [`StableswapPool`]. @@ -190,7 +188,6 @@ impl Pool for StableswapPool { let mut calculated_amount = 0; let mut fees_paid = 0; - let mut initialized_ticks_crossed = 0; let mut amount_remaining = amount; let mut moved_out_of_boundary = false; let starting_sqrt_ratio = sqrt_ratio; @@ -240,16 +237,12 @@ impl Pool for StableswapPool { fees_paid += step.fee_amount; sqrt_ratio = step.sqrt_ratio_next; - if let Some(next_tick_sqrt_ratio) = next_tick_sqrt_ratio { - if next_tick_sqrt_ratio == sqrt_ratio { - initialized_ticks_crossed += 1; - - if sqrt_ratio == self.upper_price && increasing - || sqrt_ratio == self.lower_price && !increasing - { - moved_out_of_boundary = true; - } - } + if next_tick_sqrt_ratio.is_some_and(|next_tick_sqrt_ratio| { + next_tick_sqrt_ratio == sqrt_ratio + && (sqrt_ratio == self.upper_price && increasing + || sqrt_ratio == self.lower_price && !increasing) + }) { + moved_out_of_boundary = true; } } @@ -257,7 +250,6 @@ impl Pool for StableswapPool { no_override_price_change: u32::from( starting_sqrt_ratio == self.state.sqrt_ratio && starting_sqrt_ratio != sqrt_ratio, ), - initialized_ticks_crossed, }; Ok(Quote { @@ -510,7 +502,6 @@ mod tests { assert!(quote.consumed_amount > 0); assert!(quote.calculated_amount > 0); assert!(quote.state_after.sqrt_ratio <= to_sqrt_ratio::(lower + 10).unwrap()); - assert_eq!(quote.execution_resources.initialized_ticks_crossed, 0); } #[test] diff --git a/src/quoting/pools/twamm.rs b/src/quoting/pools/twamm.rs index 5b183bc..be4960f 100644 --- a/src/quoting/pools/twamm.rs +++ b/src/quoting/pools/twamm.rs @@ -1,6 +1,7 @@ use crate::quoting::types::{ BlockTimestamp, Pool, PoolConfig, PoolKey, Quote, QuoteParams, TimeRateDelta, TokenAmount, }; +use crate::quoting::util::approximate_extra_distinct_time_bitmap_lookups; use crate::{chain::Chain, math::twamm::sqrt_ratio::calculate_next_sqrt_ratio}; use crate::{private, quoting::types::PoolState}; @@ -39,8 +40,8 @@ pub struct TwammPoolState { #[derive(Clone, Debug, Copy, Default, PartialEq, Eq, Hash, Add, AddAssign, Sub, SubAssign)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct TwammStandalonePoolResources { - /// The number of seconds that passed since the last virtual order execution - pub virtual_order_seconds_executed: u32, + /// Number of additional distinct time bitmap lookups (besides the mandatory one). + pub extra_distinct_bitmap_lookups: u32, /// The amount of order updates that were applied to the sale rate pub virtual_order_delta_times_crossed: u32, /// Whether the virtual orders were executed or not (for a single swap, 1 or 0) @@ -346,9 +347,10 @@ impl Pool for TwammPool { execution_resources: TwammPoolResources { full_range: full_range_pool_execution_resources + final_quote.execution_resources, twamm: TwammStandalonePoolResources { - virtual_order_seconds_executed: (current_time - - initial_state.last_execution_time) - as u32, + extra_distinct_bitmap_lookups: approximate_extra_distinct_time_bitmap_lookups( + initial_state.last_execution_time, + current_time, + ), virtual_order_delta_times_crossed, virtual_orders_executed: u32::from( current_time > initial_state.last_execution_time, @@ -614,13 +616,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (999, 32, 0) + (999, 0, 0) ); }); @@ -652,13 +654,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (990, 32, 0) + (990, 0, 0) ); }); @@ -694,13 +696,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (998, 32, 0) + (998, 0, 0) ); }); @@ -736,13 +738,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (999, 32, 0) + (999, 0, 0) ); }); @@ -778,13 +780,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (0, 32, 0) + (0, 0, 0) ); }); chain_test!( @@ -828,13 +830,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (2555, 32, 1) + (2555, 0, 1) ); } ); @@ -880,13 +882,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (390, 32, 1) + (390, 0, 1) ); } ); @@ -932,13 +934,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (390, 32, 1) + (390, 0, 1) ); } ); @@ -984,13 +986,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (2555, 32, 1) + (2555, 0, 1) ); } ); @@ -1027,13 +1029,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (990, 32, 0) + (990, 0, 0) ); }); @@ -1069,13 +1071,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (989, 32, 0) + (989, 0, 0) ); }); @@ -1113,13 +1115,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (717, 32, 0) + (717, 0, 0) ); } ); @@ -1158,13 +1160,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (984, 32, 0) + (984, 0, 0) ); } ); @@ -1203,13 +1205,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (983, 32, 0) + (983, 0, 0) ); } ); @@ -1248,13 +1250,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (994, 32, 0) + (994, 0, 0) ); } ); @@ -1293,13 +1295,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (989, 32, 1) + (989, 0, 1) ); } ); @@ -1345,13 +1347,13 @@ mod tests { quote .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, quote .execution_resources .twamm .virtual_order_delta_times_crossed ), - (989, 32, 1) + (989, 0, 1) ); } ); @@ -1546,7 +1548,7 @@ mod tests { first_swap .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, first_swap .execution_resources .twamm @@ -1558,7 +1560,7 @@ mod tests { ChainEnum::Starknet => 19_993_991_114_278_789_950_510, }, 10_000_000_000_000_000_000_000, - 2_040, + 0, 0 ) ); @@ -1582,7 +1584,7 @@ mod tests { second_swap .execution_resources .twamm - .virtual_order_seconds_executed, + .extra_distinct_bitmap_lookups, second_swap .execution_resources .twamm @@ -1594,7 +1596,7 @@ mod tests { ChainEnum::Starknet => 19_985_938_387_207_961_531_114, }, 10_000_000_000_000_000_000_000, - 60, + 0, 0 ) ); diff --git a/src/quoting/types.rs b/src/quoting/types.rs index f3b2966..cdc8fb2 100644 --- a/src/quoting/types.rs +++ b/src/quoting/types.rs @@ -161,7 +161,7 @@ impl LastTimeInfo { pub fn real_time(self) -> u64 { match self { - Self::Stored { stored, current } => real_last_time(stored, current), + Self::Stored { stored, current } => real_last_time(current, stored), Self::Real(real) => real, } } diff --git a/src/quoting/util.rs b/src/quoting/util.rs index 67ae125..e182e9d 100644 --- a/src/quoting/util.rs +++ b/src/quoting/util.rs @@ -1,8 +1,6 @@ +use crate::chain::Chain; +use crate::math::tick::approximate_sqrt_ratio_to_tick; use crate::quoting::types::Tick; -use crate::{ - chain::Chain, - math::{facade::ln, uint::u256_to_float_base_x128}, -}; use alloc::vec::Vec; use num_traits::Zero; use ruint::aliases::U256; @@ -30,22 +28,50 @@ pub fn find_nearest_initialized_tick_index(sorted_ticks: &[Tick], tick: i32) -> None } -const LOG_BASE_SQRT_TICK_SIZE: f64 = 4.9999975000016666654166676666658333340476184226196031741031750577196410537756684185262518589393595459766211405607685305832e-7; +const TICK_BITMAP_STORAGE_OFFSET: i32 = 89421695; #[must_use] -pub fn approximate_number_of_tick_spacings_crossed( +pub fn approximate_extra_distinct_tick_bitmap_lookups( starting_sqrt_ratio: U256, ending_sqrt_ratio: U256, tick_spacing: u32, ) -> u32 { - if tick_spacing == 0 { + if tick_spacing.is_zero() { return 0; } - let start: f64 = u256_to_float_base_x128(starting_sqrt_ratio); - let end: f64 = u256_to_float_base_x128(ending_sqrt_ratio); - let ticks_crossed = ((ln(start) - ln(end)).abs() / LOG_BASE_SQRT_TICK_SIZE) as u32; - ticks_crossed / tick_spacing + let tick_spacing = tick_spacing + .try_into() + .expect("tick spacing to fit into i32"); + + let (start_word, end_word) = ( + bitmap_word_from_sqrt_ratio(starting_sqrt_ratio, tick_spacing), + bitmap_word_from_sqrt_ratio(ending_sqrt_ratio, tick_spacing), + ); + + start_word.abs_diff(end_word) +} + +#[must_use] +pub fn approximate_extra_distinct_time_bitmap_lookups(start_time: u64, end_time: u64) -> u32 { + u32::try_from( + bitmap_word_from_time(end_time) + .checked_sub(bitmap_word_from_time(start_time)) + .expect("start_time to be lte end_time"), + ) + .expect("time bitmap word diff to fit into u32") +} + +fn bitmap_word_from_sqrt_ratio(sqrt_ratio: U256, tick_spacing: i32) -> u32 { + let tick = approximate_sqrt_ratio_to_tick(sqrt_ratio); + ((tick / tick_spacing) - i32::from((tick % tick_spacing).is_negative()) + + TICK_BITMAP_STORAGE_OFFSET) + .cast_unsigned() + >> 8 +} + +fn bitmap_word_from_time(time: u64) -> u64 { + time >> 16 } #[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, Error)] @@ -160,7 +186,7 @@ pub fn construct_sorted_ticks( Ok(result) } -pub fn real_last_time(stored: u32, current: u64) -> u64 { +pub fn real_last_time(current: u64, stored: u32) -> u64 { current - (current.wrapping_sub(stored.into()) & u64::from(u32::MAX)) } @@ -169,7 +195,7 @@ mod tests { use super::*; use crate::{ chain::{tests::run_for_all_chains, Chain}, - math::sqrt_ratio::SQRT_RATIO_ONE, + math::tick::to_sqrt_ratio, quoting::types::Tick, }; use alloc::vec; @@ -284,70 +310,51 @@ mod tests { ); } - chain_test!(approximate_number_of_tick_spacings_crossed_for_doubling, { - let min = ChainTy::min_sqrt_ratio(); - let max = ChainTy::max_sqrt_ratio(); - let cases = [ - (min, min * U256::from(2), 0, 0), - (SQRT_RATIO_ONE, SQRT_RATIO_ONE * U256::from(2), 1, 1_386_295), - (min, min * U256::from(2), 1, 1_386_295), - (max, max / U256::from(2), 1, 1_386_295), - ]; - - for (start, end, spacing, expected) in cases { - assert_eq!( - approximate_number_of_tick_spacings_crossed(start, end, spacing), - expected - ); - } - }); - - chain_test!( - approximate_number_of_tick_spacings_crossed_for_doubling_big_tick_spacing, - { - let min = ChainTy::min_sqrt_ratio(); - let max = ChainTy::max_sqrt_ratio(); - let cases = [ - (SQRT_RATIO_ONE, SQRT_RATIO_ONE * U256::from(2), 5, 277_259), - (min, min * U256::from(2), 3, 462_098), - (max, max / U256::from(2), 200, 6_931), - ]; - - for (start, end, spacing, expected) in cases { - assert_eq!( - approximate_number_of_tick_spacings_crossed(start, end, spacing), - expected - ); - } - } - ); + chain_test!(approximate_extra_distinct_bitmap_lookups_word_boundaries, { + let spacing = 1u32; + let base = to_sqrt_ratio::(0).unwrap(); + let same_word = to_sqrt_ratio::(128).unwrap(); + let next_word = to_sqrt_ratio::(129).unwrap(); - #[test] - fn u256_to_fraction() { assert_eq!( - u256_to_float_base_x128(U256::from_limbs([ - 16403144882676588163, - 1525053501570699700, - 35, - 0 - ])), - 35.08267331597798 + approximate_extra_distinct_tick_bitmap_lookups(base, same_word, spacing), + 0 ); assert_eq!( - u256_to_float_base_x128(U256::from_limbs([123456, 0, 0, 0])), - 3.628045764377908e-34 + approximate_extra_distinct_tick_bitmap_lookups(base, next_word, spacing), + 1 + ); + }); + + chain_test!(approximate_extra_distinct_bitmap_lookups_negative_ticks, { + let spacing = 1u32; + let base = to_sqrt_ratio::(0).unwrap(); + let negative_same_word = to_sqrt_ratio::(-1).unwrap(); + let negative_prev_word = to_sqrt_ratio::(-128).unwrap(); + + assert_eq!( + approximate_extra_distinct_tick_bitmap_lookups(base, negative_same_word, spacing), + 0 ); assert_eq!( - u256_to_float_base_x128(U256::from_limbs([0, 123456, 0, 0])), - 6.692563170318522e-15 + approximate_extra_distinct_tick_bitmap_lookups(base, negative_prev_word, spacing), + 1 ); + }); + + #[test] + fn approximate_extra_distinct_time_bitmap_lookups_word_boundaries() { + let base = 0_u64; + let same_word = (1_u64 << 16) - 1; + let next_word = 1_u64 << 16; + assert_eq!( - u256_to_float_base_x128(U256::from_limbs([0, 0, 123456, 0])), - 123456.0 + approximate_extra_distinct_time_bitmap_lookups(base, same_word), + 0 ); assert_eq!( - u256_to_float_base_x128(U256::from_limbs([0, 0, 0, 123456])), - 2.2773612363638864e24 + approximate_extra_distinct_time_bitmap_lookups(base, next_word), + 1 ); }