use alloy_primitives::U256;
use wp_evm_base::types::SlippageBps;
#[cfg(test)]
const Q128: U256 = U256::from_limbs([0, 0, 1, 0]);
const MASK_24: u32 = 0xFF_FF_FF;
fn sign_extend_24(raw: u32) -> i32 {
let low = raw & MASK_24;
if low & 0x80_00_00 != 0 {
(low | 0xFF_00_00_00) as i32
} else {
low as i32
}
}
fn extract_u32(info: U256, bit_offset: usize) -> u32 {
let shifted = info >> bit_offset;
let limbs = shifted.as_limbs();
limbs[0] as u32
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PositionInfo {
pub tick_lower: i32,
pub tick_upper: i32,
pub has_subscriber: bool,
}
pub fn decode_position_info(info: U256) -> PositionInfo {
let has_subscriber = (info.as_limbs()[0] as u8) != 0;
let tick_lower = sign_extend_24(extract_u32(info, 8));
let tick_upper = sign_extend_24(extract_u32(info, 32));
PositionInfo { tick_lower, tick_upper, has_subscriber }
}
pub fn compute_accrued_fees(
liquidity: u128,
fee_growth_inside0_now_x128: U256,
fee_growth_inside1_now_x128: U256,
fee_growth_inside0_last_x128: U256,
fee_growth_inside1_last_x128: U256,
) -> (U256, U256) {
wp_evm_amm_math::fee_growth::get_tokens_owed(
fee_growth_inside0_last_x128,
fee_growth_inside1_last_x128,
liquidity,
fee_growth_inside0_now_x128,
fee_growth_inside1_now_x128,
)
}
pub fn apply_slippage_max(quoted: U256, slippage: SlippageBps) -> U256 {
let bps = U256::from(slippage.as_bps());
let denom = U256::from(10_000u64);
quoted * (denom + bps) / denom
}
#[cfg(test)]
mod tests {
use super::*;
fn pack(tick_lower: i32, tick_upper: i32, has_subscriber: bool) -> U256 {
let lo = (tick_lower as u32) & MASK_24;
let up = (tick_upper as u32) & MASK_24;
let mut out = U256::ZERO;
if has_subscriber {
out |= U256::from(1u64);
}
out |= U256::from(lo) << 8;
out |= U256::from(up) << 32;
out
}
#[test]
fn decode_round_trip_positive_ticks() {
let packed = pack(60, 120, false);
let pi = decode_position_info(packed);
assert_eq!(pi.tick_lower, 60);
assert_eq!(pi.tick_upper, 120);
assert!(!pi.has_subscriber);
}
#[test]
fn decode_round_trip_negative_ticks() {
let packed = pack(-60, 60, false);
let pi = decode_position_info(packed);
assert_eq!(pi.tick_lower, -60);
assert_eq!(pi.tick_upper, 60);
}
#[test]
fn decode_round_trip_extreme_ticks() {
let packed = pack(-887272, 887272, true);
let pi = decode_position_info(packed);
assert_eq!(pi.tick_lower, -887272);
assert_eq!(pi.tick_upper, 887272);
assert!(pi.has_subscriber);
}
#[test]
fn decode_zero_info_is_all_defaults() {
let pi = decode_position_info(U256::ZERO);
assert_eq!(pi.tick_lower, 0);
assert_eq!(pi.tick_upper, 0);
assert!(!pi.has_subscriber);
}
#[test]
fn decode_ignores_high_bits_pool_id_noise() {
let base = pack(-60, 60, false);
let noise = U256::from_str_radix("deadbeefcafebabedeadbeefcafebabe", 16).unwrap() << 56;
let polluted = base | noise;
let pi = decode_position_info(polluted);
assert_eq!(pi.tick_lower, -60);
assert_eq!(pi.tick_upper, 60);
}
#[test]
fn compute_accrued_fees_zero_liquidity_is_zero() {
let (fee0, fee1) = compute_accrued_fees(
0,
Q128 * U256::from(10u64),
Q128 * U256::from(20u64),
U256::ZERO,
U256::ZERO,
);
assert_eq!(fee0, U256::ZERO);
assert_eq!(fee1, U256::ZERO);
}
#[test]
fn compute_accrued_fees_scales_q128_deltas_by_liquidity() {
let (fee0, fee1) = compute_accrued_fees(
100,
Q128 * U256::from(3u64),
Q128 * U256::from(5u64),
Q128,
Q128 * U256::from(2u64),
);
assert_eq!(fee0, U256::from(200u64));
assert_eq!(fee1, U256::from(300u64));
}
#[test]
fn compute_accrued_fees_truncates_fractional_tokens() {
let (fee0, fee1) = compute_accrued_fees(
3,
Q128 / U256::from(2u64),
Q128 + Q128 / U256::from(3u64),
U256::ZERO,
U256::ZERO,
);
assert_eq!(fee0, U256::from(1u64));
assert_eq!(fee1, U256::from(3u64));
}
#[test]
fn apply_slippage_max_scales_up() {
let quoted = U256::from(1_000_000u64);
let got = apply_slippage_max(quoted, SlippageBps::new(100));
assert_eq!(got, U256::from(1_010_000u64));
}
#[test]
fn apply_slippage_max_zero_bps_is_identity() {
let quoted = U256::from(1_000_000u64);
let got = apply_slippage_max(quoted, SlippageBps::new(0));
assert_eq!(got, quoted);
}
#[test]
fn apply_slippage_max_10000_bps_doubles_amount() {
let quoted = U256::from(1_000_000u64);
let got = apply_slippage_max(quoted, SlippageBps::new(10_000));
assert_eq!(got, U256::from(2_000_000u64));
}
#[test]
fn apply_slippage_max_zero_quoted_returns_zero() {
let got = apply_slippage_max(U256::ZERO, SlippageBps::new(250));
assert_eq!(got, U256::ZERO);
}
}