use ahash::AHashMap;
use alloy_primitives::U256;
use crate::defi::tick_map::{
liquidity_math::tick_spacing_to_max_liquidity_per_tick, tick::PoolTick, tick_bitmap::TickBitmap,
};
pub mod bit_math;
pub mod full_math;
pub mod liquidity_math;
pub mod sqrt_price_math;
pub mod tick;
pub mod tick_bitmap;
pub mod tick_math;
#[derive(Debug, Clone)]
pub struct TickMap {
ticks: AHashMap<i32, PoolTick>,
tick_bitmap: TickBitmap,
pub liquidity: u128,
pub max_liquidity_per_tick: u128,
}
impl Default for TickMap {
fn default() -> Self {
Self::new(1)
}
}
impl TickMap {
pub fn new(tick_spacing: u32) -> Self {
assert!(tick_spacing > 0, "Tick spacing must be greater than zero");
Self {
ticks: AHashMap::new(),
tick_bitmap: TickBitmap::new(tick_spacing),
liquidity: 0,
max_liquidity_per_tick: tick_spacing_to_max_liquidity_per_tick(tick_spacing as i32),
}
}
pub fn get_tick(&self, tick: i32) -> Option<&PoolTick> {
self.ticks.get(&tick)
}
pub fn get_tick_or_init(&mut self, tick: i32) -> &mut PoolTick {
self.ticks
.entry(tick)
.or_insert_with(|| PoolTick::from_tick(tick))
}
pub fn get_fee_growth_inside(
&mut self,
lower_tick: i32,
upper_tick: i32,
current_tick: i32,
fee_growth_global_0: U256,
fee_growth_global_1: U256,
) -> (U256, U256) {
self.ticks
.entry(lower_tick)
.or_insert_with(|| PoolTick::from_tick(lower_tick));
self.ticks
.entry(upper_tick)
.or_insert_with(|| PoolTick::from_tick(upper_tick));
let lower_tick = &self.ticks[&lower_tick];
let upper_tick = &self.ticks[&upper_tick];
let fee_growth_below_0 = if current_tick >= lower_tick.value {
lower_tick.fee_growth_outside_0
} else {
fee_growth_global_0 - lower_tick.fee_growth_outside_0
};
let fee_growth_below_1 = if current_tick >= lower_tick.value {
lower_tick.fee_growth_outside_1
} else {
fee_growth_global_1 - lower_tick.fee_growth_outside_1
};
let fee_growth_above_0 = if current_tick < upper_tick.value {
upper_tick.fee_growth_outside_0
} else {
fee_growth_global_0 - upper_tick.fee_growth_outside_0
};
let fee_growth_above_1 = if current_tick < upper_tick.value {
upper_tick.fee_growth_outside_1
} else {
fee_growth_global_1 - upper_tick.fee_growth_outside_1
};
let fee_growth_inside_0 = fee_growth_global_0 - fee_growth_below_0 - fee_growth_above_0;
let fee_growth_inside_1 = fee_growth_global_1 - fee_growth_below_1 - fee_growth_above_1;
(fee_growth_inside_0, fee_growth_inside_1)
}
fn update_tick_data(
&mut self,
tick: i32,
tick_current: i32,
liquidity_delta: i128,
upper: bool,
fee_growth_global_0: U256,
fee_growth_global_1: U256,
) -> bool {
let max_liquidity_per_tick = self.max_liquidity_per_tick;
let tick = self.get_tick_or_init(tick);
let liquidity_gross_before = tick.update_liquidity(liquidity_delta, upper);
let liquidity_gross_after = tick.liquidity_gross;
assert!(
liquidity_gross_after <= max_liquidity_per_tick,
"Liquidity exceeds maximum per tick"
);
if liquidity_gross_before == 0 {
if tick.value <= tick_current {
tick.fee_growth_outside_0 = fee_growth_global_0;
tick.fee_growth_outside_1 = fee_growth_global_1;
}
tick.initialized = true;
}
(liquidity_gross_after == 0) != (liquidity_gross_before == 0)
}
pub fn update(
&mut self,
tick: i32,
tick_current: i32,
liquidity_delta: i128,
upper: bool,
fee_growth_global_0: U256,
fee_growth_global_1: U256,
) -> bool {
let flipped = self.update_tick_data(
tick,
tick_current,
liquidity_delta,
upper,
fee_growth_global_0,
fee_growth_global_1,
);
if flipped {
self.tick_bitmap.flip_tick(tick);
}
flipped
}
pub fn cross_tick(
&mut self,
tick: i32,
fee_growth_global_0: U256,
fee_growth_global_1: U256,
) -> i128 {
let tick = self.get_tick_or_init(tick);
tick.update_fee_growth(fee_growth_global_0, fee_growth_global_1);
tick.liquidity_net
}
#[must_use]
pub fn active_tick_count(&self) -> usize {
self.ticks
.iter()
.filter(|(_, tick)| self.is_tick_initialized(tick.value))
.count()
}
#[must_use]
pub fn total_tick_count(&self) -> usize {
self.ticks.len()
}
#[must_use]
pub fn get_all_ticks(&self) -> &AHashMap<i32, PoolTick> {
&self.ticks
}
pub fn set_tick(&mut self, tick_data: PoolTick) {
let tick = tick_data.value;
self.ticks.insert(tick, tick_data);
}
pub fn restore_tick(&mut self, tick_data: PoolTick) {
let is_initialized = tick_data.initialized;
let tick_value = tick_data.value;
self.set_tick(tick_data);
if is_initialized {
self.tick_bitmap.flip_tick(tick_value);
}
}
pub fn clear(&mut self, tick: i32) {
self.ticks.remove(&tick);
}
pub fn next_initialized_tick(&self, tick: i32, lte: bool) -> (i32, bool) {
self.tick_bitmap
.next_initialized_tick_within_one_word(tick, lte)
}
pub fn is_tick_initialized(&self, tick: i32) -> bool {
self.tick_bitmap.is_initialized(tick)
}
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use rstest::{fixture, rstest};
use super::*;
#[fixture]
fn tick_map() -> TickMap {
TickMap::new(1)
}
#[rstest]
fn test_new_tick_maps(tick_map: TickMap) {
assert_eq!(tick_map.active_tick_count(), 0);
assert_eq!(tick_map.liquidity, 0);
}
#[rstest]
fn test_get_fee_growth_inside_uninitialized_ticks(mut tick_map: TickMap) {
let fee_growth_global_0 = U256::from(15);
let fee_growth_global_1 = U256::from(15);
let (fee_growth_inside_0, fee_growth_inside_1) =
tick_map.get_fee_growth_inside(-2, 2, 0, fee_growth_global_0, fee_growth_global_1);
assert_eq!(fee_growth_inside_0, U256::from_str("15").unwrap());
assert_eq!(fee_growth_inside_1, U256::from_str("15").unwrap());
let (fee_growth_inside_0, fee_growth_inside_1) =
tick_map.get_fee_growth_inside(-2, 2, 4, fee_growth_global_0, fee_growth_global_1);
assert_eq!(fee_growth_inside_0, U256::ZERO);
assert_eq!(fee_growth_inside_1, U256::ZERO);
let (fee_growth_inside_0, fee_growth_inside_1) =
tick_map.get_fee_growth_inside(-2, 2, -4, fee_growth_global_0, fee_growth_global_1);
assert_eq!(fee_growth_inside_0, U256::ZERO);
assert_eq!(fee_growth_inside_1, U256::ZERO);
}
#[rstest]
fn test_get_fee_growth_inside_if_upper_tick_is_below(mut tick_map: TickMap) {
tick_map.set_tick(PoolTick::new(
2, 0,
0,
U256::from(2),
U256::from(3),
true,
0,
));
let fee_growth_global_0 = U256::from(15);
let fee_growth_global_1 = U256::from(15);
let (fee_growth_inside_0, fee_growth_inside_1) =
tick_map.get_fee_growth_inside(-2, 2, 0, fee_growth_global_0, fee_growth_global_1);
assert_eq!(fee_growth_inside_0, U256::from(13));
assert_eq!(fee_growth_inside_1, U256::from(12));
}
#[rstest]
fn test_get_fee_growth_inside_if_lower_tick_is_above(mut tick_map: TickMap) {
tick_map.set_tick(PoolTick::new(
-2, 0,
0,
U256::from(2),
U256::from(3),
true,
0,
));
let fee_growth_global_0 = U256::from(15);
let fee_growth_global_1 = U256::from(15);
let (fee_growth_inside_0, fee_growth_inside_1) =
tick_map.get_fee_growth_inside(-2, 2, 0, fee_growth_global_0, fee_growth_global_1);
assert_eq!(fee_growth_inside_0, U256::from(13));
assert_eq!(fee_growth_inside_1, U256::from(12));
}
#[rstest]
fn test_get_fee_growth_inside_if_upper_and_lower_tick_are_initialized(mut tick_map: TickMap) {
tick_map.set_tick(PoolTick::new(
-2, 0,
0,
U256::from(2),
U256::from(3),
true,
0,
));
tick_map.set_tick(PoolTick::new(
2, 0,
0,
U256::from(4),
U256::from(1),
true,
0,
));
let fee_growth_global_0 = U256::from(15);
let fee_growth_global_1 = U256::from(15);
let (fee_growth_inside_0, fee_growth_inside_1) =
tick_map.get_fee_growth_inside(-2, 2, 0, fee_growth_global_0, fee_growth_global_1);
assert_eq!(fee_growth_inside_0, U256::from(9));
assert_eq!(fee_growth_inside_1, U256::from(11));
}
#[rstest]
fn test_get_fee_growth_inside_with_overflow(mut tick_map: TickMap) {
tick_map.set_tick(PoolTick::new(
-2,
0,
0,
U256::MAX - U256::from(3u32), U256::MAX - U256::from(2u32), true,
0,
));
tick_map.set_tick(PoolTick::new(
2,
0,
0,
U256::from(3u32),
U256::from(5u32),
true,
0,
));
let fee_growth_global_0 = U256::from(15);
let fee_growth_global_1 = U256::from(15);
let (fee_growth_inside_0, fee_growth_inside_1) =
tick_map.get_fee_growth_inside(-2, 2, 0, fee_growth_global_0, fee_growth_global_1);
assert_eq!(fee_growth_inside_0, U256::from(16u32));
assert_eq!(fee_growth_inside_1, U256::from(13u32));
}
#[rstest]
fn test_update_flips_from_zero_to_nonzero(mut tick_map: TickMap) {
assert!(!tick_map.is_tick_initialized(0));
let flipped = tick_map.update(0, 0, 1, false, U256::ZERO, U256::ZERO);
assert!(flipped);
assert!(tick_map.is_tick_initialized(0));
}
#[rstest]
fn test_update_does_not_flip_from_nonzero_to_greater_nonzero(mut tick_map: TickMap) {
tick_map.update(0, 0, 1, false, U256::ZERO, U256::ZERO);
assert!(tick_map.is_tick_initialized(0));
let flipped = tick_map.update(0, 0, 1, false, U256::ZERO, U256::ZERO);
assert!(!flipped);
assert!(tick_map.is_tick_initialized(0));
}
#[rstest]
fn test_update_flips_from_nonzero_to_zero(mut tick_map: TickMap) {
let flipped_first = tick_map.update(0, 0, 1, false, U256::ZERO, U256::ZERO);
assert!(flipped_first);
assert!(tick_map.is_tick_initialized(0));
let flipped_second = tick_map.update(0, 0, -1, false, U256::ZERO, U256::ZERO);
assert!(flipped_second);
assert!(!tick_map.is_tick_initialized(0));
}
#[rstest]
fn test_update_does_not_flip_from_nonzero_to_lesser_nonzero(mut tick_map: TickMap) {
tick_map.update(0, 0, 2, false, U256::ZERO, U256::ZERO);
assert!(tick_map.is_tick_initialized(0));
let flipped = tick_map.update(0, 0, -1, false, U256::ZERO, U256::ZERO);
assert!(!flipped);
assert!(tick_map.is_tick_initialized(0));
}
#[rstest]
#[should_panic(expected = "Liquidity exceeds maximum per tick")]
fn test_update_reverts_if_total_liquidity_gross_exceeds_max() {
let mut tick_map = TickMap::new(200);
let max_liquidity = tick_map.max_liquidity_per_tick;
tick_map.update(
0,
0,
(max_liquidity / 2) as i128,
false,
U256::ZERO,
U256::ZERO,
);
tick_map.update(
0,
0,
(max_liquidity / 2) as i128,
true,
U256::ZERO,
U256::ZERO,
);
tick_map.update(0, 0, 1, false, U256::ZERO, U256::ZERO);
}
#[rstest]
fn test_update_nets_liquidity_based_on_upper_flag(mut tick_map: TickMap) {
tick_map.update(0, 0, 2, false, U256::ZERO, U256::ZERO);
tick_map.update(0, 0, 1, true, U256::ZERO, U256::ZERO);
tick_map.update(0, 0, 3, true, U256::ZERO, U256::ZERO);
tick_map.update(0, 0, 1, false, U256::ZERO, U256::ZERO);
let tick = tick_map.get_tick(0).unwrap();
assert_eq!(tick.liquidity_gross, 7);
assert_eq!(tick.liquidity_net, -1);
}
#[rstest]
fn test_update_assumes_all_growth_happens_below_ticks_lte_current_tick() {
let mut tick_map = TickMap::new(1);
let fee_growth_global_0 = U256::from(15);
let fee_growth_global_1 = U256::from(2);
tick_map.update(1, 1, 1, false, fee_growth_global_0, fee_growth_global_1);
let tick = tick_map.get_tick(1).unwrap();
assert_eq!(tick.fee_growth_outside_0, U256::from(15u32));
assert_eq!(tick.fee_growth_outside_1, U256::from(2u32));
assert!(tick.initialized);
assert_eq!(tick.liquidity_gross, 1);
assert_eq!(tick.liquidity_net, 1);
}
#[rstest]
fn test_update_does_not_set_growth_fields_if_tick_already_initialized() {
let mut tick_map = TickMap::new(1);
let fee_growth_0_initial = U256::from(1);
let fee_growth_1_initial = U256::from(2);
tick_map.update(1, 1, 1, false, fee_growth_0_initial, fee_growth_1_initial);
let fee_growth_0_second = U256::from(6);
let fee_growth_1_second = U256::from(7);
tick_map.update(1, 1, 1, false, fee_growth_0_second, fee_growth_1_second);
let tick = tick_map.get_tick(1).unwrap();
assert_eq!(tick.fee_growth_outside_0, U256::from(1u32)); assert_eq!(tick.fee_growth_outside_1, U256::from(2u32)); assert!(tick.initialized);
assert_eq!(tick.liquidity_gross, 2); assert_eq!(tick.liquidity_net, 2); }
#[rstest]
fn test_update_does_not_set_growth_fields_for_ticks_gt_current_tick() {
let mut tick_map = TickMap::new(1);
let fee_growth_global_0 = U256::from(1);
let fee_growth_global_1 = U256::from(2u32);
tick_map.update(2, 1, 1, false, fee_growth_global_0, fee_growth_global_1);
let tick = tick_map.get_tick(2).unwrap();
assert_eq!(tick.fee_growth_outside_0, U256::ZERO); assert_eq!(tick.fee_growth_outside_1, U256::ZERO); assert!(tick.initialized);
assert_eq!(tick.liquidity_gross, 1);
assert_eq!(tick.liquidity_net, 1);
}
#[rstest]
fn test_clear_deletes_all_data_in_tick(mut tick_map: TickMap) {
tick_map.set_tick(PoolTick::new(
2,
3,
4,
U256::from(1u32),
U256::from(2u32),
true,
0,
));
let tick_before = tick_map.get_tick(2).unwrap();
assert_eq!(tick_before.fee_growth_outside_0, U256::from(1u32));
assert_eq!(tick_before.fee_growth_outside_1, U256::from(2u32));
assert_eq!(tick_before.liquidity_gross, 3);
assert_eq!(tick_before.liquidity_net, 4);
assert!(tick_before.initialized);
tick_map.clear(2);
assert_eq!(tick_map.get_tick(2), None);
}
#[rstest]
fn test_cross_tick_flips_growth_variables(mut tick_map: TickMap) {
tick_map.set_tick(PoolTick::new(
2,
3,
4,
U256::from(1u32),
U256::from(2u32),
true,
7,
));
let liquidity_net = tick_map.cross_tick(
2,
U256::from(7u32), U256::from(9u32), );
let tick = tick_map.get_tick(2).unwrap();
assert_eq!(tick.fee_growth_outside_0, U256::from(6u32)); assert_eq!(tick.fee_growth_outside_1, U256::from(7u32));
assert_eq!(liquidity_net, 4);
assert_eq!(tick.liquidity_gross, 3);
assert_eq!(tick.liquidity_net, 4);
assert!(tick.initialized);
}
}