use alloy_primitives::U256;
const Q128: U256 = U256::from_limbs([0, 0, 1, 0]);
#[allow(clippy::too_many_arguments)]
pub fn get_fee_growth_inside(
tick_lower: i32,
tick_upper: i32,
tick_current: i32,
fee_growth_global_0x128: U256,
fee_growth_global_1x128: U256,
fee_growth_outside_0x128_lower: U256,
fee_growth_outside_1x128_lower: U256,
fee_growth_outside_0x128_upper: U256,
fee_growth_outside_1x128_upper: U256,
) -> (U256, U256) {
if tick_current < tick_lower {
(
fee_growth_outside_0x128_lower.wrapping_sub(fee_growth_outside_0x128_upper),
fee_growth_outside_1x128_lower.wrapping_sub(fee_growth_outside_1x128_upper),
)
} else if tick_current >= tick_upper {
(
fee_growth_outside_0x128_upper.wrapping_sub(fee_growth_outside_0x128_lower),
fee_growth_outside_1x128_upper.wrapping_sub(fee_growth_outside_1x128_lower),
)
} else {
(
fee_growth_global_0x128
.wrapping_sub(fee_growth_outside_0x128_lower)
.wrapping_sub(fee_growth_outside_0x128_upper),
fee_growth_global_1x128
.wrapping_sub(fee_growth_outside_1x128_lower)
.wrapping_sub(fee_growth_outside_1x128_upper),
)
}
}
pub fn get_tokens_owed(
fee_growth_inside_0_last_x128: U256,
fee_growth_inside_1_last_x128: U256,
liquidity: u128,
fee_growth_inside_0x128: U256,
fee_growth_inside_1x128: U256,
) -> (U256, U256) {
let liq = U256::from(liquidity);
let delta_0 = fee_growth_inside_0x128.wrapping_sub(fee_growth_inside_0_last_x128);
let delta_1 = fee_growth_inside_1x128.wrapping_sub(fee_growth_inside_1_last_x128);
let tokens_owed_0 = mul_div_or_wrapping(delta_0, liq, Q128);
let tokens_owed_1 = mul_div_or_wrapping(delta_1, liq, Q128);
(tokens_owed_0, tokens_owed_1)
}
fn mul_div_or_wrapping(a: U256, b: U256, d: U256) -> U256 {
crate::full_math::mul_div(a, b, d).unwrap_or_else(|_| {
a.wrapping_mul(b) >> 128
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fee_growth_inside_in_range() {
let (fg0, fg1) = get_fee_growth_inside(
-100, 100, 0, U256::from(1000u64),
U256::from(2000u64),
U256::from(100u64),
U256::from(200u64),
U256::from(200u64),
U256::from(300u64),
);
assert_eq!(fg0, U256::from(700u64));
assert_eq!(fg1, U256::from(1500u64));
}
#[test]
fn test_fee_growth_wrapping_below() {
let (fg0, _) = get_fee_growth_inside(
-100,
100,
-200,
U256::from(1000u64),
U256::from(1000u64),
U256::from(10u64),
U256::from(10u64),
U256::from(20u64),
U256::from(20u64),
);
let expected = U256::from(10u64).wrapping_sub(U256::from(20u64));
assert_eq!(fg0, expected);
}
#[test]
fn test_fee_growth_wrapping_above() {
let (fg0, _) = get_fee_growth_inside(
-100,
100,
200,
U256::from(1000u64),
U256::from(1000u64),
U256::from(30u64),
U256::from(30u64),
U256::from(5u64),
U256::from(5u64),
);
let expected = U256::from(5u64).wrapping_sub(U256::from(30u64));
assert_eq!(fg0, expected);
}
#[test]
fn test_tokens_owed_zero_liquidity() {
let (t0, t1) =
get_tokens_owed(U256::ZERO, U256::ZERO, 0, U256::from(1000u64), U256::from(2000u64));
assert_eq!(t0, U256::ZERO);
assert_eq!(t1, U256::ZERO);
}
#[test]
fn test_tokens_owed_basic() {
let (t0, _) = get_tokens_owed(U256::ZERO, U256::ZERO, 1, Q128, U256::ZERO);
assert_eq!(t0, U256::from(1u64));
}
#[test]
fn test_tokens_owed_wrapping_sub() {
let (t0, _) =
get_tokens_owed(U256::from(100u64), U256::ZERO, 0, U256::from(50u64), U256::ZERO);
assert_eq!(t0, U256::ZERO);
}
#[test]
fn test_fee_growth_wraps_lower_gt_upper() {
let (fg0, _) = get_fee_growth_inside(
-100, 100, 0, U256::from(1000u64), U256::from(0u64), U256::from(500u64), U256::from(0u64), U256::from(100u64), U256::from(0u64), );
assert_eq!(fg0, U256::from(400u64));
let (fg0_wrap, _) = get_fee_growth_inside(
-100,
100,
0,
U256::from(50u64),
U256::from(0u64),
U256::from(30u64),
U256::from(0u64),
U256::from(40u64),
U256::from(0u64),
);
let expected =
U256::from(50u64).wrapping_sub(U256::from(30u64)).wrapping_sub(U256::from(40u64));
assert_eq!(fg0_wrap, expected);
}
#[test]
fn test_tokens_owed_unit_liquidity_q128_growth() {
let (t0, t1) = get_tokens_owed(U256::ZERO, U256::ZERO, 1, Q128, Q128);
assert_eq!(t0, U256::from(1u64));
assert_eq!(t1, U256::from(1u64));
}
#[test]
fn fuzz_fee_growth_inside_no_panic() {
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_lower: i32 = rng.random_range(-500_000..500_000);
let tick_upper: i32 = rng.random_range((tick_lower + 1)..=500_000);
let tick_current: i32 = rng.random_range(-500_000..=500_000);
let global_0 = U256::from(rng.random::<u128>());
let global_1 = U256::from(rng.random::<u128>());
let outside_0_lower = U256::from(rng.random::<u128>());
let outside_1_lower = U256::from(rng.random::<u128>());
let outside_0_upper = U256::from(rng.random::<u128>());
let outside_1_upper = U256::from(rng.random::<u128>());
let _ = get_fee_growth_inside(
tick_lower,
tick_upper,
tick_current,
global_0,
global_1,
outside_0_lower,
outside_1_lower,
outside_0_upper,
outside_1_upper,
);
}
}
#[test]
fn fuzz_fee_growth_inside_deterministic_branch() {
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_lower: i32 = rng.random_range(-500_000..0);
let tick_upper: i32 = rng.random_range(1..=500_000);
let global_0 = U256::from(rng.random::<u128>());
let global_1 = U256::from(rng.random::<u128>());
let outside_0_lower = U256::from(rng.random::<u128>());
let outside_1_lower = U256::from(rng.random::<u128>());
let outside_0_upper = U256::from(rng.random::<u128>());
let outside_1_upper = U256::from(rng.random::<u128>());
let tick_below = tick_lower - 1;
let (fg0_below, fg1_below) = get_fee_growth_inside(
tick_lower,
tick_upper,
tick_below,
global_0,
global_1,
outside_0_lower,
outside_1_lower,
outside_0_upper,
outside_1_upper,
);
assert_eq!(
fg0_below,
outside_0_lower.wrapping_sub(outside_0_upper),
"below-range token0 formula wrong at tick_below={tick_below}"
);
assert_eq!(
fg1_below,
outside_1_lower.wrapping_sub(outside_1_upper),
"below-range token1 formula wrong at tick_below={tick_below}"
);
let tick_above = tick_upper;
let (fg0_above, fg1_above) = get_fee_growth_inside(
tick_lower,
tick_upper,
tick_above,
global_0,
global_1,
outside_0_lower,
outside_1_lower,
outside_0_upper,
outside_1_upper,
);
assert_eq!(
fg0_above,
outside_0_upper.wrapping_sub(outside_0_lower),
"above-range token0 formula wrong at tick_above={tick_above}"
);
assert_eq!(
fg1_above,
outside_1_upper.wrapping_sub(outside_1_lower),
"above-range token1 formula wrong at tick_above={tick_above}"
);
let tick_inside: i32 = rng.random_range(tick_lower..tick_upper);
let (fg0_inside, fg1_inside) = get_fee_growth_inside(
tick_lower,
tick_upper,
tick_inside,
global_0,
global_1,
outside_0_lower,
outside_1_lower,
outside_0_upper,
outside_1_upper,
);
assert_eq!(
fg0_inside,
global_0.wrapping_sub(outside_0_lower).wrapping_sub(outside_0_upper),
"inside-range token0 formula wrong at tick_inside={tick_inside}"
);
assert_eq!(
fg1_inside,
global_1.wrapping_sub(outside_1_lower).wrapping_sub(outside_1_upper),
"inside-range token1 formula wrong at tick_inside={tick_inside}"
);
}
}
#[test]
fn fuzz_tokens_owed_no_panic() {
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 fg_inside_0_last = U256::from(rng.random::<u128>());
let fg_inside_1_last = U256::from(rng.random::<u128>());
let fg_inside_0 = U256::from(rng.random::<u128>());
let fg_inside_1 = U256::from(rng.random::<u128>());
let liquidity: u128 = rng.random();
let _ = get_tokens_owed(
fg_inside_0_last,
fg_inside_1_last,
liquidity,
fg_inside_0,
fg_inside_1,
);
}
}
#[test]
fn fuzz_tokens_owed_zero_liquidity_always_zero() {
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 fg_inside_0_last = U256::from(rng.random::<u128>());
let fg_inside_1_last = U256::from(rng.random::<u128>());
let fg_inside_0 = U256::from(rng.random::<u128>());
let fg_inside_1 = U256::from(rng.random::<u128>());
let (t0, t1) = get_tokens_owed(
fg_inside_0_last,
fg_inside_1_last,
0, fg_inside_0,
fg_inside_1,
);
assert_eq!(t0, U256::ZERO, "zero liquidity must yield zero tokens_owed_0");
assert_eq!(t1, U256::ZERO, "zero liquidity must yield zero tokens_owed_1");
}
}
#[test]
fn uniswap_reference_vector_in_range() {
let tick_lower: i32 = -276_320;
let tick_upper: i32 = -276_300;
let tick_current: i32 = -276_310;
let global_0_x128 =
U256::from_str_radix("1234567890123456789012345678901234567890", 10).unwrap();
let global_1_x128 =
U256::from_str_radix("9876543210987654321098765432109876543210", 10).unwrap();
let outside_0_lower_x128 = U256::from_str_radix("100000000000000000000", 10).unwrap();
let outside_1_lower_x128 = U256::from_str_radix("200000000000000000000", 10).unwrap();
let outside_0_upper_x128 = U256::from_str_radix("50000000000000000000", 10).unwrap();
let outside_1_upper_x128 = U256::from_str_radix("150000000000000000000", 10).unwrap();
let (inside_0, inside_1) = get_fee_growth_inside(
tick_lower,
tick_upper,
tick_current,
global_0_x128,
global_1_x128,
outside_0_lower_x128,
outside_1_lower_x128,
outside_0_upper_x128,
outside_1_upper_x128,
);
let expected_0 =
global_0_x128.wrapping_sub(outside_0_lower_x128).wrapping_sub(outside_0_upper_x128);
let expected_1 =
global_1_x128.wrapping_sub(outside_1_lower_x128).wrapping_sub(outside_1_upper_x128);
assert_eq!(inside_0, expected_0);
assert_eq!(inside_1, expected_1);
}
#[test]
fn fuzz_tokens_owed_monotonic_in_liquidity() {
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 delta: u64 = rng.random_range(1..=u64::MAX);
let fg_inside_0 = U256::from(delta);
let liq_a: u128 = rng.random_range(1..u64::MAX as u128);
let liq_b: u128 = rng.random_range(liq_a..=u64::MAX as u128);
let (t0_a, _) = get_tokens_owed(U256::ZERO, U256::ZERO, liq_a, fg_inside_0, U256::ZERO);
let (t0_b, _) = get_tokens_owed(U256::ZERO, U256::ZERO, liq_b, fg_inside_0, U256::ZERO);
assert!(
t0_b >= t0_a,
"tokens_owed must be monotonic in liquidity: liq_a={liq_a}, liq_b={liq_b}, \
delta={delta}, t0_a={t0_a}, t0_b={t0_b}"
);
}
}
}