@paraswap/dex-lib 4.8.36 → 4.8.37-native-dex-math.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/dex/aave-gsm/config.js +2 -2
- package/build/dex/idex.d.ts +1 -1
- package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.d.ts +2 -0
- package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.js +5 -0
- package/build/dex/pancakeswap-v3/pancakeswap-v3-pool.js.map +1 -1
- package/build/dex/pancakeswap-v3/pancakeswap-v3.d.ts +6 -3
- package/build/dex/pancakeswap-v3/pancakeswap-v3.js +85 -9
- package/build/dex/pancakeswap-v3/pancakeswap-v3.js.map +1 -1
- package/build/dex/solidly-v3/solidly-v3-pool.d.ts +3 -0
- package/build/dex/solidly-v3/solidly-v3-pool.js +8 -0
- package/build/dex/solidly-v3/solidly-v3-pool.js.map +1 -1
- package/build/dex/solidly-v3/solidly-v3.d.ts +5 -2
- package/build/dex/solidly-v3/solidly-v3.js +84 -8
- package/build/dex/solidly-v3/solidly-v3.js.map +1 -1
- package/build/dex/uniswap-v3/scripts/measure-calc-time.js +222 -110
- package/build/dex/uniswap-v3/scripts/measure-calc-time.js.map +1 -1
- package/build/dex/uniswap-v3/types.d.ts +1 -0
- package/build/dex/uniswap-v3/types.js.map +1 -1
- package/build/dex/uniswap-v3/uniswap-v3-pool.d.ts +2 -0
- package/build/dex/uniswap-v3/uniswap-v3-pool.js +5 -0
- package/build/dex/uniswap-v3/uniswap-v3-pool.js.map +1 -1
- package/build/dex/uniswap-v3/uniswap-v3.d.ts +11 -3
- package/build/dex/uniswap-v3/uniswap-v3.js +86 -9
- package/build/dex/uniswap-v3/uniswap-v3.js.map +1 -1
- package/build/dex/uniswap-v4/types.d.ts +1 -0
- package/build/dex/uniswap-v4/uniswap-v4-pool-manager.d.ts +2 -0
- package/build/dex/uniswap-v4/uniswap-v4-pool-manager.js +3 -0
- package/build/dex/uniswap-v4/uniswap-v4-pool-manager.js.map +1 -1
- package/build/dex/uniswap-v4/uniswap-v4-pool.d.ts +3 -0
- package/build/dex/uniswap-v4/uniswap-v4-pool.js +18 -0
- package/build/dex/uniswap-v4/uniswap-v4-pool.js.map +1 -1
- package/build/dex/uniswap-v4/uniswap-v4.d.ts +3 -1
- package/build/dex/uniswap-v4/uniswap-v4.js +74 -14
- package/build/dex/uniswap-v4/uniswap-v4.js.map +1 -1
- package/build/pricing-helper.d.ts +1 -1
- package/build/pricing-helper.js +2 -2
- package/build/pricing-helper.js.map +1 -1
- package/native/Cargo.lock +331 -0
- package/native/Cargo.toml +22 -0
- package/native/build.rs +5 -0
- package/native/package-lock.json +32 -0
- package/native/package.json +20 -0
- package/native/src/config.rs +73 -0
- package/native/src/lib.rs +528 -0
- package/native/src/math/bit_math.rs +177 -0
- package/native/src/math/full_math.rs +217 -0
- package/native/src/math/liquidity_math.rs +72 -0
- package/native/src/math/mod.rs +10 -0
- package/native/src/math/oracle.rs +493 -0
- package/native/src/math/sqrt_price_math.rs +272 -0
- package/native/src/math/swap_math.rs +306 -0
- package/native/src/math/tick.rs +239 -0
- package/native/src/math/tick_bitmap.rs +312 -0
- package/native/src/math/tick_math.rs +321 -0
- package/native/src/math/unsafe_math.rs +67 -0
- package/native/src/pool_state.rs +41 -0
- package/native/src/query_outputs.rs +379 -0
- package/native/src/v4_query_outputs.rs +255 -0
- package/package.json +2 -1
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
use ethnum::{I256, U256};
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
use super::liquidity_math;
|
|
4
|
+
|
|
5
|
+
/// Information stored for each initialized individual tick.
|
|
6
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
7
|
+
pub struct TickInfo {
|
|
8
|
+
pub liquidity_gross: U256,
|
|
9
|
+
pub liquidity_net: I256,
|
|
10
|
+
pub initialized: bool,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
impl Default for TickInfo {
|
|
14
|
+
fn default() -> Self {
|
|
15
|
+
TickInfo {
|
|
16
|
+
liquidity_gross: U256::ZERO,
|
|
17
|
+
liquidity_net: I256::ZERO,
|
|
18
|
+
initialized: false,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Truncate an I256 to signed 128-bit range, matching `BigInt.asIntN(128, x)`.
|
|
24
|
+
fn as_int128(val: I256) -> I256 {
|
|
25
|
+
// Mask to 128 bits, then sign-extend from bit 127
|
|
26
|
+
let mask: I256 = (I256::ONE << 128) - I256::ONE;
|
|
27
|
+
let masked = val & mask;
|
|
28
|
+
if masked & (I256::ONE << 127) != I256::ZERO {
|
|
29
|
+
masked | !mask
|
|
30
|
+
} else {
|
|
31
|
+
masked
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Updates a tick and returns whether the tick was flipped from initialized to uninitialized,
|
|
36
|
+
/// or vice versa.
|
|
37
|
+
///
|
|
38
|
+
/// Parameters:
|
|
39
|
+
/// - `ticks`: mutable reference to tick storage
|
|
40
|
+
/// - `tick`: the tick to update
|
|
41
|
+
/// - `tick_current`: the current tick
|
|
42
|
+
/// - `liquidity_delta`: signed liquidity change
|
|
43
|
+
/// - `upper`: true if this is the upper tick of a position being modified
|
|
44
|
+
/// - `max_liquidity`: maximum liquidity per tick
|
|
45
|
+
///
|
|
46
|
+
/// Returns `true` if the tick was flipped (transitioned between zero and non-zero gross liquidity).
|
|
47
|
+
pub fn update(
|
|
48
|
+
ticks: &mut HashMap<i32, TickInfo>,
|
|
49
|
+
tick: i32,
|
|
50
|
+
_tick_current: I256,
|
|
51
|
+
liquidity_delta: I256,
|
|
52
|
+
upper: bool,
|
|
53
|
+
max_liquidity: U256,
|
|
54
|
+
) -> bool {
|
|
55
|
+
let info = ticks.entry(tick).or_insert_with(TickInfo::default);
|
|
56
|
+
|
|
57
|
+
let liquidity_gross_before = info.liquidity_gross;
|
|
58
|
+
let liquidity_gross_after = liquidity_math::add_delta(liquidity_gross_before, liquidity_delta);
|
|
59
|
+
|
|
60
|
+
assert!(
|
|
61
|
+
liquidity_gross_after <= max_liquidity,
|
|
62
|
+
"LO"
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
let flipped = (liquidity_gross_after == U256::ZERO) != (liquidity_gross_before == U256::ZERO);
|
|
66
|
+
|
|
67
|
+
if liquidity_gross_before == U256::ZERO {
|
|
68
|
+
info.initialized = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
info.liquidity_gross = liquidity_gross_after;
|
|
72
|
+
|
|
73
|
+
// info.liquidityNet = upper
|
|
74
|
+
// ? BigInt.asIntN(128, BigInt.asIntN(256, info.liquidityNet) - liquidityDelta)
|
|
75
|
+
// : BigInt.asIntN(128, BigInt.asIntN(256, info.liquidityNet) + liquidityDelta)
|
|
76
|
+
let net_i256 = info.liquidity_net; // already I256 (256-bit signed)
|
|
77
|
+
info.liquidity_net = if upper {
|
|
78
|
+
as_int128(net_i256 - liquidity_delta)
|
|
79
|
+
} else {
|
|
80
|
+
as_int128(net_i256 + liquidity_delta)
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
flipped
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Clears tick data. Equivalent to `delete state.ticks[tick]`.
|
|
87
|
+
pub fn clear(ticks: &mut HashMap<i32, TickInfo>, tick: i32) {
|
|
88
|
+
ticks.remove(&tick);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/// Transitions to the next tick as needed by crossing an initialized tick.
|
|
92
|
+
/// Returns the `liquidity_net` of the crossed tick.
|
|
93
|
+
pub fn cross(ticks: &HashMap<i32, TickInfo>, tick: i32) -> I256 {
|
|
94
|
+
let info = ticks.get(&tick).expect("tick not found in cross");
|
|
95
|
+
info.liquidity_net
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#[cfg(test)]
|
|
99
|
+
mod tests {
|
|
100
|
+
use super::*;
|
|
101
|
+
|
|
102
|
+
#[test]
|
|
103
|
+
fn test_update_new_tick() {
|
|
104
|
+
let mut ticks = HashMap::new();
|
|
105
|
+
let flipped = update(
|
|
106
|
+
&mut ticks,
|
|
107
|
+
100,
|
|
108
|
+
I256::from(50i64),
|
|
109
|
+
I256::from(1000i64),
|
|
110
|
+
false,
|
|
111
|
+
U256::from(1_000_000u64),
|
|
112
|
+
);
|
|
113
|
+
assert!(flipped);
|
|
114
|
+
let info = ticks.get(&100).unwrap();
|
|
115
|
+
assert_eq!(info.liquidity_gross, U256::from(1000u64));
|
|
116
|
+
assert_eq!(info.liquidity_net, I256::from(1000i64));
|
|
117
|
+
assert!(info.initialized);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
#[test]
|
|
121
|
+
fn test_update_existing_tick() {
|
|
122
|
+
let mut ticks = HashMap::new();
|
|
123
|
+
update(
|
|
124
|
+
&mut ticks,
|
|
125
|
+
100,
|
|
126
|
+
I256::from(50i64),
|
|
127
|
+
I256::from(1000i64),
|
|
128
|
+
false,
|
|
129
|
+
U256::from(1_000_000u64),
|
|
130
|
+
);
|
|
131
|
+
let flipped = update(
|
|
132
|
+
&mut ticks,
|
|
133
|
+
100,
|
|
134
|
+
I256::from(50i64),
|
|
135
|
+
I256::from(500i64),
|
|
136
|
+
false,
|
|
137
|
+
U256::from(1_000_000u64),
|
|
138
|
+
);
|
|
139
|
+
assert!(!flipped); // Not flipped because it was already initialized
|
|
140
|
+
let info = ticks.get(&100).unwrap();
|
|
141
|
+
assert_eq!(info.liquidity_gross, U256::from(1500u64));
|
|
142
|
+
assert_eq!(info.liquidity_net, I256::from(1500i64));
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
#[test]
|
|
146
|
+
fn test_update_upper_tick() {
|
|
147
|
+
let mut ticks = HashMap::new();
|
|
148
|
+
let flipped = update(
|
|
149
|
+
&mut ticks,
|
|
150
|
+
200,
|
|
151
|
+
I256::from(50i64),
|
|
152
|
+
I256::from(1000i64),
|
|
153
|
+
true,
|
|
154
|
+
U256::from(1_000_000u64),
|
|
155
|
+
);
|
|
156
|
+
assert!(flipped);
|
|
157
|
+
let info = ticks.get(&200).unwrap();
|
|
158
|
+
assert_eq!(info.liquidity_gross, U256::from(1000u64));
|
|
159
|
+
// Upper tick: liquidityNet = -liquidityDelta
|
|
160
|
+
assert_eq!(info.liquidity_net, I256::from(-1000i64));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn test_update_removes_liquidity_flips() {
|
|
165
|
+
let mut ticks = HashMap::new();
|
|
166
|
+
update(
|
|
167
|
+
&mut ticks,
|
|
168
|
+
100,
|
|
169
|
+
I256::from(50i64),
|
|
170
|
+
I256::from(1000i64),
|
|
171
|
+
false,
|
|
172
|
+
U256::from(1_000_000u64),
|
|
173
|
+
);
|
|
174
|
+
let flipped = update(
|
|
175
|
+
&mut ticks,
|
|
176
|
+
100,
|
|
177
|
+
I256::from(50i64),
|
|
178
|
+
I256::from(-1000i64),
|
|
179
|
+
false,
|
|
180
|
+
U256::from(1_000_000u64),
|
|
181
|
+
);
|
|
182
|
+
assert!(flipped);
|
|
183
|
+
let info = ticks.get(&100).unwrap();
|
|
184
|
+
assert_eq!(info.liquidity_gross, U256::ZERO);
|
|
185
|
+
assert_eq!(info.liquidity_net, I256::ZERO);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
#[test]
|
|
189
|
+
#[should_panic(expected = "LO")]
|
|
190
|
+
fn test_update_exceeds_max_liquidity() {
|
|
191
|
+
let mut ticks = HashMap::new();
|
|
192
|
+
update(
|
|
193
|
+
&mut ticks,
|
|
194
|
+
100,
|
|
195
|
+
I256::from(50i64),
|
|
196
|
+
I256::from(2000i64),
|
|
197
|
+
false,
|
|
198
|
+
U256::from(1000u64), // max is 1000
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
#[test]
|
|
203
|
+
fn test_clear() {
|
|
204
|
+
let mut ticks = HashMap::new();
|
|
205
|
+
update(
|
|
206
|
+
&mut ticks,
|
|
207
|
+
100,
|
|
208
|
+
I256::from(50i64),
|
|
209
|
+
I256::from(1000i64),
|
|
210
|
+
false,
|
|
211
|
+
U256::from(1_000_000u64),
|
|
212
|
+
);
|
|
213
|
+
assert!(ticks.contains_key(&100));
|
|
214
|
+
clear(&mut ticks, 100);
|
|
215
|
+
assert!(!ticks.contains_key(&100));
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
#[test]
|
|
219
|
+
fn test_cross() {
|
|
220
|
+
let mut ticks = HashMap::new();
|
|
221
|
+
update(
|
|
222
|
+
&mut ticks,
|
|
223
|
+
100,
|
|
224
|
+
I256::from(50i64),
|
|
225
|
+
I256::from(1000i64),
|
|
226
|
+
false,
|
|
227
|
+
U256::from(1_000_000u64),
|
|
228
|
+
);
|
|
229
|
+
let net = cross(&ticks, 100);
|
|
230
|
+
assert_eq!(net, I256::from(1000i64));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
#[test]
|
|
234
|
+
#[should_panic]
|
|
235
|
+
fn test_cross_nonexistent_tick() {
|
|
236
|
+
let ticks = HashMap::new();
|
|
237
|
+
cross(&ticks, 100);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
use ethnum::{I256, U256};
|
|
2
|
+
use std::collections::HashMap;
|
|
3
|
+
use super::bit_math;
|
|
4
|
+
|
|
5
|
+
/// Computes the word position and bit position within that word for a given tick.
|
|
6
|
+
///
|
|
7
|
+
/// Equivalent to: `[BigInt.asIntN(16, tick >> 8), BigInt.asUintN(8, tick % 256)]`
|
|
8
|
+
///
|
|
9
|
+
/// Returns `(word_pos, bit_pos)` where `word_pos` is an i16 and `bit_pos` is a u8.
|
|
10
|
+
pub fn position(tick: I256) -> (i16, u8) {
|
|
11
|
+
// word_pos = asIntN(16, tick >> 8)
|
|
12
|
+
let shifted = tick >> 8u32;
|
|
13
|
+
// Take the low 16 bits and sign-extend from bit 15
|
|
14
|
+
let word_pos = shifted.0[0] as i16;
|
|
15
|
+
|
|
16
|
+
// bit_pos = asUintN(8, tick % 256)
|
|
17
|
+
// BigInt.asUintN(8, x) takes the low 8 bits of the two's complement representation.
|
|
18
|
+
let bit_pos = tick.0[0] as u8;
|
|
19
|
+
|
|
20
|
+
(word_pos, bit_pos)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// Flips the tick's initialized state in the bitmap.
|
|
24
|
+
///
|
|
25
|
+
/// `tick` must be divisible by `tick_spacing`.
|
|
26
|
+
pub fn flip_tick(bitmap: &mut HashMap<i16, U256>, tick: I256, tick_spacing: I256) {
|
|
27
|
+
assert!(
|
|
28
|
+
tick % tick_spacing == I256::ZERO,
|
|
29
|
+
"tick % tick_spacing == 0"
|
|
30
|
+
);
|
|
31
|
+
let (word_pos, bit_pos) = position(tick / tick_spacing);
|
|
32
|
+
let mask = U256::ONE << bit_pos;
|
|
33
|
+
|
|
34
|
+
let entry = bitmap.entry(word_pos).or_insert(U256::ZERO);
|
|
35
|
+
*entry ^= mask;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Returns the next initialized tick within one word of the current tick.
|
|
39
|
+
///
|
|
40
|
+
/// `lte` indicates whether we're searching to the left (less-than-or-equal) or right.
|
|
41
|
+
/// `is_price_query` controls whether bounds are checked against bitmap range.
|
|
42
|
+
/// `bitmap_range` is (lower, upper) inclusive bounds for valid wordPos values.
|
|
43
|
+
///
|
|
44
|
+
/// Returns `Ok((next_tick, initialized))` or `Err` if out of bitmap range.
|
|
45
|
+
pub fn next_initialized_tick_within_one_word(
|
|
46
|
+
bitmap: &HashMap<i16, U256>,
|
|
47
|
+
tick: I256,
|
|
48
|
+
tick_spacing: I256,
|
|
49
|
+
lte: bool,
|
|
50
|
+
is_price_query: bool,
|
|
51
|
+
bitmap_range: Option<(i16, i16)>,
|
|
52
|
+
) -> Result<(I256, bool), &'static str> {
|
|
53
|
+
let mut compressed = tick / tick_spacing;
|
|
54
|
+
if tick < I256::ZERO && tick % tick_spacing != I256::ZERO {
|
|
55
|
+
compressed = compressed - I256::ONE;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if lte {
|
|
59
|
+
let (word_pos, bit_pos) = position(compressed);
|
|
60
|
+
|
|
61
|
+
// Bounds check — mirrors TS isWordPosOut
|
|
62
|
+
if is_price_query {
|
|
63
|
+
if let Some((lower, upper)) = bitmap_range {
|
|
64
|
+
if word_pos < lower || word_pos > upper {
|
|
65
|
+
return Err("INVALID_TICK_BIT_MAP_RANGES");
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let mask = (U256::ONE << bit_pos) - U256::ONE + (U256::ONE << bit_pos);
|
|
71
|
+
let tick_bitmap_value = bitmap.get(&word_pos).copied().unwrap_or(U256::ZERO);
|
|
72
|
+
let masked = tick_bitmap_value & mask;
|
|
73
|
+
|
|
74
|
+
let initialized = masked != U256::ZERO;
|
|
75
|
+
let next = if initialized {
|
|
76
|
+
let msb = bit_math::most_significant_bit(masked);
|
|
77
|
+
let diff = I256::from(bit_pos as i32) - I256::from(msb as i32);
|
|
78
|
+
let diff_i24 = sign_extend_i24(diff);
|
|
79
|
+
(compressed - diff_i24) * tick_spacing
|
|
80
|
+
} else {
|
|
81
|
+
let bp = I256::from(bit_pos as i32);
|
|
82
|
+
let bp_i24 = sign_extend_i24(bp);
|
|
83
|
+
(compressed - bp_i24) * tick_spacing
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
Ok((next, initialized))
|
|
87
|
+
} else {
|
|
88
|
+
let (word_pos, bit_pos) = position(compressed + I256::ONE);
|
|
89
|
+
|
|
90
|
+
// Bounds check
|
|
91
|
+
if is_price_query {
|
|
92
|
+
if let Some((lower, upper)) = bitmap_range {
|
|
93
|
+
if word_pos < lower || word_pos > upper {
|
|
94
|
+
return Err("INVALID_TICK_BIT_MAP_RANGES");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
let mask = !((U256::ONE << bit_pos) - U256::ONE);
|
|
100
|
+
let tick_bitmap_value = bitmap.get(&word_pos).copied().unwrap_or(U256::ZERO);
|
|
101
|
+
let masked = tick_bitmap_value & mask;
|
|
102
|
+
|
|
103
|
+
let initialized = masked != U256::ZERO;
|
|
104
|
+
let next = if initialized {
|
|
105
|
+
let lsb = bit_math::least_significant_bit(masked);
|
|
106
|
+
let diff = I256::from(lsb as i32) - I256::from(bit_pos as i32);
|
|
107
|
+
let diff_i24 = sign_extend_i24(diff);
|
|
108
|
+
(compressed + I256::ONE + diff_i24) * tick_spacing
|
|
109
|
+
} else {
|
|
110
|
+
let diff = I256::from(255i32) - I256::from(bit_pos as i32);
|
|
111
|
+
let diff_i24 = sign_extend_i24(diff);
|
|
112
|
+
(compressed + I256::ONE + diff_i24) * tick_spacing
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
Ok((next, initialized))
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// Convenience wrapper without bounds checking (for tests and non-pricing calls).
|
|
120
|
+
#[allow(dead_code)]
|
|
121
|
+
fn next_initialized_tick_within_one_word_unchecked(
|
|
122
|
+
bitmap: &HashMap<i16, U256>,
|
|
123
|
+
tick: I256,
|
|
124
|
+
tick_spacing: I256,
|
|
125
|
+
lte: bool,
|
|
126
|
+
is_price_query: bool,
|
|
127
|
+
) -> (I256, bool) {
|
|
128
|
+
next_initialized_tick_within_one_word(bitmap, tick, tick_spacing, lte, is_price_query, None).unwrap()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// Sign-extend a value to 24-bit signed (equivalent to BigInt.asIntN(24, x)).
|
|
132
|
+
fn sign_extend_i24(val: I256) -> I256 {
|
|
133
|
+
let mask = I256::new(0x00FFFFFF);
|
|
134
|
+
let masked = val & mask;
|
|
135
|
+
if masked & I256::new(0x00800000) != I256::ZERO {
|
|
136
|
+
masked | !mask
|
|
137
|
+
} else {
|
|
138
|
+
masked
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
#[cfg(test)]
|
|
143
|
+
mod tests {
|
|
144
|
+
use super::*;
|
|
145
|
+
|
|
146
|
+
#[test]
|
|
147
|
+
fn test_position_zero() {
|
|
148
|
+
let (word, bit) = position(I256::ZERO);
|
|
149
|
+
assert_eq!(word, 0);
|
|
150
|
+
assert_eq!(bit, 0);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#[test]
|
|
154
|
+
fn test_position_positive() {
|
|
155
|
+
// tick = 256 => word_pos = 256 >> 8 = 1, bit_pos = 256 % 256 = 0
|
|
156
|
+
let (word, bit) = position(I256::from(256i64));
|
|
157
|
+
assert_eq!(word, 1);
|
|
158
|
+
assert_eq!(bit, 0);
|
|
159
|
+
|
|
160
|
+
// tick = 257 => word_pos = 257 >> 8 = 1, bit_pos = 257 % 256 = 1
|
|
161
|
+
let (word, bit) = position(I256::from(257i64));
|
|
162
|
+
assert_eq!(word, 1);
|
|
163
|
+
assert_eq!(bit, 1);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#[test]
|
|
167
|
+
fn test_position_negative() {
|
|
168
|
+
// tick = -1 => in two's complement, tick >> 8 = -1 => word_pos = -1
|
|
169
|
+
// bit_pos = low 8 bits of -1 = 0xFF = 255
|
|
170
|
+
let (word, bit) = position(I256::from(-1i64));
|
|
171
|
+
assert_eq!(word, -1);
|
|
172
|
+
assert_eq!(bit, 255);
|
|
173
|
+
|
|
174
|
+
// tick = -256 => tick >> 8 = -1, bit_pos = low 8 bits of -256 = 0
|
|
175
|
+
let (word, bit) = position(I256::from(-256i64));
|
|
176
|
+
assert_eq!(word, -1);
|
|
177
|
+
assert_eq!(bit, 0);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
#[test]
|
|
181
|
+
fn test_flip_tick() {
|
|
182
|
+
let mut bitmap = HashMap::new();
|
|
183
|
+
let tick_spacing = I256::ONE;
|
|
184
|
+
|
|
185
|
+
// Flip tick 0
|
|
186
|
+
flip_tick(&mut bitmap, I256::ZERO, tick_spacing);
|
|
187
|
+
let (word, bit) = position(I256::ZERO);
|
|
188
|
+
assert_eq!(*bitmap.get(&word).unwrap() & (U256::ONE << bit), U256::ONE);
|
|
189
|
+
|
|
190
|
+
// Flip tick 0 again (should toggle back to 0)
|
|
191
|
+
flip_tick(&mut bitmap, I256::ZERO, tick_spacing);
|
|
192
|
+
assert_eq!(
|
|
193
|
+
*bitmap.get(&word).unwrap() & (U256::ONE << bit),
|
|
194
|
+
U256::ZERO
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
#[test]
|
|
199
|
+
fn test_flip_tick_with_spacing() {
|
|
200
|
+
let mut bitmap = HashMap::new();
|
|
201
|
+
let tick_spacing = I256::from(60i64);
|
|
202
|
+
|
|
203
|
+
flip_tick(&mut bitmap, I256::from(120i64), tick_spacing);
|
|
204
|
+
// 120 / 60 = 2, position(2) = (0, 2)
|
|
205
|
+
let val = *bitmap.get(&0i16).unwrap_or(&U256::ZERO);
|
|
206
|
+
assert_eq!(val & (U256::ONE << 2), U256::from(4u64));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#[test]
|
|
210
|
+
#[should_panic]
|
|
211
|
+
fn test_flip_tick_not_aligned() {
|
|
212
|
+
let mut bitmap = HashMap::new();
|
|
213
|
+
flip_tick(&mut bitmap, I256::from(1i64), I256::from(60i64));
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#[test]
|
|
217
|
+
fn test_next_initialized_tick_lte() {
|
|
218
|
+
let mut bitmap = HashMap::new();
|
|
219
|
+
let tick_spacing = I256::ONE;
|
|
220
|
+
|
|
221
|
+
// Set tick 10 as initialized
|
|
222
|
+
flip_tick(&mut bitmap, I256::from(10i64), tick_spacing);
|
|
223
|
+
|
|
224
|
+
// Search from tick 15, going left (lte=true)
|
|
225
|
+
let (next, initialized) = next_initialized_tick_within_one_word_unchecked(
|
|
226
|
+
&bitmap,
|
|
227
|
+
I256::from(15i64),
|
|
228
|
+
tick_spacing,
|
|
229
|
+
true,
|
|
230
|
+
false,
|
|
231
|
+
);
|
|
232
|
+
assert!(initialized);
|
|
233
|
+
assert_eq!(next, I256::from(10i64));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
#[test]
|
|
237
|
+
fn test_next_initialized_tick_gt() {
|
|
238
|
+
let mut bitmap = HashMap::new();
|
|
239
|
+
let tick_spacing = I256::ONE;
|
|
240
|
+
|
|
241
|
+
// Set tick 20 as initialized
|
|
242
|
+
flip_tick(&mut bitmap, I256::from(20i64), tick_spacing);
|
|
243
|
+
|
|
244
|
+
// Search from tick 10, going right (lte=false)
|
|
245
|
+
let (next, initialized) = next_initialized_tick_within_one_word_unchecked(
|
|
246
|
+
&bitmap,
|
|
247
|
+
I256::from(10i64),
|
|
248
|
+
tick_spacing,
|
|
249
|
+
false,
|
|
250
|
+
false,
|
|
251
|
+
);
|
|
252
|
+
assert!(initialized);
|
|
253
|
+
assert_eq!(next, I256::from(20i64));
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
#[test]
|
|
257
|
+
fn test_next_initialized_tick_not_found_lte() {
|
|
258
|
+
let bitmap = HashMap::new();
|
|
259
|
+
let tick_spacing = I256::ONE;
|
|
260
|
+
|
|
261
|
+
// No ticks initialized; searching left from tick 100
|
|
262
|
+
let (next, initialized) = next_initialized_tick_within_one_word_unchecked(
|
|
263
|
+
&bitmap,
|
|
264
|
+
I256::from(100i64),
|
|
265
|
+
tick_spacing,
|
|
266
|
+
true,
|
|
267
|
+
false,
|
|
268
|
+
);
|
|
269
|
+
assert!(!initialized);
|
|
270
|
+
// Should return the leftmost tick in this word
|
|
271
|
+
// compressed = 100, position(100) = (0, 100), next = (100 - 100) * 1 = 0
|
|
272
|
+
assert_eq!(next, I256::ZERO);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
#[test]
|
|
276
|
+
fn test_next_initialized_tick_not_found_gt() {
|
|
277
|
+
let bitmap = HashMap::new();
|
|
278
|
+
let tick_spacing = I256::ONE;
|
|
279
|
+
|
|
280
|
+
// No ticks initialized; searching right from tick 0
|
|
281
|
+
let (next, initialized) = next_initialized_tick_within_one_word_unchecked(
|
|
282
|
+
&bitmap,
|
|
283
|
+
I256::ZERO,
|
|
284
|
+
tick_spacing,
|
|
285
|
+
false,
|
|
286
|
+
false,
|
|
287
|
+
);
|
|
288
|
+
assert!(!initialized);
|
|
289
|
+
// compressed = 0, position(1) = (0, 1), next = (0 + 1 + (255 - 1)) * 1 = 255
|
|
290
|
+
assert_eq!(next, I256::from(255i64));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
#[test]
|
|
294
|
+
fn test_next_initialized_tick_negative_range() {
|
|
295
|
+
let mut bitmap = HashMap::new();
|
|
296
|
+
let tick_spacing = I256::ONE;
|
|
297
|
+
|
|
298
|
+
// Set tick -10 as initialized
|
|
299
|
+
flip_tick(&mut bitmap, I256::from(-10i64), tick_spacing);
|
|
300
|
+
|
|
301
|
+
// Search from tick -5, going left
|
|
302
|
+
let (next, initialized) = next_initialized_tick_within_one_word_unchecked(
|
|
303
|
+
&bitmap,
|
|
304
|
+
I256::from(-5i64),
|
|
305
|
+
tick_spacing,
|
|
306
|
+
true,
|
|
307
|
+
false,
|
|
308
|
+
);
|
|
309
|
+
assert!(initialized);
|
|
310
|
+
assert_eq!(next, I256::from(-10i64));
|
|
311
|
+
}
|
|
312
|
+
}
|