use crate::{bn::U192, curve::StableSwap};
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
pub struct SaberSwap {
pub initial_amp_factor: u64,
pub target_amp_factor: u64,
pub current_ts: i64,
pub start_ramp_ts: i64,
pub stop_ramp_ts: i64,
pub lp_mint_supply: u64,
pub token_a_reserve: u64,
pub token_b_reserve: u64,
}
impl From<&SaberSwap> for crate::curve::StableSwap {
fn from(swap: &SaberSwap) -> Self {
crate::curve::StableSwap::new(
swap.initial_amp_factor,
swap.target_amp_factor,
swap.current_ts,
swap.start_ramp_ts,
swap.stop_ramp_ts,
)
}
}
impl SaberSwap {
pub fn calculate_pool_tokens_from_virtual_amount(&self, virtual_amount: u64) -> Option<u64> {
U192::from(virtual_amount)
.checked_mul(self.lp_mint_supply.into())?
.checked_div(self.compute_d()?)?
.to_u64()
}
pub fn calculate_virtual_price_of_pool_tokens(&self, pool_token_amount: u64) -> Option<u64> {
self.compute_d()?
.checked_mul(pool_token_amount.into())?
.checked_div(self.lp_mint_supply.into())?
.to_u64()
}
pub fn compute_d(&self) -> Option<U192> {
let calculator = StableSwap::from(self);
calculator.compute_d(self.token_a_reserve, self.token_b_reserve)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use proptest::prelude::*;
use super::SaberSwap;
prop_compose! {
fn arb_swap_unsafe()(
token_a_reserve in 1_u64..=u64::MAX,
token_b_reserve in 1_u64..=u64::MAX,
lp_mint_supply in 1_u64..=u64::MAX
) -> SaberSwap {
SaberSwap {
initial_amp_factor: 1,
target_amp_factor: 1,
current_ts: 1,
start_ramp_ts: 1,
stop_ramp_ts: 1,
lp_mint_supply,
token_a_reserve,
token_b_reserve
}
}
}
prop_compose! {
#[allow(clippy::integer_arithmetic)]
fn arb_token_amount(decimals: u8)(
amount in 1_u64..=(u64::MAX / 10u64.pow(decimals.into())),
) -> u64 {
amount
}
}
prop_compose! {
fn arb_swap_reserves()(
decimals in 0_u8..=19_u8,
swap in arb_swap_unsafe()
) (
token_a_reserve in arb_token_amount(decimals),
token_b_reserve in arb_token_amount(decimals),
swap in Just(swap)
) -> SaberSwap {
SaberSwap {
token_a_reserve,
token_b_reserve,
..swap
}
}
}
prop_compose! {
fn arb_swap()(
swap in arb_swap_reserves()
) (
lp_mint_supply in 1_u64.max((swap.token_a_reserve.min(swap.token_b_reserve)) / 4)..=(swap.token_a_reserve.checked_add(swap.token_b_reserve).unwrap_or(u64::MAX)),
swap in Just(swap)
) -> SaberSwap {
SaberSwap {
lp_mint_supply,
..swap
}
}
}
proptest! {
#[test]
fn test_invertible(
swap in arb_swap(),
amount in 0_u64..=u64::MAX
) {
let maybe_virt = swap.calculate_virtual_price_of_pool_tokens(amount);
if maybe_virt.is_none() {
return Ok(());
}
let virt = maybe_virt.unwrap();
if virt == 0 {
return Ok(());
}
let result_lp = swap.calculate_pool_tokens_from_virtual_amount(virt).unwrap();
prop_assert!(result_lp <= amount);
prop_assert!(1.0_f64 - (result_lp as f64) / (amount as f64) < 0.001_f64);
}
}
}