use hopper_runtime::error::ProgramError;
#[inline(always)]
pub fn isqrt(val: u128) -> Result<u64, ProgramError> {
if val == 0 {
return Ok(0);
}
let mut x = val;
let mut y = (x + 1) >> 1;
while y < x {
x = y;
y = (x + val / x) >> 1;
}
if x > u64::MAX as u128 {
return Err(ProgramError::ArithmeticOverflow);
}
Ok(x as u64)
}
#[inline(always)]
pub fn constant_product_out(
reserve_in: u64,
reserve_out: u64,
amount_in: u64,
fee_bps: u16,
) -> Result<u64, ProgramError> {
if reserve_in == 0 || reserve_out == 0 || amount_in == 0 {
return Err(ProgramError::ArithmeticOverflow);
}
let fee_factor = 10_000u128 - fee_bps as u128;
let amount_in_after_fee = (amount_in as u128)
.checked_mul(fee_factor)
.ok_or(ProgramError::ArithmeticOverflow)?;
let numerator = (reserve_out as u128)
.checked_mul(amount_in_after_fee)
.ok_or(ProgramError::ArithmeticOverflow)?;
let denominator = (reserve_in as u128)
.checked_mul(10_000)
.ok_or(ProgramError::ArithmeticOverflow)?
.checked_add(amount_in_after_fee)
.ok_or(ProgramError::ArithmeticOverflow)?;
let out = numerator / denominator;
if out > u64::MAX as u128 || out == 0 {
return Err(ProgramError::ArithmeticOverflow);
}
Ok(out as u64)
}
#[inline(always)]
pub fn constant_product_in(
reserve_in: u64,
reserve_out: u64,
amount_out: u64,
fee_bps: u16,
) -> Result<u64, ProgramError> {
if reserve_in == 0 || reserve_out == 0 || amount_out == 0 || amount_out >= reserve_out {
return Err(ProgramError::ArithmeticOverflow);
}
let fee_factor = 10_000u128 - fee_bps as u128;
let numerator = (reserve_in as u128)
.checked_mul(amount_out as u128)
.ok_or(ProgramError::ArithmeticOverflow)?
.checked_mul(10_000)
.ok_or(ProgramError::ArithmeticOverflow)?;
let denominator = (reserve_out as u128 - amount_out as u128)
.checked_mul(fee_factor)
.ok_or(ProgramError::ArithmeticOverflow)?;
let result = numerator
.checked_add(denominator - 1)
.ok_or(ProgramError::ArithmeticOverflow)?
/ denominator;
if result > u64::MAX as u128 {
return Err(ProgramError::ArithmeticOverflow);
}
Ok(result as u64)
}
#[inline(always)]
pub fn check_k_invariant(
reserve_a_before: u64,
reserve_b_before: u64,
reserve_a_after: u64,
reserve_b_after: u64,
) -> Result<(), ProgramError> {
let k_before = (reserve_a_before as u128) * (reserve_b_before as u128);
let k_after = (reserve_a_after as u128) * (reserve_b_after as u128);
if k_after < k_before {
return Err(ProgramError::InvalidAccountData);
}
Ok(())
}
#[inline(always)]
pub fn price_impact_bps(amount_in: u64, reserve_in: u64) -> u16 {
if reserve_in == 0 {
return 10_000; }
let total = reserve_in as u128 + amount_in as u128;
let impact = (amount_in as u128 * 10_000) / total;
if impact > 10_000 {
10_000
} else {
impact as u16
}
}
#[inline(always)]
pub fn initial_lp_amount(amount_a: u64, amount_b: u64) -> Result<u64, ProgramError> {
let product = (amount_a as u128)
.checked_mul(amount_b as u128)
.ok_or(ProgramError::ArithmeticOverflow)?;
isqrt(product)
}
#[inline(always)]
pub fn proportional_lp_amount(
amount_a: u64,
amount_b: u64,
reserve_a: u64,
reserve_b: u64,
lp_supply: u64,
) -> Result<u64, ProgramError> {
if reserve_a == 0 || reserve_b == 0 || lp_supply == 0 {
return Err(ProgramError::ArithmeticOverflow);
}
let lp_a = (amount_a as u128)
.checked_mul(lp_supply as u128)
.ok_or(ProgramError::ArithmeticOverflow)?
/ reserve_a as u128;
let lp_b = (amount_b as u128)
.checked_mul(lp_supply as u128)
.ok_or(ProgramError::ArithmeticOverflow)?
/ reserve_b as u128;
let min_lp = if lp_a < lp_b { lp_a } else { lp_b };
if min_lp > u64::MAX as u128 || min_lp == 0 {
return Err(ProgramError::ArithmeticOverflow);
}
Ok(min_lp as u64)
}