Skip to content

Commit

Permalink
feat: add TickBitMap and TickBitMapProvider (#96)
Browse files Browse the repository at this point in the history
* feat: add `TickBitMap` and `TickBitMapProvider`

Introduce `TickBitMap` and `TickBitMapProvider` for improved tick data management. Updated `TickMap` to utilize these abstractions, simplifying code. Also, upgraded dependencies to latest versions.

* Update `get_word` function in `tick_bit_map.rs`

Changed the return value handling of `get_word` to dereference the reference directly, improving readability and potentially slightly optimizing performance. This change ensures consistency with Rust's idiomatic handling of references.
  • Loading branch information
shuhuiluo authored Oct 20, 2024
1 parent a90ad4b commit d68ef7e
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 41 deletions.
8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ exclude = [".github", ".gitignore", "rustfmt.toml"]
all-features = true

[dependencies]
alloy = { version = "0.4", optional = true, features = ["contract"] }
alloy = { version = "0.5", optional = true, features = ["contract"] }
alloy-primitives = "0.8"
alloy-sol-types = "0.8"
anyhow = { version = "1.0", optional = true }
Expand All @@ -29,7 +29,7 @@ regex = { version = "1.11", optional = true }
rustc-hash = "2.0"
serde_json = { version = "1.0", optional = true }
thiserror = { version = "1.0", optional = true }
uniswap-lens = { version = "0.4", optional = true }
uniswap-lens = { version = "0.5", optional = true }
uniswap-sdk-core = "3.0.0"

[features]
Expand All @@ -38,8 +38,8 @@ extensions = ["uniswap-lens/std", "alloy", "anyhow", "base64", "regex", "serde_j
std = ["thiserror", "uniswap-sdk-core/std"]

[dev-dependencies]
alloy-signer = "0.4"
alloy-signer-local = "0.4"
alloy-signer = "0.5"
alloy-signer-local = "0.5"
criterion = "0.5.1"
dotenv = "0.15.0"
tokio = { version = "1.40", features = ["full"] }
Expand Down
2 changes: 2 additions & 0 deletions src/extensions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ mod ephemeral_tick_map_data_provider;
mod pool;
mod position;
mod price_tick_conversions;
mod tick_bit_map;
mod tick_map;

pub use ephemeral_tick_data_provider::EphemeralTickDataProvider;
pub use ephemeral_tick_map_data_provider::EphemeralTickMapDataProvider;
pub use pool::*;
pub use position::*;
pub use price_tick_conversions::*;
pub use tick_bit_map::*;
pub use tick_map::*;
75 changes: 75 additions & 0 deletions src/extensions/tick_bit_map.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! ## Tick Bit Map
//! The [`TickBitMapProvider`] trait provides
//! [`TickBitMapProvider::next_initialized_tick_within_one_word`] for a tick bit map that implements
//! [`TickBitMapProvider::get_word`].

use crate::prelude::*;
use alloy::uint;
use alloy_primitives::{aliases::I24, U256};
use rustc_hash::FxHashMap;

pub type TickBitMap<I = I24> = FxHashMap<I, U256>;

/// Provides [`Self::next_initialized_tick_within_one_word`] for a tick bit map that implements
/// [`Self::get_word`]
pub trait TickBitMapProvider {
type Index: TickIndex;

/// Get a bit map word at a specific index
fn get_word(&self, index: Self::Index) -> U256;

#[inline]
fn next_initialized_tick_within_one_word(
&self,
tick: Self::Index,
lte: bool,
tick_spacing: Self::Index,
) -> Result<(Self::Index, bool), Error> {
let compressed = tick.compress(tick_spacing);
if lte {
let (word_pos, bit_pos) = compressed.position();
// all the 1s at or to the right of the current `bit_pos`
// (2 << bitPos) may overflow but fine since 2 << 255 = 0
let mask = (TWO << bit_pos) - uint!(1_U256);
let word = self.get_word(word_pos);
let masked = word & mask;
let initialized = masked != U256::ZERO;
let msb = if initialized {
masked.most_significant_bit() as u8 as i32
} else {
0
}
.try_into()
.unwrap();
let next = ((word_pos << 8) + msb) * tick_spacing;
Ok((next, initialized))
} else {
// start from the word of the next tick, since the current tick state doesn't matter
let compressed = compressed + Self::Index::ONE;
let (word_pos, bit_pos) = compressed.position();
// all the 1s at or to the left of the `bit_pos`
let mask = U256::ZERO - (uint!(1_U256) << bit_pos);
let word = self.get_word(word_pos);
let masked = word & mask;
let initialized = masked != U256::ZERO;
let lsb = if initialized {
masked.least_significant_bit() as u8 as i32
} else {
255
}
.try_into()
.unwrap();
let next = ((word_pos << 8) + lsb) * tick_spacing;
Ok((next, initialized))
}
}
}

impl<I: TickIndex> TickBitMapProvider for TickBitMap<I> {
type Index = I;

#[inline]
fn get_word(&self, index: Self::Index) -> U256 {
*self.get(&index).unwrap_or(&U256::ZERO)
}
}
41 changes: 4 additions & 37 deletions src/extensions/tick_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use rustc_hash::FxHashMap;

#[derive(Clone, Debug)]
pub struct TickMap<I = I24> {
pub bitmap: FxHashMap<I, U256>,
pub bitmap: TickBitMap<I>,
pub inner: FxHashMap<I, Tick<I>>,
pub tick_spacing: I,
}
Expand All @@ -19,7 +19,7 @@ impl<I: TickIndex> TickMap<I> {
#[must_use]
pub fn new(ticks: Vec<Tick<I>>, tick_spacing: I) -> Self {
ticks.validate_list(tick_spacing);
let mut bitmap = FxHashMap::<I, U256>::default();
let mut bitmap = TickBitMap::default();
for tick in &ticks {
let compressed = tick.index.compress(tick_spacing);
let (word_pos, bit_pos) = compressed.position();
Expand Down Expand Up @@ -51,40 +51,7 @@ impl<I: TickIndex> TickDataProvider for TickMap<I> {
lte: bool,
tick_spacing: Self::Index,
) -> Result<(Self::Index, bool), Error> {
let compressed = tick.compress(tick_spacing);
if lte {
let (word_pos, bit_pos) = compressed.position();
// all the 1s at or to the right of the current `bit_pos`
// (2 << bitPos) may overflow but fine since 2 << 255 = 0
let mask = (TWO << bit_pos) - uint!(1_U256);
let word = self.bitmap.get(&word_pos).unwrap_or(&U256::ZERO);
let masked = word & mask;
let initialized = masked != U256::ZERO;
let bit_pos = if initialized {
let msb = masked.most_significant_bit() as u8;
(bit_pos - msb) as i32
} else {
bit_pos as i32
};
let next = (compressed - Self::Index::try_from(bit_pos).unwrap()) * tick_spacing;
Ok((next, initialized))
} else {
// start from the word of the next tick, since the current tick state doesn't matter
let compressed = compressed + Self::Index::ONE;
let (word_pos, bit_pos) = compressed.position();
// all the 1s at or to the left of the `bit_pos`
let mask = U256::ZERO - (uint!(1_U256) << bit_pos);
let word = self.bitmap.get(&word_pos).unwrap_or(&U256::ZERO);
let masked = word & mask;
let initialized = masked != U256::ZERO;
let bit_pos = if initialized {
let lsb = masked.least_significant_bit() as u8;
(lsb - bit_pos) as i32
} else {
(255 - bit_pos) as i32
};
let next = (compressed + Self::Index::try_from(bit_pos).unwrap()) * tick_spacing;
Ok((next, initialized))
}
self.bitmap
.next_initialized_tick_within_one_word(tick, lte, tick_spacing)
}
}

0 comments on commit d68ef7e

Please sign in to comment.