use ethnum::U256;
use rust_decimal::{prelude::*, Decimal};
use crate::AmmMathError;
pub const MIN_TICK: i32 = -443636;
pub const MAX_TICK: i32 = 443636;
pub const MIN_SQRT_PRICE: u128 = 4295048016;
pub const MAX_SQRT_PRICE: u128 = 79226673515401279992447579055;
fn mul_shift_96(a: u128, b: u128) -> u128 {
let product: U256 = U256::from(a) * U256::from(b);
let shifted: U256 = product >> 96;
shifted.as_u128()
}
pub fn tick_to_sqrt_price_x64(tick: i32) -> Result<u128, AmmMathError> {
if !(MIN_TICK..=MAX_TICK).contains(&tick) {
return Err(AmmMathError::TickOutOfRange(tick));
}
if tick >= 0 {
Ok(sqrt_price_positive_tick(tick))
} else {
Ok(sqrt_price_negative_tick(tick))
}
}
fn sqrt_price_positive_tick(tick: i32) -> u128 {
let mut ratio: u128 = if tick & 1 != 0 {
79232123823359799118286999567
} else {
79228162514264337593543950336 };
if tick & 2 != 0 {
ratio = mul_shift_96(ratio, 79236085330515764027303304731);
}
if tick & 4 != 0 {
ratio = mul_shift_96(ratio, 79244008939048815603706035061);
}
if tick & 8 != 0 {
ratio = mul_shift_96(ratio, 79259858533276714757314932305);
}
if tick & 16 != 0 {
ratio = mul_shift_96(ratio, 79291567232598584799939703904);
}
if tick & 32 != 0 {
ratio = mul_shift_96(ratio, 79355022692464371645785046466);
}
if tick & 64 != 0 {
ratio = mul_shift_96(ratio, 79482085999252804386437311141);
}
if tick & 128 != 0 {
ratio = mul_shift_96(ratio, 79736823300114093921829183326);
}
if tick & 256 != 0 {
ratio = mul_shift_96(ratio, 80248749790819932309965073892);
}
if tick & 512 != 0 {
ratio = mul_shift_96(ratio, 81282483887344747381513967011);
}
if tick & 1024 != 0 {
ratio = mul_shift_96(ratio, 83390072131320151908154831281);
}
if tick & 2048 != 0 {
ratio = mul_shift_96(ratio, 87770609709833776024991924138);
}
if tick & 4096 != 0 {
ratio = mul_shift_96(ratio, 97234110755111693312479820773);
}
if tick & 8192 != 0 {
ratio = mul_shift_96(ratio, 119332217159966728226237229890);
}
if tick & 16384 != 0 {
ratio = mul_shift_96(ratio, 179736315981702064433883588727);
}
if tick & 32768 != 0 {
ratio = mul_shift_96(ratio, 407748233172238350107850275304);
}
if tick & 65536 != 0 {
ratio = mul_shift_96(ratio, 2098478828474011932436660412517);
}
if tick & 131072 != 0 {
ratio = mul_shift_96(ratio, 55581415166113811149459800483533);
}
if tick & 262144 != 0 {
ratio = mul_shift_96(ratio, 38992368544603139932233054999993551);
}
ratio >> 32
}
fn sqrt_price_negative_tick(tick: i32) -> u128 {
let abs_tick = tick.unsigned_abs();
let mut ratio: u128 = if abs_tick & 1 != 0 {
18445821805675392311
} else {
18446744073709551616 };
if abs_tick & 2 != 0 {
ratio = (ratio * 18444899583751176498) >> 64;
}
if abs_tick & 4 != 0 {
ratio = (ratio * 18443055278223354162) >> 64;
}
if abs_tick & 8 != 0 {
ratio = (ratio * 18439367220385604838) >> 64;
}
if abs_tick & 16 != 0 {
ratio = (ratio * 18431993317065449817) >> 64;
}
if abs_tick & 32 != 0 {
ratio = (ratio * 18417254355718160513) >> 64;
}
if abs_tick & 64 != 0 {
ratio = (ratio * 18387811781193591352) >> 64;
}
if abs_tick & 128 != 0 {
ratio = (ratio * 18329067761203520168) >> 64;
}
if abs_tick & 256 != 0 {
ratio = (ratio * 18212142134806087854) >> 64;
}
if abs_tick & 512 != 0 {
ratio = (ratio * 17980523815641551639) >> 64;
}
if abs_tick & 1024 != 0 {
ratio = (ratio * 17526086738831147013) >> 64;
}
if abs_tick & 2048 != 0 {
ratio = (ratio * 16651378430235024244) >> 64;
}
if abs_tick & 4096 != 0 {
ratio = (ratio * 15030750278693429944) >> 64;
}
if abs_tick & 8192 != 0 {
ratio = (ratio * 12247334978882834399) >> 64;
}
if abs_tick & 16384 != 0 {
ratio = (ratio * 8131365268884726200) >> 64;
}
if abs_tick & 32768 != 0 {
ratio = (ratio * 3584323654723342297) >> 64;
}
if abs_tick & 65536 != 0 {
ratio = (ratio * 696457651847595233) >> 64;
}
if abs_tick & 131072 != 0 {
ratio = (ratio * 26294789957452057) >> 64;
}
if abs_tick & 262144 != 0 {
ratio = (ratio * 37481735321082) >> 64;
}
ratio
}
pub fn sqrt_price_x64_to_tick(sqrt_price_x64: u128) -> Result<i32, AmmMathError> {
if !(MIN_SQRT_PRICE..=MAX_SQRT_PRICE).contains(&sqrt_price_x64) {
return Err(AmmMathError::SqrtPriceOutOfRange(sqrt_price_x64));
}
let msb = 127 - sqrt_price_x64.leading_zeros() as i32;
let log2_approx = msb - 64;
let tick_estimate = ((log2_approx as f64) * 6931.47 * 2.0) as i32;
let window = 14000;
let mut lo = (tick_estimate - window).max(MIN_TICK);
let mut hi = (tick_estimate + window).min(MAX_TICK);
while tick_to_sqrt_price_x64(lo)? > sqrt_price_x64 {
lo = (lo - window).max(MIN_TICK);
}
while tick_to_sqrt_price_x64(hi)? < sqrt_price_x64 {
hi = (hi + window).min(MAX_TICK);
}
while lo < hi {
let mid = lo + (hi - lo + 1) / 2;
let price = tick_to_sqrt_price_x64(mid)?;
if price <= sqrt_price_x64 {
lo = mid;
} else {
hi = mid - 1;
}
}
Ok(lo)
}
fn decimal_pow10(exp: u32) -> Decimal {
let mut result = Decimal::ONE;
let ten = Decimal::from(10u64);
for _ in 0..exp {
result *= ten;
}
result
}
pub fn decimal_price_to_sqrt_price_x64(price: Decimal, decimals_a: u8, decimals_b: u8) -> u128 {
let decimal_diff = decimals_a as i32 - decimals_b as i32;
let power = decimal_pow10(decimal_diff.unsigned_abs());
let adjusted = if decimal_diff >= 0 { price / power } else { price * power };
let adjusted_f64 = adjusted.to_f64().unwrap_or(0.0);
if adjusted_f64 <= 0.0 {
return 0;
}
let sqrt_val = adjusted_f64.sqrt();
let two_64 = (1u128 << 64) as f64;
(sqrt_val * two_64).floor() as u128
}
pub fn price_to_tick_index(price: Decimal, decimals_a: u8, decimals_b: u8) -> i32 {
let sqrt_price_x64 = decimal_price_to_sqrt_price_x64(price, decimals_a, decimals_b);
sqrt_price_x64_to_tick(sqrt_price_x64).unwrap_or(if price >= Decimal::ONE {
MAX_TICK
} else {
MIN_TICK
})
}
pub fn tick_index_to_price(tick_index: i32, decimals_a: u8, decimals_b: u8) -> Decimal {
let sqrt_price_x64 = tick_to_sqrt_price_x64(tick_index).unwrap_or(if tick_index >= 0 {
MAX_SQRT_PRICE
} else {
MIN_SQRT_PRICE
});
crate::price_math::sqrt_price_x64_to_price(sqrt_price_x64, decimals_a, decimals_b)
}
pub const WHIRLPOOL_TICK_ARRAY_SIZE: i32 = 88;
pub const RAYDIUM_TICK_ARRAY_SIZE: i32 = 60;
pub fn tick_count(tick_spacing: u16, tick_array_size: i32) -> i32 {
tick_array_size * tick_spacing as i32
}
pub fn get_tick_array_start_index(tick_index: i32, tick_spacing: u16, tick_array_size: i32) -> i32 {
let ticks_in_array = tick_count(tick_spacing, tick_array_size);
let mut start = tick_index / ticks_in_array;
if tick_index < 0 && tick_index % ticks_in_array != 0 {
start -= 1;
}
start * ticks_in_array
}
pub fn get_tick_index_in_array(
tick_index: i32,
start_tick_index: i32,
tick_spacing: u16,
tick_array_size: i32,
) -> Option<usize> {
if tick_spacing == 0 {
return None;
}
let offset = tick_index - start_tick_index;
if offset < 0 {
return None;
}
let index = offset / tick_spacing as i32;
if index < 0 || index >= tick_array_size {
return None;
}
Some(index as usize)
}
pub fn get_initializable_tick_index(
tick_index: i32,
tick_spacing: u16,
round_up: Option<bool>,
) -> i32 {
let ts = tick_spacing as i32;
let remainder = tick_index.rem_euclid(ts);
let result = tick_index.div_euclid(ts) * ts;
let should_round_up = if let Some(up) = round_up {
up && remainder > 0
} else {
remainder >= ts / 2 && remainder > 0
};
if should_round_up {
result + ts
} else {
result
}
}
pub fn is_tick_in_bounds(tick: i32) -> bool {
(MIN_TICK..=MAX_TICK).contains(&tick)
}
pub fn is_tick_initializable(tick: i32, tick_spacing: u16) -> bool {
tick_spacing > 0 && tick % tick_spacing as i32 == 0
}
pub fn invert_tick_index(tick: i32) -> i32 {
-tick
}
pub fn get_full_range_tick_indexes(tick_spacing: u16) -> crate::liquidity_math::TickRange {
let ts = tick_spacing as i32;
let lower = (MIN_TICK / ts) * ts;
let upper = (MAX_TICK / ts) * ts;
crate::liquidity_math::TickRange { tick_lower_index: lower, tick_upper_index: upper }
}
pub fn get_prev_initializable_tick_index(tick: i32, tick_spacing: u16) -> i32 {
let ts = tick_spacing as i32;
tick.div_euclid(ts) * ts
}
pub fn get_next_initializable_tick_index(tick: i32, tick_spacing: u16) -> i32 {
let ts = tick_spacing as i32;
let floor = tick.div_euclid(ts) * ts;
if floor == tick {
tick
} else {
floor + ts
}
}
pub fn is_position_in_range(
sqrt_price_current: u128,
tick_lower: i32,
tick_upper: i32,
) -> Result<bool, AmmMathError> {
let sqrt_lower = tick_to_sqrt_price_x64(tick_lower)?;
let sqrt_upper = tick_to_sqrt_price_x64(tick_upper)?;
Ok(sqrt_price_current >= sqrt_lower && sqrt_price_current < sqrt_upper)
}
#[deprecated(note = "Use price_to_tick_index (Decimal) for better precision at extreme prices")]
pub fn price_to_tick_index_f64(price: f64, decimals_a: u8, decimals_b: u8) -> i32 {
let sqrt_price_x64 = price_to_sqrt_price_x64_f64(price, decimals_a, decimals_b);
sqrt_price_x64_to_tick(sqrt_price_x64).unwrap_or(if price >= 1.0 { MAX_TICK } else { MIN_TICK })
}
#[deprecated(note = "Use tick_index_to_price (Decimal) for better precision at extreme prices")]
pub fn tick_index_to_price_f64(tick_index: i32, decimals_a: u8, decimals_b: u8) -> f64 {
let sqrt_price_x64 = tick_to_sqrt_price_x64(tick_index).unwrap_or(if tick_index >= 0 {
MAX_SQRT_PRICE
} else {
MIN_SQRT_PRICE
});
crate::price_math::sqrt_price_to_price(sqrt_price_x64, decimals_a, decimals_b)
}
fn price_to_sqrt_price_x64_f64(price: f64, decimals_a: u8, decimals_b: u8) -> u128 {
let power = 10f64.powi(decimals_a as i32 - decimals_b as i32);
(f64::floor(f64::sqrt(price / power) * (1u128 << 64) as f64)) as u128
}
#[cfg(test)]
mod tests {
use super::*;
const Q64: u128 = 1u128 << 64;
#[test]
fn test_tick_zero() {
let result = tick_to_sqrt_price_x64(0).unwrap();
assert_eq!(result, Q64, "tick 0 should produce 2^64");
}
#[test]
fn test_tick_positive_one() {
let result = tick_to_sqrt_price_x64(1).unwrap();
assert!(result > Q64);
let ratio = result as f64 / Q64 as f64;
assert!((ratio - 1.000_05).abs() < 0.000_001, "tick 1 ratio {ratio} not close to 1.00005");
}
#[test]
fn test_tick_negative_one() {
let result = tick_to_sqrt_price_x64(-1).unwrap();
assert!(result < Q64, "negative tick should produce price < 2^64");
let ratio = result as f64 / Q64 as f64;
assert!((ratio - 0.999_95).abs() < 0.000_001, "tick -1 ratio {ratio} not close to 0.99995");
}
#[test]
fn test_min_tick() {
let result = tick_to_sqrt_price_x64(MIN_TICK).unwrap();
assert_eq!(result, MIN_SQRT_PRICE, "min tick should produce min sqrt price");
}
#[test]
fn test_max_tick() {
let result = tick_to_sqrt_price_x64(MAX_TICK).unwrap();
assert_eq!(result, MAX_SQRT_PRICE, "max tick should produce max sqrt price");
}
#[test]
fn test_tick_out_of_range() {
assert!(tick_to_sqrt_price_x64(MIN_TICK - 1).is_err());
assert!(tick_to_sqrt_price_x64(MAX_TICK + 1).is_err());
}
#[test]
fn test_sqrt_price_to_tick_zero() {
let tick = sqrt_price_x64_to_tick(Q64).unwrap();
assert_eq!(tick, 0);
}
#[test]
fn test_sqrt_price_to_tick_min() {
let tick = sqrt_price_x64_to_tick(MIN_SQRT_PRICE).unwrap();
assert_eq!(tick, MIN_TICK);
}
#[test]
fn test_sqrt_price_to_tick_max() {
let tick = sqrt_price_x64_to_tick(MAX_SQRT_PRICE).unwrap();
assert_eq!(tick, MAX_TICK);
}
#[test]
fn test_roundtrip_positive_ticks() {
for tick in [0, 1, 10, 100, 1000, 10000, 100000] {
let price = tick_to_sqrt_price_x64(tick).unwrap();
let recovered = sqrt_price_x64_to_tick(price).unwrap();
assert_eq!(recovered, tick, "roundtrip failed for tick {tick}");
}
}
#[test]
fn test_roundtrip_negative_ticks() {
for tick in [-1, -10, -100, -1000, -10000, -100000] {
let price = tick_to_sqrt_price_x64(tick).unwrap();
let recovered = sqrt_price_x64_to_tick(price).unwrap();
assert_eq!(recovered, tick, "roundtrip failed for tick {tick}");
}
}
#[test]
fn test_sqrt_price_out_of_range() {
assert!(sqrt_price_x64_to_tick(0).is_err());
assert!(sqrt_price_x64_to_tick(MIN_SQRT_PRICE - 1).is_err());
assert!(sqrt_price_x64_to_tick(MAX_SQRT_PRICE + 1).is_err());
}
#[test]
fn test_monotonically_increasing() {
let ticks = [-100000, -10000, -1000, -100, -10, 0, 10, 100, 1000, 10000, 100000];
let prices: Vec<u128> = ticks.iter().map(|&t| tick_to_sqrt_price_x64(t).unwrap()).collect();
for i in 1..prices.len() {
assert!(
prices[i] > prices[i - 1],
"prices not monotonic: tick {} => {}, tick {} => {}",
ticks[i - 1],
prices[i - 1],
ticks[i],
prices[i]
);
}
}
#[test]
fn test_symmetry() {
for tick in [1, 10, 100, 1000, 10000] {
let p_pos = tick_to_sqrt_price_x64(tick).unwrap();
let p_neg = tick_to_sqrt_price_x64(-tick).unwrap();
let product = (p_pos as f64) * (p_neg as f64);
let expected = (Q64 as f64) * (Q64 as f64);
let ratio = product / expected;
assert!((ratio - 1.0).abs() < 0.001, "symmetry broken for tick {tick}: ratio={ratio}");
}
}
#[test]
fn sqrt_price_roundtrip() {
for tick in [-100_000, -1000, -1, 0, 1, 1000, 100_000] {
let sqrt = tick_to_sqrt_price_x64(tick).unwrap();
let back = sqrt_price_x64_to_tick(sqrt).unwrap();
assert!((back - tick).abs() <= 1, "tick={} roundtrip={}", tick, back);
}
}
#[test]
fn test_known_values() {
assert_eq!(tick_to_sqrt_price_x64(0).unwrap(), Q64);
let price_10000 = tick_to_sqrt_price_x64(10000).unwrap();
let ratio = price_10000 as f64 / Q64 as f64;
assert!((ratio - 1.6487).abs() < 0.001, "tick 10000 ratio: {ratio}");
let price_neg_10000 = tick_to_sqrt_price_x64(-10000).unwrap();
let ratio_neg = price_neg_10000 as f64 / Q64 as f64;
assert!((ratio_neg - 0.6065).abs() < 0.001, "tick -10000 ratio: {ratio_neg}");
}
#[test]
fn test_price_to_tick_index_known_values() {
assert_eq!(price_to_tick_index(Decimal::from_str("0.009998").unwrap(), 8, 6), -92111);
assert_eq!(price_to_tick_index(Decimal::ONE, 6, 6), 0);
assert_eq!(price_to_tick_index(Decimal::from_str("99.999912").unwrap(), 6, 8), 92108);
}
#[test]
fn test_tick_index_to_price_known_values() {
let p1 = tick_index_to_price(-92111, 8, 6);
let diff1 = (p1 - Decimal::from_str("0.009998").unwrap()).abs();
assert!(diff1 < Decimal::from_str("0.00001").unwrap(), "got {p1}");
let p2 = tick_index_to_price(0, 6, 6);
let diff2 = (p2 - Decimal::ONE).abs();
assert!(diff2 < Decimal::from_str("0.0000000001").unwrap(), "got {p2}");
let p3 = tick_index_to_price(92108, 6, 8);
let diff3 = (p3 - Decimal::from_str("99.999912").unwrap()).abs();
assert!(diff3 < Decimal::from_str("0.001").unwrap(), "got {p3}");
}
#[test]
fn test_price_tick_roundtrip() {
let cases: &[(Decimal, u8, u8)] = &[
(Decimal::ONE, 6, 6),
(Decimal::from_str("100.0").unwrap(), 9, 6),
(Decimal::from_str("0.001").unwrap(), 6, 9),
(Decimal::from_str("50000.0").unwrap(), 8, 6),
];
for &(price, da, db) in cases {
let tick = price_to_tick_index(price, da, db);
let recovered = tick_index_to_price(tick, da, db);
let err = ((recovered - price) / price).abs();
assert!(
err < Decimal::from_str("0.0002").unwrap(),
"roundtrip error too large for price={price}: got {recovered}, err={err}"
);
}
}
#[test]
fn test_price_to_tick_normal_range() {
let price = Decimal::from_str("150.0").unwrap();
let tick = price_to_tick_index(price, 9, 6);
let recovered = tick_index_to_price(tick, 9, 6);
let err = ((recovered - price) / price).abs();
assert!(
err < Decimal::from_str("0.001").unwrap(),
"normal range: price={price}, recovered={recovered}, err={err}"
);
}
#[test]
fn test_price_to_tick_extreme_low() {
let price = Decimal::from_str("0.00001").unwrap();
let tick = price_to_tick_index(price, 9, 6);
let recovered = tick_index_to_price(tick, 9, 6);
let err = ((recovered - price) / price).abs();
assert!(
err < Decimal::from_str("0.001").unwrap(),
"extreme low: price={price}, recovered={recovered}, err={err}"
);
}
#[test]
fn test_price_to_tick_extreme_high() {
let price = Decimal::from_str("100000.0").unwrap();
let tick = price_to_tick_index(price, 8, 6);
let recovered = tick_index_to_price(tick, 8, 6);
let err = ((recovered - price) / price).abs();
assert!(
err < Decimal::from_str("0.001").unwrap(),
"extreme high: price={price}, recovered={recovered}, err={err}"
);
}
#[test]
fn test_price_to_tick_roundtrip_precision() {
let cases: &[(Decimal, u8, u8)] = &[
(Decimal::from_str("0.0000001").unwrap(), 9, 6), (Decimal::from_str("0.001").unwrap(), 6, 6), (Decimal::from_str("1.0").unwrap(), 6, 6), (Decimal::from_str("1000.0").unwrap(), 9, 6), (Decimal::from_str("100000.0").unwrap(), 8, 6), ];
for &(price, da, db) in cases {
let tick = price_to_tick_index(price, da, db);
let recovered = tick_index_to_price(tick, da, db);
let err = ((recovered - price) / price).abs();
assert!(
err < Decimal::from_str("0.001").unwrap(),
"round-trip error >= 0.1% for price={price} (da={da}, db={db}): \
recovered={recovered}, err={err}"
);
}
}
#[test]
fn fuzz_tick_to_price_roundtrip() {
use rand::Rng;
let mut rng = rand::rng();
for _ in 0..1000 {
let tick: i32 = rng.random_range(MIN_TICK..=MAX_TICK);
let price = tick_to_sqrt_price_x64(tick).unwrap();
let recovered = sqrt_price_x64_to_tick(price).unwrap();
assert!(
(recovered - tick).abs() <= 1,
"roundtrip failed: tick={tick}, price={price}, recovered={recovered}"
);
}
}
#[test]
fn fuzz_tick_monotonicity() {
use rand::Rng;
let mut rng = rand::rng();
for _ in 0..1000 {
let tick_a: i32 = rng.random_range(MIN_TICK..=MAX_TICK);
let tick_b: i32 = rng.random_range(MIN_TICK..=MAX_TICK);
let price_a = tick_to_sqrt_price_x64(tick_a).unwrap();
let price_b = tick_to_sqrt_price_x64(tick_b).unwrap();
if tick_a < tick_b {
assert!(
price_a < price_b,
"monotonicity violated: tick_a={tick_a} (price={price_a}) >= tick_b={tick_b} \
(price={price_b})"
);
} else if tick_a > tick_b {
assert!(
price_a > price_b,
"monotonicity violated: tick_a={tick_a} (price={price_a}) <= tick_b={tick_b} \
(price={price_b})"
);
} else {
assert_eq!(price_a, price_b);
}
}
}
#[test]
fn fuzz_sqrt_price_bounds() {
use rand::Rng;
let mut rng = rand::rng();
for _ in 0..1000 {
let tick: i32 = rng.random_range(MIN_TICK..=MAX_TICK);
let price = tick_to_sqrt_price_x64(tick).unwrap();
assert!(
(MIN_SQRT_PRICE..=MAX_SQRT_PRICE).contains(&price),
"price out of bounds: tick={tick}, price={price}"
);
}
}
#[test]
fn test_deprecated_f64_still_works() {
#[allow(deprecated)]
{
let tick = price_to_tick_index_f64(1.0, 6, 6);
assert_eq!(tick, 0);
let price = tick_index_to_price_f64(0, 6, 6);
assert!((price - 1.0).abs() < 1e-10, "got {price}");
}
}
#[test]
fn test_tick_count() {
assert_eq!(tick_count(64, WHIRLPOOL_TICK_ARRAY_SIZE), 88 * 64);
assert_eq!(tick_count(1, WHIRLPOOL_TICK_ARRAY_SIZE), 88);
assert_eq!(tick_count(10, WHIRLPOOL_TICK_ARRAY_SIZE), 880);
assert_eq!(tick_count(1, RAYDIUM_TICK_ARRAY_SIZE), 60);
}
#[test]
fn test_get_tick_array_start_index_at_zero() {
assert_eq!(get_tick_array_start_index(0, 64, WHIRLPOOL_TICK_ARRAY_SIZE), 0,);
}
#[test]
fn test_get_tick_array_start_index_positive() {
assert_eq!(get_tick_array_start_index(100, 64, WHIRLPOOL_TICK_ARRAY_SIZE), 0,);
assert_eq!(get_tick_array_start_index(5632, 64, WHIRLPOOL_TICK_ARRAY_SIZE), 5632,);
}
#[test]
fn test_get_tick_array_start_index_negative() {
let s = WHIRLPOOL_TICK_ARRAY_SIZE;
assert_eq!(get_tick_array_start_index(-1, 64, s), -5632);
assert_eq!(get_tick_array_start_index(-5632, 64, s), -5632);
assert_eq!(get_tick_array_start_index(-5633, 64, s), -11264);
}
#[test]
fn test_get_tick_index_in_array_at_start() {
assert_eq!(get_tick_index_in_array(0, 0, 64, WHIRLPOOL_TICK_ARRAY_SIZE), Some(0),);
}
#[test]
fn test_get_tick_index_in_array_zero_spacing() {
assert_eq!(get_tick_index_in_array(0, 0, 0, WHIRLPOOL_TICK_ARRAY_SIZE), None,);
}
#[test]
fn test_get_tick_index_in_array_out_of_range() {
assert_eq!(get_tick_index_in_array(-1, 0, 64, WHIRLPOOL_TICK_ARRAY_SIZE), None,);
}
#[test]
fn test_get_tick_index_in_array_middle() {
assert_eq!(get_tick_index_in_array(128, 0, 64, WHIRLPOOL_TICK_ARRAY_SIZE), Some(2),);
}
#[test]
fn test_get_initializable_tick_index_round_down() {
assert_eq!(get_initializable_tick_index(5, 64, None), 0);
assert_eq!(get_initializable_tick_index(33, 64, Some(false)), 0);
}
#[test]
fn test_get_initializable_tick_index_round_up() {
assert_eq!(get_initializable_tick_index(33, 64, None), 64);
assert_eq!(get_initializable_tick_index(33, 64, Some(true)), 64);
}
#[test]
fn test_get_initializable_tick_index_negative() {
assert_eq!(get_initializable_tick_index(-33, 64, Some(false)), -64);
assert_eq!(get_initializable_tick_index(-33, 64, Some(true)), 0);
}
#[test]
fn test_get_initializable_tick_index_exact() {
assert_eq!(get_initializable_tick_index(128, 64, Some(true)), 128);
assert_eq!(get_initializable_tick_index(128, 64, Some(false)), 128);
}
#[test]
fn test_is_tick_in_bounds() {
assert!(is_tick_in_bounds(0));
assert!(is_tick_in_bounds(MIN_TICK));
assert!(is_tick_in_bounds(MAX_TICK));
assert!(!is_tick_in_bounds(MIN_TICK - 1));
assert!(!is_tick_in_bounds(MAX_TICK + 1));
}
#[test]
fn test_is_tick_initializable() {
assert!(is_tick_initializable(0, 64));
assert!(is_tick_initializable(128, 64));
assert!(is_tick_initializable(-128, 64));
assert!(!is_tick_initializable(1, 64));
assert!(!is_tick_initializable(63, 64));
assert!(!is_tick_initializable(0, 0));
}
#[test]
fn test_invert_tick_index() {
assert_eq!(invert_tick_index(100), -100);
assert_eq!(invert_tick_index(-100), 100);
assert_eq!(invert_tick_index(0), 0);
}
#[test]
fn test_get_full_range_tick_indexes() {
let range = get_full_range_tick_indexes(64);
assert_eq!(range.tick_lower_index % 64, 0);
assert_eq!(range.tick_upper_index % 64, 0);
assert!(range.tick_lower_index >= MIN_TICK);
assert!(range.tick_upper_index <= MAX_TICK);
assert!(range.tick_lower_index <= MIN_TICK + 63);
assert!(range.tick_upper_index >= MAX_TICK - 63);
}
#[test]
fn test_get_prev_initializable_tick_index() {
assert_eq!(get_prev_initializable_tick_index(100, 64), 64);
assert_eq!(get_prev_initializable_tick_index(128, 64), 128);
assert_eq!(get_prev_initializable_tick_index(0, 64), 0);
assert_eq!(get_prev_initializable_tick_index(-1, 64), -64);
assert_eq!(get_prev_initializable_tick_index(-64, 64), -64);
}
#[test]
fn test_get_next_initializable_tick_index() {
assert_eq!(get_next_initializable_tick_index(100, 64), 128);
assert_eq!(get_next_initializable_tick_index(128, 64), 128);
assert_eq!(get_next_initializable_tick_index(0, 64), 0);
assert_eq!(get_next_initializable_tick_index(-1, 64), 0);
assert_eq!(get_next_initializable_tick_index(-64, 64), -64);
assert_eq!(get_next_initializable_tick_index(-65, 64), -64);
}
#[test]
fn test_is_position_in_range() {
let sqrt_price = tick_to_sqrt_price_x64(0).unwrap();
assert!(is_position_in_range(sqrt_price, -100, 100).unwrap());
assert!(!is_position_in_range(sqrt_price, 1, 100).unwrap());
let sqrt_upper = tick_to_sqrt_price_x64(100).unwrap();
assert!(!is_position_in_range(sqrt_upper, 0, 100).unwrap());
}
#[test]
fn test_is_position_in_range_error() {
assert!(is_position_in_range(Q64, MIN_TICK - 1, 0).is_err());
assert!(is_position_in_range(Q64, 0, MAX_TICK + 1).is_err());
}
}