From 891b9aa761a7d19e231e8a0aa3f42f53320540f2 Mon Sep 17 00:00:00 2001 From: die-herdplatte <173669014+die-herdplatte@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:03:40 +0100 Subject: [PATCH] Fix limit orders behavior when changing swap directions (#4) * Finish limit order implementation * Run cargo fmt * Fix wrong interpretation of active_tick_index = None Saturate instead of wrapping around when we're at the high tick bounds * Fix behavior when changing swap directions --- src/quoting/limit_order_pool.rs | 92 +++++++++++++++++++++------------ 1 file changed, 60 insertions(+), 32 deletions(-) diff --git a/src/quoting/limit_order_pool.rs b/src/quoting/limit_order_pool.rs index 63435c5..3c26e32 100644 --- a/src/quoting/limit_order_pool.rs +++ b/src/quoting/limit_order_pool.rs @@ -14,14 +14,12 @@ use super::util::approximate_number_of_tick_spacings_crossed; #[derive(Clone, Copy)] pub struct LimitOrderPoolState { pub base_pool_state: BasePoolState, - // the maximum active tick index we've reached after a swap of token1 for token0 - // if None, then we haven't seen any swaps from token1 to token0 - // if Some(sorted_ticks.len() - 1), then we have swapped through all the ticks greater than the current active tick index - pub max_tick_index_after_swap: Option>, - // the minimum active tick index we've reached after a swap of token0 for token1 - // if None, then we haven't seen any swaps from token0 to token1 - // if Some(None), then we have swapped through all the ticks less than the current active tick index - pub min_tick_index_after_swap: Option>, + + // the minimum and maximum active tick index we've reached since the sorted ticks were last updated. + // if None, then we haven't seen any swap since the last sorted ticks update. + // if the minimum active tick index is Some(None), then we have swapped through all the ticks less than the current active tick index + // if the maxmimum active tick index is Some(sorted_ticks.len() - 1), then we have swapped through all the ticks greater than the current active tick index + pub tick_indices_reached: Option<(Option, Option)>, } #[derive(Default, Clone, Copy)] @@ -93,8 +91,7 @@ impl Pool for LimitOrderPool { fn get_state(&self) -> Self::State { LimitOrderPoolState { base_pool_state: self.base_pool.get_state(), - max_tick_index_after_swap: None, - min_tick_index_after_swap: None, + tick_indices_reached: None, } } @@ -115,14 +112,24 @@ impl Pool for LimitOrderPool { params.token_amount.token == self.get_key().token1, ); - let next_unpulled_order_tick_index = if is_increasing { - initial_state.max_tick_index_after_swap - } else { - initial_state.min_tick_index_after_swap - }; - let sorted_ticks = self.base_pool.get_sorted_ticks(); + let next_unpulled_order_tick_index = + initial_state.tick_indices_reached.map(|(lower, upper)| { + if lower == upper { + lower // we haven't moved out of the active tick, so the current tick is unpulled + } else if is_increasing { + upper.map(|upper| { + Ord::min( + sorted_ticks.len() - 1, // will not overflow because upper is Some + upper + 1, + ) + }) + } else { + lower.and_then(|lower| lower.checked_sub(1)) + } + }); + if let Some(next_unpulled_order_tick_index) = next_unpulled_order_tick_index { let active_tick_index = base_pool_state.active_tick_index; @@ -290,18 +297,23 @@ impl Pool for LimitOrderPool { base_pool_state = quote_simple.state_after; } - let tick_index_after = base_pool_state.active_tick_index; + let (tick_index_before, tick_index_after) = ( + initial_state.base_pool_state.active_tick_index, + base_pool_state.active_tick_index, + ); - let (min_tick_index_after_swap, max_tick_index_after_swap) = if is_increasing { - ( - initial_state.min_tick_index_after_swap, - Some(tick_index_after), - ) + let new_tick_indices_reached = if is_increasing { + initial_state + .tick_indices_reached + .map_or((tick_index_before, tick_index_after), |(lower, upper)| { + (lower, Ord::max(upper, tick_index_after)) + }) } else { - ( - Some(tick_index_after), - initial_state.max_tick_index_after_swap, - ) + initial_state + .tick_indices_reached + .map_or((tick_index_after, tick_index_before), |(lower, upper)| { + (Ord::min(lower, tick_index_after), upper) + }) }; let from_tick_index = next_unpulled_order_tick_index @@ -326,8 +338,7 @@ impl Pool for LimitOrderPool { is_price_increasing: is_increasing, state_after: LimitOrderPoolState { base_pool_state, - min_tick_index_after_swap, - max_tick_index_after_swap, + tick_indices_reached: Some(new_tick_indices_reached), }, }) } @@ -420,8 +431,10 @@ mod tests { .expect("Quote failed"); assert_eq!(quote.fees_paid, 0); - assert_eq!(quote.state_after.min_tick_index_after_swap, None); - assert_eq!(quote.state_after.max_tick_index_after_swap, Some(Some(1))); + assert_eq!( + quote.state_after.tick_indices_reached, + Some((Some(0), Some(1))) + ); assert_eq!(quote.consumed_amount, 641); assert_eq!(quote.calculated_amount, 639); assert_eq!(quote.execution_resources.orders_pulled, 1); @@ -491,8 +504,10 @@ mod tests { .expect("Quote failed"); assert_eq!(quote.fees_paid, 0); - assert_eq!(quote.state_after.min_tick_index_after_swap, None); - assert_eq!(quote.state_after.max_tick_index_after_swap, Some(Some(2))); + assert_eq!( + quote.state_after.tick_indices_reached, + Some((Some(0), Some(2))) + ); assert_eq!(quote.consumed_amount, 1000); assert_eq!(quote.calculated_amount, 997); assert_eq!(quote.execution_resources.orders_pulled, 1); @@ -562,6 +577,11 @@ mod tests { quote0.state_after.base_pool_state.sqrt_ratio, to_sqrt_ratio(LIMIT_ORDER_TICK_SPACING).unwrap() ); + assert_eq!( + quote0.state_after.tick_indices_reached, + Some((Some(0), Some(1))) + ); + assert_eq!(quote0.execution_resources.orders_pulled, 1); // swap back through the order which should be pulled let quote1 = pool @@ -634,6 +654,10 @@ mod tests { to_sqrt_ratio(LIMIT_ORDER_TICK_SPACING).unwrap() ); assert_eq!(quote0.state_after.base_pool_state.active_tick_index, None); + assert_eq!( + quote0.state_after.tick_indices_reached, + Some((None, Some(1))) + ); // swap back through the order which should be pulled let quote1 = pool @@ -662,6 +686,10 @@ mod tests { assert_eq!(quote1.consumed_amount, 0); assert_eq!(quote1.calculated_amount, 0); + assert_eq!( + quote1.state_after.tick_indices_reached, + Some((None, Some(1))) + ); assert_eq!(quote2.consumed_amount, 0); assert_eq!(quote2.calculated_amount, 0); }