use crate::curve::Fees;
use crate::curve::constant_product::ConstantProductCurve;
use anchor_lang::prelude::*;
use {crate::error::ErrorCode, std::fmt::Debug};
pub fn map_zero_to_none(x: u128) -> Option<u128> {
if x == 0 {
None
} else {
Some(x)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum TradeDirection {
ZeroForOne,
OneForZero,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum RoundDirection {
Floor,
Ceiling,
}
impl TradeDirection {
pub fn opposite(&self) -> TradeDirection {
match self {
TradeDirection::ZeroForOne => TradeDirection::OneForZero,
TradeDirection::OneForZero => TradeDirection::ZeroForOne,
}
}
}
#[derive(Debug, PartialEq)]
pub struct TradingTokenResult {
pub token_0_amount: u128,
pub token_1_amount: u128,
}
#[derive(Debug, PartialEq)]
pub struct SwapResult {
pub new_swap_source_amount: u128,
pub new_swap_destination_amount: u128,
pub source_amount_swapped: u128,
pub destination_amount_swapped: u128,
pub total_fees: u128,
pub protocol_fee: u128,
pub creator_fee: u128,
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct CurveCalculator {}
impl CurveCalculator {
pub fn validate_supply(token_0_amount: u64, token_1_amount: u64) -> Result<()> {
if token_0_amount == 0 {
return Err(ErrorCode::EmptySupply.into());
}
if token_1_amount == 0 {
return Err(ErrorCode::EmptySupply.into());
}
Ok(())
}
pub fn swap_base_input(
source_amount: u128,
swap_source_amount: u128,
swap_destination_amount: u128,
total_fees: u128,
protocol_fee_rate: u64,
creator_fee_rate: u64,
) -> Option<SwapResult> {
let protocol_fee = Fees::protocol_fee(total_fees, protocol_fee_rate)?;
let creator_fee = Fees::fund_fee(total_fees, creator_fee_rate)?;
let source_amount_less_fees = source_amount.checked_sub(total_fees as u128)?;
let destination_amount_swapped = ConstantProductCurve::swap_base_input_without_fees(
source_amount_less_fees,
swap_source_amount,
swap_destination_amount,
);
Some(SwapResult {
new_swap_source_amount: swap_source_amount.checked_add(source_amount)?,
new_swap_destination_amount: swap_destination_amount
.checked_sub(destination_amount_swapped)?,
source_amount_swapped: source_amount,
destination_amount_swapped,
total_fees: total_fees as u128,
protocol_fee: protocol_fee as u128,
creator_fee: creator_fee as u128,
})
}
pub fn swap_base_output(
destinsation_amount: u128,
swap_source_amount: u128,
swap_destination_amount: u128,
total_fees: u128,
protocol_fee_rate: u64,
creator_fee_rate: u64,
) -> Option<SwapResult> {
let protocol_fee = Fees::protocol_fee(total_fees, protocol_fee_rate)?;
let creator_fee = Fees::fund_fee(total_fees, creator_fee_rate)?;
let source_amount_swapped = ConstantProductCurve::swap_base_output_without_fees(
destinsation_amount,
swap_source_amount,
swap_destination_amount,
);
let source_amount = source_amount_swapped.checked_add(total_fees as u128)?;
Some(SwapResult {
new_swap_source_amount: swap_source_amount.checked_add(source_amount)?,
new_swap_destination_amount: swap_destination_amount
.checked_sub(destinsation_amount)?,
source_amount_swapped: source_amount,
destination_amount_swapped: destinsation_amount,
total_fees: total_fees as u128,
protocol_fee: protocol_fee as u128,
creator_fee: creator_fee as u128,
})
}
pub fn lp_tokens_to_trading_tokens(
lp_token_amount: u128,
lp_token_supply: u128,
swap_token_0_amount: u128,
swap_token_1_amount: u128,
round_direction: RoundDirection,
) -> Option<TradingTokenResult> {
ConstantProductCurve::lp_tokens_to_trading_tokens(
lp_token_amount,
lp_token_supply,
swap_token_0_amount,
swap_token_1_amount,
round_direction,
)
}
}
#[cfg(test)]
pub mod test {
use {
super::*, proptest::prelude::*, spl_math::precise_number::PreciseNumber,
spl_math::uint::U256,
};
pub const CONVERSION_BASIS_POINTS_GUARANTEE: u128 = 50;
pub fn normalized_value(
swap_token_a_amount: u128,
swap_token_b_amount: u128,
) -> Option<PreciseNumber> {
let swap_token_a_amount = PreciseNumber::new(swap_token_a_amount)?;
let swap_token_b_amount = PreciseNumber::new(swap_token_b_amount)?;
swap_token_a_amount
.checked_mul(&swap_token_b_amount)?
.sqrt()
}
pub fn check_curve_value_from_swap(
source_token_amount: u128,
swap_source_amount: u128,
swap_destination_amount: u128,
trade_direction: TradeDirection,
) {
let destination_amount_swapped = ConstantProductCurve::swap_base_input_without_fees(
source_token_amount,
swap_source_amount,
swap_destination_amount,
);
let (swap_token_0_amount, swap_token_1_amount) = match trade_direction {
TradeDirection::ZeroForOne => (swap_source_amount, swap_destination_amount),
TradeDirection::OneForZero => (swap_destination_amount, swap_source_amount),
};
let previous_value = swap_token_0_amount
.checked_mul(swap_token_1_amount)
.unwrap();
let new_swap_source_amount = swap_source_amount.checked_add(source_token_amount).unwrap();
let new_swap_destination_amount = swap_destination_amount
.checked_sub(destination_amount_swapped)
.unwrap();
let (swap_token_0_amount, swap_token_1_amount) = match trade_direction {
TradeDirection::ZeroForOne => (new_swap_source_amount, new_swap_destination_amount),
TradeDirection::OneForZero => (new_swap_destination_amount, new_swap_source_amount),
};
let new_value = swap_token_0_amount
.checked_mul(swap_token_1_amount)
.unwrap();
assert!(new_value >= previous_value);
}
pub fn check_pool_value_from_deposit(
lp_token_amount: u128,
lp_token_supply: u128,
swap_token_0_amount: u128,
swap_token_1_amount: u128,
) {
let deposit_result = CurveCalculator::lp_tokens_to_trading_tokens(
lp_token_amount,
lp_token_supply,
swap_token_0_amount,
swap_token_1_amount,
RoundDirection::Ceiling,
)
.unwrap();
let new_swap_token_0_amount = swap_token_0_amount + deposit_result.token_0_amount;
let new_swap_token_1_amount = swap_token_1_amount + deposit_result.token_1_amount;
let new_lp_token_supply = lp_token_supply + lp_token_amount;
let lp_token_supply = U256::from(lp_token_supply);
let new_lp_token_supply = U256::from(new_lp_token_supply);
let swap_token_0_amount = U256::from(swap_token_0_amount);
let new_swap_token_0_amount = U256::from(new_swap_token_0_amount);
let swap_token_b_amount = U256::from(swap_token_1_amount);
let new_swap_token_b_amount = U256::from(new_swap_token_1_amount);
assert!(
new_swap_token_0_amount * lp_token_supply >= swap_token_0_amount * new_lp_token_supply
);
assert!(
new_swap_token_b_amount * lp_token_supply >= swap_token_b_amount * new_lp_token_supply
);
}
pub fn check_pool_value_from_withdraw(
lp_token_amount: u128,
lp_token_supply: u128,
swap_token_0_amount: u128,
swap_token_1_amount: u128,
) {
let withdraw_result = CurveCalculator::lp_tokens_to_trading_tokens(
lp_token_amount,
lp_token_supply,
swap_token_0_amount,
swap_token_1_amount,
RoundDirection::Floor,
)
.unwrap();
let new_swap_token_0_amount = swap_token_0_amount - withdraw_result.token_0_amount;
let new_swap_token_1_amount = swap_token_1_amount - withdraw_result.token_1_amount;
let new_pool_token_supply = lp_token_supply - lp_token_amount;
let value = normalized_value(swap_token_0_amount, swap_token_1_amount).unwrap();
let new_value = normalized_value(new_swap_token_0_amount, new_swap_token_1_amount).unwrap();
let lp_token_supply = PreciseNumber::new(lp_token_supply).unwrap();
let new_lp_token_supply = PreciseNumber::new(new_pool_token_supply).unwrap();
assert!(new_value
.checked_mul(&lp_token_supply)
.unwrap()
.greater_than_or_equal(&value.checked_mul(&new_lp_token_supply).unwrap()));
}
prop_compose! {
pub fn total_and_intermediate(max_value: u64)(total in 1..max_value)
(intermediate in 1..total, total in Just(total))
-> (u64, u64) {
(total, intermediate)
}
}
}