use crate::PoolAmm;
pub fn v2_swap_output(
amount_in: u128,
reserve_in: u128,
reserve_out: u128,
fee_ppm: u32,
) -> Option<u128> {
if amount_in == 0 || reserve_in == 0 || reserve_out == 0 {
return None;
}
if fee_ppm >= 1_000_000 {
return None;
}
let fee_factor = (1_000_000 - fee_ppm) as u128;
let amount_in_with_fee = amount_in.checked_mul(fee_factor)?;
let denominator = reserve_in
.checked_mul(1_000_000)?
.checked_add(amount_in_with_fee)?;
if denominator == 0 {
return None;
}
mul_div(amount_in_with_fee, reserve_out, denominator)
}
pub fn v2_swap_input(
amount_out: u128,
reserve_in: u128,
reserve_out: u128,
fee_ppm: u32,
) -> Option<u128> {
if amount_out == 0 || reserve_in == 0 || amount_out >= reserve_out {
return None;
}
if fee_ppm >= 1_000_000 {
return None;
}
let fee_factor = (1_000_000 - fee_ppm) as u128;
let denominator = (reserve_out - amount_out).checked_mul(fee_factor)?;
if denominator == 0 {
return None;
}
let amount_out_scaled = amount_out.checked_mul(1_000_000)?;
let base = mul_div(reserve_in, amount_out_scaled, denominator)?;
Some(base + 1)
}
const Q64: u128 = 1u128 << 64;
pub fn v3_quote_single_tick(
amount_in: u128,
sqrt_price_x64: u128,
liquidity: u128,
fee_ppm: u32,
) -> Option<u128> {
if liquidity == 0 || sqrt_price_x64 == 0 || amount_in == 0 {
return None;
}
if fee_ppm >= 1_000_000 {
return None;
}
let amount_after_fee = amount_in * (1_000_000 - fee_ppm as u128) / 1_000_000;
let price_ratio = sqrt_price_x64;
let step1 = mul_div(amount_after_fee, price_ratio, Q64)?;
let result = mul_div(step1, price_ratio, Q64)?;
if result > 0 { Some(result) } else { None }
}
pub fn tick_to_sqrt_price_x64(tick: i32) -> u128 {
let abs_tick = tick.unsigned_abs();
let mut ratio: u128 = Q64;
const MAGIC: [u128; 19] = [
18447666387855958016, 18448588748116918272, 18450433606991728640, 18454123878217453568, 18461506635089977344, 18476281010653851648, 18505865242158133248, 18565175891880198144, 18684368066214465536, 18925053041274802176, 19415764168675909632, 20435687552629014528, 22639080592215080960, 27784196929975758848, 41848122137926787072, 94936283577910951936, 488590176324437606400, 12941056668150515367936, 9078618265592131460530176, ];
for (i, &magic) in MAGIC.iter().enumerate() {
if abs_tick & (1u32 << i) != 0 {
ratio = mul_shift(ratio, magic);
}
}
if tick < 0 {
if ratio == 0 { return 0; }
u128::MAX / ratio
} else {
ratio
}
}
pub fn sqrt_price_x64_to_tick(sqrt_price_x64: u128) -> i32 {
if sqrt_price_x64 == 0 {
return i32::MIN;
}
let mut lo: i32 = -443636;
let mut hi: i32 = 443636;
while lo < hi {
let mid = lo + (hi - lo + 1) / 2;
if tick_to_sqrt_price_x64(mid) <= sqrt_price_x64 {
lo = mid;
} else {
hi = mid - 1;
}
}
lo
}
pub fn quote_from_pool(
pool: &PoolAmm,
amount_in: u128,
zero_for_one: bool,
) -> Option<u128> {
if !pool.is_valid() {
return None;
}
match pool.pool_type {
super::pool_type::V2 => {
let (reserve_in, reserve_out) = if zero_for_one {
(pool.reserve0(), pool.reserve1())
} else {
(pool.reserve1(), pool.reserve0())
};
v2_swap_output(amount_in, reserve_in, reserve_out, pool.fee_ppm)
}
super::pool_type::V3_CLMM => {
v3_quote_single_tick(amount_in, pool.sqrt_price_x64, pool.liquidity, pool.fee_ppm)
}
_ => None, }
}
#[inline(always)]
fn mul_shift(a: u128, b: u128) -> u128 {
let a_lo = a & 0xFFFF_FFFF_FFFF_FFFF;
let a_hi = a >> 64;
let b_lo = b & 0xFFFF_FFFF_FFFF_FFFF;
let b_hi = b >> 64;
let hh = a_hi * b_hi;
let hl = a_hi * b_lo;
let lh = a_lo * b_hi;
let ll_hi = (a_lo * b_lo) >> 64;
let mid = hl.wrapping_add(lh).wrapping_add(ll_hi);
(hh << 64).wrapping_add(mid)
}
#[inline(always)]
fn full_mul_u128(a: u128, b: u128) -> (u128, u128) {
let a_lo = a & 0xFFFF_FFFF_FFFF_FFFF;
let a_hi = a >> 64;
let b_lo = b & 0xFFFF_FFFF_FFFF_FFFF;
let b_hi = b >> 64;
let ll = a_lo * b_lo;
let lh = a_lo * b_hi;
let hl = a_hi * b_lo;
let hh = a_hi * b_hi;
let (mid, mid_carry) = lh.overflowing_add(hl);
let (lo, lo_carry) = ll.overflowing_add(mid << 64);
let hi = hh + (mid >> 64) + ((mid_carry as u128) << 64) + lo_carry as u128;
(hi, lo)
}
#[inline(always)]
fn div_256_by_128(hi: u128, lo: u128, d: u128) -> u128 {
debug_assert!(hi < d, "quotient would overflow u128");
if hi == 0 {
return lo / d;
}
let mut rem = hi;
let mut quot: u128 = 0;
for i in (0..128).rev() {
let bit = (lo >> i) & 1;
let will_overflow = rem >> 127 != 0;
rem = rem.wrapping_shl(1) | bit;
if will_overflow || rem >= d {
rem = rem.wrapping_sub(d);
quot |= 1u128 << i;
}
}
quot
}
#[inline(always)]
fn mul_div(a: u128, b: u128, c: u128) -> Option<u128> {
if c == 0 {
return None;
}
if let Some(product) = a.checked_mul(b) {
return Some(product / c);
}
let (hi, lo) = full_mul_u128(a, b);
if hi >= c {
return None; }
Some(div_256_by_128(hi, lo, c))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v2_basic_swap() {
let out = v2_swap_output(
1_000_000_000_000_000_000, 100_000_000_000_000_000_000, 200_000_000_000, 3000, );
assert!(out.is_some());
let o = out.unwrap();
assert!(o > 1_970_000_000 && o < 2_000_000_000, "got {}", o);
}
#[test]
fn v2_zero_inputs() {
assert!(v2_swap_output(0, 100, 200, 3000).is_none());
assert!(v2_swap_output(100, 0, 200, 3000).is_none());
assert!(v2_swap_output(100, 200, 0, 3000).is_none());
}
#[test]
fn v2_inverse_round_trip() {
let reserve_in = 1_000_000_000u128;
let reserve_out = 2_000_000_000u128;
let amount_in = 10_000_000u128;
let fee_ppm = 3000u32;
let out = v2_swap_output(amount_in, reserve_in, reserve_out, fee_ppm).unwrap();
let needed = v2_swap_input(out, reserve_in, reserve_out, fee_ppm).unwrap();
assert!(needed >= amount_in);
assert!(needed <= amount_in + 1, "needed={} vs in={}", needed, amount_in);
}
#[test]
fn tick_to_price_zero() {
let p = tick_to_sqrt_price_x64(0);
assert_eq!(p, Q64);
}
#[test]
fn tick_positive_gives_higher_price() {
let p0 = tick_to_sqrt_price_x64(0);
let p1 = tick_to_sqrt_price_x64(100);
assert!(p1 > p0, "p1={}, p0={}", p1, p0);
}
#[test]
fn tick_negative_gives_lower_price() {
let p0 = tick_to_sqrt_price_x64(0);
let pn = tick_to_sqrt_price_x64(-100);
assert!(pn < p0, "pn={}, p0={}", pn, p0);
}
#[test]
fn tick_round_trip() {
for tick in [-1000, -100, -1, 0, 1, 100, 1000] {
let price = tick_to_sqrt_price_x64(tick);
let recovered = sqrt_price_x64_to_tick(price);
assert_eq!(
recovered, tick,
"tick={} -> price={} -> recovered={}",
tick, price, recovered
);
}
}
}