use ahash::AHashMap;
use alloy_primitives::U256;
use crate::defi::tick_map::bit_math::{least_significant_bit, most_significant_bit};
fn tick_position(tick: i32) -> (i16, u8) {
let word_pos = (tick >> 8) as i16;
let bit_pos = (tick & 0xFF) as u8;
(word_pos, bit_pos)
}
#[derive(Debug, Clone, Default)]
pub struct TickBitmap {
words: AHashMap<i16, U256>,
tick_spacing: i32,
}
impl TickBitmap {
pub fn new(tick_spacing: u32) -> Self {
assert!(tick_spacing > 0, "Tick spacing must be greater than zero");
Self {
words: AHashMap::new(),
tick_spacing: tick_spacing as i32,
}
}
fn compress_tick(&self, tick: i32) -> i32 {
tick / self.tick_spacing
}
pub fn flip_tick(&mut self, tick: i32) {
let remainder = tick % self.tick_spacing;
assert!(
remainder == 0,
"Tick must be multiple of tick spacing: tick={}, tick_spacing={}, remainder={}",
tick,
self.tick_spacing,
remainder
);
let compressed_tick = self.compress_tick(tick);
let (word_position, bit_position) = tick_position(compressed_tick);
let word = self.words.entry(word_position).or_insert(U256::ZERO);
*word ^= U256::from(1u128) << bit_position;
if *word == U256::ZERO {
self.words.remove(&word_position);
}
}
pub fn is_initialized(&self, tick: i32) -> bool {
let compressed_tick = self.compress_tick(tick);
let (word_position, bit_position) = tick_position(compressed_tick);
if let Some(&word) = self.words.get(&word_position) {
(word & (U256::from(1u128) << bit_position)) != U256::ZERO
} else {
false
}
}
pub fn next_initialized_tick_within_one_word(
&self,
tick: i32,
less_than_or_equal: bool,
) -> (i32, bool) {
let mut compressed_tick = self.compress_tick(tick);
if tick < 0 && tick % self.tick_spacing != 0 {
compressed_tick -= 1;
}
if less_than_or_equal {
let (word_pos, bit_pos) = tick_position(compressed_tick);
let mask =
(U256::from(1u128) << bit_pos) - U256::from(1u128) + (U256::from(1u128) << bit_pos);
let word = self.words.get(&word_pos).copied().unwrap_or(U256::ZERO);
let masked = word & mask;
let initialized = !masked.is_zero();
let next = if initialized {
(compressed_tick - (bit_pos as i32) + most_significant_bit(masked))
* self.tick_spacing
} else {
(compressed_tick - (bit_pos as i32)) * self.tick_spacing
};
(next, initialized)
} else {
let (word_pos, bit_pos) = tick_position(compressed_tick + 1);
let mask = !((U256::from(1u128) << bit_pos) - U256::from(1u128));
let word = self.words.get(&word_pos).copied().unwrap_or(U256::ZERO);
let masked = word & mask;
let initialized = !masked.is_zero();
let next = if initialized {
(compressed_tick + 1 + least_significant_bit(masked) - (bit_pos as i32))
* self.tick_spacing
} else {
(compressed_tick + 1 + (255i32) - (bit_pos as i32)) * self.tick_spacing };
(next, initialized)
}
}
}
#[cfg(test)]
mod tests {
use rstest::{fixture, rstest};
use super::*;
#[fixture]
fn tick_bitmap() -> TickBitmap {
TickBitmap::new(1)
}
#[rstest]
fn test_tick_to_positions() {
assert_eq!(tick_position(256), (1, 0));
assert_eq!(tick_position(-256), (-1, 0));
assert_eq!(tick_position(100), (0, 100));
}
#[rstest]
fn test_flip_tick_toggle(mut tick_bitmap: TickBitmap) {
assert!(!tick_bitmap.is_initialized(100));
tick_bitmap.flip_tick(100);
assert!(tick_bitmap.is_initialized(100));
tick_bitmap.flip_tick(100);
assert!(!tick_bitmap.is_initialized(100));
assert!(!tick_bitmap.is_initialized(99));
assert!(!tick_bitmap.is_initialized(101));
}
#[rstest]
fn test_multiple_ticks_same_word(mut tick_bitmap: TickBitmap) {
tick_bitmap.flip_tick(50);
tick_bitmap.flip_tick(100);
tick_bitmap.flip_tick(200);
assert!(tick_bitmap.is_initialized(50));
assert!(tick_bitmap.is_initialized(100));
assert!(tick_bitmap.is_initialized(200));
assert!(!tick_bitmap.is_initialized(51));
}
#[rstest]
fn test_multiple_ticks_different_words(mut tick_bitmap: TickBitmap) {
tick_bitmap.flip_tick(100); tick_bitmap.flip_tick(300); tick_bitmap.flip_tick(-100);
assert!(tick_bitmap.is_initialized(100));
assert!(tick_bitmap.is_initialized(300));
assert!(tick_bitmap.is_initialized(-100));
}
#[rstest]
fn test_next_initialized_tick_within_one_word_basic(mut tick_bitmap: TickBitmap) {
tick_bitmap.flip_tick(2); tick_bitmap.flip_tick(3);
let (tick, initialized) = tick_bitmap.next_initialized_tick_within_one_word(1, false);
assert!(initialized);
assert_eq!(tick, 2); }
#[rstest]
fn test_next_initialized_tick_within_one_word_backward(mut tick_bitmap: TickBitmap) {
tick_bitmap.flip_tick(1); tick_bitmap.flip_tick(2);
let (tick, initialized) = tick_bitmap.next_initialized_tick_within_one_word(3, true);
assert!(initialized);
assert_eq!(tick, 2); }
#[rstest]
fn test_next_initialized_tick_within_one_word_no_match(tick_bitmap: TickBitmap) {
let (_, initialized) = tick_bitmap.next_initialized_tick_within_one_word(60, false);
assert!(!initialized);
let (_, initialized) = tick_bitmap.next_initialized_tick_within_one_word(60, true);
assert!(!initialized);
}
#[rstest]
fn test_next_initialized_tick_with_negative_ticks(mut tick_bitmap: TickBitmap) {
tick_bitmap.flip_tick(-2); tick_bitmap.flip_tick(-1);
let (tick, initialized) = tick_bitmap.next_initialized_tick_within_one_word(-3, false);
assert!(initialized);
assert_eq!(tick, -2); }
#[fixture]
fn tick_bitmap_uniswapv3_testing() -> TickBitmap {
let mut tick_bitmap = TickBitmap::new(1);
tick_bitmap.flip_tick(-200);
tick_bitmap.flip_tick(-55);
tick_bitmap.flip_tick(-4);
tick_bitmap.flip_tick(70);
tick_bitmap.flip_tick(78);
tick_bitmap.flip_tick(84);
tick_bitmap.flip_tick(139);
tick_bitmap.flip_tick(240);
tick_bitmap.flip_tick(535);
tick_bitmap
}
#[rstest]
fn test_uniswapv3_test_cases_lte_false(tick_bitmap_uniswapv3_testing: TickBitmap) {
let mut bitmap = tick_bitmap_uniswapv3_testing;
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(78, false);
assert_eq!(next, 84);
assert!(initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(-55, false);
assert_eq!(next, -4);
assert!(initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(77, false);
assert_eq!(next, 78);
assert!(initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(-56, false);
assert_eq!(next, -55);
assert!(initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(255, false);
assert_eq!(next, 511); assert!(!initialized); let (next, initialized) = bitmap.next_initialized_tick_within_one_word(-257, false);
assert_eq!(next, -200);
assert!(initialized);
bitmap.flip_tick(340);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(328, false);
assert_eq!(next, 340);
assert!(initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(508, false);
assert_eq!(next, 511);
assert!(!initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(383, false);
assert_eq!(next, 511);
assert!(!initialized);
}
#[rstest]
fn test_uniswapv3_test_cases_lte_true(tick_bitmap_uniswapv3_testing: TickBitmap) {
let mut bitmap = tick_bitmap_uniswapv3_testing;
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(78, true);
assert_eq!(next, 78);
assert!(initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(79, true);
assert_eq!(next, 78);
assert!(initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(258, true);
assert_eq!(next, 256);
assert!(!initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(256, true);
assert_eq!(next, 256);
assert!(!initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(255, true);
assert_eq!(next, 240);
assert!(initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(72, true);
assert_eq!(next, 70);
assert!(initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(-257, true);
assert_eq!(next, -512);
assert!(!initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(1023, true);
assert_eq!(next, 768);
assert!(!initialized);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(900, true);
assert_eq!(next, 768);
assert!(!initialized);
bitmap.flip_tick(768);
let (next, initialized) = bitmap.next_initialized_tick_within_one_word(900, true);
assert_eq!(next, 768);
assert!(initialized);
}
}