token-swap: Make single token withdrawal fair with better calcs (#1794)
* Separate deposit and withdraw single side calcs * token-swap: Make single token withdrawal fair with better calcs * Fix JS test calcs
This commit is contained in:
parent
6b3fbb8ff5
commit
5f692a0b61
|
@ -59,7 +59,7 @@ let currentFeeAmount = 0;
|
|||
// need to get slightly tweaked in the two cases.
|
||||
const SWAP_AMOUNT_IN = 100000;
|
||||
const SWAP_AMOUNT_OUT = SWAP_PROGRAM_OWNER_FEE_ADDRESS ? 90661 : 90674;
|
||||
const SWAP_FEE = SWAP_PROGRAM_OWNER_FEE_ADDRESS ? 22273 : 22276;
|
||||
const SWAP_FEE = SWAP_PROGRAM_OWNER_FEE_ADDRESS ? 22273 : 22277;
|
||||
const HOST_SWAP_FEE = SWAP_PROGRAM_OWNER_FEE_ADDRESS
|
||||
? Math.floor((SWAP_FEE * HOST_FEE_NUMERATOR) / HOST_FEE_DENOMINATOR)
|
||||
: 0;
|
||||
|
|
|
@ -7,7 +7,7 @@ use spl_token_swap_fuzz::{
|
|||
use spl_token_swap::{
|
||||
curve::{
|
||||
base::{CurveType, SwapCurve},
|
||||
calculator::CurveCalculator,
|
||||
calculator::{CurveCalculator, TradeDirection},
|
||||
constant_product::ConstantProductCurve,
|
||||
fees::Fees,
|
||||
},
|
||||
|
@ -60,13 +60,6 @@ enum FuzzInstruction {
|
|||
},
|
||||
}
|
||||
|
||||
/// Helper enum to tell which direction a swap is meant to go.
|
||||
#[derive(Debug, Arbitrary, Clone)]
|
||||
enum TradeDirection {
|
||||
AtoB,
|
||||
BtoA,
|
||||
}
|
||||
|
||||
/// Use u8 as an account id to simplify the address space and re-use accounts
|
||||
/// more often.
|
||||
type AccountId = u8;
|
||||
|
|
|
@ -6,7 +6,7 @@ use solana_program::{
|
|||
};
|
||||
|
||||
use crate::curve::{
|
||||
calculator::{CurveCalculator, RoundDirection, SwapWithoutFeesResult, TradeDirection},
|
||||
calculator::{CurveCalculator, SwapWithoutFeesResult, TradeDirection},
|
||||
constant_price::ConstantPriceCurve,
|
||||
constant_product::ConstantProductCurve,
|
||||
fees::Fees,
|
||||
|
@ -17,7 +17,11 @@ use arrayref::{array_mut_ref, array_ref, array_refs, mut_array_refs};
|
|||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "fuzz")]
|
||||
use arbitrary::Arbitrary;
|
||||
|
||||
/// Curve types supported by the token-swap program.
|
||||
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum CurveType {
|
||||
|
@ -100,16 +104,14 @@ impl SwapCurve {
|
|||
})
|
||||
}
|
||||
|
||||
/// Get the amount of pool tokens for the given amount of token A or B
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn trading_tokens_to_pool_tokens(
|
||||
/// Get the amount of pool tokens for the deposited amount of token A or B
|
||||
pub 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,
|
||||
round_direction: RoundDirection,
|
||||
fees: &Fees,
|
||||
) -> Option<u128> {
|
||||
if source_amount == 0 {
|
||||
|
@ -121,13 +123,40 @@ impl SwapCurve {
|
|||
let half_source_amount = std::cmp::max(1, source_amount.checked_div(2)?);
|
||||
let trade_fee = fees.trading_fee(half_source_amount)?;
|
||||
let source_amount = source_amount.checked_sub(trade_fee)?;
|
||||
self.calculator.trading_tokens_to_pool_tokens(
|
||||
self.calculator.deposit_single_token_type(
|
||||
source_amount,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
pool_supply,
|
||||
trade_direction,
|
||||
)
|
||||
}
|
||||
|
||||
/// Get the amount of pool tokens for the withdrawn amount of token A or B
|
||||
pub 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,
|
||||
fees: &Fees,
|
||||
) -> Option<u128> {
|
||||
if source_amount == 0 {
|
||||
return Some(0);
|
||||
}
|
||||
// Get the trading fee incurred if *half* the source amount is swapped
|
||||
// for the other side. Reference at:
|
||||
// https://github.com/balancer-labs/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BMath.sol#L117
|
||||
let half_source_amount = std::cmp::max(1, source_amount.checked_div(2)?);
|
||||
let trade_fee = fees.trading_fee(half_source_amount)?;
|
||||
let source_amount = source_amount.checked_sub(trade_fee)?;
|
||||
self.calculator.withdraw_single_token_type_exact_out(
|
||||
source_amount,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
pool_supply,
|
||||
trade_direction,
|
||||
round_direction,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
use {crate::error::SwapError, spl_math::precise_number::PreciseNumber, std::fmt::Debug};
|
||||
|
||||
#[cfg(feature = "fuzz")]
|
||||
use arbitrary::Arbitrary;
|
||||
|
||||
/// Initial amount of pool tokens for swap contract, hard-coded to something
|
||||
/// "sensible" given a maximum of u128.
|
||||
/// Note that on Ethereum, Uniswap uses the geometric mean of all provided
|
||||
|
@ -23,6 +26,7 @@ pub fn map_zero_to_none(x: u128) -> Option<u128> {
|
|||
|
||||
/// The direction of a trade, since curves can be specialized to treat each
|
||||
/// token differently (by adding offsets or weights)
|
||||
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TradeDirection {
|
||||
|
@ -108,23 +112,39 @@ pub trait CurveCalculator: Debug + DynPack {
|
|||
round_direction: RoundDirection,
|
||||
) -> Option<TradingTokenResult>;
|
||||
|
||||
/// Get the amount of pool tokens for the given amount of token A or B.
|
||||
/// Get the amount of pool tokens for the deposited amount of token A or B.
|
||||
///
|
||||
/// This is used for single-sided deposits or withdrawals and owner trade
|
||||
/// fee calculation. It essentially performs a swap followed by a deposit,
|
||||
/// or a withdrawal followed by a swap. Because a swap is implicitly
|
||||
/// performed, this will change the spot price of the pool.
|
||||
/// This is used for single-sided deposits. It essentially performs a swap
|
||||
/// followed by a deposit. Because a swap is implicitly performed, this will
|
||||
/// change the spot price of the pool.
|
||||
///
|
||||
/// See more background for the calculation at:
|
||||
/// https://balancer.finance/whitepaper/#single-asset-deposit-withdrawal
|
||||
fn trading_tokens_to_pool_tokens(
|
||||
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>;
|
||||
|
||||
/// Get the amount of pool tokens for the withdrawn amount of token A or B.
|
||||
///
|
||||
/// This is used for single-sided withdrawals and owner trade fee
|
||||
/// calculation. It essentially performs a withdrawal followed by a swap.
|
||||
/// Because a swap is implicitly performed, this will change the spot price
|
||||
/// of the pool.
|
||||
///
|
||||
/// See more background for the calculation at:
|
||||
/// https://balancer.finance/whitepaper/#single-asset-deposit-withdrawal
|
||||
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,
|
||||
round_direction: RoundDirection,
|
||||
) -> Option<u128>;
|
||||
|
||||
/// Validate that the given curve has no invalid parameters
|
||||
|
@ -187,7 +207,7 @@ pub mod test {
|
|||
/// We guarantee that the relative error between depositing one side and
|
||||
/// performing a swap plus deposit will be at most some epsilon provided by
|
||||
/// the curve. Most curves guarantee accuracy within 0.5%.
|
||||
pub fn check_pool_token_conversion(
|
||||
pub fn check_deposit_token_conversion(
|
||||
curve: &dyn CurveCalculator,
|
||||
source_token_amount: u128,
|
||||
swap_source_amount: u128,
|
||||
|
@ -213,13 +233,12 @@ pub mod test {
|
|||
|
||||
// base amount
|
||||
let pool_tokens_from_one_side = curve
|
||||
.trading_tokens_to_pool_tokens(
|
||||
.deposit_single_token_type(
|
||||
source_token_amount,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
pool_supply,
|
||||
trade_direction,
|
||||
RoundDirection::Floor,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -235,23 +254,21 @@ pub mod test {
|
|||
),
|
||||
};
|
||||
let pool_tokens_from_source = curve
|
||||
.trading_tokens_to_pool_tokens(
|
||||
.deposit_single_token_type(
|
||||
source_token_amount - results.source_amount_swapped,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
pool_supply,
|
||||
trade_direction,
|
||||
RoundDirection::Floor,
|
||||
)
|
||||
.unwrap();
|
||||
let pool_tokens_from_destination = curve
|
||||
.trading_tokens_to_pool_tokens(
|
||||
.deposit_single_token_type(
|
||||
results.destination_amount_swapped,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
pool_supply + pool_tokens_from_source,
|
||||
opposite_direction,
|
||||
RoundDirection::Floor,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
|
@ -275,6 +292,90 @@ pub mod test {
|
|||
);
|
||||
}
|
||||
|
||||
/// Test function to check that withdrawing token A is the same as withdrawing
|
||||
/// both and swapping one side.
|
||||
/// Since calculations use unsigned integers, there will be truncation at
|
||||
/// some point, meaning we can't have perfect equality.
|
||||
/// We guarantee that the relative error between withdrawing one side and
|
||||
/// performing a withdraw plus a swap will be at most some epsilon provided by
|
||||
/// the curve. Most curves guarantee accuracy within 0.5%.
|
||||
pub fn check_withdraw_token_conversion(
|
||||
curve: &dyn CurveCalculator,
|
||||
pool_token_amount: u128,
|
||||
pool_token_supply: u128,
|
||||
swap_token_a_amount: u128,
|
||||
swap_token_b_amount: u128,
|
||||
trade_direction: TradeDirection,
|
||||
epsilon_in_basis_points: u128,
|
||||
) {
|
||||
// withdraw the pool tokens
|
||||
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();
|
||||
|
||||
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;
|
||||
|
||||
// swap one side of them
|
||||
let source_token_amount = match trade_direction {
|
||||
TradeDirection::AtoB => {
|
||||
let results = curve
|
||||
.swap_without_fees(
|
||||
withdraw_result.token_a_amount,
|
||||
new_swap_token_a_amount,
|
||||
new_swap_token_b_amount,
|
||||
trade_direction,
|
||||
)
|
||||
.unwrap();
|
||||
withdraw_result.token_b_amount + results.destination_amount_swapped
|
||||
}
|
||||
TradeDirection::BtoA => {
|
||||
let results = curve
|
||||
.swap_without_fees(
|
||||
withdraw_result.token_b_amount,
|
||||
new_swap_token_b_amount,
|
||||
new_swap_token_a_amount,
|
||||
trade_direction,
|
||||
)
|
||||
.unwrap();
|
||||
withdraw_result.token_a_amount + results.destination_amount_swapped
|
||||
}
|
||||
};
|
||||
|
||||
// see how many pool tokens it would cost to withdraw one side for the
|
||||
// total amount of tokens, should be close!
|
||||
let opposite_direction = trade_direction.opposite();
|
||||
let pool_token_amount_from_single_side_withdraw = curve
|
||||
.withdraw_single_token_type_exact_out(
|
||||
source_token_amount,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
pool_token_supply,
|
||||
opposite_direction,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// slippage due to rounding or truncation errors
|
||||
let epsilon = std::cmp::max(1, pool_token_amount * epsilon_in_basis_points / 10000);
|
||||
let difference = if pool_token_amount >= pool_token_amount_from_single_side_withdraw {
|
||||
pool_token_amount - pool_token_amount_from_single_side_withdraw
|
||||
} else {
|
||||
pool_token_amount_from_single_side_withdraw - pool_token_amount
|
||||
};
|
||||
assert!(
|
||||
difference <= epsilon,
|
||||
"difference expected to be less than {}, actually {}",
|
||||
epsilon,
|
||||
difference
|
||||
);
|
||||
}
|
||||
|
||||
/// Test function checking that a swap never reduces the overall value of
|
||||
/// the pool.
|
||||
///
|
||||
|
|
|
@ -15,6 +15,46 @@ use {
|
|||
spl_math::{checked_ceil_div::CheckedCeilDiv, precise_number::PreciseNumber, uint::U256},
|
||||
};
|
||||
|
||||
/// Get the amount of pool tokens for the given amount of token A or B.
|
||||
///
|
||||
/// The constant product implementation uses the Balancer formulas found at
|
||||
/// https://balancer.finance/whitepaper/#single-asset-deposit, specifically
|
||||
/// in the case for 2 tokens, each weighted at 1/2.
|
||||
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(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// ConstantPriceCurve struct implementing CurveCalculator
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct ConstantPriceCurve {
|
||||
|
@ -107,39 +147,42 @@ impl CurveCalculator for ConstantPriceCurve {
|
|||
/// Get the amount of pool tokens for the given amount of token A and B
|
||||
/// For the constant price curve, the total value of the pool is weighted
|
||||
/// by the price of token B.
|
||||
fn trading_tokens_to_pool_tokens(
|
||||
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,
|
||||
round_direction: RoundDirection,
|
||||
) -> Option<u128> {
|
||||
let token_b_price = U256::from(self.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(),
|
||||
),
|
||||
}
|
||||
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> {
|
||||
|
@ -221,7 +264,8 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::curve::calculator::{
|
||||
test::{
|
||||
check_curve_value_from_swap, check_pool_token_conversion, total_and_intermediate,
|
||||
check_curve_value_from_swap, check_deposit_token_conversion,
|
||||
check_withdraw_token_conversion, total_and_intermediate,
|
||||
CONVERSION_BASIS_POINTS_GUARANTEE,
|
||||
},
|
||||
INITIAL_SWAP_POOL_AMOUNT,
|
||||
|
@ -343,7 +387,7 @@ mod tests {
|
|||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn pool_token_conversion_a_to_b(
|
||||
fn deposit_token_conversion_a_to_b(
|
||||
// in the pool token conversion calcs, we simulate trading half of
|
||||
// source_token_amount, so this needs to be at least 2
|
||||
source_token_amount in 2..u64::MAX,
|
||||
|
@ -361,7 +405,7 @@ mod tests {
|
|||
let curve = ConstantPriceCurve {
|
||||
token_b_price,
|
||||
};
|
||||
check_pool_token_conversion(
|
||||
check_deposit_token_conversion(
|
||||
&curve,
|
||||
source_token_amount as u128,
|
||||
swap_source_amount as u128,
|
||||
|
@ -375,7 +419,7 @@ mod tests {
|
|||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn pool_token_conversion_b_to_a(
|
||||
fn deposit_token_conversion_b_to_a(
|
||||
// in the pool token conversion calcs, we simulate trading half of
|
||||
// source_token_amount, so this needs to be at least 2
|
||||
source_token_amount in 2..u32::MAX, // kept small to avoid proptest rejections
|
||||
|
@ -395,7 +439,7 @@ mod tests {
|
|||
// on the other side to complete the swap
|
||||
prop_assume!(token_b_price * source_token_amount / 2 <= swap_destination_amount);
|
||||
|
||||
check_pool_token_conversion(
|
||||
check_deposit_token_conversion(
|
||||
&curve,
|
||||
source_token_amount,
|
||||
swap_source_amount,
|
||||
|
@ -407,6 +451,61 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
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, // kept small to avoid proptest rejections
|
||||
token_b_price in 1..u32::MAX, // kept small to avoid proptest rejections
|
||||
) {
|
||||
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();
|
||||
|
||||
// Make sure we trade at least one of each token
|
||||
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(
|
||||
|
|
|
@ -92,12 +92,12 @@ pub fn pool_tokens_to_trading_tokens(
|
|||
})
|
||||
}
|
||||
|
||||
/// Get the amount of pool tokens for the given amount of token A or B.
|
||||
/// Get the amount of pool tokens for the deposited amount of token A or B.
|
||||
///
|
||||
/// The constant product implementation uses the Balancer formulas found at
|
||||
/// https://balancer.finance/whitepaper/#single-asset-deposit, specifically
|
||||
/// in the case for 2 tokens, each weighted at 1/2.
|
||||
pub fn trading_tokens_to_pool_tokens(
|
||||
pub fn deposit_single_token_type(
|
||||
source_amount: u128,
|
||||
swap_token_a_amount: u128,
|
||||
swap_token_b_amount: u128,
|
||||
|
@ -123,6 +123,37 @@ pub fn trading_tokens_to_pool_tokens(
|
|||
}
|
||||
}
|
||||
|
||||
/// Get the amount of pool tokens for the withdrawn amount of token A or B.
|
||||
///
|
||||
/// The constant product implementation uses the Balancer formulas found at
|
||||
/// https://balancer.finance/whitepaper/#single-asset-withdrawal, specifically
|
||||
/// in the case for 2 tokens, each weighted at 1/2.
|
||||
pub fn withdraw_single_token_type_exact_out(
|
||||
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 swap_source_amount = match trade_direction {
|
||||
TradeDirection::AtoB => swap_token_a_amount,
|
||||
TradeDirection::BtoA => swap_token_b_amount,
|
||||
};
|
||||
let swap_source_amount = PreciseNumber::new(swap_source_amount)?;
|
||||
let source_amount = PreciseNumber::new(source_amount)?;
|
||||
let ratio = source_amount.checked_div(&swap_source_amount)?;
|
||||
let one = PreciseNumber::new(1)?;
|
||||
let base = one.checked_sub(&ratio)?;
|
||||
let root = one.checked_sub(&base.sqrt()?)?;
|
||||
let pool_supply = PreciseNumber::new(pool_supply)?;
|
||||
let pool_tokens = pool_supply.checked_mul(&root)?;
|
||||
match round_direction {
|
||||
RoundDirection::Floor => pool_tokens.floor()?.to_imprecise(),
|
||||
RoundDirection::Ceiling => pool_tokens.ceiling()?.to_imprecise(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the total normalized value of the curve given the liquidity
|
||||
/// parameters.
|
||||
///
|
||||
|
@ -170,23 +201,40 @@ impl CurveCalculator for ConstantProductCurve {
|
|||
)
|
||||
}
|
||||
|
||||
/// Get the amount of pool tokens for the given amount of token A or B.
|
||||
fn trading_tokens_to_pool_tokens(
|
||||
/// Get the amount of pool tokens for the deposited amount of token A or B.
|
||||
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,
|
||||
round_direction: RoundDirection,
|
||||
) -> Option<u128> {
|
||||
trading_tokens_to_pool_tokens(
|
||||
deposit_single_token_type(
|
||||
source_amount,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
pool_supply,
|
||||
trade_direction,
|
||||
round_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> {
|
||||
withdraw_single_token_type_exact_out(
|
||||
source_amount,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
pool_supply,
|
||||
trade_direction,
|
||||
RoundDirection::Ceiling,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -230,8 +278,9 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::curve::calculator::{
|
||||
test::{
|
||||
check_curve_value_from_swap, check_pool_token_conversion,
|
||||
check_pool_value_from_deposit, check_pool_value_from_withdraw, total_and_intermediate,
|
||||
check_curve_value_from_swap, check_deposit_token_conversion,
|
||||
check_pool_value_from_deposit, check_pool_value_from_withdraw,
|
||||
check_withdraw_token_conversion, total_and_intermediate,
|
||||
CONVERSION_BASIS_POINTS_GUARANTEE,
|
||||
},
|
||||
RoundDirection, INITIAL_SWAP_POOL_AMOUNT,
|
||||
|
@ -367,7 +416,7 @@ mod tests {
|
|||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn pool_token_conversion(
|
||||
fn deposit_token_conversion(
|
||||
// in the pool token conversion calcs, we simulate trading half of
|
||||
// source_token_amount, so this needs to be at least 2
|
||||
source_token_amount in 2..u64::MAX,
|
||||
|
@ -376,7 +425,7 @@ mod tests {
|
|||
pool_supply in INITIAL_SWAP_POOL_AMOUNT..u64::MAX as u128,
|
||||
) {
|
||||
let curve = ConstantProductCurve {};
|
||||
check_pool_token_conversion(
|
||||
check_deposit_token_conversion(
|
||||
&curve,
|
||||
source_token_amount as u128,
|
||||
swap_source_amount as u128,
|
||||
|
@ -386,7 +435,7 @@ mod tests {
|
|||
CONVERSION_BASIS_POINTS_GUARANTEE,
|
||||
);
|
||||
|
||||
check_pool_token_conversion(
|
||||
check_deposit_token_conversion(
|
||||
&curve,
|
||||
source_token_amount as u128,
|
||||
swap_source_amount as u128,
|
||||
|
@ -398,6 +447,35 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
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..u64::MAX,
|
||||
) {
|
||||
let curve = ConstantProductCurve {};
|
||||
check_withdraw_token_conversion(
|
||||
&curve,
|
||||
pool_token_amount as u128,
|
||||
pool_token_supply as u128,
|
||||
swap_token_a_amount as u128,
|
||||
swap_token_b_amount as u128,
|
||||
TradeDirection::AtoB,
|
||||
CONVERSION_BASIS_POINTS_GUARANTEE
|
||||
);
|
||||
check_withdraw_token_conversion(
|
||||
&curve,
|
||||
pool_token_amount as u128,
|
||||
pool_token_supply as u128,
|
||||
swap_token_a_amount as u128,
|
||||
swap_token_b_amount as u128,
|
||||
TradeDirection::BtoA,
|
||||
CONVERSION_BASIS_POINTS_GUARANTEE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn curve_value_does_not_decrease_from_swap(
|
||||
|
|
|
@ -8,8 +8,8 @@ use {
|
|||
TradingTokenResult,
|
||||
},
|
||||
constant_product::{
|
||||
normalized_value, pool_tokens_to_trading_tokens, swap,
|
||||
trading_tokens_to_pool_tokens,
|
||||
deposit_single_token_type, normalized_value, pool_tokens_to_trading_tokens, swap,
|
||||
withdraw_single_token_type_exact_out,
|
||||
},
|
||||
},
|
||||
error::SwapError,
|
||||
|
@ -78,23 +78,41 @@ impl CurveCalculator for OffsetCurve {
|
|||
|
||||
/// Get the amount of pool tokens for the given amount of token A and B,
|
||||
/// taking into account the offset
|
||||
fn trading_tokens_to_pool_tokens(
|
||||
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,
|
||||
round_direction: RoundDirection,
|
||||
) -> Option<u128> {
|
||||
let token_b_offset = self.token_b_offset as u128;
|
||||
trading_tokens_to_pool_tokens(
|
||||
deposit_single_token_type(
|
||||
source_amount,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount.checked_add(token_b_offset)?,
|
||||
pool_supply,
|
||||
trade_direction,
|
||||
round_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> {
|
||||
let token_b_offset = self.token_b_offset as u128;
|
||||
withdraw_single_token_type_exact_out(
|
||||
source_amount,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount.checked_add(token_b_offset)?,
|
||||
pool_supply,
|
||||
trade_direction,
|
||||
RoundDirection::Ceiling,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -170,8 +188,9 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::curve::calculator::{
|
||||
test::{
|
||||
check_curve_value_from_swap, check_pool_token_conversion,
|
||||
check_pool_value_from_deposit, check_pool_value_from_withdraw, total_and_intermediate,
|
||||
check_curve_value_from_swap, check_deposit_token_conversion,
|
||||
check_pool_value_from_deposit, check_pool_value_from_withdraw,
|
||||
check_withdraw_token_conversion, total_and_intermediate,
|
||||
CONVERSION_BASIS_POINTS_GUARANTEE,
|
||||
},
|
||||
INITIAL_SWAP_POOL_AMOUNT,
|
||||
|
@ -297,7 +316,7 @@ mod tests {
|
|||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn pool_token_conversion_a_to_b(
|
||||
fn deposit_token_conversion_a_to_b(
|
||||
// in the pool token conversion calcs, we simulate trading half of
|
||||
// source_token_amount, so this needs to be at least 2
|
||||
source_token_amount in 2..u64::MAX,
|
||||
|
@ -326,7 +345,7 @@ mod tests {
|
|||
// The invariant needs to fit in a u128.
|
||||
// invariant = swap_source_amount * (swap_destination_amount + token_b_offset)
|
||||
prop_assume!(!(swap_destination_amount + token_b_offset).overflowing_mul(swap_source_amount).1);
|
||||
check_pool_token_conversion(
|
||||
check_deposit_token_conversion(
|
||||
&curve,
|
||||
source_token_amount,
|
||||
swap_source_amount,
|
||||
|
@ -340,7 +359,7 @@ mod tests {
|
|||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn pool_token_conversion_b_to_a(
|
||||
fn deposit_token_conversion_b_to_a(
|
||||
// in the pool token conversion calcs, we simulate trading half of
|
||||
// source_token_amount, so this needs to be at least 2
|
||||
source_token_amount in 2..u64::MAX,
|
||||
|
@ -360,7 +379,7 @@ mod tests {
|
|||
// The invariant needs to fit in a u128
|
||||
// invariant = swap_destination_amount * (swap_source_amount + token_b_offset)
|
||||
prop_assume!(!(swap_source_amount + token_b_offset).overflowing_mul(swap_destination_amount).1);
|
||||
check_pool_token_conversion(
|
||||
check_deposit_token_conversion(
|
||||
&curve,
|
||||
source_token_amount,
|
||||
swap_source_amount,
|
||||
|
@ -372,6 +391,59 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
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, token_b_offset) in values_sum_within_u64(),
|
||||
) {
|
||||
let curve = OffsetCurve {
|
||||
token_b_offset,
|
||||
};
|
||||
|
||||
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_offset = token_b_offset as u128;
|
||||
let pool_token_amount = pool_token_amount as u128;
|
||||
let pool_token_supply = pool_token_supply as u128;
|
||||
// The invariant needs to fit in a u128
|
||||
// invariant = swap_destination_amount * (swap_source_amount + token_b_offset)
|
||||
prop_assume!(!(swap_token_b_amount + token_b_offset).overflowing_mul(swap_token_a_amount).1);
|
||||
prop_assume!(pool_token_amount * swap_token_a_amount / pool_token_supply >= 1);
|
||||
prop_assume!(pool_token_amount * (swap_token_b_amount + token_b_offset) / pool_token_supply >= 1);
|
||||
// make sure we don't overdraw from either side
|
||||
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_b_amount <= swap_token_b_amount); // avoid overdrawing to 0 for calc
|
||||
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(
|
||||
|
|
|
@ -200,14 +200,13 @@ impl CurveCalculator for StableCurve {
|
|||
|
||||
/// Get the amount of pool tokens for the given amount of token A or B.
|
||||
/// Re-implementation of `calc_token_amount`: https://github.com/curvefi/curve-contract/blob/80bbe179083c9a7062e4c482b0be3bfb7501f2bd/contracts/pool-templates/base/SwapTemplateBase.vy#L267
|
||||
fn trading_tokens_to_pool_tokens(
|
||||
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,
|
||||
round_direction: RoundDirection,
|
||||
) -> Option<u128> {
|
||||
if source_amount == 0 {
|
||||
return Some(0);
|
||||
|
@ -231,10 +230,40 @@ impl CurveCalculator for StableCurve {
|
|||
let diff = d1.checked_sub(&d0)?;
|
||||
let final_amount =
|
||||
(diff.checked_mul(&PreciseNumber::new(pool_supply)?))?.checked_div(&d0)?;
|
||||
match round_direction {
|
||||
RoundDirection::Floor => final_amount.floor()?.to_imprecise(),
|
||||
RoundDirection::Ceiling => final_amount.ceiling()?.to_imprecise(),
|
||||
final_amount.floor()?.to_imprecise()
|
||||
}
|
||||
|
||||
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> {
|
||||
if source_amount == 0 {
|
||||
return Some(0);
|
||||
}
|
||||
let leverage = self.amp.checked_mul(N_COINS as u64)?;
|
||||
let d0 = PreciseNumber::new(compute_d(
|
||||
leverage,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
)?)?;
|
||||
let (withdraw_token_amount, other_token_amount) = match trade_direction {
|
||||
TradeDirection::AtoB => (swap_token_a_amount, swap_token_b_amount),
|
||||
TradeDirection::BtoA => (swap_token_b_amount, swap_token_a_amount),
|
||||
};
|
||||
let updated_deposit_token_amount = withdraw_token_amount.checked_sub(source_amount)?;
|
||||
let d1 = PreciseNumber::new(compute_d(
|
||||
leverage,
|
||||
updated_deposit_token_amount,
|
||||
other_token_amount,
|
||||
)?)?;
|
||||
let diff = d0.checked_sub(&d1)?;
|
||||
let final_amount =
|
||||
(diff.checked_mul(&PreciseNumber::new(pool_supply)?))?.checked_div(&d0)?;
|
||||
final_amount.ceiling()?.to_imprecise()
|
||||
}
|
||||
|
||||
fn normalized_value(
|
||||
|
@ -315,8 +344,9 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::curve::calculator::{
|
||||
test::{
|
||||
check_curve_value_from_swap, check_pool_token_conversion,
|
||||
check_pool_value_from_deposit, check_pool_value_from_withdraw, total_and_intermediate,
|
||||
check_curve_value_from_swap, check_deposit_token_conversion,
|
||||
check_pool_value_from_deposit, check_pool_value_from_withdraw,
|
||||
check_withdraw_token_conversion, total_and_intermediate,
|
||||
CONVERSION_BASIS_POINTS_GUARANTEE,
|
||||
},
|
||||
RoundDirection, INITIAL_SWAP_POOL_AMOUNT,
|
||||
|
@ -502,7 +532,7 @@ mod tests {
|
|||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn pool_token_conversion(
|
||||
fn deposit_token_conversion(
|
||||
// in the pool token conversion calcs, we simulate trading half of
|
||||
// source_token_amount, so this needs to be at least 2
|
||||
source_token_amount in 2..u64::MAX,
|
||||
|
@ -512,7 +542,7 @@ mod tests {
|
|||
amp in 1..100u64,
|
||||
) {
|
||||
let curve = StableCurve { amp };
|
||||
check_pool_token_conversion(
|
||||
check_deposit_token_conversion(
|
||||
&curve,
|
||||
source_token_amount as u128,
|
||||
swap_source_amount as u128,
|
||||
|
@ -522,7 +552,7 @@ mod tests {
|
|||
CONVERSION_BASIS_POINTS_GUARANTEE * 100,
|
||||
);
|
||||
|
||||
check_pool_token_conversion(
|
||||
check_deposit_token_conversion(
|
||||
&curve,
|
||||
source_token_amount as u128,
|
||||
swap_source_amount as u128,
|
||||
|
@ -533,4 +563,34 @@ mod tests {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
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..u64::MAX,
|
||||
amp in 1..100u64,
|
||||
) {
|
||||
let curve = StableCurve { amp };
|
||||
check_withdraw_token_conversion(
|
||||
&curve,
|
||||
pool_token_amount as u128,
|
||||
pool_token_supply as u128,
|
||||
swap_token_a_amount as u128,
|
||||
swap_token_b_amount as u128,
|
||||
TradeDirection::AtoB,
|
||||
CONVERSION_BASIS_POINTS_GUARANTEE
|
||||
);
|
||||
check_withdraw_token_conversion(
|
||||
&curve,
|
||||
pool_token_amount as u128,
|
||||
pool_token_supply as u128,
|
||||
swap_token_a_amount as u128,
|
||||
swap_token_b_amount as u128,
|
||||
TradeDirection::BtoA,
|
||||
CONVERSION_BASIS_POINTS_GUARANTEE
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -427,13 +427,12 @@ impl Processor {
|
|||
|
||||
let mut pool_token_amount = token_swap
|
||||
.swap_curve()
|
||||
.trading_tokens_to_pool_tokens(
|
||||
.withdraw_single_token_type_exact_out(
|
||||
result.owner_fee,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
to_u128(pool_mint.supply)?,
|
||||
trade_direction,
|
||||
RoundDirection::Ceiling,
|
||||
token_swap.fees(),
|
||||
)
|
||||
.ok_or(SwapError::FeeCalculationFailure)?;
|
||||
|
@ -782,13 +781,12 @@ impl Processor {
|
|||
let pool_token_amount = if pool_mint_supply > 0 {
|
||||
token_swap
|
||||
.swap_curve()
|
||||
.trading_tokens_to_pool_tokens(
|
||||
.deposit_single_token_type(
|
||||
to_u128(source_token_amount)?,
|
||||
to_u128(swap_token_a.amount)?,
|
||||
to_u128(swap_token_b.amount)?,
|
||||
pool_mint_supply,
|
||||
trade_direction,
|
||||
RoundDirection::Floor,
|
||||
token_swap.fees(),
|
||||
)
|
||||
.ok_or(SwapError::ZeroTradingTokens)?
|
||||
|
@ -896,36 +894,17 @@ impl Processor {
|
|||
|
||||
let pool_mint = Self::unpack_mint(pool_mint_info, &token_swap.token_program_id())?;
|
||||
let pool_mint_supply = to_u128(pool_mint.supply)?;
|
||||
let (swap_token_a_amount, swap_token_b_amount) = match trade_direction {
|
||||
TradeDirection::AtoB => (
|
||||
to_u128(
|
||||
swap_token_a
|
||||
.amount
|
||||
.checked_sub(destination_token_amount)
|
||||
.ok_or(SwapError::CalculationFailure)?,
|
||||
)?,
|
||||
to_u128(swap_token_b.amount)?,
|
||||
),
|
||||
TradeDirection::BtoA => (
|
||||
to_u128(swap_token_a.amount)?,
|
||||
to_u128(
|
||||
swap_token_b
|
||||
.amount
|
||||
.checked_sub(destination_token_amount)
|
||||
.ok_or(SwapError::CalculationFailure)?,
|
||||
)?,
|
||||
),
|
||||
};
|
||||
let swap_token_a_amount = to_u128(swap_token_a.amount)?;
|
||||
let swap_token_b_amount = to_u128(swap_token_b.amount)?;
|
||||
|
||||
let burn_pool_token_amount = token_swap
|
||||
.swap_curve()
|
||||
.trading_tokens_to_pool_tokens(
|
||||
.withdraw_single_token_type_exact_out(
|
||||
to_u128(destination_token_amount)?,
|
||||
swap_token_a_amount,
|
||||
swap_token_b_amount,
|
||||
pool_mint_supply,
|
||||
trade_direction,
|
||||
RoundDirection::Ceiling,
|
||||
token_swap.fees(),
|
||||
)
|
||||
.ok_or(SwapError::ZeroTradingTokens)?;
|
||||
|
@ -5330,15 +5309,12 @@ mod tests {
|
|||
|
||||
let pool_token_amount = accounts
|
||||
.swap_curve
|
||||
.trading_tokens_to_pool_tokens(
|
||||
.withdraw_single_token_type_exact_out(
|
||||
destination_a_amount.try_into().unwrap(),
|
||||
(swap_token_a.amount - destination_a_amount)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
swap_token_a.amount.try_into().unwrap(),
|
||||
swap_token_b.amount.try_into().unwrap(),
|
||||
pool_mint.supply.try_into().unwrap(),
|
||||
TradeDirection::AtoB,
|
||||
RoundDirection::Ceiling,
|
||||
&accounts.fees,
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -5505,13 +5481,12 @@ mod tests {
|
|||
);
|
||||
|
||||
let first_fee = swap_curve
|
||||
.trading_tokens_to_pool_tokens(
|
||||
.withdraw_single_token_type_exact_out(
|
||||
results.owner_fee,
|
||||
token_a_amount.try_into().unwrap(),
|
||||
token_b_amount.try_into().unwrap(),
|
||||
initial_supply.try_into().unwrap(),
|
||||
TradeDirection::AtoB,
|
||||
RoundDirection::Ceiling,
|
||||
&fees,
|
||||
)
|
||||
.unwrap();
|
||||
|
@ -5582,13 +5557,12 @@ mod tests {
|
|||
);
|
||||
|
||||
let second_fee = swap_curve
|
||||
.trading_tokens_to_pool_tokens(
|
||||
.withdraw_single_token_type_exact_out(
|
||||
results.owner_fee,
|
||||
token_a_amount.try_into().unwrap(),
|
||||
token_b_amount.try_into().unwrap(),
|
||||
initial_supply.try_into().unwrap(),
|
||||
TradeDirection::BtoA,
|
||||
RoundDirection::Ceiling,
|
||||
&fees,
|
||||
)
|
||||
.unwrap();
|
||||
|
|
Loading…
Reference in New Issue