From c7df009fec8ffaad586af05f2a41cf5dbf4a17bf Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Tue, 25 Mar 2025 20:58:46 +0100 Subject: [PATCH 1/3] Replace i32 to TickIndex where needed --- pallets/swap/src/lib.rs | 571 ++++++++++++++++++++++++++++----------- pallets/swap/src/tick.rs | 91 ++++++- 2 files changed, 502 insertions(+), 160 deletions(-) diff --git a/pallets/swap/src/lib.rs b/pallets/swap/src/lib.rs index d2d261d32..04a4fd1f7 100644 --- a/pallets/swap/src/lib.rs +++ b/pallets/swap/src/lib.rs @@ -3,22 +3,20 @@ use core::marker::PhantomData; use core::ops::Neg; +use frame_support::pallet_prelude::*; use pallet_subtensor_swap_interface::OrderType; use safe_math::*; use sp_arithmetic::helpers_128bit::sqrt; use substrate_fixed::types::U64F64; -use frame_support::pallet_prelude::*; -use self::tick::{ - MAX_TICK_INDEX, MIN_TICK_INDEX, Tick, TickIndex, -}; +use self::tick::{Tick, TickIndex}; pub mod pallet; mod tick; type SqrtPrice = U64F64; -/// All tick indexes are offset by TICK_OFFSET for the search and active tick storage needs +/// All tick indexes are offset by TICK_OFFSET for the search and active tick storage needs /// so that tick indexes are positive, which simplifies bit logic pub const TICK_OFFSET: u32 = 887272; @@ -87,10 +85,14 @@ impl Position { pub fn to_token_amounts(&self, sqrt_price_curr: SqrtPrice) -> Result<(u64, u64), SwapError> { let one: U64F64 = U64F64::saturating_from_num(1); - let sqrt_pa: SqrtPrice = - self.tick_low.try_to_sqrt_price().map_err(|_| SwapError::InvalidTickRange)?; - let sqrt_pb: SqrtPrice = - self.tick_high.try_to_sqrt_price().map_err(|_| SwapError::InvalidTickRange)?; + let sqrt_pa: SqrtPrice = self + .tick_low + .try_to_sqrt_price() + .map_err(|_| SwapError::InvalidTickRange)?; + let sqrt_pb: SqrtPrice = self + .tick_high + .try_to_sqrt_price() + .map_err(|_| SwapError::InvalidTickRange)?; let liquidity_fixed: U64F64 = U64F64::saturating_from_num(self.liquidity); Ok(if sqrt_price_curr < sqrt_pa { @@ -609,9 +611,11 @@ where } else { // Current price is out of allow the min-max range, and it should be corrected to // maintain the range. - let max_price = TickIndex::MAX.try_to_sqrt_price() + let max_price = TickIndex::MAX + .try_to_sqrt_price() .unwrap_or(SqrtPrice::saturating_from_num(1000)); - let min_price = TickIndex::MIN.try_to_sqrt_price() + let min_price = TickIndex::MIN + .try_to_sqrt_price() .unwrap_or(SqrtPrice::saturating_from_num(0.000001)); if current_price > max_price { self.state_ops.set_alpha_sqrt_price(max_price); @@ -647,7 +651,7 @@ where self.update_reserves(order_type, delta_in, delta_out); // Get current tick - let TickIndex(current_tick_index) = self.get_current_tick_index(); + let current_tick_index = self.get_current_tick_index(); match action { SwapStepAction::Crossing => { @@ -666,7 +670,7 @@ where .saturating_sub(tick.fees_out_alpha); self.update_liquidity_at_crossing(order_type)?; self.state_ops - .insert_tick_by_index(TickIndex(current_tick_index), tick); + .insert_tick_by_index(current_tick_index, tick); } else { return Err(SwapError::InsufficientLiquidity); } @@ -687,7 +691,7 @@ where .get_fee_global_alpha() .saturating_sub(tick.fees_out_alpha); self.state_ops - .insert_tick_by_index(TickIndex(current_tick_index), tick); + .insert_tick_by_index(current_tick_index, tick); } else { return Err(SwapError::InsufficientLiquidity); } @@ -724,7 +728,9 @@ where if let Ok(current_tick_index) = maybe_current_tick_index { match order_type { - OrderType::Buy => TickIndex::new_unchecked(current_tick_index.get().saturating_add(1)), + OrderType::Buy => { + TickIndex::new_unchecked(current_tick_index.get().saturating_add(1)) + } OrderType::Sell => current_tick_index, } .try_to_sqrt_price() @@ -960,7 +966,7 @@ where /// fn update_liquidity_at_crossing(&mut self, order_type: &OrderType) -> Result<(), SwapError> { let mut liquidity_curr = self.state_ops.get_current_liquidity(); - let TickIndex(current_tick_index) = self.get_current_tick_index(); + let current_tick_index = self.get_current_tick_index(); match order_type { OrderType::Sell => { let maybe_tick = self.find_closest_lower_active_tick(current_tick_index); @@ -1101,13 +1107,13 @@ where } /// Active tick operations - /// - /// Data structure: + /// + /// Data structure: /// Active ticks are stored in three hash maps, each representing a "Level" /// Level 0 stores one u128 word, where each bit represents one Level 1 word. /// Level 1 words each store also a u128 word, where each bit represents one Level 2 word. /// Level 2 words each store u128 word, where each bit represents a tick. - /// + /// /// Insertion: 3 reads, 3 writes /// Search: 3-5 reads /// Deletion: 2 reads, 1-3 writes @@ -1125,16 +1131,15 @@ where word.saturating_mul(128).saturating_add(bit) } - pub fn insert_active_tick(&mut self, index: i32) { + pub fn insert_active_tick(&mut self, index: tick::TickIndex) { // Check the range - if (index < MIN_TICK_INDEX) || (index > MAX_TICK_INDEX) { + if (index < tick::TickIndex::MIN) || (index > tick::TickIndex::MAX) { return; } - // Ticks are between -443636 and 443636, so there is no - // overflow here. The u32 offset_index is the format we store indexes in the tree + // Convert the tick index value to an offset_index for the tree representation // to avoid working with the sign bit. - let offset_index = (index.saturating_add(TICK_OFFSET as i32)) as u32; + let offset_index = index.saturating_add(TICK_OFFSET as i32).get() as u32; // Calculate index in each layer let (layer2_word, layer2_bit) = Self::index_to_address(offset_index); @@ -1144,7 +1149,7 @@ where // Update layer words let mut word0_value = self.state_ops.get_layer0_word(layer0_word); let mut word1_value = self.state_ops.get_layer1_word(layer1_word); - let mut word2_value = self.state_ops.get_layer2_word(layer2_word); + let mut word2_value = self.state_ops.get_layer2_word(layer2_word); let bit: u128 = 1; word0_value = word0_value | bit.wrapping_shl(layer0_bit); @@ -1153,19 +1158,18 @@ where self.state_ops.set_layer0_word(layer0_word, word0_value); self.state_ops.set_layer1_word(layer1_word, word1_value); - self.state_ops.set_layer2_word(layer2_word, word2_value); + self.state_ops.set_layer2_word(layer2_word, word2_value); } - pub fn remove_active_tick(&mut self, index: i32) { + pub fn remove_active_tick(&mut self, index: tick::TickIndex) { // Check the range - if (index < MIN_TICK_INDEX) || (index > MAX_TICK_INDEX) { + if (index < tick::TickIndex::MIN) || (index > tick::TickIndex::MAX) { return; } - // Ticks are between -443636 and 443636, so there is no - // overflow here. The u32 offset_index is the format we store indexes in the tree + // Convert the tick index value to an offset_index for the tree representation // to avoid working with the sign bit. - let offset_index = (index.saturating_add(TICK_OFFSET as i32)) as u32; + let offset_index = index.saturating_add(TICK_OFFSET as i32).get() as u32; // Calculate index in each layer let (layer2_word, layer2_bit) = Self::index_to_address(offset_index); @@ -1175,12 +1179,12 @@ where // Update layer words let mut word0_value = self.state_ops.get_layer0_word(layer0_word); let mut word1_value = self.state_ops.get_layer1_word(layer1_word); - let mut word2_value = self.state_ops.get_layer2_word(layer2_word); + let mut word2_value = self.state_ops.get_layer2_word(layer2_word); // Turn the bit off (& !bit) and save as needed let bit: u128 = 1; word2_value = word2_value & !bit.wrapping_shl(layer2_bit); - self.state_ops.set_layer2_word(layer2_word, word2_value); + self.state_ops.set_layer2_word(layer2_word, word2_value); if word2_value == 0 { word1_value = word1_value & !bit.wrapping_shl(layer1_bit); self.state_ops.set_layer1_word(layer1_word, word1_value); @@ -1219,19 +1223,17 @@ where pub fn find_closest_active_tick_index( &self, - index: i32, + index: tick::TickIndex, lower: bool, - ) -> Option - { + ) -> Option { // Check the range - if (index < MIN_TICK_INDEX) || (index > MAX_TICK_INDEX) { + if (index < tick::TickIndex::MIN) || (index > tick::TickIndex::MAX) { return None; } - // Ticks are between -443636 and 443636, so there is no - // overflow here. The u32 offset_index is the format we store indexes in the tree + // Convert the tick index value to an offset_index for the tree representation // to avoid working with the sign bit. - let offset_index = (index.saturating_add(TICK_OFFSET as i32)) as u32; + let offset_index = index.saturating_add(TICK_OFFSET as i32).get() as u32; let mut found = false; let mut result: u32 = 0; @@ -1253,38 +1255,39 @@ where let word1_index = Self::address_to_index(0, closest_bit_l0); // Layer 1 words are different, shift the bit to the word edge - let start_from_l1_bit = - if word1_index < layer1_word { - 127 - } else if word1_index > layer1_word { - 0 - } else { - layer1_bit - }; + let start_from_l1_bit = if word1_index < layer1_word { + 127 + } else if word1_index > layer1_word { + 0 + } else { + layer1_bit + }; let word1_value = self.state_ops.get_layer1_word(word1_index); - let closest_bits_l1 = self.find_closest_active_bit_candidates(word1_value, start_from_l1_bit, lower); + let closest_bits_l1 = + self.find_closest_active_bit_candidates(word1_value, start_from_l1_bit, lower); closest_bits_l1.iter().for_each(|&closest_bit_l1| { /////////////// // Level 2 let word2_index = Self::address_to_index(word1_index, closest_bit_l1); // Layer 2 words are different, shift the bit to the word edge - let start_from_l2_bit = - if word2_index < layer2_word { - 127 - } else if word2_index > layer2_word { - 0 - } else { - layer2_bit - }; + let start_from_l2_bit = if word2_index < layer2_word { + 127 + } else if word2_index > layer2_word { + 0 + } else { + layer2_bit + }; let word2_value = self.state_ops.get_layer2_word(word2_index); - let closest_bits_l2 = self.find_closest_active_bit_candidates(word2_value, start_from_l2_bit, lower); + let closest_bits_l2 = + self.find_closest_active_bit_candidates(word2_value, start_from_l2_bit, lower); if closest_bits_l2.len() > 0 { // The active tick is found, restore its full index and return - let offset_found_index = Self::address_to_index(word2_index, closest_bits_l2[0]); + let offset_found_index = + Self::address_to_index(word2_index, closest_bits_l2[0]); if lower { if (offset_found_index > result) || (!found) { @@ -1302,7 +1305,9 @@ where }); if found { - Some((result as i32).saturating_sub(TICK_OFFSET as i32)) + // Convert the tree offset_index back to a tick index value + let tick_value = (result as i32).saturating_sub(TICK_OFFSET as i32); + Some(tick::TickIndex::new_unchecked(tick_value)) } else { None } @@ -1310,33 +1315,31 @@ where pub fn find_closest_lower_active_tick_index( &self, - index: i32, - ) -> Option - { + index: tick::TickIndex, + ) -> Option { self.find_closest_active_tick_index(index, true) } pub fn find_closest_higher_active_tick_index( &self, - index: i32, - ) -> Option - { + index: tick::TickIndex, + ) -> Option { self.find_closest_active_tick_index(index, false) } - pub fn find_closest_lower_active_tick(&self, index: i32) -> Option { + pub fn find_closest_lower_active_tick(&self, index: tick::TickIndex) -> Option { let maybe_tick_index = self.find_closest_lower_active_tick_index(index); if let Some(tick_index) = maybe_tick_index { - self.state_ops.get_tick_by_index(TickIndex(tick_index)) + self.state_ops.get_tick_by_index(tick_index) } else { None } } - pub fn find_closest_higher_active_tick(&self, index: i32) -> Option { + pub fn find_closest_higher_active_tick(&self, index: tick::TickIndex) -> Option { let maybe_tick_index = self.find_closest_higher_active_tick_index(index); if let Some(tick_index) = maybe_tick_index { - self.state_ops.get_tick_by_index(TickIndex(tick_index)) + self.state_ops.get_tick_by_index(tick_index) } else { None } @@ -1656,7 +1659,7 @@ mod tests { tick = TickIndex::MIN; } tick - }, + } // Default to a reasonable value when conversion fails Err(_) => { if price > 1.0 { @@ -1846,9 +1849,21 @@ mod tests { [ // For our tests, we'll construct TickIndex values that are intentionally // outside the valid range for testing purposes only - (TickIndex::new_unchecked(TickIndex::MIN.get() - 1), TickIndex::MAX, 1_000_000_000_u64), - (TickIndex::MIN, TickIndex::new_unchecked(TickIndex::MAX.get() + 1), 1_000_000_000_u64), - (TickIndex::new_unchecked(TickIndex::MIN.get() - 1), TickIndex::new_unchecked(TickIndex::MAX.get() + 1), 1_000_000_000_u64), + ( + TickIndex::new_unchecked(TickIndex::MIN.get() - 1), + TickIndex::MAX, + 1_000_000_000_u64, + ), + ( + TickIndex::MIN, + TickIndex::new_unchecked(TickIndex::MAX.get() + 1), + 1_000_000_000_u64, + ), + ( + TickIndex::new_unchecked(TickIndex::MIN.get() - 1), + TickIndex::new_unchecked(TickIndex::MAX.get() + 1), + 1_000_000_000_u64, + ), ( TickIndex::new_unchecked(TickIndex::MIN.get() - 100), TickIndex::new_unchecked(TickIndex::MAX.get() + 100), @@ -2043,24 +2058,80 @@ mod tests { let mut swap = Swap::::new(mock_ops); swap.insert_active_tick(MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX/2).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX - 1).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX+1).unwrap(), MIN_TICK_INDEX); - - assert_eq!(swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX), None); - assert_eq!(swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX/2), None); - assert_eq!(swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX - 1), None); - assert_eq!(swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX+1), None); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX / 2) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX - 1) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 1) + .unwrap(), + MIN_TICK_INDEX + ); + + assert_eq!( + swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX), + None + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX / 2), + None + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX - 1), + None + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 1), + None + ); swap.insert_active_tick(MAX_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX).unwrap(), MAX_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX/2).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX - 1).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 1).unwrap(), MIN_TICK_INDEX); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX) + .unwrap(), + MAX_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX / 2) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX - 1) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 1) + .unwrap(), + MIN_TICK_INDEX + ); } #[test] @@ -2069,17 +2140,53 @@ mod tests { let mut swap = Swap::::new(mock_ops); swap.insert_active_tick(MIN_TICK_INDEX + 10); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 10).unwrap(), MIN_TICK_INDEX + 10); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 11).unwrap(), MIN_TICK_INDEX + 10); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 12).unwrap(), MIN_TICK_INDEX + 10); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX), None); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 9), None); - - assert_eq!(swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 10).unwrap(), MIN_TICK_INDEX + 10); - assert_eq!(swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 11), None); - assert_eq!(swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 12), None); - assert_eq!(swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX).unwrap(), MIN_TICK_INDEX + 10); - assert_eq!(swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 9).unwrap(), MIN_TICK_INDEX + 10); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 10) + .unwrap(), + MIN_TICK_INDEX + 10 + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 11) + .unwrap(), + MIN_TICK_INDEX + 10 + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 12) + .unwrap(), + MIN_TICK_INDEX + 10 + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX), + None + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 9), + None + ); + + assert_eq!( + swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 10) + .unwrap(), + MIN_TICK_INDEX + 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 11), + None + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 12), + None + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX) + .unwrap(), + MIN_TICK_INDEX + 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 9) + .unwrap(), + MIN_TICK_INDEX + 10 + ); } #[test] @@ -2091,8 +2198,16 @@ mod tests { swap.insert_active_tick(MIN_TICK_INDEX + i); } for i in 0..1000 { - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + i).unwrap(), MIN_TICK_INDEX + i); - assert_eq!(swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + i).unwrap(), MIN_TICK_INDEX + i); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + i) + .unwrap(), + MIN_TICK_INDEX + i + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + i) + .unwrap(), + MIN_TICK_INDEX + i + ); } } @@ -2106,13 +2221,35 @@ mod tests { swap.insert_active_tick(i * 10); } for i in 1..count { - assert_eq!(swap.find_closest_lower_active_tick_index(i * 10).unwrap(), i * 10); - assert_eq!(swap.find_closest_higher_active_tick_index(i * 10).unwrap(), i * 10); + assert_eq!( + swap.find_closest_lower_active_tick_index(i * 10).unwrap(), + i * 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(i * 10).unwrap(), + i * 10 + ); for j in 1..=9 { - assert_eq!(swap.find_closest_lower_active_tick_index(i * 10 - j).unwrap(), (i-1) * 10); - assert_eq!(swap.find_closest_lower_active_tick_index(i * 10 + j).unwrap(), i * 10); - assert_eq!(swap.find_closest_higher_active_tick_index(i * 10 - j).unwrap(), i * 10); - assert_eq!(swap.find_closest_higher_active_tick_index(i * 10 + j).unwrap(), (i+1) * 10); + assert_eq!( + swap.find_closest_lower_active_tick_index(i * 10 - j) + .unwrap(), + (i - 1) * 10 + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(i * 10 + j) + .unwrap(), + i * 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(i * 10 - j) + .unwrap(), + i * 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(i * 10 + j) + .unwrap(), + (i + 1) * 10 + ); } } } @@ -2127,15 +2264,37 @@ mod tests { swap.insert_active_tick(i * 10); } for i in 1..count { - assert_eq!(swap.find_closest_lower_active_tick_index(i * 10).unwrap(), i * 10); - assert_eq!(swap.find_closest_higher_active_tick_index(i * 10).unwrap(), i * 10); + assert_eq!( + swap.find_closest_lower_active_tick_index(i * 10).unwrap(), + i * 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(i * 10).unwrap(), + i * 10 + ); for j in 1..=9 { - assert_eq!(swap.find_closest_lower_active_tick_index(i * 10 - j).unwrap(), (i-1) * 10); - assert_eq!(swap.find_closest_lower_active_tick_index(i * 10 + j).unwrap(), i * 10); - assert_eq!(swap.find_closest_higher_active_tick_index(i * 10 - j).unwrap(), i * 10); - assert_eq!(swap.find_closest_higher_active_tick_index(i * 10 + j).unwrap(), (i+1) * 10); + assert_eq!( + swap.find_closest_lower_active_tick_index(i * 10 - j) + .unwrap(), + (i - 1) * 10 + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(i * 10 + j) + .unwrap(), + i * 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(i * 10 - j) + .unwrap(), + i * 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(i * 10 + j) + .unwrap(), + (i + 1) * 10 + ); } - } + } } #[test] @@ -2149,13 +2308,35 @@ mod tests { swap.insert_active_tick(i * 10); } for i in 1..count { - assert_eq!(swap.find_closest_lower_active_tick_index(i * 10).unwrap(), i * 10); - assert_eq!(swap.find_closest_higher_active_tick_index(i * 10).unwrap(), i * 10); + assert_eq!( + swap.find_closest_lower_active_tick_index(i * 10).unwrap(), + i * 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(i * 10).unwrap(), + i * 10 + ); for j in 1..=9 { - assert_eq!(swap.find_closest_lower_active_tick_index(i * 10 - j).unwrap(), (i-1) * 10); - assert_eq!(swap.find_closest_lower_active_tick_index(i * 10 + j).unwrap(), i * 10); - assert_eq!(swap.find_closest_higher_active_tick_index(i * 10 - j).unwrap(), i * 10); - assert_eq!(swap.find_closest_higher_active_tick_index(i * 10 + j).unwrap(), (i+1) * 10); + assert_eq!( + swap.find_closest_lower_active_tick_index(i * 10 - j) + .unwrap(), + (i - 1) * 10 + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(i * 10 + j) + .unwrap(), + i * 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(i * 10 - j) + .unwrap(), + i * 10 + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(i * 10 + j) + .unwrap(), + (i + 1) * 10 + ); } } } @@ -2165,7 +2346,7 @@ mod tests { fn test_tick_search_full_range() { let mock_ops = MockSwapDataOperations::new(); let mut swap = Swap::::new(mock_ops); - let step= 1019; + let step = 1019; let count = (MAX_TICK_INDEX - MIN_TICK_INDEX) / step; for i in 0..=count { @@ -2175,23 +2356,69 @@ mod tests { for i in 1..count { let index = MIN_TICK_INDEX + i * step; - assert_eq!(swap.find_closest_lower_active_tick_index(index - step).unwrap(), index - step); - assert_eq!(swap.find_closest_lower_active_tick_index(index).unwrap(), index); - assert_eq!(swap.find_closest_lower_active_tick_index(index + step - 1).unwrap(), index); - assert_eq!(swap.find_closest_lower_active_tick_index(index + step/2).unwrap(), index); + assert_eq!( + swap.find_closest_lower_active_tick_index(index - step) + .unwrap(), + index - step + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(index).unwrap(), + index + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(index + step - 1) + .unwrap(), + index + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(index + step / 2) + .unwrap(), + index + ); - assert_eq!(swap.find_closest_higher_active_tick_index(index).unwrap(), index); - assert_eq!(swap.find_closest_higher_active_tick_index(index + step).unwrap(), index + step); - assert_eq!(swap.find_closest_higher_active_tick_index(index + step/2).unwrap(), index + step); - assert_eq!(swap.find_closest_higher_active_tick_index(index + step - 1).unwrap(), index + step); + assert_eq!( + swap.find_closest_higher_active_tick_index(index).unwrap(), + index + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(index + step) + .unwrap(), + index + step + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(index + step / 2) + .unwrap(), + index + step + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(index + step - 1) + .unwrap(), + index + step + ); for j in 1..=9 { - assert_eq!(swap.find_closest_lower_active_tick_index(index - j).unwrap(), index - step); - assert_eq!(swap.find_closest_lower_active_tick_index(index + j).unwrap(), index); - assert_eq!(swap.find_closest_higher_active_tick_index(index - j).unwrap(), index); - assert_eq!(swap.find_closest_higher_active_tick_index(index + j).unwrap(), index + step); + assert_eq!( + swap.find_closest_lower_active_tick_index(index - j) + .unwrap(), + index - step + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(index + j) + .unwrap(), + index + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(index - j) + .unwrap(), + index + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(index + j) + .unwrap(), + index + step + ); } } - } + } #[test] fn test_tick_remove_basic() { @@ -2202,24 +2429,60 @@ mod tests { swap.insert_active_tick(MAX_TICK_INDEX); swap.remove_active_tick(MAX_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX/2).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX - 1).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX+1).unwrap(), MIN_TICK_INDEX); - - assert_eq!(swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX).unwrap(), MIN_TICK_INDEX); - assert_eq!(swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX), None); - assert_eq!(swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX/2), None); - assert_eq!(swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX - 1), None); - assert_eq!(swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX+1), None); - } + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX / 2) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX - 1) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 1) + .unwrap(), + MIN_TICK_INDEX + ); + + assert_eq!( + swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX) + .unwrap(), + MIN_TICK_INDEX + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX), + None + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX / 2), + None + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX - 1), + None + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 1), + None + ); + } #[test] fn test_tick_remove_full_range() { let mock_ops = MockSwapDataOperations::new(); let mut swap = Swap::::new(mock_ops); - let step= 1019; + let step = 1019; let count = (MAX_TICK_INDEX - MIN_TICK_INDEX) / step; let remove_frequency = 5; // Remove every 5th tick @@ -2247,8 +2510,14 @@ mod tests { assert!(lower != Some(index)); assert!(higher != Some(index)); } else { - assert_eq!(swap.find_closest_lower_active_tick_index(index).unwrap(), index); - assert_eq!(swap.find_closest_higher_active_tick_index(index).unwrap(), index); + assert_eq!( + swap.find_closest_lower_active_tick_index(index).unwrap(), + index + ); + assert_eq!( + swap.find_closest_higher_active_tick_index(index).unwrap(), + index + ); } } } diff --git a/pallets/swap/src/tick.rs b/pallets/swap/src/tick.rs index 610f7cd2d..7f2272b41 100644 --- a/pallets/swap/src/tick.rs +++ b/pallets/swap/src/tick.rs @@ -2,7 +2,8 @@ use core::convert::TryFrom; use core::error::Error; use core::fmt; -use core::ops::{BitOr, Neg, Shl, Shr}; +use core::hash::Hash; +use core::ops::{Add, AddAssign, BitOr, Deref, Neg, Shl, Shr, Sub, SubAssign}; use alloy_primitives::{I256, U256}; use frame_support::pallet_prelude::*; @@ -44,9 +45,6 @@ const U256_MAX_TICK: U256 = U256::from_limbs([887272, 0, 0, 0]); const MIN_TICK: i32 = -887272; const MAX_TICK: i32 = -MIN_TICK; -pub const MIN_TICK_INDEX: i32 = -443636; -pub const MAX_TICK_INDEX: i32 = -MIN_TICK_INDEX; - const MIN_SQRT_RATIO: U256 = U256::from_limbs([4295128739, 0, 0, 0]); const MAX_SQRT_RATIO: U256 = U256::from_limbs([6743328256752651558, 17280870778742802505, 4294805859, 0]); @@ -71,9 +69,6 @@ const TICK_HIGH: I256 = I256::from_raw(U256::from_limbs([ /// - Net liquidity /// - Gross liquidity /// - Fees (above global) in both currencies -use core::hash::Hash; -use core::ops::{Add, AddAssign, Deref, Sub, SubAssign}; - #[derive(Debug, Default, Clone, Encode, Decode, TypeInfo, MaxEncodedLen, PartialEq, Eq)] pub struct Tick { pub liquidity_net: i128, @@ -98,7 +93,7 @@ pub struct Tick { Ord, Hash, )] -pub struct TickIndex(pub i32); +pub struct TickIndex(i32); impl Add for TickIndex { type Output = Self; @@ -256,6 +251,82 @@ impl TickIndex { Self::new(self.0 - value) } + /// Add a value to this tick index, saturating at the bounds instead of overflowing + pub fn saturating_add(&self, value: i32) -> Self { + match self.checked_add(value) { + Ok(result) => result, + Err(_) => { + if value > 0 { + Self::MAX + } else { + Self::MIN + } + } + } + } + + /// Subtract a value from this tick index, saturating at the bounds instead of overflowing + pub fn saturating_sub(&self, value: i32) -> Self { + match self.checked_sub(value) { + Ok(result) => result, + Err(_) => { + if value > 0 { + Self::MIN + } else { + Self::MAX + } + } + } + } + + /// Divide the tick index by a value with bounds checking + pub fn checked_div(&self, value: i32) -> Result { + if value == 0 { + return Err(TickMathError::DivisionByZero); + } + Self::new(self.0 / value) + } + + /// Divide the tick index by a value, saturating at the bounds + pub fn saturating_div(&self, value: i32) -> Self { + if value == 0 { + return Self::MAX; // Return MAX for division by zero + } + match self.checked_div(value) { + Ok(result) => result, + Err(_) => { + if (self.0 < 0 && value > 0) || (self.0 > 0 && value < 0) { + Self::MIN + } else { + Self::MAX + } + } + } + } + + /// Multiply the tick index by a value with bounds checking + pub fn checked_mul(&self, value: i32) -> Result { + // Check for potential overflow + match self.0.checked_mul(value) { + Some(result) => Self::new(result), + None => Err(TickMathError::Overflow), + } + } + + /// Multiply the tick index by a value, saturating at the bounds + pub fn saturating_mul(&self, value: i32) -> Self { + match self.checked_mul(value) { + Ok(result) => result, + Err(_) => { + if (self.0 < 0 && value > 0) || (self.0 > 0 && value < 0) { + Self::MIN + } else { + Self::MAX + } + } + } + } + /// Converts tick index into SQRT of lower price of this tick In order to find the higher price /// of this tick, call tick_index_to_sqrt_price(tick_idx + 1) pub fn try_to_sqrt_price(&self) -> Result { @@ -600,6 +671,7 @@ pub enum TickMathError { SqrtPriceOutOfBounds, ConversionError, Overflow, + DivisionByZero, } impl fmt::Display for TickMathError { @@ -608,7 +680,8 @@ impl fmt::Display for TickMathError { Self::TickOutOfBounds => f.write_str("The given tick is outside of the minimum/maximum values."), Self::SqrtPriceOutOfBounds =>f.write_str("Second inequality must be < because the price can never reach the price at the max tick"), Self::ConversionError => f.write_str("Error converting from one number type into another"), - Self::Overflow => f.write_str("Number overflow in arithmetic operation") + Self::Overflow => f.write_str("Number overflow in arithmetic operation"), + Self::DivisionByZero => f.write_str("Division by zero is not allowed") } } } From 9e92b07ec7c5b2e566f7734d365ddbc62fd988d9 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Wed, 26 Mar 2025 12:05:36 +0100 Subject: [PATCH 2/3] Replace TICK_OFFSET with TickIndex::OFFSET --- pallets/swap/src/lib.rs | 42 +++++++++++++++------------------------- pallets/swap/src/tick.rs | 4 ++++ 2 files changed, 20 insertions(+), 26 deletions(-) diff --git a/pallets/swap/src/lib.rs b/pallets/swap/src/lib.rs index 04a4fd1f7..bfb597833 100644 --- a/pallets/swap/src/lib.rs +++ b/pallets/swap/src/lib.rs @@ -16,10 +16,6 @@ mod tick; type SqrtPrice = U64F64; -/// All tick indexes are offset by TICK_OFFSET for the search and active tick storage needs -/// so that tick indexes are positive, which simplifies bit logic -pub const TICK_OFFSET: u32 = 887272; - pub enum SwapStepAction { Crossing, StopOn, @@ -1131,15 +1127,15 @@ where word.saturating_mul(128).saturating_add(bit) } - pub fn insert_active_tick(&mut self, index: tick::TickIndex) { + pub fn insert_active_tick(&mut self, index: TickIndex) { // Check the range - if (index < tick::TickIndex::MIN) || (index > tick::TickIndex::MAX) { + if (index < TickIndex::MIN) || (index > TickIndex::MAX) { return; } // Convert the tick index value to an offset_index for the tree representation // to avoid working with the sign bit. - let offset_index = index.saturating_add(TICK_OFFSET as i32).get() as u32; + let offset_index = (index.get() + TickIndex::OFFSET.get()) as u32; // Calculate index in each layer let (layer2_word, layer2_bit) = Self::index_to_address(offset_index); @@ -1161,15 +1157,15 @@ where self.state_ops.set_layer2_word(layer2_word, word2_value); } - pub fn remove_active_tick(&mut self, index: tick::TickIndex) { + pub fn remove_active_tick(&mut self, index: TickIndex) { // Check the range - if (index < tick::TickIndex::MIN) || (index > tick::TickIndex::MAX) { + if (index < TickIndex::MIN) || (index > TickIndex::MAX) { return; } // Convert the tick index value to an offset_index for the tree representation // to avoid working with the sign bit. - let offset_index = index.saturating_add(TICK_OFFSET as i32).get() as u32; + let offset_index = (index.get() + TickIndex::OFFSET.get()) as u32; // Calculate index in each layer let (layer2_word, layer2_bit) = Self::index_to_address(offset_index); @@ -1223,17 +1219,17 @@ where pub fn find_closest_active_tick_index( &self, - index: tick::TickIndex, + index: TickIndex, lower: bool, - ) -> Option { + ) -> Option { // Check the range - if (index < tick::TickIndex::MIN) || (index > tick::TickIndex::MAX) { + if (index < TickIndex::MIN) || (index > TickIndex::MAX) { return None; } // Convert the tick index value to an offset_index for the tree representation // to avoid working with the sign bit. - let offset_index = index.saturating_add(TICK_OFFSET as i32).get() as u32; + let offset_index = (index.get() + TickIndex::OFFSET.get()) as u32; let mut found = false; let mut result: u32 = 0; @@ -1306,28 +1302,22 @@ where if found { // Convert the tree offset_index back to a tick index value - let tick_value = (result as i32).saturating_sub(TICK_OFFSET as i32); - Some(tick::TickIndex::new_unchecked(tick_value)) + let tick_value = (result as i32).saturating_sub(TickIndex::OFFSET.get()); + Some(TickIndex::new_unchecked(tick_value)) } else { None } } - pub fn find_closest_lower_active_tick_index( - &self, - index: tick::TickIndex, - ) -> Option { + pub fn find_closest_lower_active_tick_index(&self, index: TickIndex) -> Option { self.find_closest_active_tick_index(index, true) } - pub fn find_closest_higher_active_tick_index( - &self, - index: tick::TickIndex, - ) -> Option { + pub fn find_closest_higher_active_tick_index(&self, index: TickIndex) -> Option { self.find_closest_active_tick_index(index, false) } - pub fn find_closest_lower_active_tick(&self, index: tick::TickIndex) -> Option { + pub fn find_closest_lower_active_tick(&self, index: TickIndex) -> Option { let maybe_tick_index = self.find_closest_lower_active_tick_index(index); if let Some(tick_index) = maybe_tick_index { self.state_ops.get_tick_by_index(tick_index) @@ -1336,7 +1326,7 @@ where } } - pub fn find_closest_higher_active_tick(&self, index: tick::TickIndex) -> Option { + pub fn find_closest_higher_active_tick(&self, index: TickIndex) -> Option { let maybe_tick_index = self.find_closest_higher_active_tick_index(index); if let Some(tick_index) = maybe_tick_index { self.state_ops.get_tick_by_index(tick_index) diff --git a/pallets/swap/src/tick.rs b/pallets/swap/src/tick.rs index 7f2272b41..809b26170 100644 --- a/pallets/swap/src/tick.rs +++ b/pallets/swap/src/tick.rs @@ -166,6 +166,10 @@ impl TickIndex { /// Minimum value of the tick index /// The tick_math library uses different bitness, so we have to divide by 2. pub const MIN: Self = Self(MIN_TICK / 2); + + /// All tick indexes are offset by this value for storage needs + /// so that tick indexes are positive, which simplifies bit logic + pub const OFFSET: Self = Self(MAX_TICK); /// Converts a sqrt price to a tick index, ensuring it's within valid bounds /// From 548cf7b1a36a1a9784ae61b47c906f159c401010 Mon Sep 17 00:00:00 2001 From: Aliaksandr Tsurko Date: Wed, 26 Mar 2025 14:23:08 +0100 Subject: [PATCH 3/3] Actualize tests in pallet-subtensor-swap --- pallets/swap/src/lib.rs | 304 ++++++++++++++++++++++------------------ 1 file changed, 170 insertions(+), 134 deletions(-) diff --git a/pallets/swap/src/lib.rs b/pallets/swap/src/lib.rs index bfb597833..ab98205b4 100644 --- a/pallets/swap/src/lib.rs +++ b/pallets/swap/src/lib.rs @@ -2047,80 +2047,80 @@ mod tests { let mock_ops = MockSwapDataOperations::new(); let mut swap = Swap::::new(mock_ops); - swap.insert_active_tick(MIN_TICK_INDEX); + swap.insert_active_tick(TickIndex::MIN); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX) + swap.find_closest_lower_active_tick_index(TickIndex::MIN) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX) + swap.find_closest_lower_active_tick_index(TickIndex::MAX) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX / 2) + swap.find_closest_lower_active_tick_index(TickIndex::MAX.saturating_div(2)) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX - 1) + swap.find_closest_lower_active_tick_index(TickIndex::MAX.prev().unwrap()) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 1) + swap.find_closest_lower_active_tick_index(TickIndex::MIN.next().unwrap()) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX) + swap.find_closest_higher_active_tick_index(TickIndex::MIN) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX), + swap.find_closest_higher_active_tick_index(TickIndex::MAX), None ); assert_eq!( - swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX / 2), + swap.find_closest_higher_active_tick_index(TickIndex::MAX.saturating_div(2)), None ); assert_eq!( - swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX - 1), + swap.find_closest_higher_active_tick_index(TickIndex::MAX.prev().unwrap()), None ); assert_eq!( - swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 1), + swap.find_closest_higher_active_tick_index(TickIndex::MIN.next().unwrap()), None ); - swap.insert_active_tick(MAX_TICK_INDEX); + swap.insert_active_tick(TickIndex::MAX); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX) + swap.find_closest_lower_active_tick_index(TickIndex::MIN) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX) + swap.find_closest_lower_active_tick_index(TickIndex::MAX) .unwrap(), - MAX_TICK_INDEX + TickIndex::MAX ); assert_eq!( - swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX / 2) + swap.find_closest_lower_active_tick_index(TickIndex::MAX.saturating_div(2)) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX - 1) + swap.find_closest_lower_active_tick_index(TickIndex::MAX.prev().unwrap()) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 1) + swap.find_closest_lower_active_tick_index(TickIndex::MIN.next().unwrap()) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); } @@ -2129,53 +2129,54 @@ mod tests { let mock_ops = MockSwapDataOperations::new(); let mut swap = Swap::::new(mock_ops); - swap.insert_active_tick(MIN_TICK_INDEX + 10); + let active_index = TickIndex::MIN.saturating_add(10); + swap.insert_active_tick(active_index); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 10) + swap.find_closest_lower_active_tick_index(active_index) .unwrap(), - MIN_TICK_INDEX + 10 + active_index ); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 11) + swap.find_closest_lower_active_tick_index(TickIndex::MIN.saturating_add(11)) .unwrap(), - MIN_TICK_INDEX + 10 + active_index ); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 12) + swap.find_closest_lower_active_tick_index(TickIndex::MIN.saturating_add(12)) .unwrap(), - MIN_TICK_INDEX + 10 + active_index ); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX), + swap.find_closest_lower_active_tick_index(TickIndex::MIN), None ); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 9), + swap.find_closest_lower_active_tick_index(TickIndex::MIN.saturating_add(9)), None ); assert_eq!( - swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 10) + swap.find_closest_higher_active_tick_index(active_index) .unwrap(), - MIN_TICK_INDEX + 10 + active_index ); assert_eq!( - swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 11), + swap.find_closest_higher_active_tick_index(TickIndex::MIN.saturating_add(11)), None ); assert_eq!( - swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 12), + swap.find_closest_higher_active_tick_index(TickIndex::MIN.saturating_add(12)), None ); assert_eq!( - swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX) + swap.find_closest_higher_active_tick_index(TickIndex::MIN) .unwrap(), - MIN_TICK_INDEX + 10 + active_index ); assert_eq!( - swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 9) + swap.find_closest_higher_active_tick_index(TickIndex::MIN.saturating_add(9)) .unwrap(), - MIN_TICK_INDEX + 10 + active_index ); } @@ -2184,19 +2185,21 @@ mod tests { let mock_ops = MockSwapDataOperations::new(); let mut swap = Swap::::new(mock_ops); + (0..1000).for_each(|i| { + swap.insert_active_tick(TickIndex::MIN.saturating_add(i)); + }); + for i in 0..1000 { - swap.insert_active_tick(MIN_TICK_INDEX + i); - } - for i in 0..1000 { + let test_index = TickIndex::MIN.saturating_add(i); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + i) + swap.find_closest_lower_active_tick_index(test_index) .unwrap(), - MIN_TICK_INDEX + i + test_index ); assert_eq!( - swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + i) + swap.find_closest_higher_active_tick_index(test_index) .unwrap(), - MIN_TICK_INDEX + i + test_index ); } } @@ -2208,37 +2211,42 @@ mod tests { let count: i32 = 1000; for i in 0..=count { - swap.insert_active_tick(i * 10); + swap.insert_active_tick(TickIndex::new_unchecked(i * 10)); } for i in 1..count { + let tick = TickIndex::new_unchecked(i * 10); assert_eq!( - swap.find_closest_lower_active_tick_index(i * 10).unwrap(), - i * 10 + swap.find_closest_lower_active_tick_index(tick).unwrap(), + tick ); assert_eq!( - swap.find_closest_higher_active_tick_index(i * 10).unwrap(), - i * 10 + swap.find_closest_higher_active_tick_index(tick).unwrap(), + tick ); for j in 1..=9 { + let before_tick = TickIndex::new_unchecked(i * 10 - j); + let after_tick = TickIndex::new_unchecked(i * 10 + j); + let prev_tick = TickIndex::new_unchecked((i - 1) * 10); + let next_tick = TickIndex::new_unchecked((i + 1) * 10); assert_eq!( - swap.find_closest_lower_active_tick_index(i * 10 - j) + swap.find_closest_lower_active_tick_index(before_tick) .unwrap(), - (i - 1) * 10 + prev_tick ); assert_eq!( - swap.find_closest_lower_active_tick_index(i * 10 + j) + swap.find_closest_lower_active_tick_index(after_tick) .unwrap(), - i * 10 + tick ); assert_eq!( - swap.find_closest_higher_active_tick_index(i * 10 - j) + swap.find_closest_higher_active_tick_index(before_tick) .unwrap(), - i * 10 + tick ); assert_eq!( - swap.find_closest_higher_active_tick_index(i * 10 + j) + swap.find_closest_higher_active_tick_index(after_tick) .unwrap(), - (i + 1) * 10 + next_tick ); } } @@ -2251,37 +2259,43 @@ mod tests { let count: i32 = 1000; for i in (0..=count).rev() { - swap.insert_active_tick(i * 10); + swap.insert_active_tick(TickIndex::new_unchecked(i * 10)); } for i in 1..count { + let tick = TickIndex::new_unchecked(i * 10); assert_eq!( - swap.find_closest_lower_active_tick_index(i * 10).unwrap(), - i * 10 + swap.find_closest_lower_active_tick_index(tick).unwrap(), + tick ); assert_eq!( - swap.find_closest_higher_active_tick_index(i * 10).unwrap(), - i * 10 + swap.find_closest_higher_active_tick_index(tick).unwrap(), + tick ); for j in 1..=9 { + let before_tick = TickIndex::new_unchecked(i * 10 - j); + let after_tick = TickIndex::new_unchecked(i * 10 + j); + let prev_tick = TickIndex::new_unchecked((i - 1) * 10); + let next_tick = TickIndex::new_unchecked((i + 1) * 10); + assert_eq!( - swap.find_closest_lower_active_tick_index(i * 10 - j) + swap.find_closest_lower_active_tick_index(before_tick) .unwrap(), - (i - 1) * 10 + prev_tick ); assert_eq!( - swap.find_closest_lower_active_tick_index(i * 10 + j) + swap.find_closest_lower_active_tick_index(after_tick) .unwrap(), - i * 10 + tick ); assert_eq!( - swap.find_closest_higher_active_tick_index(i * 10 - j) + swap.find_closest_higher_active_tick_index(before_tick) .unwrap(), - i * 10 + tick ); assert_eq!( - swap.find_closest_higher_active_tick_index(i * 10 + j) + swap.find_closest_higher_active_tick_index(after_tick) .unwrap(), - (i + 1) * 10 + next_tick ); } } @@ -2295,37 +2309,44 @@ mod tests { for _ in 0..10 { for i in 0..=count { - swap.insert_active_tick(i * 10); + let tick = TickIndex::new_unchecked(i * 10); + swap.insert_active_tick(tick); } for i in 1..count { + let tick = TickIndex::new_unchecked(i * 10); assert_eq!( - swap.find_closest_lower_active_tick_index(i * 10).unwrap(), - i * 10 + swap.find_closest_lower_active_tick_index(tick).unwrap(), + tick ); assert_eq!( - swap.find_closest_higher_active_tick_index(i * 10).unwrap(), - i * 10 + swap.find_closest_higher_active_tick_index(tick).unwrap(), + tick ); for j in 1..=9 { + let before_tick = TickIndex::new_unchecked(i * 10 - j); + let after_tick = TickIndex::new_unchecked(i * 10 + j); + let prev_tick = TickIndex::new_unchecked((i - 1) * 10); + let next_tick = TickIndex::new_unchecked((i + 1) * 10); + assert_eq!( - swap.find_closest_lower_active_tick_index(i * 10 - j) + swap.find_closest_lower_active_tick_index(before_tick) .unwrap(), - (i - 1) * 10 + prev_tick ); assert_eq!( - swap.find_closest_lower_active_tick_index(i * 10 + j) + swap.find_closest_lower_active_tick_index(after_tick) .unwrap(), - i * 10 + tick ); assert_eq!( - swap.find_closest_higher_active_tick_index(i * 10 - j) + swap.find_closest_higher_active_tick_index(before_tick) .unwrap(), - i * 10 + tick ); assert_eq!( - swap.find_closest_higher_active_tick_index(i * 10 + j) + swap.find_closest_higher_active_tick_index(after_tick) .unwrap(), - (i + 1) * 10 + next_tick ); } } @@ -2337,32 +2358,37 @@ mod tests { let mock_ops = MockSwapDataOperations::new(); let mut swap = Swap::::new(mock_ops); let step = 1019; - let count = (MAX_TICK_INDEX - MIN_TICK_INDEX) / step; + // Get the full valid tick range by subtracting MIN from MAX + let count = (TickIndex::MAX.get() - TickIndex::MIN.get()) / step; for i in 0..=count { - let index = MIN_TICK_INDEX + i * step; + let index = TickIndex::MIN.saturating_add(i * step); swap.insert_active_tick(index); } for i in 1..count { - let index = MIN_TICK_INDEX + i * step; + let index = TickIndex::MIN.saturating_add(i * step); + + let prev_index = TickIndex::new_unchecked(index.get() - step); + let next_minus_one = TickIndex::new_unchecked(index.get() + step - 1); assert_eq!( - swap.find_closest_lower_active_tick_index(index - step) + swap.find_closest_lower_active_tick_index(prev_index) .unwrap(), - index - step + prev_index ); assert_eq!( swap.find_closest_lower_active_tick_index(index).unwrap(), index ); assert_eq!( - swap.find_closest_lower_active_tick_index(index + step - 1) + swap.find_closest_lower_active_tick_index(next_minus_one) .unwrap(), index ); + + let mid_next = TickIndex::new_unchecked(index.get() + step / 2); assert_eq!( - swap.find_closest_lower_active_tick_index(index + step / 2) - .unwrap(), + swap.find_closest_lower_active_tick_index(mid_next).unwrap(), index ); @@ -2370,41 +2396,50 @@ mod tests { swap.find_closest_higher_active_tick_index(index).unwrap(), index ); + + let next_index = TickIndex::new_unchecked(index.get() + step); assert_eq!( - swap.find_closest_higher_active_tick_index(index + step) + swap.find_closest_higher_active_tick_index(next_index) .unwrap(), - index + step + next_index ); + + let mid_next = TickIndex::new_unchecked(index.get() + step / 2); assert_eq!( - swap.find_closest_higher_active_tick_index(index + step / 2) + swap.find_closest_higher_active_tick_index(mid_next) .unwrap(), - index + step + next_index ); + + let next_minus_1 = TickIndex::new_unchecked(index.get() + step - 1); assert_eq!( - swap.find_closest_higher_active_tick_index(index + step - 1) + swap.find_closest_higher_active_tick_index(next_minus_1) .unwrap(), - index + step + next_index ); for j in 1..=9 { + let before_index = TickIndex::new_unchecked(index.get() - j); + let after_index = TickIndex::new_unchecked(index.get() + j); + assert_eq!( - swap.find_closest_lower_active_tick_index(index - j) + swap.find_closest_lower_active_tick_index(before_index) .unwrap(), - index - step + prev_index ); assert_eq!( - swap.find_closest_lower_active_tick_index(index + j) + swap.find_closest_lower_active_tick_index(after_index) .unwrap(), index ); assert_eq!( - swap.find_closest_higher_active_tick_index(index - j) + swap.find_closest_higher_active_tick_index(before_index) .unwrap(), index ); assert_eq!( - swap.find_closest_higher_active_tick_index(index + j) + swap.find_closest_higher_active_tick_index(after_index) .unwrap(), - index + step + next_index ); } } @@ -2415,55 +2450,55 @@ mod tests { let mock_ops = MockSwapDataOperations::new(); let mut swap = Swap::::new(mock_ops); - swap.insert_active_tick(MIN_TICK_INDEX); - swap.insert_active_tick(MAX_TICK_INDEX); - swap.remove_active_tick(MAX_TICK_INDEX); + swap.insert_active_tick(TickIndex::MIN); + swap.insert_active_tick(TickIndex::MAX); + swap.remove_active_tick(TickIndex::MAX); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX) + swap.find_closest_lower_active_tick_index(TickIndex::MIN) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX) + swap.find_closest_lower_active_tick_index(TickIndex::MAX) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX / 2) + swap.find_closest_lower_active_tick_index(TickIndex::MAX.saturating_div(2)) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MAX_TICK_INDEX - 1) + swap.find_closest_lower_active_tick_index(TickIndex::MAX.prev().unwrap()) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_lower_active_tick_index(MIN_TICK_INDEX + 1) + swap.find_closest_lower_active_tick_index(TickIndex::MIN.next().unwrap()) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX) + swap.find_closest_higher_active_tick_index(TickIndex::MIN) .unwrap(), - MIN_TICK_INDEX + TickIndex::MIN ); assert_eq!( - swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX), + swap.find_closest_higher_active_tick_index(TickIndex::MAX), None ); assert_eq!( - swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX / 2), + swap.find_closest_higher_active_tick_index(TickIndex::MAX.saturating_div(2)), None ); assert_eq!( - swap.find_closest_higher_active_tick_index(MAX_TICK_INDEX - 1), + swap.find_closest_higher_active_tick_index(TickIndex::MAX.prev().unwrap()), None ); assert_eq!( - swap.find_closest_higher_active_tick_index(MIN_TICK_INDEX + 1), + swap.find_closest_higher_active_tick_index(TickIndex::MIN.next().unwrap()), None ); } @@ -2473,26 +2508,27 @@ mod tests { let mock_ops = MockSwapDataOperations::new(); let mut swap = Swap::::new(mock_ops); let step = 1019; - let count = (MAX_TICK_INDEX - MIN_TICK_INDEX) / step; + // Get the full valid tick range by subtracting MIN from MAX + let count = (TickIndex::MAX.get() - TickIndex::MIN.get()) / step; let remove_frequency = 5; // Remove every 5th tick // Insert ticks for i in 0..=count { - let index = MIN_TICK_INDEX + i * step; + let index = TickIndex::MIN.saturating_add(i * step); swap.insert_active_tick(index); } // Remove some ticks for i in 1..count { if i % remove_frequency == 0 { - let index = MIN_TICK_INDEX + i * step; + let index = TickIndex::MIN.saturating_add(i * step); swap.remove_active_tick(index); } } // Verify for i in 1..count { - let index = MIN_TICK_INDEX + i * step; + let index = TickIndex::MIN.saturating_add(i * step); if i % remove_frequency == 0 { let lower = swap.find_closest_lower_active_tick_index(index);