use crate::AmmMathError;
pub fn apply_transfer_fee(amount: u64, fee_bps: u16, max_fee: u64) -> Result<u64, AmmMathError> {
if fee_bps == 0 || amount == 0 {
return Ok(amount);
}
if fee_bps > 10_000 {
return Err(AmmMathError::InvalidFeeRate(fee_bps));
}
let fee = ((amount as u128) * fee_bps as u128 / 10_000).min(max_fee as u128) as u64;
amount.checked_sub(fee).ok_or(AmmMathError::Overflow)
}
pub fn reverse_apply_transfer_fee(
post_fee_amount: u64,
fee_bps: u16,
max_fee: u64,
) -> Result<u64, AmmMathError> {
if fee_bps == 0 || post_fee_amount == 0 {
return Ok(post_fee_amount);
}
if fee_bps >= 10_000 {
return Err(AmmMathError::InvalidFeeRate(fee_bps));
}
let denom = 10_000u128 - fee_bps as u128;
let pre = (post_fee_amount as u128 * 10_000).div_ceil(denom);
let fee = pre.saturating_sub(post_fee_amount as u128).min(max_fee as u128);
Ok((post_fee_amount as u128 + fee) as u64)
}
pub fn min_amount_with_slippage(amount: u64, slippage_bps: u16) -> u64 {
if slippage_bps >= 10_000 {
return 0;
}
let reduction = (amount as u128) * slippage_bps as u128 / 10_000;
amount.saturating_sub(reduction as u64)
}
pub fn max_amount_with_slippage(amount: u64, slippage_bps: u16) -> u64 {
let increase = (amount as u128) * slippage_bps as u128 / 10_000;
(amount as u128 + increase).min(u64::MAX as u128) as u64
}
pub fn sqrt_price_slippage_bounds(sqrt_price: u128, slippage_bps: u16) -> (u128, u128) {
let delta = sqrt_price * slippage_bps as u128 / 10_000;
let lower = sqrt_price.saturating_sub(delta);
let upper = sqrt_price.saturating_add(delta).min(crate::tick_math::MAX_SQRT_PRICE);
(lower.max(crate::tick_math::MIN_SQRT_PRICE), upper)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_apply_transfer_fee_zero_fee() {
assert_eq!(apply_transfer_fee(1000, 0, u64::MAX).unwrap(), 1000);
}
#[test]
fn test_apply_transfer_fee_zero_amount() {
assert_eq!(apply_transfer_fee(0, 100, u64::MAX).unwrap(), 0);
}
#[test]
fn test_apply_transfer_fee_1_percent() {
assert_eq!(apply_transfer_fee(10_000, 100, u64::MAX).unwrap(), 9_900,);
}
#[test]
fn test_apply_transfer_fee_capped() {
assert_eq!(apply_transfer_fee(10_000, 100, 50).unwrap(), 9_950,);
}
#[test]
fn test_reverse_apply_roundtrip() {
let original = 10_000u64;
let post = apply_transfer_fee(original, 100, u64::MAX).unwrap();
let recovered = reverse_apply_transfer_fee(post, 100, u64::MAX).unwrap();
assert!(
recovered == original || recovered == original + 1,
"recovered={recovered}, original={original}",
);
}
#[test]
fn test_reverse_apply_zero() {
assert_eq!(reverse_apply_transfer_fee(0, 100, u64::MAX).unwrap(), 0,);
}
#[test]
fn test_reverse_apply_100_percent_errors() {
assert_eq!(
reverse_apply_transfer_fee(100, 10_000, u64::MAX),
Err(AmmMathError::InvalidFeeRate(10_000)),
);
}
#[test]
fn test_apply_transfer_fee_invalid_bps() {
assert_eq!(
apply_transfer_fee(1000, 10_001, u64::MAX),
Err(AmmMathError::InvalidFeeRate(10_001)),
);
}
#[test]
fn test_reverse_apply_transfer_fee_invalid_bps() {
assert_eq!(
reverse_apply_transfer_fee(1000, 10_001, u64::MAX),
Err(AmmMathError::InvalidFeeRate(10_001)),
);
}
#[test]
fn test_min_amount_slippage_50bps() {
assert_eq!(min_amount_with_slippage(10_000, 50), 9_950);
}
#[test]
fn test_min_amount_slippage_full() {
assert_eq!(min_amount_with_slippage(10_000, 10_000), 0);
}
#[test]
fn test_min_amount_slippage_zero() {
assert_eq!(min_amount_with_slippage(10_000, 0), 10_000);
}
#[test]
fn test_max_amount_slippage_50bps() {
assert_eq!(max_amount_with_slippage(10_000, 50), 10_050);
}
#[test]
fn test_max_amount_slippage_saturates() {
assert_eq!(max_amount_with_slippage(u64::MAX, 10_000), u64::MAX,);
}
#[test]
fn test_sqrt_price_bounds_basic() {
let price = 18446744073709551616u128; let (lo, hi) = sqrt_price_slippage_bounds(price, 100);
let delta = price / 100;
assert_eq!(lo, price - delta);
assert_eq!(hi, price + delta);
}
#[test]
fn test_sqrt_price_bounds_clamped() {
let (lo, _hi) = sqrt_price_slippage_bounds(crate::tick_math::MIN_SQRT_PRICE, 100);
assert_eq!(lo, crate::tick_math::MIN_SQRT_PRICE);
let (_lo, hi) = sqrt_price_slippage_bounds(crate::tick_math::MAX_SQRT_PRICE, 100);
assert_eq!(hi, crate::tick_math::MAX_SQRT_PRICE);
}
}