use {
crate::{
curve::calculator::{
map_zero_to_none, CurveCalculator, DynPack, RoundDirection, SwapWithoutFeesResult,
TradeDirection, TradingTokenResult,
},
error::SwapError,
},
arrayref::{array_mut_ref, array_ref},
gemachain_program::{
program_error::ProgramError,
program_pack::{IsInitialized, Pack, Sealed},
},
gpl_math::{checked_ceil_div::CheckedCeilDiv, precise_number::PreciseNumber, uint::U256},
};
pub fn trading_tokens_to_pool_tokens(
token_b_price: u64,
source_amount: u128,
swap_token_a_amount: u128,
swap_token_b_amount: u128,
pool_supply: u128,
trade_direction: TradeDirection,
round_direction: RoundDirection,
) -> Option<u128> {
let token_b_price = U256::from(token_b_price);
let given_value = match trade_direction {
TradeDirection::AtoB => U256::from(source_amount),
TradeDirection::BtoA => U256::from(source_amount).checked_mul(token_b_price)?,
};
let total_value = U256::from(swap_token_b_amount)
.checked_mul(token_b_price)?
.checked_add(U256::from(swap_token_a_amount))?;
let pool_supply = U256::from(pool_supply);
match round_direction {
RoundDirection::Floor => Some(
pool_supply
.checked_mul(given_value)?
.checked_div(total_value)?
.as_u128(),
),
RoundDirection::Ceiling => Some(
pool_supply
.checked_mul(given_value)?
.checked_ceil_div(total_value)?
.0
.as_u128(),
),
}
}
#[derive(Clone, Debug, Default, PartialEq)]
pub struct ConstantPriceCurve {
pub token_b_price: u64,
}
impl CurveCalculator for ConstantPriceCurve {
fn swap_without_fees(
&self,
source_amount: u128,
_swap_source_amount: u128,
_swap_destination_amount: u128,
trade_direction: TradeDirection,
) -> Option<SwapWithoutFeesResult> {
let token_b_price = self.token_b_price as u128;
let (source_amount_swapped, destination_amount_swapped) = match trade_direction {
TradeDirection::BtoA => (source_amount, source_amount.checked_mul(token_b_price)?),
TradeDirection::AtoB => {
let destination_amount_swapped = source_amount.checked_div(token_b_price)?;
let mut source_amount_swapped = source_amount;
let remainder = source_amount_swapped.checked_rem(token_b_price)?;
if remainder > 0 {
source_amount_swapped = source_amount.checked_sub(remainder)?;
}
(source_amount_swapped, destination_amount_swapped)
}
};
let source_amount_swapped = map_zero_to_none(source_amount_swapped)?;
let destination_amount_swapped = map_zero_to_none(destination_amount_swapped)?;
Some(SwapWithoutFeesResult {
source_amount_swapped,
destination_amount_swapped,
})
}
fn pool_tokens_to_trading_tokens(
&self,
pool_tokens: u128,
pool_token_supply: u128,
swap_token_a_amount: u128,
swap_token_b_amount: u128,
round_direction: RoundDirection,
) -> Option<TradingTokenResult> {
let token_b_price = self.token_b_price as u128;
let total_value = self
.normalized_value(swap_token_a_amount, swap_token_b_amount)?
.to_imprecise()?;
let (token_a_amount, token_b_amount) = match round_direction {
RoundDirection::Floor => {
let token_a_amount = pool_tokens
.checked_mul(total_value)?
.checked_div(pool_token_supply)?;
let token_b_amount = pool_tokens
.checked_mul(total_value)?
.checked_div(token_b_price)?
.checked_div(pool_token_supply)?;
(token_a_amount, token_b_amount)
}
RoundDirection::Ceiling => {
let (token_a_amount, _) = pool_tokens
.checked_mul(total_value)?
.checked_ceil_div(pool_token_supply)?;
let (pool_value_as_token_b, _) = pool_tokens
.checked_mul(total_value)?
.checked_ceil_div(token_b_price)?;
let (token_b_amount, _) =
pool_value_as_token_b.checked_ceil_div(pool_token_supply)?;
(token_a_amount, token_b_amount)
}
};
Some(TradingTokenResult {
token_a_amount,
token_b_amount,
})
}
fn deposit_single_token_type(
&self,
source_amount: u128,
swap_token_a_amount: u128,
swap_token_b_amount: u128,
pool_supply: u128,
trade_direction: TradeDirection,
) -> Option<u128> {
trading_tokens_to_pool_tokens(
self.token_b_price,
source_amount,
swap_token_a_amount,
swap_token_b_amount,
pool_supply,
trade_direction,
RoundDirection::Floor,
)
}
fn withdraw_single_token_type_exact_out(
&self,
source_amount: u128,
swap_token_a_amount: u128,
swap_token_b_amount: u128,
pool_supply: u128,
trade_direction: TradeDirection,
) -> Option<u128> {
trading_tokens_to_pool_tokens(
self.token_b_price,
source_amount,
swap_token_a_amount,
swap_token_b_amount,
pool_supply,
trade_direction,
RoundDirection::Ceiling,
)
}
fn validate(&self) -> Result<(), SwapError> {
if self.token_b_price == 0 {
Err(SwapError::InvalidCurve)
} else {
Ok(())
}
}
fn validate_supply(&self, token_a_amount: u64, _token_b_amount: u64) -> Result<(), SwapError> {
if token_a_amount == 0 {
return Err(SwapError::EmptySupply);
}
Ok(())
}
fn normalized_value(
&self,
swap_token_a_amount: u128,
swap_token_b_amount: u128,
) -> Option<PreciseNumber> {
let swap_token_b_value = swap_token_b_amount.checked_mul(self.token_b_price as u128)?;
let value = if swap_token_b_value.saturating_sub(std::u64::MAX.into())
> (std::u128::MAX.saturating_sub(std::u64::MAX.into()))
{
swap_token_b_value
.checked_div(2)?
.checked_add(swap_token_a_amount.checked_div(2)?)?
} else {
swap_token_a_amount
.checked_add(swap_token_b_value)?
.checked_div(2)?
};
PreciseNumber::new(value)
}
}
impl IsInitialized for ConstantPriceCurve {
fn is_initialized(&self) -> bool {
true
}
}
impl Sealed for ConstantPriceCurve {}
impl Pack for ConstantPriceCurve {
const LEN: usize = 8;
fn pack_into_slice(&self, output: &mut [u8]) {
(self as &dyn DynPack).pack_into_slice(output);
}
fn unpack_from_slice(input: &[u8]) -> Result<ConstantPriceCurve, ProgramError> {
let token_b_price = array_ref![input, 0, 8];
Ok(Self {
token_b_price: u64::from_le_bytes(*token_b_price),
})
}
}
impl DynPack for ConstantPriceCurve {
fn pack_into_slice(&self, output: &mut [u8]) {
let token_b_price = array_mut_ref![output, 0, 8];
*token_b_price = self.token_b_price.to_le_bytes();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::curve::calculator::{
test::{
check_curve_value_from_swap, check_deposit_token_conversion,
check_withdraw_token_conversion, total_and_intermediate,
CONVERSION_BASIS_POINTS_GUARANTEE,
},
INITIAL_SWAP_POOL_AMOUNT,
};
use proptest::prelude::*;
#[test]
fn swap_calculation_no_price() {
let swap_source_amount: u128 = 0;
let swap_destination_amount: u128 = 0;
let source_amount: u128 = 100;
let token_b_price = 1;
let curve = ConstantPriceCurve { token_b_price };
let expected_result = SwapWithoutFeesResult {
source_amount_swapped: source_amount,
destination_amount_swapped: source_amount,
};
let result = curve
.swap_without_fees(
source_amount,
swap_source_amount,
swap_destination_amount,
TradeDirection::AtoB,
)
.unwrap();
assert_eq!(result, expected_result);
let result = curve
.swap_without_fees(
source_amount,
swap_source_amount,
swap_destination_amount,
TradeDirection::BtoA,
)
.unwrap();
assert_eq!(result, expected_result);
}
#[test]
fn pack_flat_curve() {
let token_b_price = 1_251_258;
let curve = ConstantPriceCurve { token_b_price };
let mut packed = [0u8; ConstantPriceCurve::LEN];
Pack::pack_into_slice(&curve, &mut packed[..]);
let unpacked = ConstantPriceCurve::unpack(&packed).unwrap();
assert_eq!(curve, unpacked);
let mut packed = vec![];
packed.extend_from_slice(&token_b_price.to_le_bytes());
let unpacked = ConstantPriceCurve::unpack(&packed).unwrap();
assert_eq!(curve, unpacked);
}
#[test]
fn swap_calculation_large_price() {
let token_b_price = 1123513u128;
let curve = ConstantPriceCurve {
token_b_price: token_b_price as u64,
};
let token_b_amount = 500u128;
let token_a_amount = token_b_amount * token_b_price;
let bad_result = curve.swap_without_fees(
token_b_price - 1u128,
token_a_amount,
token_b_amount,
TradeDirection::AtoB,
);
assert!(bad_result.is_none());
let bad_result =
curve.swap_without_fees(1u128, token_a_amount, token_b_amount, TradeDirection::AtoB);
assert!(bad_result.is_none());
let result = curve
.swap_without_fees(
token_b_price,
token_a_amount,
token_b_amount,
TradeDirection::AtoB,
)
.unwrap();
assert_eq!(result.source_amount_swapped, token_b_price);
assert_eq!(result.destination_amount_swapped, 1u128);
}
#[test]
fn swap_calculation_max_min() {
let token_b_price = u64::MAX as u128;
let curve = ConstantPriceCurve {
token_b_price: token_b_price as u64,
};
let token_b_amount = 1u128;
let token_a_amount = token_b_price;
let bad_result = curve.swap_without_fees(
token_b_price - 1u128,
token_a_amount,
token_b_amount,
TradeDirection::AtoB,
);
assert!(bad_result.is_none());
let bad_result =
curve.swap_without_fees(1u128, token_a_amount, token_b_amount, TradeDirection::AtoB);
assert!(bad_result.is_none());
let bad_result =
curve.swap_without_fees(0u128, token_a_amount, token_b_amount, TradeDirection::AtoB);
assert!(bad_result.is_none());
let result = curve
.swap_without_fees(
token_b_price,
token_a_amount,
token_b_amount,
TradeDirection::AtoB,
)
.unwrap();
assert_eq!(result.source_amount_swapped, token_b_price);
assert_eq!(result.destination_amount_swapped, 1u128);
}
proptest! {
#[test]
fn deposit_token_conversion_a_to_b(
source_token_amount in 2..u64::MAX,
swap_source_amount in 1..u64::MAX,
swap_destination_amount in 1..u64::MAX,
pool_supply in INITIAL_SWAP_POOL_AMOUNT..u64::MAX as u128,
token_b_price in 1..u64::MAX,
) {
let traded_source_amount = source_token_amount / 2;
prop_assume!(traded_source_amount / token_b_price >= 1);
prop_assume!(traded_source_amount / token_b_price <= swap_destination_amount);
let curve = ConstantPriceCurve {
token_b_price,
};
check_deposit_token_conversion(
&curve,
source_token_amount as u128,
swap_source_amount as u128,
swap_destination_amount as u128,
TradeDirection::AtoB,
pool_supply,
CONVERSION_BASIS_POINTS_GUARANTEE,
);
}
}
proptest! {
#[test]
fn deposit_token_conversion_b_to_a(
source_token_amount in 2..u32::MAX, swap_source_amount in 1..u64::MAX,
swap_destination_amount in 1..u64::MAX,
pool_supply in INITIAL_SWAP_POOL_AMOUNT..u64::MAX as u128,
token_b_price in 1..u32::MAX, ) {
let curve = ConstantPriceCurve {
token_b_price: token_b_price as u64,
};
let token_b_price = token_b_price as u128;
let source_token_amount = source_token_amount as u128;
let swap_source_amount = swap_source_amount as u128;
let swap_destination_amount = swap_destination_amount as u128;
prop_assume!(token_b_price * source_token_amount / 2 <= swap_destination_amount);
check_deposit_token_conversion(
&curve,
source_token_amount,
swap_source_amount,
swap_destination_amount,
TradeDirection::BtoA,
pool_supply,
CONVERSION_BASIS_POINTS_GUARANTEE,
);
}
}
proptest! {
#[test]
fn withdraw_token_conversion(
(pool_token_supply, pool_token_amount) in total_and_intermediate(),
swap_token_a_amount in 1..u64::MAX,
swap_token_b_amount in 1..u32::MAX, token_b_price in 1..u32::MAX, ) {
let curve = ConstantPriceCurve {
token_b_price: token_b_price as u64,
};
let token_b_price = token_b_price as u128;
let pool_token_amount = pool_token_amount as u128;
let pool_token_supply = pool_token_supply as u128;
let swap_token_a_amount = swap_token_a_amount as u128;
let swap_token_b_amount = swap_token_b_amount as u128;
let value = curve.normalized_value(swap_token_a_amount, swap_token_b_amount).unwrap();
prop_assume!(pool_token_amount * value.to_imprecise().unwrap() >= 2 * token_b_price * pool_token_supply);
let withdraw_result = curve
.pool_tokens_to_trading_tokens(
pool_token_amount,
pool_token_supply,
swap_token_a_amount,
swap_token_b_amount,
RoundDirection::Floor,
)
.unwrap();
prop_assume!(withdraw_result.token_a_amount <= swap_token_a_amount);
prop_assume!(withdraw_result.token_b_amount <= swap_token_b_amount);
check_withdraw_token_conversion(
&curve,
pool_token_amount,
pool_token_supply,
swap_token_a_amount,
swap_token_b_amount,
TradeDirection::AtoB,
CONVERSION_BASIS_POINTS_GUARANTEE
);
check_withdraw_token_conversion(
&curve,
pool_token_amount,
pool_token_supply,
swap_token_a_amount,
swap_token_b_amount,
TradeDirection::BtoA,
CONVERSION_BASIS_POINTS_GUARANTEE
);
}
}
proptest! {
#[test]
fn curve_value_does_not_decrease_from_swap_a_to_b(
source_token_amount in 1..u64::MAX,
swap_source_amount in 1..u64::MAX,
swap_destination_amount in 1..u64::MAX,
token_b_price in 1..u64::MAX,
) {
prop_assume!(source_token_amount / token_b_price >= 1);
prop_assume!(source_token_amount / token_b_price <= swap_destination_amount);
let curve = ConstantPriceCurve { token_b_price };
check_curve_value_from_swap(
&curve,
source_token_amount as u128,
swap_source_amount as u128,
swap_destination_amount as u128,
TradeDirection::AtoB
);
}
}
proptest! {
#[test]
fn curve_value_does_not_decrease_from_swap_b_to_a(
source_token_amount in 1..u32::MAX, swap_source_amount in 1..u64::MAX,
swap_destination_amount in 1..u64::MAX,
token_b_price in 1..u32::MAX, ) {
let curve = ConstantPriceCurve { token_b_price: token_b_price as u64 };
let token_b_price = token_b_price as u128;
let source_token_amount = source_token_amount as u128;
let swap_destination_amount = swap_destination_amount as u128;
let swap_source_amount = swap_source_amount as u128;
prop_assume!(token_b_price * source_token_amount <= swap_destination_amount);
check_curve_value_from_swap(
&curve,
source_token_amount,
swap_source_amount,
swap_destination_amount,
TradeDirection::BtoA
);
}
}
proptest! {
#[test]
fn curve_value_does_not_decrease_from_deposit(
pool_token_amount in 2..u64::MAX, pool_token_supply in INITIAL_SWAP_POOL_AMOUNT..u64::MAX as u128,
swap_token_a_amount in 1..u64::MAX,
swap_token_b_amount in 1..u32::MAX, token_b_price in 1..u32::MAX, ) {
let curve = ConstantPriceCurve { token_b_price: token_b_price as u64 };
let pool_token_amount = pool_token_amount as u128;
let pool_token_supply = pool_token_supply as u128;
let swap_token_a_amount = swap_token_a_amount as u128;
let swap_token_b_amount = swap_token_b_amount as u128;
let token_b_price = token_b_price as u128;
let value = curve.normalized_value(swap_token_a_amount, swap_token_b_amount).unwrap();
prop_assume!(pool_token_amount * value.to_imprecise().unwrap() >= 2 * token_b_price * pool_token_supply);
let deposit_result = curve
.pool_tokens_to_trading_tokens(
pool_token_amount,
pool_token_supply,
swap_token_a_amount,
swap_token_b_amount,
RoundDirection::Ceiling
)
.unwrap();
let new_swap_token_a_amount = swap_token_a_amount + deposit_result.token_a_amount;
let new_swap_token_b_amount = swap_token_b_amount + deposit_result.token_b_amount;
let new_pool_token_supply = pool_token_supply + pool_token_amount;
let new_value = curve.normalized_value(new_swap_token_a_amount, new_swap_token_b_amount).unwrap();
let pool_token_supply = PreciseNumber::new(pool_token_supply).unwrap();
let new_pool_token_supply = PreciseNumber::new(new_pool_token_supply).unwrap();
assert!(new_value.checked_mul(&pool_token_supply).unwrap().greater_than_or_equal(&value.checked_mul(&new_pool_token_supply).unwrap()));
}
}
proptest! {
#[test]
fn curve_value_does_not_decrease_from_withdraw(
(pool_token_supply, pool_token_amount) in total_and_intermediate(),
swap_token_a_amount in 1..u64::MAX,
swap_token_b_amount in 1..u32::MAX, token_b_price in 1..u32::MAX, ) {
let curve = ConstantPriceCurve { token_b_price: token_b_price as u64 };
let pool_token_amount = pool_token_amount as u128;
let pool_token_supply = pool_token_supply as u128;
let swap_token_a_amount = swap_token_a_amount as u128;
let swap_token_b_amount = swap_token_b_amount as u128;
let token_b_price = token_b_price as u128;
let value = curve.normalized_value(swap_token_a_amount, swap_token_b_amount).unwrap();
prop_assume!(pool_token_amount * value.to_imprecise().unwrap() >= 2 * token_b_price * pool_token_supply);
prop_assume!(pool_token_amount <= pool_token_supply);
let withdraw_result = curve
.pool_tokens_to_trading_tokens(
pool_token_amount,
pool_token_supply,
swap_token_a_amount,
swap_token_b_amount,
RoundDirection::Floor,
)
.unwrap();
prop_assume!(withdraw_result.token_a_amount <= swap_token_a_amount);
prop_assume!(withdraw_result.token_b_amount <= swap_token_b_amount);
let new_swap_token_a_amount = swap_token_a_amount - withdraw_result.token_a_amount;
let new_swap_token_b_amount = swap_token_b_amount - withdraw_result.token_b_amount;
let new_pool_token_supply = pool_token_supply - pool_token_amount;
let new_value = curve.normalized_value(new_swap_token_a_amount, new_swap_token_b_amount).unwrap();
let pool_token_supply = PreciseNumber::new(pool_token_supply).unwrap();
let new_pool_token_supply = PreciseNumber::new(new_pool_token_supply).unwrap();
assert!(new_value.checked_mul(&pool_token_supply).unwrap().greater_than_or_equal(&value.checked_mul(&new_pool_token_supply).unwrap()));
}
}
}