use alloy_primitives::U256;
use crate::AmmMathError;
pub const MIN_TICK: i32 = -887272;
pub const MAX_TICK: i32 = 887272;
pub const MIN_SQRT_RATIO: U256 = U256::from_limbs([4295128739, 0, 0, 0]);
pub const MAX_SQRT_RATIO: U256 =
U256::from_limbs([6743328256752651558, 17280870778742802505, 4294805859, 0]);
pub fn get_sqrt_ratio_at_tick(tick: i32) -> crate::Result<U256> {
if !(MIN_TICK..=MAX_TICK).contains(&tick) {
return Err(AmmMathError::TickOutOfRange { tick });
}
let abs_tick = tick.unsigned_abs();
let mut ratio: U256 = if abs_tick & 0x1 != 0 {
U256::from_str_radix("fffcb933bd6fad37aa2d162d1a594001", 16).unwrap()
} else {
U256::from(1u64) << 128
};
macro_rules! apply_bit {
($bit:expr, $hex:expr) => {
if abs_tick & (1 << $bit) != 0 {
ratio = (ratio * U256::from_str_radix($hex, 16).unwrap()) >> 128;
}
};
}
apply_bit!(1, "fff97272373d413259a46990580e213a");
apply_bit!(2, "fff2e50f5f656932ef12357cf3c7fdcc");
apply_bit!(3, "ffe5caca7e10e4e61c3624eaa0941cd0");
apply_bit!(4, "ffcb9843d60f6159c9db58835c926644");
apply_bit!(5, "ff973b41fa98c081472e6896dfb254c0");
apply_bit!(6, "ff2ea16466c96a3843ec78b326b52861");
apply_bit!(7, "fe5dee046a99a2a811c461f1969c3053");
apply_bit!(8, "fcbe86c7900a88aedcffc83b479aa3a4");
apply_bit!(9, "f987a7253ac413176f2b074cf7815e54");
apply_bit!(10, "f3392b0822b70005940c7a398e4b70f3");
apply_bit!(11, "e7159475a2c29b7443b29c7fa6e889d9");
apply_bit!(12, "d097f3bdfd2022b8845ad8f792aa5825");
apply_bit!(13, "a9f746462d870fdf8a65dc1f90e061e5");
apply_bit!(14, "70d869a156d2a1b890bb3df62baf32f7");
apply_bit!(15, "31be135f97d08fd981231505542fcfa6");
apply_bit!(16, "9aa508b5b7a84e1c677de54f3e99bc9");
apply_bit!(17, "5d6af8dedb81196699c329225ee604");
apply_bit!(18, "2216e584f5fa1ea926041bedfe98");
apply_bit!(19, "48a170391f7dc42444e8fa2");
if tick > 0 {
ratio = U256::MAX / ratio;
}
let shift: U256 = ratio % (U256::from(1u64) << 32);
let result = (ratio >> 32) + if shift.is_zero() { U256::ZERO } else { U256::from(1u64) };
Ok(result)
}
pub fn get_tick_at_sqrt_ratio(sqrt_price_x96: U256) -> crate::Result<i32> {
if sqrt_price_x96 < MIN_SQRT_RATIO || sqrt_price_x96 >= MAX_SQRT_RATIO {
return Err(AmmMathError::SqrtPriceOutOfRange);
}
let msb = most_significant_bit(sqrt_price_x96);
let mut log_2_x64: U256 = U256::from(msb as u64).wrapping_sub(U256::from(96u64)) << 64;
let mut r: U256 = (sqrt_price_x96 << 96) >> (msb - 31);
let mut decimals: u64 = 0;
macro_rules! refine {
($bit_pos:expr, $last:expr) => {
let square: U256 = r * r;
let f = square.as_limbs()[3] >> 63;
r = square >> (127 + f as u8);
decimals |= f << $bit_pos;
};
($bit_pos:expr) => {
let square: U256 = r * r;
let f = square.as_limbs()[3] >> 63;
r = square >> (127 + f as u8);
decimals |= f << $bit_pos;
};
}
refine!(63);
refine!(62);
refine!(61);
refine!(60);
refine!(59);
refine!(58);
refine!(57);
refine!(56);
refine!(55);
refine!(54);
refine!(53);
refine!(52);
refine!(51);
{
let square: U256 = r * r;
let f = square.as_limbs()[3] >> 63;
decimals |= f << 50;
}
log_2_x64 |= U256::from(decimals);
let magic = U256::from_str_radix("255738958999603826347141", 10).unwrap();
let log_sqrt10001: U256 = log_2_x64.wrapping_mul(magic);
let sub_const = U256::from_str_radix("3402992956809132418596140100660247210", 10).unwrap();
let add_const = U256::from_str_radix("291339464771989622907027621153398088495", 10).unwrap();
let tick_low_u256: U256 = log_sqrt10001.wrapping_sub(sub_const) >> 128;
let tick_low = tick_low_u256.as_limbs()[0] as i32;
let tick_high_u256: U256 = log_sqrt10001.wrapping_add(add_const) >> 128;
let tick_high = tick_high_u256.as_limbs()[0] as i32;
if tick_low == tick_high {
Ok(tick_low)
} else {
let sqrt_at_high = get_sqrt_ratio_at_tick(tick_high)?;
if sqrt_at_high <= sqrt_price_x96 {
Ok(tick_high)
} else {
Ok(tick_low)
}
}
}
fn most_significant_bit(x: U256) -> u32 {
255 - x.leading_zeros() as u32
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tick_0() {
let r = get_sqrt_ratio_at_tick(0).unwrap();
assert_eq!(r, U256::from(1u64) << 96);
}
#[test]
fn test_tick_1() {
let r = get_sqrt_ratio_at_tick(1).unwrap();
assert_eq!(r, U256::from_str_radix("79232123823359799118286999568", 10).unwrap());
}
#[test]
fn test_tick_neg_1() {
let r = get_sqrt_ratio_at_tick(-1).unwrap();
assert_eq!(r, U256::from_str_radix("79224201403219477170569942574", 10).unwrap());
}
#[test]
fn test_min_tick() {
let r = get_sqrt_ratio_at_tick(MIN_TICK).unwrap();
assert_eq!(r, MIN_SQRT_RATIO);
}
#[test]
fn test_max_tick() {
let r = get_sqrt_ratio_at_tick(MAX_TICK).unwrap();
assert_eq!(r, MAX_SQRT_RATIO);
}
#[test]
fn test_out_of_range() {
assert!(get_sqrt_ratio_at_tick(MIN_TICK - 1).is_err());
assert!(get_sqrt_ratio_at_tick(MAX_TICK + 1).is_err());
}
#[test]
fn test_get_tick_at_sqrt_ratio_tick_0() {
let q96 = U256::from(1u64) << 96;
let tick = get_tick_at_sqrt_ratio(q96).unwrap();
assert_eq!(tick, 0);
}
#[test]
fn test_get_tick_at_sqrt_ratio_tick_1() {
let sqrt = get_sqrt_ratio_at_tick(1).unwrap();
let tick = get_tick_at_sqrt_ratio(sqrt).unwrap();
assert_eq!(tick, 1);
}
#[test]
fn test_roundtrip_various_ticks() {
for t in [-887272, -50000, -1000, -100, -1, 0, 1, 100, 1000, 50000, 887271] {
let sqrt = get_sqrt_ratio_at_tick(t).unwrap();
let recovered = get_tick_at_sqrt_ratio(sqrt).unwrap();
assert_eq!(recovered, t, "roundtrip failed for tick {t}");
}
}
#[test]
fn test_min_sqrt_ratio_value() {
assert_eq!(MIN_SQRT_RATIO, U256::from(4295128739u64));
}
#[test]
fn test_max_sqrt_ratio_value() {
let expected =
U256::from_str_radix("1461446703485210103287273052203988822378723970342", 10).unwrap();
assert_eq!(MAX_SQRT_RATIO, expected);
}
#[test]
fn test_roundtrip_large_ticks() {
for t in [-100000i32, 100000] {
let sqrt = get_sqrt_ratio_at_tick(t).unwrap();
let recovered = get_tick_at_sqrt_ratio(sqrt).unwrap();
assert_eq!(recovered, t, "roundtrip failed for tick {t}");
}
}
#[test]
fn fuzz_tick_to_sqrt_price_roundtrip() {
use rand::{rngs::StdRng, SeedableRng};
fn test_rng() -> StdRng {
StdRng::from_os_rng()
}
use rand::Rng;
let mut rng = test_rng();
for _ in 0..1000 {
let tick: i32 = rng.random_range(MIN_TICK..MAX_TICK);
let sqrt = get_sqrt_ratio_at_tick(tick).unwrap_or_else(|e| {
panic!("get_sqrt_ratio_at_tick({tick}) failed: {e}");
});
let recovered = get_tick_at_sqrt_ratio(sqrt).unwrap_or_else(|e| {
panic!("get_tick_at_sqrt_ratio failed for tick={tick}, sqrt={sqrt}: {e}");
});
assert_eq!(
recovered, tick,
"roundtrip failed: tick={tick}, sqrt={sqrt}, recovered={recovered}"
);
}
}
#[test]
fn fuzz_sqrt_ratio_bounds() {
use rand::{rngs::StdRng, SeedableRng};
fn test_rng() -> StdRng {
StdRng::from_os_rng()
}
use rand::Rng;
let mut rng = test_rng();
for _ in 0..1000 {
let tick: i32 = rng.random_range(MIN_TICK..=MAX_TICK);
let sqrt = get_sqrt_ratio_at_tick(tick).unwrap_or_else(|e| {
panic!("get_sqrt_ratio_at_tick({tick}) failed: {e}");
});
assert!(
sqrt >= MIN_SQRT_RATIO,
"tick={tick} produced sqrt={sqrt} below MIN_SQRT_RATIO"
);
assert!(
sqrt <= MAX_SQRT_RATIO,
"tick={tick} produced sqrt={sqrt} above MAX_SQRT_RATIO"
);
}
}
#[test]
fn fuzz_tick_monotonicity() {
use rand::{rngs::StdRng, SeedableRng};
fn test_rng() -> StdRng {
StdRng::from_os_rng()
}
use rand::Rng;
let mut rng = test_rng();
for _ in 0..1000 {
let tick_a: i32 = rng.random_range(MIN_TICK..MAX_TICK);
let tick_b: i32 = rng.random_range((tick_a + 1)..=MAX_TICK);
let sqrt_a = get_sqrt_ratio_at_tick(tick_a).unwrap();
let sqrt_b = get_sqrt_ratio_at_tick(tick_b).unwrap();
assert!(
sqrt_a < sqrt_b,
"monotonicity violated: tick_a={tick_a} sqrt_a={sqrt_a}, tick_b={tick_b} \
sqrt_b={sqrt_b}"
);
}
}
}