6885 lines
244 KiB
Rust
6885 lines
244 KiB
Rust
//! Program state processor
|
|
|
|
use crate::constraints::{SwapConstraints, SWAP_CONSTRAINTS};
|
|
use crate::{
|
|
curve::{
|
|
base::SwapCurve,
|
|
calculator::{RoundDirection, TradeDirection},
|
|
fees::Fees,
|
|
},
|
|
error::SwapError,
|
|
instruction::{
|
|
DepositAllTokenTypes, DepositSingleTokenTypeExactAmountIn, Initialize, Swap,
|
|
SwapInstruction, WithdrawAllTokenTypes, WithdrawSingleTokenTypeExactAmountOut,
|
|
},
|
|
state::{SwapState, SwapV1, SwapVersion},
|
|
};
|
|
use num_traits::FromPrimitive;
|
|
use solana_program::{
|
|
account_info::{next_account_info, AccountInfo},
|
|
decode_error::DecodeError,
|
|
entrypoint::ProgramResult,
|
|
msg,
|
|
program::invoke_signed,
|
|
program_error::{PrintProgramError, ProgramError},
|
|
program_option::COption,
|
|
program_pack::Pack,
|
|
pubkey::Pubkey,
|
|
};
|
|
use std::convert::TryInto;
|
|
|
|
/// Program state handler.
|
|
pub struct Processor {}
|
|
impl Processor {
|
|
/// Unpacks a spl_token `Account`.
|
|
pub fn unpack_token_account(
|
|
account_info: &AccountInfo,
|
|
token_program_id: &Pubkey,
|
|
) -> Result<spl_token::state::Account, SwapError> {
|
|
if account_info.owner != token_program_id {
|
|
Err(SwapError::IncorrectTokenProgramId)
|
|
} else {
|
|
spl_token::state::Account::unpack(&account_info.data.borrow())
|
|
.map_err(|_| SwapError::ExpectedAccount)
|
|
}
|
|
}
|
|
|
|
/// Unpacks a spl_token `Mint`.
|
|
pub fn unpack_mint(
|
|
account_info: &AccountInfo,
|
|
token_program_id: &Pubkey,
|
|
) -> Result<spl_token::state::Mint, SwapError> {
|
|
if account_info.owner != token_program_id {
|
|
Err(SwapError::IncorrectTokenProgramId)
|
|
} else {
|
|
spl_token::state::Mint::unpack(&account_info.data.borrow())
|
|
.map_err(|_| SwapError::ExpectedMint)
|
|
}
|
|
}
|
|
|
|
/// Calculates the authority id by generating a program address.
|
|
pub fn authority_id(
|
|
program_id: &Pubkey,
|
|
my_info: &Pubkey,
|
|
nonce: u8,
|
|
) -> Result<Pubkey, SwapError> {
|
|
Pubkey::create_program_address(&[&my_info.to_bytes()[..32], &[nonce]], program_id)
|
|
.or(Err(SwapError::InvalidProgramAddress))
|
|
}
|
|
|
|
/// Issue a spl_token `Burn` instruction.
|
|
pub fn token_burn<'a>(
|
|
swap: &Pubkey,
|
|
token_program: AccountInfo<'a>,
|
|
burn_account: AccountInfo<'a>,
|
|
mint: AccountInfo<'a>,
|
|
authority: AccountInfo<'a>,
|
|
nonce: u8,
|
|
amount: u64,
|
|
) -> Result<(), ProgramError> {
|
|
let swap_bytes = swap.to_bytes();
|
|
let authority_signature_seeds = [&swap_bytes[..32], &[nonce]];
|
|
let signers = &[&authority_signature_seeds[..]];
|
|
|
|
let ix = spl_token::instruction::burn(
|
|
token_program.key,
|
|
burn_account.key,
|
|
mint.key,
|
|
authority.key,
|
|
&[],
|
|
amount,
|
|
)?;
|
|
|
|
invoke_signed(
|
|
&ix,
|
|
&[burn_account, mint, authority, token_program],
|
|
signers,
|
|
)
|
|
}
|
|
|
|
/// Issue a spl_token `MintTo` instruction.
|
|
pub fn token_mint_to<'a>(
|
|
swap: &Pubkey,
|
|
token_program: AccountInfo<'a>,
|
|
mint: AccountInfo<'a>,
|
|
destination: AccountInfo<'a>,
|
|
authority: AccountInfo<'a>,
|
|
nonce: u8,
|
|
amount: u64,
|
|
) -> Result<(), ProgramError> {
|
|
let swap_bytes = swap.to_bytes();
|
|
let authority_signature_seeds = [&swap_bytes[..32], &[nonce]];
|
|
let signers = &[&authority_signature_seeds[..]];
|
|
let ix = spl_token::instruction::mint_to(
|
|
token_program.key,
|
|
mint.key,
|
|
destination.key,
|
|
authority.key,
|
|
&[],
|
|
amount,
|
|
)?;
|
|
|
|
invoke_signed(&ix, &[mint, destination, authority, token_program], signers)
|
|
}
|
|
|
|
/// Issue a spl_token `Transfer` instruction.
|
|
pub fn token_transfer<'a>(
|
|
swap: &Pubkey,
|
|
token_program: AccountInfo<'a>,
|
|
source: AccountInfo<'a>,
|
|
destination: AccountInfo<'a>,
|
|
authority: AccountInfo<'a>,
|
|
nonce: u8,
|
|
amount: u64,
|
|
) -> Result<(), ProgramError> {
|
|
let swap_bytes = swap.to_bytes();
|
|
let authority_signature_seeds = [&swap_bytes[..32], &[nonce]];
|
|
let signers = &[&authority_signature_seeds[..]];
|
|
let ix = spl_token::instruction::transfer(
|
|
token_program.key,
|
|
source.key,
|
|
destination.key,
|
|
authority.key,
|
|
&[],
|
|
amount,
|
|
)?;
|
|
invoke_signed(
|
|
&ix,
|
|
&[source, destination, authority, token_program],
|
|
signers,
|
|
)
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn check_accounts(
|
|
token_swap: &dyn SwapState,
|
|
program_id: &Pubkey,
|
|
swap_account_info: &AccountInfo,
|
|
authority_info: &AccountInfo,
|
|
token_a_info: &AccountInfo,
|
|
token_b_info: &AccountInfo,
|
|
pool_mint_info: &AccountInfo,
|
|
token_program_info: &AccountInfo,
|
|
user_token_a_info: Option<&AccountInfo>,
|
|
user_token_b_info: Option<&AccountInfo>,
|
|
pool_fee_account_info: Option<&AccountInfo>,
|
|
) -> ProgramResult {
|
|
if swap_account_info.owner != program_id {
|
|
return Err(ProgramError::IncorrectProgramId);
|
|
}
|
|
if *authority_info.key
|
|
!= Self::authority_id(program_id, swap_account_info.key, token_swap.nonce())?
|
|
{
|
|
return Err(SwapError::InvalidProgramAddress.into());
|
|
}
|
|
if *token_a_info.key != *token_swap.token_a_account() {
|
|
return Err(SwapError::IncorrectSwapAccount.into());
|
|
}
|
|
if *token_b_info.key != *token_swap.token_b_account() {
|
|
return Err(SwapError::IncorrectSwapAccount.into());
|
|
}
|
|
if *pool_mint_info.key != *token_swap.pool_mint() {
|
|
return Err(SwapError::IncorrectPoolMint.into());
|
|
}
|
|
if *token_program_info.key != *token_swap.token_program_id() {
|
|
return Err(SwapError::IncorrectTokenProgramId.into());
|
|
}
|
|
if let Some(user_token_a_info) = user_token_a_info {
|
|
if token_a_info.key == user_token_a_info.key {
|
|
return Err(SwapError::InvalidInput.into());
|
|
}
|
|
}
|
|
if let Some(user_token_b_info) = user_token_b_info {
|
|
if token_b_info.key == user_token_b_info.key {
|
|
return Err(SwapError::InvalidInput.into());
|
|
}
|
|
}
|
|
if let Some(pool_fee_account_info) = pool_fee_account_info {
|
|
if *pool_fee_account_info.key != *token_swap.pool_fee_account() {
|
|
return Err(SwapError::IncorrectFeeAccount.into());
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Processes an [Initialize](enum.Instruction.html).
|
|
pub fn process_initialize(
|
|
program_id: &Pubkey,
|
|
nonce: u8,
|
|
fees: Fees,
|
|
swap_curve: SwapCurve,
|
|
accounts: &[AccountInfo],
|
|
swap_constraints: &Option<SwapConstraints>,
|
|
) -> ProgramResult {
|
|
let account_info_iter = &mut accounts.iter();
|
|
let swap_info = next_account_info(account_info_iter)?;
|
|
let authority_info = next_account_info(account_info_iter)?;
|
|
let token_a_info = next_account_info(account_info_iter)?;
|
|
let token_b_info = next_account_info(account_info_iter)?;
|
|
let pool_mint_info = next_account_info(account_info_iter)?;
|
|
let fee_account_info = next_account_info(account_info_iter)?;
|
|
let destination_info = next_account_info(account_info_iter)?;
|
|
let token_program_info = next_account_info(account_info_iter)?;
|
|
|
|
let token_program_id = *token_program_info.key;
|
|
if SwapVersion::is_initialized(&swap_info.data.borrow()) {
|
|
return Err(SwapError::AlreadyInUse.into());
|
|
}
|
|
|
|
if *authority_info.key != Self::authority_id(program_id, swap_info.key, nonce)? {
|
|
return Err(SwapError::InvalidProgramAddress.into());
|
|
}
|
|
let token_a = Self::unpack_token_account(token_a_info, &token_program_id)?;
|
|
let token_b = Self::unpack_token_account(token_b_info, &token_program_id)?;
|
|
let fee_account = Self::unpack_token_account(fee_account_info, &token_program_id)?;
|
|
let destination = Self::unpack_token_account(destination_info, &token_program_id)?;
|
|
let pool_mint = Self::unpack_mint(pool_mint_info, &token_program_id)?;
|
|
if *authority_info.key != token_a.owner {
|
|
return Err(SwapError::InvalidOwner.into());
|
|
}
|
|
if *authority_info.key != token_b.owner {
|
|
return Err(SwapError::InvalidOwner.into());
|
|
}
|
|
if *authority_info.key == destination.owner {
|
|
return Err(SwapError::InvalidOutputOwner.into());
|
|
}
|
|
if *authority_info.key == fee_account.owner {
|
|
return Err(SwapError::InvalidOutputOwner.into());
|
|
}
|
|
if COption::Some(*authority_info.key) != pool_mint.mint_authority {
|
|
return Err(SwapError::InvalidOwner.into());
|
|
}
|
|
|
|
if token_a.mint == token_b.mint {
|
|
return Err(SwapError::RepeatedMint.into());
|
|
}
|
|
swap_curve
|
|
.calculator
|
|
.validate_supply(token_a.amount, token_b.amount)?;
|
|
if token_a.delegate.is_some() {
|
|
return Err(SwapError::InvalidDelegate.into());
|
|
}
|
|
if token_b.delegate.is_some() {
|
|
return Err(SwapError::InvalidDelegate.into());
|
|
}
|
|
if token_a.close_authority.is_some() {
|
|
return Err(SwapError::InvalidCloseAuthority.into());
|
|
}
|
|
if token_b.close_authority.is_some() {
|
|
return Err(SwapError::InvalidCloseAuthority.into());
|
|
}
|
|
|
|
if pool_mint.supply != 0 {
|
|
return Err(SwapError::InvalidSupply.into());
|
|
}
|
|
if pool_mint.freeze_authority.is_some() {
|
|
return Err(SwapError::InvalidFreezeAuthority.into());
|
|
}
|
|
if *pool_mint_info.key != fee_account.mint {
|
|
return Err(SwapError::IncorrectPoolMint.into());
|
|
}
|
|
|
|
if let Some(swap_constraints) = swap_constraints {
|
|
let owner_key = swap_constraints
|
|
.owner_key
|
|
.parse::<Pubkey>()
|
|
.map_err(|_| SwapError::InvalidOwner)?;
|
|
if fee_account.owner != owner_key {
|
|
return Err(SwapError::InvalidOwner.into());
|
|
}
|
|
swap_constraints.validate_curve(&swap_curve)?;
|
|
swap_constraints.validate_fees(&fees)?;
|
|
}
|
|
fees.validate()?;
|
|
swap_curve.calculator.validate()?;
|
|
|
|
let initial_amount = swap_curve.calculator.new_pool_supply();
|
|
|
|
Self::token_mint_to(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
pool_mint_info.clone(),
|
|
destination_info.clone(),
|
|
authority_info.clone(),
|
|
nonce,
|
|
to_u64(initial_amount)?,
|
|
)?;
|
|
|
|
let obj = SwapVersion::SwapV1(SwapV1 {
|
|
is_initialized: true,
|
|
nonce,
|
|
token_program_id,
|
|
token_a: *token_a_info.key,
|
|
token_b: *token_b_info.key,
|
|
pool_mint: *pool_mint_info.key,
|
|
token_a_mint: token_a.mint,
|
|
token_b_mint: token_b.mint,
|
|
pool_fee_account: *fee_account_info.key,
|
|
fees,
|
|
swap_curve,
|
|
});
|
|
SwapVersion::pack(obj, &mut swap_info.data.borrow_mut())?;
|
|
Ok(())
|
|
}
|
|
|
|
/// Processes an [Swap](enum.Instruction.html).
|
|
pub fn process_swap(
|
|
program_id: &Pubkey,
|
|
amount_in: u64,
|
|
minimum_amount_out: u64,
|
|
accounts: &[AccountInfo],
|
|
) -> ProgramResult {
|
|
let account_info_iter = &mut accounts.iter();
|
|
let swap_info = next_account_info(account_info_iter)?;
|
|
let authority_info = next_account_info(account_info_iter)?;
|
|
let user_transfer_authority_info = next_account_info(account_info_iter)?;
|
|
let source_info = next_account_info(account_info_iter)?;
|
|
let swap_source_info = next_account_info(account_info_iter)?;
|
|
let swap_destination_info = next_account_info(account_info_iter)?;
|
|
let destination_info = next_account_info(account_info_iter)?;
|
|
let pool_mint_info = next_account_info(account_info_iter)?;
|
|
let pool_fee_account_info = next_account_info(account_info_iter)?;
|
|
let token_program_info = next_account_info(account_info_iter)?;
|
|
|
|
if swap_info.owner != program_id {
|
|
return Err(ProgramError::IncorrectProgramId);
|
|
}
|
|
let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?;
|
|
|
|
if *authority_info.key != Self::authority_id(program_id, swap_info.key, token_swap.nonce())?
|
|
{
|
|
return Err(SwapError::InvalidProgramAddress.into());
|
|
}
|
|
if !(*swap_source_info.key == *token_swap.token_a_account()
|
|
|| *swap_source_info.key == *token_swap.token_b_account())
|
|
{
|
|
return Err(SwapError::IncorrectSwapAccount.into());
|
|
}
|
|
if !(*swap_destination_info.key == *token_swap.token_a_account()
|
|
|| *swap_destination_info.key == *token_swap.token_b_account())
|
|
{
|
|
return Err(SwapError::IncorrectSwapAccount.into());
|
|
}
|
|
if *swap_source_info.key == *swap_destination_info.key {
|
|
return Err(SwapError::InvalidInput.into());
|
|
}
|
|
if swap_source_info.key == source_info.key {
|
|
return Err(SwapError::InvalidInput.into());
|
|
}
|
|
if swap_destination_info.key == destination_info.key {
|
|
return Err(SwapError::InvalidInput.into());
|
|
}
|
|
if *pool_mint_info.key != *token_swap.pool_mint() {
|
|
return Err(SwapError::IncorrectPoolMint.into());
|
|
}
|
|
if *pool_fee_account_info.key != *token_swap.pool_fee_account() {
|
|
return Err(SwapError::IncorrectFeeAccount.into());
|
|
}
|
|
if *token_program_info.key != *token_swap.token_program_id() {
|
|
return Err(SwapError::IncorrectTokenProgramId.into());
|
|
}
|
|
|
|
let source_account =
|
|
Self::unpack_token_account(swap_source_info, token_swap.token_program_id())?;
|
|
let dest_account =
|
|
Self::unpack_token_account(swap_destination_info, token_swap.token_program_id())?;
|
|
let pool_mint = Self::unpack_mint(pool_mint_info, token_swap.token_program_id())?;
|
|
|
|
let trade_direction = if *swap_source_info.key == *token_swap.token_a_account() {
|
|
TradeDirection::AtoB
|
|
} else {
|
|
TradeDirection::BtoA
|
|
};
|
|
let result = token_swap
|
|
.swap_curve()
|
|
.swap(
|
|
to_u128(amount_in)?,
|
|
to_u128(source_account.amount)?,
|
|
to_u128(dest_account.amount)?,
|
|
trade_direction,
|
|
token_swap.fees(),
|
|
)
|
|
.ok_or(SwapError::ZeroTradingTokens)?;
|
|
if result.destination_amount_swapped < to_u128(minimum_amount_out)? {
|
|
return Err(SwapError::ExceededSlippage.into());
|
|
}
|
|
|
|
let (swap_token_a_amount, swap_token_b_amount) = match trade_direction {
|
|
TradeDirection::AtoB => (
|
|
result.new_swap_source_amount,
|
|
result.new_swap_destination_amount,
|
|
),
|
|
TradeDirection::BtoA => (
|
|
result.new_swap_destination_amount,
|
|
result.new_swap_source_amount,
|
|
),
|
|
};
|
|
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
source_info.clone(),
|
|
swap_source_info.clone(),
|
|
user_transfer_authority_info.clone(),
|
|
token_swap.nonce(),
|
|
to_u64(result.source_amount_swapped)?,
|
|
)?;
|
|
|
|
let mut pool_token_amount = token_swap
|
|
.swap_curve()
|
|
.withdraw_single_token_type_exact_out(
|
|
result.owner_fee,
|
|
swap_token_a_amount,
|
|
swap_token_b_amount,
|
|
to_u128(pool_mint.supply)?,
|
|
trade_direction,
|
|
token_swap.fees(),
|
|
)
|
|
.ok_or(SwapError::FeeCalculationFailure)?;
|
|
|
|
if pool_token_amount > 0 {
|
|
// Allow error to fall through
|
|
if let Ok(host_fee_account_info) = next_account_info(account_info_iter) {
|
|
let host_fee_account = Self::unpack_token_account(
|
|
host_fee_account_info,
|
|
token_swap.token_program_id(),
|
|
)?;
|
|
if *pool_mint_info.key != host_fee_account.mint {
|
|
return Err(SwapError::IncorrectPoolMint.into());
|
|
}
|
|
let host_fee = token_swap
|
|
.fees()
|
|
.host_fee(pool_token_amount)
|
|
.ok_or(SwapError::FeeCalculationFailure)?;
|
|
if host_fee > 0 {
|
|
pool_token_amount = pool_token_amount
|
|
.checked_sub(host_fee)
|
|
.ok_or(SwapError::FeeCalculationFailure)?;
|
|
Self::token_mint_to(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
pool_mint_info.clone(),
|
|
host_fee_account_info.clone(),
|
|
authority_info.clone(),
|
|
token_swap.nonce(),
|
|
to_u64(host_fee)?,
|
|
)?;
|
|
}
|
|
}
|
|
Self::token_mint_to(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
pool_mint_info.clone(),
|
|
pool_fee_account_info.clone(),
|
|
authority_info.clone(),
|
|
token_swap.nonce(),
|
|
to_u64(pool_token_amount)?,
|
|
)?;
|
|
}
|
|
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
swap_destination_info.clone(),
|
|
destination_info.clone(),
|
|
authority_info.clone(),
|
|
token_swap.nonce(),
|
|
to_u64(result.destination_amount_swapped)?,
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Processes an [DepositAllTokenTypes](enum.Instruction.html).
|
|
pub fn process_deposit_all_token_types(
|
|
program_id: &Pubkey,
|
|
pool_token_amount: u64,
|
|
maximum_token_a_amount: u64,
|
|
maximum_token_b_amount: u64,
|
|
accounts: &[AccountInfo],
|
|
) -> ProgramResult {
|
|
let account_info_iter = &mut accounts.iter();
|
|
let swap_info = next_account_info(account_info_iter)?;
|
|
let authority_info = next_account_info(account_info_iter)?;
|
|
let user_transfer_authority_info = next_account_info(account_info_iter)?;
|
|
let source_a_info = next_account_info(account_info_iter)?;
|
|
let source_b_info = next_account_info(account_info_iter)?;
|
|
let token_a_info = next_account_info(account_info_iter)?;
|
|
let token_b_info = next_account_info(account_info_iter)?;
|
|
let pool_mint_info = next_account_info(account_info_iter)?;
|
|
let dest_info = next_account_info(account_info_iter)?;
|
|
let token_program_info = next_account_info(account_info_iter)?;
|
|
|
|
let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?;
|
|
let calculator = &token_swap.swap_curve().calculator;
|
|
if !calculator.allows_deposits() {
|
|
return Err(SwapError::UnsupportedCurveOperation.into());
|
|
}
|
|
Self::check_accounts(
|
|
token_swap.as_ref(),
|
|
program_id,
|
|
swap_info,
|
|
authority_info,
|
|
token_a_info,
|
|
token_b_info,
|
|
pool_mint_info,
|
|
token_program_info,
|
|
Some(source_a_info),
|
|
Some(source_b_info),
|
|
None,
|
|
)?;
|
|
|
|
let token_a = Self::unpack_token_account(token_a_info, token_swap.token_program_id())?;
|
|
let token_b = Self::unpack_token_account(token_b_info, token_swap.token_program_id())?;
|
|
let pool_mint = Self::unpack_mint(pool_mint_info, token_swap.token_program_id())?;
|
|
let current_pool_mint_supply = to_u128(pool_mint.supply)?;
|
|
let (pool_token_amount, pool_mint_supply) = if current_pool_mint_supply > 0 {
|
|
(to_u128(pool_token_amount)?, current_pool_mint_supply)
|
|
} else {
|
|
(calculator.new_pool_supply(), calculator.new_pool_supply())
|
|
};
|
|
|
|
let results = calculator
|
|
.pool_tokens_to_trading_tokens(
|
|
pool_token_amount,
|
|
pool_mint_supply,
|
|
to_u128(token_a.amount)?,
|
|
to_u128(token_b.amount)?,
|
|
RoundDirection::Ceiling,
|
|
)
|
|
.ok_or(SwapError::ZeroTradingTokens)?;
|
|
let token_a_amount = to_u64(results.token_a_amount)?;
|
|
if token_a_amount > maximum_token_a_amount {
|
|
return Err(SwapError::ExceededSlippage.into());
|
|
}
|
|
if token_a_amount == 0 {
|
|
return Err(SwapError::ZeroTradingTokens.into());
|
|
}
|
|
let token_b_amount = to_u64(results.token_b_amount)?;
|
|
if token_b_amount > maximum_token_b_amount {
|
|
return Err(SwapError::ExceededSlippage.into());
|
|
}
|
|
if token_b_amount == 0 {
|
|
return Err(SwapError::ZeroTradingTokens.into());
|
|
}
|
|
|
|
let pool_token_amount = to_u64(pool_token_amount)?;
|
|
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
source_a_info.clone(),
|
|
token_a_info.clone(),
|
|
user_transfer_authority_info.clone(),
|
|
token_swap.nonce(),
|
|
token_a_amount,
|
|
)?;
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
source_b_info.clone(),
|
|
token_b_info.clone(),
|
|
user_transfer_authority_info.clone(),
|
|
token_swap.nonce(),
|
|
token_b_amount,
|
|
)?;
|
|
Self::token_mint_to(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
pool_mint_info.clone(),
|
|
dest_info.clone(),
|
|
authority_info.clone(),
|
|
token_swap.nonce(),
|
|
pool_token_amount,
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Processes an [WithdrawAllTokenTypes](enum.Instruction.html).
|
|
pub fn process_withdraw_all_token_types(
|
|
program_id: &Pubkey,
|
|
pool_token_amount: u64,
|
|
minimum_token_a_amount: u64,
|
|
minimum_token_b_amount: u64,
|
|
accounts: &[AccountInfo],
|
|
) -> ProgramResult {
|
|
let account_info_iter = &mut accounts.iter();
|
|
let swap_info = next_account_info(account_info_iter)?;
|
|
let authority_info = next_account_info(account_info_iter)?;
|
|
let user_transfer_authority_info = next_account_info(account_info_iter)?;
|
|
let pool_mint_info = next_account_info(account_info_iter)?;
|
|
let source_info = next_account_info(account_info_iter)?;
|
|
let token_a_info = next_account_info(account_info_iter)?;
|
|
let token_b_info = next_account_info(account_info_iter)?;
|
|
let dest_token_a_info = next_account_info(account_info_iter)?;
|
|
let dest_token_b_info = next_account_info(account_info_iter)?;
|
|
let pool_fee_account_info = next_account_info(account_info_iter)?;
|
|
let token_program_info = next_account_info(account_info_iter)?;
|
|
|
|
let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?;
|
|
Self::check_accounts(
|
|
token_swap.as_ref(),
|
|
program_id,
|
|
swap_info,
|
|
authority_info,
|
|
token_a_info,
|
|
token_b_info,
|
|
pool_mint_info,
|
|
token_program_info,
|
|
Some(dest_token_a_info),
|
|
Some(dest_token_b_info),
|
|
Some(pool_fee_account_info),
|
|
)?;
|
|
|
|
let token_a = Self::unpack_token_account(token_a_info, token_swap.token_program_id())?;
|
|
let token_b = Self::unpack_token_account(token_b_info, token_swap.token_program_id())?;
|
|
let pool_mint = Self::unpack_mint(pool_mint_info, token_swap.token_program_id())?;
|
|
|
|
let calculator = &token_swap.swap_curve().calculator;
|
|
|
|
let withdraw_fee: u128 = if *pool_fee_account_info.key == *source_info.key {
|
|
// withdrawing from the fee account, don't assess withdraw fee
|
|
0
|
|
} else {
|
|
token_swap
|
|
.fees()
|
|
.owner_withdraw_fee(to_u128(pool_token_amount)?)
|
|
.ok_or(SwapError::FeeCalculationFailure)?
|
|
};
|
|
let pool_token_amount = to_u128(pool_token_amount)?
|
|
.checked_sub(withdraw_fee)
|
|
.ok_or(SwapError::CalculationFailure)?;
|
|
|
|
let results = calculator
|
|
.pool_tokens_to_trading_tokens(
|
|
pool_token_amount,
|
|
to_u128(pool_mint.supply)?,
|
|
to_u128(token_a.amount)?,
|
|
to_u128(token_b.amount)?,
|
|
RoundDirection::Floor,
|
|
)
|
|
.ok_or(SwapError::ZeroTradingTokens)?;
|
|
let token_a_amount = to_u64(results.token_a_amount)?;
|
|
let token_a_amount = std::cmp::min(token_a.amount, token_a_amount);
|
|
if token_a_amount < minimum_token_a_amount {
|
|
return Err(SwapError::ExceededSlippage.into());
|
|
}
|
|
if token_a_amount == 0 && token_a.amount != 0 {
|
|
return Err(SwapError::ZeroTradingTokens.into());
|
|
}
|
|
let token_b_amount = to_u64(results.token_b_amount)?;
|
|
let token_b_amount = std::cmp::min(token_b.amount, token_b_amount);
|
|
if token_b_amount < minimum_token_b_amount {
|
|
return Err(SwapError::ExceededSlippage.into());
|
|
}
|
|
if token_b_amount == 0 && token_b.amount != 0 {
|
|
return Err(SwapError::ZeroTradingTokens.into());
|
|
}
|
|
|
|
if withdraw_fee > 0 {
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
source_info.clone(),
|
|
pool_fee_account_info.clone(),
|
|
user_transfer_authority_info.clone(),
|
|
token_swap.nonce(),
|
|
to_u64(withdraw_fee)?,
|
|
)?;
|
|
}
|
|
Self::token_burn(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
source_info.clone(),
|
|
pool_mint_info.clone(),
|
|
user_transfer_authority_info.clone(),
|
|
token_swap.nonce(),
|
|
to_u64(pool_token_amount)?,
|
|
)?;
|
|
|
|
if token_a_amount > 0 {
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
token_a_info.clone(),
|
|
dest_token_a_info.clone(),
|
|
authority_info.clone(),
|
|
token_swap.nonce(),
|
|
token_a_amount,
|
|
)?;
|
|
}
|
|
if token_b_amount > 0 {
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
token_b_info.clone(),
|
|
dest_token_b_info.clone(),
|
|
authority_info.clone(),
|
|
token_swap.nonce(),
|
|
token_b_amount,
|
|
)?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Processes DepositSingleTokenTypeExactAmountIn
|
|
pub fn process_deposit_single_token_type_exact_amount_in(
|
|
program_id: &Pubkey,
|
|
source_token_amount: u64,
|
|
minimum_pool_token_amount: u64,
|
|
accounts: &[AccountInfo],
|
|
) -> ProgramResult {
|
|
let account_info_iter = &mut accounts.iter();
|
|
let swap_info = next_account_info(account_info_iter)?;
|
|
let authority_info = next_account_info(account_info_iter)?;
|
|
let user_transfer_authority_info = next_account_info(account_info_iter)?;
|
|
let source_info = next_account_info(account_info_iter)?;
|
|
let swap_token_a_info = next_account_info(account_info_iter)?;
|
|
let swap_token_b_info = next_account_info(account_info_iter)?;
|
|
let pool_mint_info = next_account_info(account_info_iter)?;
|
|
let destination_info = next_account_info(account_info_iter)?;
|
|
let token_program_info = next_account_info(account_info_iter)?;
|
|
|
|
let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?;
|
|
let source_account =
|
|
Self::unpack_token_account(source_info, token_swap.token_program_id())?;
|
|
let swap_token_a =
|
|
Self::unpack_token_account(swap_token_a_info, token_swap.token_program_id())?;
|
|
let swap_token_b =
|
|
Self::unpack_token_account(swap_token_b_info, token_swap.token_program_id())?;
|
|
|
|
let trade_direction = if source_account.mint == swap_token_a.mint {
|
|
TradeDirection::AtoB
|
|
} else if source_account.mint == swap_token_b.mint {
|
|
TradeDirection::BtoA
|
|
} else {
|
|
return Err(SwapError::IncorrectSwapAccount.into());
|
|
};
|
|
|
|
let (source_a_info, source_b_info) = match trade_direction {
|
|
TradeDirection::AtoB => (Some(source_info), None),
|
|
TradeDirection::BtoA => (None, Some(source_info)),
|
|
};
|
|
|
|
Self::check_accounts(
|
|
token_swap.as_ref(),
|
|
program_id,
|
|
swap_info,
|
|
authority_info,
|
|
swap_token_a_info,
|
|
swap_token_b_info,
|
|
pool_mint_info,
|
|
token_program_info,
|
|
source_a_info,
|
|
source_b_info,
|
|
None,
|
|
)?;
|
|
|
|
let pool_mint = Self::unpack_mint(pool_mint_info, token_swap.token_program_id())?;
|
|
let pool_mint_supply = to_u128(pool_mint.supply)?;
|
|
let pool_token_amount = if pool_mint_supply > 0 {
|
|
token_swap
|
|
.swap_curve()
|
|
.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,
|
|
token_swap.fees(),
|
|
)
|
|
.ok_or(SwapError::ZeroTradingTokens)?
|
|
} else {
|
|
token_swap.swap_curve().calculator.new_pool_supply()
|
|
};
|
|
|
|
let pool_token_amount = to_u64(pool_token_amount)?;
|
|
if pool_token_amount < minimum_pool_token_amount {
|
|
return Err(SwapError::ExceededSlippage.into());
|
|
}
|
|
if pool_token_amount == 0 {
|
|
return Err(SwapError::ZeroTradingTokens.into());
|
|
}
|
|
|
|
match trade_direction {
|
|
TradeDirection::AtoB => {
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
source_info.clone(),
|
|
swap_token_a_info.clone(),
|
|
user_transfer_authority_info.clone(),
|
|
token_swap.nonce(),
|
|
source_token_amount,
|
|
)?;
|
|
}
|
|
TradeDirection::BtoA => {
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
source_info.clone(),
|
|
swap_token_b_info.clone(),
|
|
user_transfer_authority_info.clone(),
|
|
token_swap.nonce(),
|
|
source_token_amount,
|
|
)?;
|
|
}
|
|
}
|
|
Self::token_mint_to(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
pool_mint_info.clone(),
|
|
destination_info.clone(),
|
|
authority_info.clone(),
|
|
token_swap.nonce(),
|
|
pool_token_amount,
|
|
)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Processes a [WithdrawSingleTokenTypeExactAmountOut](enum.Instruction.html).
|
|
pub fn process_withdraw_single_token_type_exact_amount_out(
|
|
program_id: &Pubkey,
|
|
destination_token_amount: u64,
|
|
maximum_pool_token_amount: u64,
|
|
accounts: &[AccountInfo],
|
|
) -> ProgramResult {
|
|
let account_info_iter = &mut accounts.iter();
|
|
let swap_info = next_account_info(account_info_iter)?;
|
|
let authority_info = next_account_info(account_info_iter)?;
|
|
let user_transfer_authority_info = next_account_info(account_info_iter)?;
|
|
let pool_mint_info = next_account_info(account_info_iter)?;
|
|
let source_info = next_account_info(account_info_iter)?;
|
|
let swap_token_a_info = next_account_info(account_info_iter)?;
|
|
let swap_token_b_info = next_account_info(account_info_iter)?;
|
|
let destination_info = next_account_info(account_info_iter)?;
|
|
let pool_fee_account_info = next_account_info(account_info_iter)?;
|
|
let token_program_info = next_account_info(account_info_iter)?;
|
|
|
|
let token_swap = SwapVersion::unpack(&swap_info.data.borrow())?;
|
|
let destination_account =
|
|
Self::unpack_token_account(destination_info, token_swap.token_program_id())?;
|
|
let swap_token_a =
|
|
Self::unpack_token_account(swap_token_a_info, token_swap.token_program_id())?;
|
|
let swap_token_b =
|
|
Self::unpack_token_account(swap_token_b_info, token_swap.token_program_id())?;
|
|
|
|
let trade_direction = if destination_account.mint == swap_token_a.mint {
|
|
TradeDirection::AtoB
|
|
} else if destination_account.mint == swap_token_b.mint {
|
|
TradeDirection::BtoA
|
|
} else {
|
|
return Err(SwapError::IncorrectSwapAccount.into());
|
|
};
|
|
|
|
let (destination_a_info, destination_b_info) = match trade_direction {
|
|
TradeDirection::AtoB => (Some(destination_info), None),
|
|
TradeDirection::BtoA => (None, Some(destination_info)),
|
|
};
|
|
Self::check_accounts(
|
|
token_swap.as_ref(),
|
|
program_id,
|
|
swap_info,
|
|
authority_info,
|
|
swap_token_a_info,
|
|
swap_token_b_info,
|
|
pool_mint_info,
|
|
token_program_info,
|
|
destination_a_info,
|
|
destination_b_info,
|
|
Some(pool_fee_account_info),
|
|
)?;
|
|
|
|
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 = 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()
|
|
.withdraw_single_token_type_exact_out(
|
|
to_u128(destination_token_amount)?,
|
|
swap_token_a_amount,
|
|
swap_token_b_amount,
|
|
pool_mint_supply,
|
|
trade_direction,
|
|
token_swap.fees(),
|
|
)
|
|
.ok_or(SwapError::ZeroTradingTokens)?;
|
|
|
|
let withdraw_fee: u128 = if *pool_fee_account_info.key == *source_info.key {
|
|
// withdrawing from the fee account, don't assess withdraw fee
|
|
0
|
|
} else {
|
|
token_swap
|
|
.fees()
|
|
.owner_withdraw_fee(burn_pool_token_amount)
|
|
.ok_or(SwapError::FeeCalculationFailure)?
|
|
};
|
|
let pool_token_amount = burn_pool_token_amount
|
|
.checked_add(withdraw_fee)
|
|
.ok_or(SwapError::CalculationFailure)?;
|
|
|
|
if to_u64(pool_token_amount)? > maximum_pool_token_amount {
|
|
return Err(SwapError::ExceededSlippage.into());
|
|
}
|
|
if pool_token_amount == 0 {
|
|
return Err(SwapError::ZeroTradingTokens.into());
|
|
}
|
|
|
|
if withdraw_fee > 0 {
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
source_info.clone(),
|
|
pool_fee_account_info.clone(),
|
|
user_transfer_authority_info.clone(),
|
|
token_swap.nonce(),
|
|
to_u64(withdraw_fee)?,
|
|
)?;
|
|
}
|
|
Self::token_burn(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
source_info.clone(),
|
|
pool_mint_info.clone(),
|
|
user_transfer_authority_info.clone(),
|
|
token_swap.nonce(),
|
|
to_u64(burn_pool_token_amount)?,
|
|
)?;
|
|
|
|
match trade_direction {
|
|
TradeDirection::AtoB => {
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
swap_token_a_info.clone(),
|
|
destination_info.clone(),
|
|
authority_info.clone(),
|
|
token_swap.nonce(),
|
|
destination_token_amount,
|
|
)?;
|
|
}
|
|
TradeDirection::BtoA => {
|
|
Self::token_transfer(
|
|
swap_info.key,
|
|
token_program_info.clone(),
|
|
swap_token_b_info.clone(),
|
|
destination_info.clone(),
|
|
authority_info.clone(),
|
|
token_swap.nonce(),
|
|
destination_token_amount,
|
|
)?;
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Processes an [Instruction](enum.Instruction.html).
|
|
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
|
|
Self::process_with_constraints(program_id, accounts, input, &SWAP_CONSTRAINTS)
|
|
}
|
|
|
|
/// Processes an instruction given extra constraint
|
|
pub fn process_with_constraints(
|
|
program_id: &Pubkey,
|
|
accounts: &[AccountInfo],
|
|
input: &[u8],
|
|
swap_constraints: &Option<SwapConstraints>,
|
|
) -> ProgramResult {
|
|
let instruction = SwapInstruction::unpack(input)?;
|
|
match instruction {
|
|
SwapInstruction::Initialize(Initialize {
|
|
nonce,
|
|
fees,
|
|
swap_curve,
|
|
}) => {
|
|
msg!("Instruction: Init");
|
|
Self::process_initialize(
|
|
program_id,
|
|
nonce,
|
|
fees,
|
|
swap_curve,
|
|
accounts,
|
|
swap_constraints,
|
|
)
|
|
}
|
|
SwapInstruction::Swap(Swap {
|
|
amount_in,
|
|
minimum_amount_out,
|
|
}) => {
|
|
msg!("Instruction: Swap");
|
|
Self::process_swap(program_id, amount_in, minimum_amount_out, accounts)
|
|
}
|
|
SwapInstruction::DepositAllTokenTypes(DepositAllTokenTypes {
|
|
pool_token_amount,
|
|
maximum_token_a_amount,
|
|
maximum_token_b_amount,
|
|
}) => {
|
|
msg!("Instruction: DepositAllTokenTypes");
|
|
Self::process_deposit_all_token_types(
|
|
program_id,
|
|
pool_token_amount,
|
|
maximum_token_a_amount,
|
|
maximum_token_b_amount,
|
|
accounts,
|
|
)
|
|
}
|
|
SwapInstruction::WithdrawAllTokenTypes(WithdrawAllTokenTypes {
|
|
pool_token_amount,
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
}) => {
|
|
msg!("Instruction: WithdrawAllTokenTypes");
|
|
Self::process_withdraw_all_token_types(
|
|
program_id,
|
|
pool_token_amount,
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
accounts,
|
|
)
|
|
}
|
|
SwapInstruction::DepositSingleTokenTypeExactAmountIn(
|
|
DepositSingleTokenTypeExactAmountIn {
|
|
source_token_amount,
|
|
minimum_pool_token_amount,
|
|
},
|
|
) => {
|
|
msg!("Instruction: DepositSingleTokenTypeExactAmountIn");
|
|
Self::process_deposit_single_token_type_exact_amount_in(
|
|
program_id,
|
|
source_token_amount,
|
|
minimum_pool_token_amount,
|
|
accounts,
|
|
)
|
|
}
|
|
SwapInstruction::WithdrawSingleTokenTypeExactAmountOut(
|
|
WithdrawSingleTokenTypeExactAmountOut {
|
|
destination_token_amount,
|
|
maximum_pool_token_amount,
|
|
},
|
|
) => {
|
|
msg!("Instruction: WithdrawSingleTokenTypeExactAmountOut");
|
|
Self::process_withdraw_single_token_type_exact_amount_out(
|
|
program_id,
|
|
destination_token_amount,
|
|
maximum_pool_token_amount,
|
|
accounts,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl PrintProgramError for SwapError {
|
|
fn print<E>(&self)
|
|
where
|
|
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
|
|
{
|
|
match self {
|
|
SwapError::AlreadyInUse => msg!("Error: Swap account already in use"),
|
|
SwapError::InvalidProgramAddress => {
|
|
msg!("Error: Invalid program address generated from nonce and key")
|
|
}
|
|
SwapError::InvalidOwner => {
|
|
msg!("Error: The input account owner is not the program address")
|
|
}
|
|
SwapError::InvalidOutputOwner => {
|
|
msg!("Error: Output pool account owner cannot be the program address")
|
|
}
|
|
SwapError::ExpectedMint => msg!("Error: Deserialized account is not an SPL Token mint"),
|
|
SwapError::ExpectedAccount => {
|
|
msg!("Error: Deserialized account is not an SPL Token account")
|
|
}
|
|
SwapError::EmptySupply => msg!("Error: Input token account empty"),
|
|
SwapError::InvalidSupply => msg!("Error: Pool token mint has a non-zero supply"),
|
|
SwapError::RepeatedMint => msg!("Error: Swap input token accounts have the same mint"),
|
|
SwapError::InvalidDelegate => msg!("Error: Token account has a delegate"),
|
|
SwapError::InvalidInput => msg!("Error: InvalidInput"),
|
|
SwapError::IncorrectSwapAccount => {
|
|
msg!("Error: Address of the provided swap token account is incorrect")
|
|
}
|
|
SwapError::IncorrectPoolMint => {
|
|
msg!("Error: Address of the provided pool token mint is incorrect")
|
|
}
|
|
SwapError::InvalidOutput => msg!("Error: InvalidOutput"),
|
|
SwapError::CalculationFailure => msg!("Error: CalculationFailure"),
|
|
SwapError::InvalidInstruction => msg!("Error: InvalidInstruction"),
|
|
SwapError::ExceededSlippage => {
|
|
msg!("Error: Swap instruction exceeds desired slippage limit")
|
|
}
|
|
SwapError::InvalidCloseAuthority => msg!("Error: Token account has a close authority"),
|
|
SwapError::InvalidFreezeAuthority => {
|
|
msg!("Error: Pool token mint has a freeze authority")
|
|
}
|
|
SwapError::IncorrectFeeAccount => msg!("Error: Pool fee token account incorrect"),
|
|
SwapError::ZeroTradingTokens => {
|
|
msg!("Error: Given pool token amount results in zero trading tokens")
|
|
}
|
|
SwapError::FeeCalculationFailure => msg!(
|
|
"Error: The fee calculation failed due to overflow, underflow, or unexpected 0"
|
|
),
|
|
SwapError::ConversionFailure => msg!("Error: Conversion to or from u64 failed."),
|
|
SwapError::InvalidFee => {
|
|
msg!("Error: The provided fee does not match the program owner's constraints")
|
|
}
|
|
SwapError::IncorrectTokenProgramId => {
|
|
msg!("Error: The provided token program does not match the token program expected by the swap")
|
|
}
|
|
SwapError::UnsupportedCurveType => {
|
|
msg!("Error: The provided curve type is not supported by the program owner")
|
|
}
|
|
SwapError::InvalidCurve => {
|
|
msg!("Error: The provided curve parameters are invalid")
|
|
}
|
|
SwapError::UnsupportedCurveOperation => {
|
|
msg!("Error: The operation cannot be performed on the given curve")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn to_u128(val: u64) -> Result<u128, SwapError> {
|
|
val.try_into().map_err(|_| SwapError::ConversionFailure)
|
|
}
|
|
|
|
fn to_u64(val: u128) -> Result<u64, SwapError> {
|
|
val.try_into().map_err(|_| SwapError::ConversionFailure)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::{
|
|
curve::calculator::{CurveCalculator, INITIAL_SWAP_POOL_AMOUNT},
|
|
curve::{
|
|
base::CurveType, constant_price::ConstantPriceCurve,
|
|
constant_product::ConstantProductCurve, offset::OffsetCurve,
|
|
},
|
|
instruction::{
|
|
deposit_all_token_types, deposit_single_token_type_exact_amount_in, initialize, swap,
|
|
withdraw_all_token_types, withdraw_single_token_type_exact_amount_out,
|
|
},
|
|
};
|
|
use solana_program::{instruction::Instruction, program_stubs, rent::Rent};
|
|
use solana_sdk::account::{create_account_for_test, create_is_signer_account_infos, Account};
|
|
use spl_token::{
|
|
error::TokenError,
|
|
instruction::{
|
|
approve, initialize_account, initialize_mint, mint_to, revoke, set_authority,
|
|
AuthorityType,
|
|
},
|
|
};
|
|
|
|
// Test program id for the swap program.
|
|
const SWAP_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
|
|
|
|
struct TestSyscallStubs {}
|
|
impl program_stubs::SyscallStubs for TestSyscallStubs {
|
|
fn sol_invoke_signed(
|
|
&self,
|
|
instruction: &Instruction,
|
|
account_infos: &[AccountInfo],
|
|
signers_seeds: &[&[&[u8]]],
|
|
) -> ProgramResult {
|
|
msg!("TestSyscallStubs::sol_invoke_signed()");
|
|
|
|
let mut new_account_infos = vec![];
|
|
|
|
// mimic check for token program in accounts
|
|
if !account_infos.iter().any(|x| *x.key == spl_token::id()) {
|
|
return Err(ProgramError::InvalidAccountData);
|
|
}
|
|
|
|
for meta in instruction.accounts.iter() {
|
|
for account_info in account_infos.iter() {
|
|
if meta.pubkey == *account_info.key {
|
|
let mut new_account_info = account_info.clone();
|
|
for seeds in signers_seeds.iter() {
|
|
let signer =
|
|
Pubkey::create_program_address(seeds, &SWAP_PROGRAM_ID).unwrap();
|
|
if *account_info.key == signer {
|
|
new_account_info.is_signer = true;
|
|
}
|
|
}
|
|
new_account_infos.push(new_account_info);
|
|
}
|
|
}
|
|
}
|
|
|
|
spl_token::processor::Processor::process(
|
|
&instruction.program_id,
|
|
&new_account_infos,
|
|
&instruction.data,
|
|
)
|
|
}
|
|
}
|
|
|
|
fn test_syscall_stubs() {
|
|
use std::sync::Once;
|
|
static ONCE: Once = Once::new();
|
|
|
|
ONCE.call_once(|| {
|
|
program_stubs::set_syscall_stubs(Box::new(TestSyscallStubs {}));
|
|
});
|
|
}
|
|
|
|
struct SwapAccountInfo {
|
|
nonce: u8,
|
|
authority_key: Pubkey,
|
|
fees: Fees,
|
|
swap_curve: SwapCurve,
|
|
swap_key: Pubkey,
|
|
swap_account: Account,
|
|
pool_mint_key: Pubkey,
|
|
pool_mint_account: Account,
|
|
pool_fee_key: Pubkey,
|
|
pool_fee_account: Account,
|
|
pool_token_key: Pubkey,
|
|
pool_token_account: Account,
|
|
token_a_key: Pubkey,
|
|
token_a_account: Account,
|
|
token_a_mint_key: Pubkey,
|
|
token_a_mint_account: Account,
|
|
token_b_key: Pubkey,
|
|
token_b_account: Account,
|
|
token_b_mint_key: Pubkey,
|
|
token_b_mint_account: Account,
|
|
}
|
|
|
|
impl SwapAccountInfo {
|
|
pub fn new(
|
|
user_key: &Pubkey,
|
|
fees: Fees,
|
|
swap_curve: SwapCurve,
|
|
token_a_amount: u64,
|
|
token_b_amount: u64,
|
|
) -> Self {
|
|
let swap_key = Pubkey::new_unique();
|
|
let swap_account = Account::new(0, SwapVersion::LATEST_LEN, &SWAP_PROGRAM_ID);
|
|
let (authority_key, nonce) =
|
|
Pubkey::find_program_address(&[&swap_key.to_bytes()[..]], &SWAP_PROGRAM_ID);
|
|
|
|
let (pool_mint_key, mut pool_mint_account) =
|
|
create_mint(&spl_token::id(), &authority_key, None);
|
|
let (pool_token_key, pool_token_account) = mint_token(
|
|
&spl_token::id(),
|
|
&pool_mint_key,
|
|
&mut pool_mint_account,
|
|
&authority_key,
|
|
user_key,
|
|
0,
|
|
);
|
|
let (pool_fee_key, pool_fee_account) = mint_token(
|
|
&spl_token::id(),
|
|
&pool_mint_key,
|
|
&mut pool_mint_account,
|
|
&authority_key,
|
|
user_key,
|
|
0,
|
|
);
|
|
let (token_a_mint_key, mut token_a_mint_account) =
|
|
create_mint(&spl_token::id(), user_key, None);
|
|
let (token_a_key, token_a_account) = mint_token(
|
|
&spl_token::id(),
|
|
&token_a_mint_key,
|
|
&mut token_a_mint_account,
|
|
user_key,
|
|
&authority_key,
|
|
token_a_amount,
|
|
);
|
|
let (token_b_mint_key, mut token_b_mint_account) =
|
|
create_mint(&spl_token::id(), user_key, None);
|
|
let (token_b_key, token_b_account) = mint_token(
|
|
&spl_token::id(),
|
|
&token_b_mint_key,
|
|
&mut token_b_mint_account,
|
|
user_key,
|
|
&authority_key,
|
|
token_b_amount,
|
|
);
|
|
|
|
SwapAccountInfo {
|
|
nonce,
|
|
authority_key,
|
|
fees,
|
|
swap_curve,
|
|
swap_key,
|
|
swap_account,
|
|
pool_mint_key,
|
|
pool_mint_account,
|
|
pool_fee_key,
|
|
pool_fee_account,
|
|
pool_token_key,
|
|
pool_token_account,
|
|
token_a_key,
|
|
token_a_account,
|
|
token_a_mint_key,
|
|
token_a_mint_account,
|
|
token_b_key,
|
|
token_b_account,
|
|
token_b_mint_key,
|
|
token_b_mint_account,
|
|
}
|
|
}
|
|
|
|
pub fn initialize_swap(&mut self) -> ProgramResult {
|
|
do_process_instruction(
|
|
initialize(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&self.swap_key,
|
|
&self.authority_key,
|
|
&self.token_a_key,
|
|
&self.token_b_key,
|
|
&self.pool_mint_key,
|
|
&self.pool_fee_key,
|
|
&self.pool_token_key,
|
|
self.nonce,
|
|
self.fees.clone(),
|
|
self.swap_curve.clone(),
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut self.swap_account,
|
|
&mut Account::default(),
|
|
&mut self.token_a_account,
|
|
&mut self.token_b_account,
|
|
&mut self.pool_mint_account,
|
|
&mut self.pool_fee_account,
|
|
&mut self.pool_token_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
}
|
|
|
|
pub fn setup_token_accounts(
|
|
&mut self,
|
|
mint_owner: &Pubkey,
|
|
account_owner: &Pubkey,
|
|
a_amount: u64,
|
|
b_amount: u64,
|
|
pool_amount: u64,
|
|
) -> (Pubkey, Account, Pubkey, Account, Pubkey, Account) {
|
|
let (token_a_key, token_a_account) = mint_token(
|
|
&spl_token::id(),
|
|
&self.token_a_mint_key,
|
|
&mut self.token_a_mint_account,
|
|
mint_owner,
|
|
account_owner,
|
|
a_amount,
|
|
);
|
|
let (token_b_key, token_b_account) = mint_token(
|
|
&spl_token::id(),
|
|
&self.token_b_mint_key,
|
|
&mut self.token_b_mint_account,
|
|
mint_owner,
|
|
account_owner,
|
|
b_amount,
|
|
);
|
|
let (pool_key, pool_account) = mint_token(
|
|
&spl_token::id(),
|
|
&self.pool_mint_key,
|
|
&mut self.pool_mint_account,
|
|
&self.authority_key,
|
|
account_owner,
|
|
pool_amount,
|
|
);
|
|
(
|
|
token_a_key,
|
|
token_a_account,
|
|
token_b_key,
|
|
token_b_account,
|
|
pool_key,
|
|
pool_account,
|
|
)
|
|
}
|
|
|
|
fn get_token_account(&self, account_key: &Pubkey) -> &Account {
|
|
if *account_key == self.token_a_key {
|
|
return &self.token_a_account;
|
|
} else if *account_key == self.token_b_key {
|
|
return &self.token_b_account;
|
|
}
|
|
panic!("Could not find matching swap token account");
|
|
}
|
|
|
|
fn set_token_account(&mut self, account_key: &Pubkey, account: Account) {
|
|
if *account_key == self.token_a_key {
|
|
self.token_a_account = account;
|
|
return;
|
|
} else if *account_key == self.token_b_key {
|
|
self.token_b_account = account;
|
|
return;
|
|
}
|
|
panic!("Could not find matching swap token account");
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn swap(
|
|
&mut self,
|
|
user_key: &Pubkey,
|
|
user_source_key: &Pubkey,
|
|
mut user_source_account: &mut Account,
|
|
swap_source_key: &Pubkey,
|
|
swap_destination_key: &Pubkey,
|
|
user_destination_key: &Pubkey,
|
|
mut user_destination_account: &mut Account,
|
|
amount_in: u64,
|
|
minimum_amount_out: u64,
|
|
) -> ProgramResult {
|
|
let user_transfer_key = Pubkey::new_unique();
|
|
// approve moving from user source account
|
|
do_process_instruction(
|
|
approve(
|
|
&spl_token::id(),
|
|
user_source_key,
|
|
&user_transfer_key,
|
|
user_key,
|
|
&[],
|
|
amount_in,
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut user_source_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
|
|
let mut swap_source_account = self.get_token_account(swap_source_key).clone();
|
|
let mut swap_destination_account = self.get_token_account(swap_destination_key).clone();
|
|
|
|
// perform the swap
|
|
do_process_instruction(
|
|
swap(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&self.swap_key,
|
|
&self.authority_key,
|
|
&user_transfer_key,
|
|
user_source_key,
|
|
swap_source_key,
|
|
swap_destination_key,
|
|
user_destination_key,
|
|
&self.pool_mint_key,
|
|
&self.pool_fee_key,
|
|
None,
|
|
Swap {
|
|
amount_in,
|
|
minimum_amount_out,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut self.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut user_source_account,
|
|
&mut swap_source_account,
|
|
&mut swap_destination_account,
|
|
&mut user_destination_account,
|
|
&mut self.pool_mint_account,
|
|
&mut self.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
)?;
|
|
|
|
self.set_token_account(swap_source_key, swap_source_account);
|
|
self.set_token_account(swap_destination_key, swap_destination_account);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn deposit_all_token_types(
|
|
&mut self,
|
|
depositor_key: &Pubkey,
|
|
depositor_token_a_key: &Pubkey,
|
|
mut depositor_token_a_account: &mut Account,
|
|
depositor_token_b_key: &Pubkey,
|
|
mut depositor_token_b_account: &mut Account,
|
|
depositor_pool_key: &Pubkey,
|
|
mut depositor_pool_account: &mut Account,
|
|
pool_token_amount: u64,
|
|
maximum_token_a_amount: u64,
|
|
maximum_token_b_amount: u64,
|
|
) -> ProgramResult {
|
|
let user_transfer_authority = Pubkey::new_unique();
|
|
do_process_instruction(
|
|
approve(
|
|
&spl_token::id(),
|
|
depositor_token_a_key,
|
|
&user_transfer_authority,
|
|
depositor_key,
|
|
&[],
|
|
maximum_token_a_amount,
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut depositor_token_a_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
|
|
do_process_instruction(
|
|
approve(
|
|
&spl_token::id(),
|
|
depositor_token_b_key,
|
|
&user_transfer_authority,
|
|
depositor_key,
|
|
&[],
|
|
maximum_token_b_amount,
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut depositor_token_b_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
|
|
do_process_instruction(
|
|
deposit_all_token_types(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&self.swap_key,
|
|
&self.authority_key,
|
|
&user_transfer_authority,
|
|
depositor_token_a_key,
|
|
depositor_token_b_key,
|
|
&self.token_a_key,
|
|
&self.token_b_key,
|
|
&self.pool_mint_key,
|
|
depositor_pool_key,
|
|
DepositAllTokenTypes {
|
|
pool_token_amount,
|
|
maximum_token_a_amount,
|
|
maximum_token_b_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut self.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut depositor_token_a_account,
|
|
&mut depositor_token_b_account,
|
|
&mut self.token_a_account,
|
|
&mut self.token_b_account,
|
|
&mut self.pool_mint_account,
|
|
&mut depositor_pool_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn withdraw_all_token_types(
|
|
&mut self,
|
|
user_key: &Pubkey,
|
|
pool_key: &Pubkey,
|
|
mut pool_account: &mut Account,
|
|
token_a_key: &Pubkey,
|
|
mut token_a_account: &mut Account,
|
|
token_b_key: &Pubkey,
|
|
mut token_b_account: &mut Account,
|
|
pool_token_amount: u64,
|
|
minimum_token_a_amount: u64,
|
|
minimum_token_b_amount: u64,
|
|
) -> ProgramResult {
|
|
let user_transfer_authority_key = Pubkey::new_unique();
|
|
// approve user transfer authority to take out pool tokens
|
|
do_process_instruction(
|
|
approve(
|
|
&spl_token::id(),
|
|
pool_key,
|
|
&user_transfer_authority_key,
|
|
user_key,
|
|
&[],
|
|
pool_token_amount,
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut pool_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
|
|
// withdraw token a and b correctly
|
|
do_process_instruction(
|
|
withdraw_all_token_types(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&self.swap_key,
|
|
&self.authority_key,
|
|
&user_transfer_authority_key,
|
|
&self.pool_mint_key,
|
|
&self.pool_fee_key,
|
|
pool_key,
|
|
&self.token_a_key,
|
|
&self.token_b_key,
|
|
token_a_key,
|
|
token_b_key,
|
|
WithdrawAllTokenTypes {
|
|
pool_token_amount,
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut self.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut self.pool_mint_account,
|
|
&mut pool_account,
|
|
&mut self.token_a_account,
|
|
&mut self.token_b_account,
|
|
&mut token_a_account,
|
|
&mut token_b_account,
|
|
&mut self.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn deposit_single_token_type_exact_amount_in(
|
|
&mut self,
|
|
depositor_key: &Pubkey,
|
|
deposit_account_key: &Pubkey,
|
|
mut deposit_token_account: &mut Account,
|
|
deposit_pool_key: &Pubkey,
|
|
mut deposit_pool_account: &mut Account,
|
|
source_token_amount: u64,
|
|
minimum_pool_token_amount: u64,
|
|
) -> ProgramResult {
|
|
let user_transfer_authority_key = Pubkey::new_unique();
|
|
do_process_instruction(
|
|
approve(
|
|
&spl_token::id(),
|
|
deposit_account_key,
|
|
&user_transfer_authority_key,
|
|
depositor_key,
|
|
&[],
|
|
source_token_amount,
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut deposit_token_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
|
|
do_process_instruction(
|
|
deposit_single_token_type_exact_amount_in(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&self.swap_key,
|
|
&self.authority_key,
|
|
&user_transfer_authority_key,
|
|
deposit_account_key,
|
|
&self.token_a_key,
|
|
&self.token_b_key,
|
|
&self.pool_mint_key,
|
|
deposit_pool_key,
|
|
DepositSingleTokenTypeExactAmountIn {
|
|
source_token_amount,
|
|
minimum_pool_token_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut self.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut deposit_token_account,
|
|
&mut self.token_a_account,
|
|
&mut self.token_b_account,
|
|
&mut self.pool_mint_account,
|
|
&mut deposit_pool_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub fn withdraw_single_token_type_exact_amount_out(
|
|
&mut self,
|
|
user_key: &Pubkey,
|
|
pool_key: &Pubkey,
|
|
mut pool_account: &mut Account,
|
|
destination_key: &Pubkey,
|
|
mut destination_account: &mut Account,
|
|
destination_token_amount: u64,
|
|
maximum_pool_token_amount: u64,
|
|
) -> ProgramResult {
|
|
let user_transfer_authority_key = Pubkey::new_unique();
|
|
// approve user transfer authority to take out pool tokens
|
|
do_process_instruction(
|
|
approve(
|
|
&spl_token::id(),
|
|
pool_key,
|
|
&user_transfer_authority_key,
|
|
user_key,
|
|
&[],
|
|
maximum_pool_token_amount,
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut pool_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
|
|
do_process_instruction(
|
|
withdraw_single_token_type_exact_amount_out(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&self.swap_key,
|
|
&self.authority_key,
|
|
&user_transfer_authority_key,
|
|
&self.pool_mint_key,
|
|
&self.pool_fee_key,
|
|
pool_key,
|
|
&self.token_a_key,
|
|
&self.token_b_key,
|
|
destination_key,
|
|
WithdrawSingleTokenTypeExactAmountOut {
|
|
destination_token_amount,
|
|
maximum_pool_token_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut self.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut self.pool_mint_account,
|
|
&mut pool_account,
|
|
&mut self.token_a_account,
|
|
&mut self.token_b_account,
|
|
&mut destination_account,
|
|
&mut self.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
}
|
|
}
|
|
|
|
fn mint_minimum_balance() -> u64 {
|
|
Rent::default().minimum_balance(spl_token::state::Mint::get_packed_len())
|
|
}
|
|
|
|
fn account_minimum_balance() -> u64 {
|
|
Rent::default().minimum_balance(spl_token::state::Account::get_packed_len())
|
|
}
|
|
|
|
fn do_process_instruction_with_fee_constraints(
|
|
instruction: Instruction,
|
|
accounts: Vec<&mut Account>,
|
|
swap_constraints: &Option<SwapConstraints>,
|
|
) -> ProgramResult {
|
|
test_syscall_stubs();
|
|
|
|
// approximate the logic in the actual runtime which runs the instruction
|
|
// and only updates accounts if the instruction is successful
|
|
let mut account_clones = accounts.iter().map(|x| (*x).clone()).collect::<Vec<_>>();
|
|
let mut meta = instruction
|
|
.accounts
|
|
.iter()
|
|
.zip(account_clones.iter_mut())
|
|
.map(|(account_meta, account)| (&account_meta.pubkey, account_meta.is_signer, account))
|
|
.collect::<Vec<_>>();
|
|
let mut account_infos = create_is_signer_account_infos(&mut meta);
|
|
let res = if instruction.program_id == SWAP_PROGRAM_ID {
|
|
Processor::process_with_constraints(
|
|
&instruction.program_id,
|
|
&account_infos,
|
|
&instruction.data,
|
|
swap_constraints,
|
|
)
|
|
} else {
|
|
spl_token::processor::Processor::process(
|
|
&instruction.program_id,
|
|
&account_infos,
|
|
&instruction.data,
|
|
)
|
|
};
|
|
|
|
if res.is_ok() {
|
|
let mut account_metas = instruction
|
|
.accounts
|
|
.iter()
|
|
.zip(accounts)
|
|
.map(|(account_meta, account)| (&account_meta.pubkey, account))
|
|
.collect::<Vec<_>>();
|
|
for account_info in account_infos.iter_mut() {
|
|
for account_meta in account_metas.iter_mut() {
|
|
if account_info.key == account_meta.0 {
|
|
let account = &mut account_meta.1;
|
|
account.owner = *account_info.owner;
|
|
account.lamports = **account_info.lamports.borrow();
|
|
account.data = account_info.data.borrow().to_vec();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
res
|
|
}
|
|
|
|
fn do_process_instruction(
|
|
instruction: Instruction,
|
|
accounts: Vec<&mut Account>,
|
|
) -> ProgramResult {
|
|
do_process_instruction_with_fee_constraints(instruction, accounts, &SWAP_CONSTRAINTS)
|
|
}
|
|
|
|
fn mint_token(
|
|
program_id: &Pubkey,
|
|
mint_key: &Pubkey,
|
|
mut mint_account: &mut Account,
|
|
mint_authority_key: &Pubkey,
|
|
account_owner_key: &Pubkey,
|
|
amount: u64,
|
|
) -> (Pubkey, Account) {
|
|
let account_key = Pubkey::new_unique();
|
|
let mut account_account = Account::new(
|
|
account_minimum_balance(),
|
|
spl_token::state::Account::get_packed_len(),
|
|
program_id,
|
|
);
|
|
let mut mint_authority_account = Account::default();
|
|
let mut rent_sysvar_account = create_account_for_test(&Rent::free());
|
|
|
|
do_process_instruction(
|
|
initialize_account(program_id, &account_key, mint_key, account_owner_key).unwrap(),
|
|
vec![
|
|
&mut account_account,
|
|
&mut mint_account,
|
|
&mut mint_authority_account,
|
|
&mut rent_sysvar_account,
|
|
],
|
|
)
|
|
.unwrap();
|
|
|
|
if amount > 0 {
|
|
do_process_instruction(
|
|
mint_to(
|
|
program_id,
|
|
mint_key,
|
|
&account_key,
|
|
mint_authority_key,
|
|
&[],
|
|
amount,
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut mint_account,
|
|
&mut account_account,
|
|
&mut mint_authority_account,
|
|
],
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
(account_key, account_account)
|
|
}
|
|
|
|
fn create_mint(
|
|
program_id: &Pubkey,
|
|
authority_key: &Pubkey,
|
|
freeze_authority: Option<&Pubkey>,
|
|
) -> (Pubkey, Account) {
|
|
let mint_key = Pubkey::new_unique();
|
|
let mut mint_account = Account::new(
|
|
mint_minimum_balance(),
|
|
spl_token::state::Mint::get_packed_len(),
|
|
program_id,
|
|
);
|
|
let mut rent_sysvar_account = create_account_for_test(&Rent::free());
|
|
|
|
do_process_instruction(
|
|
initialize_mint(program_id, &mint_key, authority_key, freeze_authority, 2).unwrap(),
|
|
vec![&mut mint_account, &mut rent_sysvar_account],
|
|
)
|
|
.unwrap();
|
|
|
|
(mint_key, mint_account)
|
|
}
|
|
|
|
#[test]
|
|
fn test_token_program_id_error() {
|
|
test_syscall_stubs();
|
|
let swap_key = Pubkey::new_unique();
|
|
let mut mint = (Pubkey::new_unique(), Account::default());
|
|
let mut destination = (Pubkey::new_unique(), Account::default());
|
|
let token_program = (spl_token::id(), Account::default());
|
|
let (authority_key, nonce) =
|
|
Pubkey::find_program_address(&[&swap_key.to_bytes()[..]], &SWAP_PROGRAM_ID);
|
|
let mut authority = (authority_key, Account::default());
|
|
let swap_bytes = swap_key.to_bytes();
|
|
let authority_signature_seeds = [&swap_bytes[..32], &[nonce]];
|
|
let signers = &[&authority_signature_seeds[..]];
|
|
let ix = mint_to(
|
|
&token_program.0,
|
|
&mint.0,
|
|
&destination.0,
|
|
&authority.0,
|
|
&[],
|
|
10,
|
|
)
|
|
.unwrap();
|
|
let mint = (&mut mint).into();
|
|
let destination = (&mut destination).into();
|
|
let authority = (&mut authority).into();
|
|
|
|
let err = invoke_signed(&ix, &[mint, destination, authority], signers).unwrap_err();
|
|
assert_eq!(err, ProgramError::InvalidAccountData);
|
|
}
|
|
|
|
#[test]
|
|
fn test_initialize() {
|
|
let user_key = Pubkey::new_unique();
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 2;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 10;
|
|
let owner_withdraw_fee_numerator = 1;
|
|
let owner_withdraw_fee_denominator = 5;
|
|
let host_fee_numerator = 20;
|
|
let host_fee_denominator = 100;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let token_a_amount = 1000;
|
|
let token_b_amount = 2000;
|
|
let pool_token_amount = 10;
|
|
let curve_type = CurveType::ConstantProduct;
|
|
let swap_curve = SwapCurve {
|
|
curve_type,
|
|
calculator: Box::new(ConstantProductCurve {}),
|
|
};
|
|
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
|
|
// wrong nonce for authority_key
|
|
{
|
|
let old_nonce = accounts.nonce;
|
|
accounts.nonce = old_nonce - 1;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidProgramAddress.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.nonce = old_nonce;
|
|
}
|
|
|
|
// uninitialized token a account
|
|
{
|
|
let old_account = accounts.token_a_account;
|
|
accounts.token_a_account = Account::new(0, 0, &spl_token::id());
|
|
assert_eq!(
|
|
Err(SwapError::ExpectedAccount.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.token_a_account = old_account;
|
|
}
|
|
|
|
// uninitialized token b account
|
|
{
|
|
let old_account = accounts.token_b_account;
|
|
accounts.token_b_account = Account::new(0, 0, &spl_token::id());
|
|
assert_eq!(
|
|
Err(SwapError::ExpectedAccount.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.token_b_account = old_account;
|
|
}
|
|
|
|
// uninitialized pool mint
|
|
{
|
|
let old_account = accounts.pool_mint_account;
|
|
accounts.pool_mint_account = Account::new(0, 0, &spl_token::id());
|
|
assert_eq!(
|
|
Err(SwapError::ExpectedMint.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.pool_mint_account = old_account;
|
|
}
|
|
|
|
// token A account owner is not swap authority
|
|
{
|
|
let (_token_a_key, token_a_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.token_a_mint_key,
|
|
&mut accounts.token_a_mint_account,
|
|
&user_key,
|
|
&user_key,
|
|
0,
|
|
);
|
|
let old_account = accounts.token_a_account;
|
|
accounts.token_a_account = token_a_account;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidOwner.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.token_a_account = old_account;
|
|
}
|
|
|
|
// token B account owner is not swap authority
|
|
{
|
|
let (_token_b_key, token_b_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.token_b_mint_key,
|
|
&mut accounts.token_b_mint_account,
|
|
&user_key,
|
|
&user_key,
|
|
0,
|
|
);
|
|
let old_account = accounts.token_b_account;
|
|
accounts.token_b_account = token_b_account;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidOwner.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.token_b_account = old_account;
|
|
}
|
|
|
|
// pool token account owner is swap authority
|
|
{
|
|
let (_pool_token_key, pool_token_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.pool_mint_key,
|
|
&mut accounts.pool_mint_account,
|
|
&accounts.authority_key,
|
|
&accounts.authority_key,
|
|
0,
|
|
);
|
|
let old_account = accounts.pool_token_account;
|
|
accounts.pool_token_account = pool_token_account;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidOutputOwner.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.pool_token_account = old_account;
|
|
}
|
|
|
|
// pool fee account owner is swap authority
|
|
{
|
|
let (_pool_fee_key, pool_fee_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.pool_mint_key,
|
|
&mut accounts.pool_mint_account,
|
|
&accounts.authority_key,
|
|
&accounts.authority_key,
|
|
0,
|
|
);
|
|
let old_account = accounts.pool_fee_account;
|
|
accounts.pool_fee_account = pool_fee_account;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidOutputOwner.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.pool_fee_account = old_account;
|
|
}
|
|
|
|
// pool mint authority is not swap authority
|
|
{
|
|
let (_pool_mint_key, pool_mint_account) =
|
|
create_mint(&spl_token::id(), &user_key, None);
|
|
let old_mint = accounts.pool_mint_account;
|
|
accounts.pool_mint_account = pool_mint_account;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidOwner.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.pool_mint_account = old_mint;
|
|
}
|
|
|
|
// pool mint token has freeze authority
|
|
{
|
|
let (_pool_mint_key, pool_mint_account) =
|
|
create_mint(&spl_token::id(), &accounts.authority_key, Some(&user_key));
|
|
let old_mint = accounts.pool_mint_account;
|
|
accounts.pool_mint_account = pool_mint_account;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidFreezeAuthority.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.pool_mint_account = old_mint;
|
|
}
|
|
|
|
// token A account owned by wrong program
|
|
{
|
|
let (_token_a_key, mut token_a_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.token_a_mint_key,
|
|
&mut accounts.token_a_mint_account,
|
|
&user_key,
|
|
&accounts.authority_key,
|
|
token_a_amount,
|
|
);
|
|
token_a_account.owner = SWAP_PROGRAM_ID;
|
|
let old_account = accounts.token_a_account;
|
|
accounts.token_a_account = token_a_account;
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectTokenProgramId.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.token_a_account = old_account;
|
|
}
|
|
|
|
// token B account owned by wrong program
|
|
{
|
|
let (_token_b_key, mut token_b_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.token_b_mint_key,
|
|
&mut accounts.token_b_mint_account,
|
|
&user_key,
|
|
&accounts.authority_key,
|
|
token_b_amount,
|
|
);
|
|
token_b_account.owner = SWAP_PROGRAM_ID;
|
|
let old_account = accounts.token_b_account;
|
|
accounts.token_b_account = token_b_account;
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectTokenProgramId.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.token_b_account = old_account;
|
|
}
|
|
|
|
// empty token A account
|
|
{
|
|
let (_token_a_key, token_a_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.token_a_mint_key,
|
|
&mut accounts.token_a_mint_account,
|
|
&user_key,
|
|
&accounts.authority_key,
|
|
0,
|
|
);
|
|
let old_account = accounts.token_a_account;
|
|
accounts.token_a_account = token_a_account;
|
|
assert_eq!(
|
|
Err(SwapError::EmptySupply.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.token_a_account = old_account;
|
|
}
|
|
|
|
// empty token B account
|
|
{
|
|
let (_token_b_key, token_b_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.token_b_mint_key,
|
|
&mut accounts.token_b_mint_account,
|
|
&user_key,
|
|
&accounts.authority_key,
|
|
0,
|
|
);
|
|
let old_account = accounts.token_b_account;
|
|
accounts.token_b_account = token_b_account;
|
|
assert_eq!(
|
|
Err(SwapError::EmptySupply.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.token_b_account = old_account;
|
|
}
|
|
|
|
// invalid pool tokens
|
|
{
|
|
let old_mint = accounts.pool_mint_account;
|
|
let old_pool_account = accounts.pool_token_account;
|
|
|
|
let (_pool_mint_key, pool_mint_account) =
|
|
create_mint(&spl_token::id(), &accounts.authority_key, None);
|
|
accounts.pool_mint_account = pool_mint_account;
|
|
|
|
let (_empty_pool_token_key, empty_pool_token_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.pool_mint_key,
|
|
&mut accounts.pool_mint_account,
|
|
&accounts.authority_key,
|
|
&user_key,
|
|
0,
|
|
);
|
|
|
|
let (_pool_token_key, pool_token_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.pool_mint_key,
|
|
&mut accounts.pool_mint_account,
|
|
&accounts.authority_key,
|
|
&user_key,
|
|
pool_token_amount,
|
|
);
|
|
|
|
// non-empty pool token account
|
|
accounts.pool_token_account = pool_token_account;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidSupply.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
|
|
// pool tokens already in circulation
|
|
accounts.pool_token_account = empty_pool_token_account;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidSupply.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
|
|
accounts.pool_mint_account = old_mint;
|
|
accounts.pool_token_account = old_pool_account;
|
|
}
|
|
|
|
// pool fee account has wrong mint
|
|
{
|
|
let (_pool_fee_key, pool_fee_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.token_a_mint_key,
|
|
&mut accounts.token_a_mint_account,
|
|
&user_key,
|
|
&user_key,
|
|
0,
|
|
);
|
|
let old_account = accounts.pool_fee_account;
|
|
accounts.pool_fee_account = pool_fee_account;
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectPoolMint.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.pool_fee_account = old_account;
|
|
}
|
|
|
|
// token A account is delegated
|
|
{
|
|
do_process_instruction(
|
|
approve(
|
|
&spl_token::id(),
|
|
&accounts.token_a_key,
|
|
&user_key,
|
|
&accounts.authority_key,
|
|
&[],
|
|
1,
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.token_a_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
Err(SwapError::InvalidDelegate.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
|
|
do_process_instruction(
|
|
revoke(
|
|
&spl_token::id(),
|
|
&accounts.token_a_key,
|
|
&accounts.authority_key,
|
|
&[],
|
|
)
|
|
.unwrap(),
|
|
vec![&mut accounts.token_a_account, &mut Account::default()],
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// token B account is delegated
|
|
{
|
|
do_process_instruction(
|
|
approve(
|
|
&spl_token::id(),
|
|
&accounts.token_b_key,
|
|
&user_key,
|
|
&accounts.authority_key,
|
|
&[],
|
|
1,
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.token_b_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
Err(SwapError::InvalidDelegate.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
|
|
do_process_instruction(
|
|
revoke(
|
|
&spl_token::id(),
|
|
&accounts.token_b_key,
|
|
&accounts.authority_key,
|
|
&[],
|
|
)
|
|
.unwrap(),
|
|
vec![&mut accounts.token_b_account, &mut Account::default()],
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// token A account has close authority
|
|
{
|
|
do_process_instruction(
|
|
set_authority(
|
|
&spl_token::id(),
|
|
&accounts.token_a_key,
|
|
Some(&user_key),
|
|
AuthorityType::CloseAccount,
|
|
&accounts.authority_key,
|
|
&[],
|
|
)
|
|
.unwrap(),
|
|
vec![&mut accounts.token_a_account, &mut Account::default()],
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
Err(SwapError::InvalidCloseAuthority.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
|
|
do_process_instruction(
|
|
set_authority(
|
|
&spl_token::id(),
|
|
&accounts.token_a_key,
|
|
None,
|
|
AuthorityType::CloseAccount,
|
|
&user_key,
|
|
&[],
|
|
)
|
|
.unwrap(),
|
|
vec![&mut accounts.token_a_account, &mut Account::default()],
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// token B account has close authority
|
|
{
|
|
do_process_instruction(
|
|
set_authority(
|
|
&spl_token::id(),
|
|
&accounts.token_b_key,
|
|
Some(&user_key),
|
|
AuthorityType::CloseAccount,
|
|
&accounts.authority_key,
|
|
&[],
|
|
)
|
|
.unwrap(),
|
|
vec![&mut accounts.token_b_account, &mut Account::default()],
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
Err(SwapError::InvalidCloseAuthority.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
|
|
do_process_instruction(
|
|
set_authority(
|
|
&spl_token::id(),
|
|
&accounts.token_b_key,
|
|
None,
|
|
AuthorityType::CloseAccount,
|
|
&user_key,
|
|
&[],
|
|
)
|
|
.unwrap(),
|
|
vec![&mut accounts.token_b_account, &mut Account::default()],
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// wrong token program id
|
|
{
|
|
let wrong_program_id = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectTokenProgramId.into()),
|
|
do_process_instruction(
|
|
initialize(
|
|
&SWAP_PROGRAM_ID,
|
|
&wrong_program_id,
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
&accounts.pool_token_key,
|
|
accounts.nonce,
|
|
accounts.fees.clone(),
|
|
accounts.swap_curve.clone(),
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut accounts.pool_token_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
);
|
|
}
|
|
|
|
// create swap with same token A and B
|
|
{
|
|
let (_token_a_repeat_key, token_a_repeat_account) = mint_token(
|
|
&spl_token::id(),
|
|
&accounts.token_a_mint_key,
|
|
&mut accounts.token_a_mint_account,
|
|
&user_key,
|
|
&accounts.authority_key,
|
|
10,
|
|
);
|
|
let old_account = accounts.token_b_account;
|
|
accounts.token_b_account = token_a_repeat_account;
|
|
assert_eq!(
|
|
Err(SwapError::RepeatedMint.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
accounts.token_b_account = old_account;
|
|
}
|
|
|
|
// create valid swap
|
|
accounts.initialize_swap().unwrap();
|
|
|
|
// create invalid flat swap
|
|
{
|
|
let token_b_price = 0;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::ConstantPrice,
|
|
calculator: Box::new(ConstantPriceCurve { token_b_price }),
|
|
};
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
assert_eq!(
|
|
Err(SwapError::InvalidCurve.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
}
|
|
|
|
// create valid flat swap
|
|
{
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
let token_b_price = 10_000;
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::ConstantPrice,
|
|
calculator: Box::new(ConstantPriceCurve { token_b_price }),
|
|
};
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
accounts.initialize_swap().unwrap();
|
|
}
|
|
|
|
// create invalid offset swap
|
|
{
|
|
let token_b_offset = 0;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::Offset,
|
|
calculator: Box::new(OffsetCurve { token_b_offset }),
|
|
};
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
assert_eq!(
|
|
Err(SwapError::InvalidCurve.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
}
|
|
|
|
// create valid offset swap
|
|
{
|
|
let token_b_offset = 10;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::Offset,
|
|
calculator: Box::new(OffsetCurve { token_b_offset }),
|
|
};
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
accounts.initialize_swap().unwrap();
|
|
}
|
|
|
|
// wrong owner key in constraint
|
|
{
|
|
let new_key = Pubkey::new_unique();
|
|
let trade_fee_numerator = 25;
|
|
let trade_fee_denominator = 10000;
|
|
let owner_trade_fee_numerator = 5;
|
|
let owner_trade_fee_denominator = 10000;
|
|
let host_fee_numerator = 20;
|
|
let host_fee_denominator = 100;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
let curve = ConstantProductCurve {};
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::ConstantProduct,
|
|
calculator: Box::new(curve),
|
|
};
|
|
let owner_key = &new_key.to_string();
|
|
let valid_curve_types = &[CurveType::ConstantProduct];
|
|
let constraints = Some(SwapConstraints {
|
|
owner_key,
|
|
valid_curve_types,
|
|
fees: &fees,
|
|
});
|
|
let mut accounts = SwapAccountInfo::new(
|
|
&user_key,
|
|
fees.clone(),
|
|
swap_curve,
|
|
token_a_amount,
|
|
token_b_amount,
|
|
);
|
|
assert_eq!(
|
|
Err(SwapError::InvalidOwner.into()),
|
|
do_process_instruction_with_fee_constraints(
|
|
initialize(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
&accounts.pool_token_key,
|
|
accounts.nonce,
|
|
accounts.fees.clone(),
|
|
accounts.swap_curve.clone(),
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut accounts.pool_token_account,
|
|
&mut Account::default(),
|
|
],
|
|
&constraints,
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong fee in constraint
|
|
{
|
|
let trade_fee_numerator = 25;
|
|
let trade_fee_denominator = 10000;
|
|
let owner_trade_fee_numerator = 5;
|
|
let owner_trade_fee_denominator = 10000;
|
|
let host_fee_numerator = 20;
|
|
let host_fee_denominator = 100;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
let curve = ConstantProductCurve {};
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::ConstantProduct,
|
|
calculator: Box::new(curve),
|
|
};
|
|
let owner_key = &user_key.to_string();
|
|
let valid_curve_types = &[CurveType::ConstantProduct];
|
|
let constraints = Some(SwapConstraints {
|
|
owner_key,
|
|
valid_curve_types,
|
|
fees: &fees,
|
|
});
|
|
let mut bad_fees = fees.clone();
|
|
bad_fees.trade_fee_numerator = trade_fee_numerator - 1;
|
|
let mut accounts = SwapAccountInfo::new(
|
|
&user_key,
|
|
bad_fees,
|
|
swap_curve,
|
|
token_a_amount,
|
|
token_b_amount,
|
|
);
|
|
assert_eq!(
|
|
Err(SwapError::InvalidFee.into()),
|
|
do_process_instruction_with_fee_constraints(
|
|
initialize(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
&accounts.pool_token_key,
|
|
accounts.nonce,
|
|
accounts.fees.clone(),
|
|
accounts.swap_curve.clone(),
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut accounts.pool_token_account,
|
|
&mut Account::default(),
|
|
],
|
|
&constraints,
|
|
)
|
|
);
|
|
}
|
|
|
|
// create valid swap with constraints
|
|
{
|
|
let trade_fee_numerator = 25;
|
|
let trade_fee_denominator = 10000;
|
|
let owner_trade_fee_numerator = 5;
|
|
let owner_trade_fee_denominator = 10000;
|
|
let host_fee_numerator = 20;
|
|
let host_fee_denominator = 100;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
let curve = ConstantProductCurve {};
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::ConstantProduct,
|
|
calculator: Box::new(curve),
|
|
};
|
|
let owner_key = &user_key.to_string();
|
|
let valid_curve_types = &[CurveType::ConstantProduct];
|
|
let constraints = Some(SwapConstraints {
|
|
owner_key,
|
|
valid_curve_types,
|
|
fees: &fees,
|
|
});
|
|
let mut accounts = SwapAccountInfo::new(
|
|
&user_key,
|
|
fees.clone(),
|
|
swap_curve,
|
|
token_a_amount,
|
|
token_b_amount,
|
|
);
|
|
do_process_instruction_with_fee_constraints(
|
|
initialize(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
&accounts.pool_token_key,
|
|
accounts.nonce,
|
|
accounts.fees,
|
|
accounts.swap_curve.clone(),
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut accounts.pool_token_account,
|
|
&mut Account::default(),
|
|
],
|
|
&constraints,
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// create again
|
|
{
|
|
assert_eq!(
|
|
Err(SwapError::AlreadyInUse.into()),
|
|
accounts.initialize_swap()
|
|
);
|
|
}
|
|
let swap_state = SwapVersion::unpack(&accounts.swap_account.data).unwrap();
|
|
assert!(swap_state.is_initialized());
|
|
assert_eq!(swap_state.nonce(), accounts.nonce);
|
|
assert_eq!(
|
|
swap_state.swap_curve().curve_type,
|
|
accounts.swap_curve.curve_type
|
|
);
|
|
assert_eq!(*swap_state.token_a_account(), accounts.token_a_key);
|
|
assert_eq!(*swap_state.token_b_account(), accounts.token_b_key);
|
|
assert_eq!(*swap_state.pool_mint(), accounts.pool_mint_key);
|
|
assert_eq!(*swap_state.token_a_mint(), accounts.token_a_mint_key);
|
|
assert_eq!(*swap_state.token_b_mint(), accounts.token_b_mint_key);
|
|
assert_eq!(*swap_state.pool_fee_account(), accounts.pool_fee_key);
|
|
let token_a = spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
assert_eq!(token_a.amount, token_a_amount);
|
|
let token_b = spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap();
|
|
assert_eq!(token_b.amount, token_b_amount);
|
|
let pool_account =
|
|
spl_token::state::Account::unpack(&accounts.pool_token_account.data).unwrap();
|
|
let pool_mint = spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap();
|
|
assert_eq!(pool_mint.supply, pool_account.amount);
|
|
}
|
|
|
|
#[test]
|
|
fn test_deposit() {
|
|
let user_key = Pubkey::new_unique();
|
|
let depositor_key = Pubkey::new_unique();
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 2;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 10;
|
|
let owner_withdraw_fee_numerator = 1;
|
|
let owner_withdraw_fee_denominator = 5;
|
|
let host_fee_numerator = 20;
|
|
let host_fee_denominator = 100;
|
|
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let token_a_amount = 1000;
|
|
let token_b_amount = 9000;
|
|
let curve_type = CurveType::ConstantProduct;
|
|
let swap_curve = SwapCurve {
|
|
curve_type,
|
|
calculator: Box::new(ConstantProductCurve {}),
|
|
};
|
|
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
|
|
// depositing 10% of the current pool amount in token A and B means
|
|
// that our pool tokens will be worth 1 / 10 of the current pool amount
|
|
let pool_amount = INITIAL_SWAP_POOL_AMOUNT / 10;
|
|
let deposit_a = token_a_amount / 10;
|
|
let deposit_b = token_b_amount / 10;
|
|
|
|
// swap not initialized
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
assert_eq!(
|
|
Err(ProgramError::UninitializedAccount),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
}
|
|
|
|
accounts.initialize_swap().unwrap();
|
|
|
|
// wrong owner for swap account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let old_swap_account = accounts.swap_account;
|
|
let mut wrong_swap_account = old_swap_account.clone();
|
|
wrong_swap_account.owner = spl_token::id();
|
|
accounts.swap_account = wrong_swap_account;
|
|
assert_eq!(
|
|
Err(ProgramError::IncorrectProgramId),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
accounts.swap_account = old_swap_account;
|
|
}
|
|
|
|
// wrong nonce for authority_key
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let old_authority = accounts.authority_key;
|
|
let (bad_authority_key, _nonce) = Pubkey::find_program_address(
|
|
&[&accounts.swap_key.to_bytes()[..]],
|
|
&spl_token::id(),
|
|
);
|
|
accounts.authority_key = bad_authority_key;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidProgramAddress.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
accounts.authority_key = old_authority;
|
|
}
|
|
|
|
// not enough token A
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&depositor_key,
|
|
deposit_a / 2,
|
|
deposit_b,
|
|
0,
|
|
);
|
|
assert_eq!(
|
|
Err(TokenError::InsufficientFunds.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
}
|
|
|
|
// not enough token B
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&depositor_key,
|
|
deposit_a,
|
|
deposit_b / 2,
|
|
0,
|
|
);
|
|
assert_eq!(
|
|
Err(TokenError::InsufficientFunds.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong swap token accounts
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
assert_eq!(
|
|
Err(TokenError::MintMismatch.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong pool token account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
mut _pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let (
|
|
wrong_token_key,
|
|
mut wrong_token_account,
|
|
_token_b_key,
|
|
mut _token_b_account,
|
|
_pool_key,
|
|
mut _pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
assert_eq!(
|
|
Err(TokenError::MintMismatch.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&wrong_token_key,
|
|
&mut wrong_token_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
}
|
|
|
|
// no approval
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let user_transfer_authority_key = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(TokenError::OwnerMismatch.into()),
|
|
do_process_instruction(
|
|
deposit_all_token_types(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&user_transfer_authority_key,
|
|
&token_a_key,
|
|
&token_b_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&pool_key,
|
|
DepositAllTokenTypes {
|
|
pool_token_amount: pool_amount.try_into().unwrap(),
|
|
maximum_token_a_amount: deposit_a,
|
|
maximum_token_b_amount: deposit_b,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut token_a_account,
|
|
&mut token_b_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut pool_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong token program id
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let wrong_key = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectTokenProgramId.into()),
|
|
do_process_instruction(
|
|
deposit_all_token_types(
|
|
&SWAP_PROGRAM_ID,
|
|
&wrong_key,
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.authority_key,
|
|
&token_a_key,
|
|
&token_b_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&pool_key,
|
|
DepositAllTokenTypes {
|
|
pool_token_amount: pool_amount.try_into().unwrap(),
|
|
maximum_token_a_amount: deposit_a,
|
|
maximum_token_b_amount: deposit_b,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut token_a_account,
|
|
&mut token_b_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut pool_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong swap token accounts
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
|
|
let old_a_key = accounts.token_a_key;
|
|
let old_a_account = accounts.token_a_account;
|
|
|
|
accounts.token_a_key = token_a_key;
|
|
accounts.token_a_account = token_a_account.clone();
|
|
|
|
// wrong swap token a account
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectSwapAccount.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
|
|
accounts.token_a_key = old_a_key;
|
|
accounts.token_a_account = old_a_account;
|
|
|
|
let old_b_key = accounts.token_b_key;
|
|
let old_b_account = accounts.token_b_account;
|
|
|
|
accounts.token_b_key = token_b_key;
|
|
accounts.token_b_account = token_b_account.clone();
|
|
|
|
// wrong swap token b account
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectSwapAccount.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
|
|
accounts.token_b_key = old_b_key;
|
|
accounts.token_b_account = old_b_account;
|
|
}
|
|
|
|
// wrong mint
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let (pool_mint_key, pool_mint_account) =
|
|
create_mint(&spl_token::id(), &accounts.authority_key, None);
|
|
let old_pool_key = accounts.pool_mint_key;
|
|
let old_pool_account = accounts.pool_mint_account;
|
|
accounts.pool_mint_key = pool_mint_key;
|
|
accounts.pool_mint_account = pool_mint_account;
|
|
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectPoolMint.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
|
|
accounts.pool_mint_key = old_pool_key;
|
|
accounts.pool_mint_account = old_pool_account;
|
|
}
|
|
|
|
// deposit 1 pool token fails beacuse it equates to 0 swap tokens
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
assert_eq!(
|
|
Err(SwapError::ZeroTradingTokens.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
1,
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
}
|
|
|
|
// slippage exceeded
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
// maximum A amount in too low
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a / 10,
|
|
deposit_b,
|
|
)
|
|
);
|
|
// maximum B amount in too low
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b / 10,
|
|
)
|
|
);
|
|
}
|
|
|
|
// invalid input: can't use swap pool tokens as source
|
|
{
|
|
let (
|
|
_token_a_key,
|
|
_token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let swap_token_a_key = accounts.token_a_key;
|
|
let mut swap_token_a_account = accounts.get_token_account(&swap_token_a_key).clone();
|
|
let swap_token_b_key = accounts.token_b_key;
|
|
let mut swap_token_b_account = accounts.get_token_account(&swap_token_b_key).clone();
|
|
let authority_key = accounts.authority_key;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidInput.into()),
|
|
accounts.deposit_all_token_types(
|
|
&authority_key,
|
|
&swap_token_a_key,
|
|
&mut swap_token_a_account,
|
|
&swap_token_b_key,
|
|
&mut swap_token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
);
|
|
}
|
|
|
|
// correctly deposit
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
accounts
|
|
.deposit_all_token_types(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount.try_into().unwrap(),
|
|
deposit_a,
|
|
deposit_b,
|
|
)
|
|
.unwrap();
|
|
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
assert_eq!(swap_token_a.amount, deposit_a + token_a_amount);
|
|
let swap_token_b =
|
|
spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap();
|
|
assert_eq!(swap_token_b.amount, deposit_b + token_b_amount);
|
|
let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap();
|
|
assert_eq!(token_a.amount, 0);
|
|
let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap();
|
|
assert_eq!(token_b.amount, 0);
|
|
let pool_account = spl_token::state::Account::unpack(&pool_account.data).unwrap();
|
|
let swap_pool_account =
|
|
spl_token::state::Account::unpack(&accounts.pool_token_account.data).unwrap();
|
|
let pool_mint =
|
|
spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap();
|
|
assert_eq!(
|
|
pool_mint.supply,
|
|
pool_account.amount + swap_pool_account.amount
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_withdraw() {
|
|
let user_key = Pubkey::new_unique();
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 2;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 10;
|
|
let owner_withdraw_fee_numerator = 1;
|
|
let owner_withdraw_fee_denominator = 5;
|
|
let host_fee_numerator = 7;
|
|
let host_fee_denominator = 100;
|
|
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let token_a_amount = 1000;
|
|
let token_b_amount = 2000;
|
|
let curve_type = CurveType::ConstantProduct;
|
|
let swap_curve = SwapCurve {
|
|
curve_type,
|
|
calculator: Box::new(ConstantProductCurve {}),
|
|
};
|
|
|
|
let withdrawer_key = Pubkey::new_unique();
|
|
let initial_a = token_a_amount / 10;
|
|
let initial_b = token_b_amount / 10;
|
|
let initial_pool = swap_curve.calculator.new_pool_supply() / 10;
|
|
let withdraw_amount = initial_pool / 4;
|
|
let minimum_token_a_amount = initial_a / 40;
|
|
let minimum_token_b_amount = initial_b / 40;
|
|
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
|
|
// swap not initialized
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &withdrawer_key, initial_a, initial_b, 0);
|
|
assert_eq!(
|
|
Err(ProgramError::UninitializedAccount),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
accounts.initialize_swap().unwrap();
|
|
|
|
// wrong owner for swap account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &withdrawer_key, initial_a, initial_b, 0);
|
|
let old_swap_account = accounts.swap_account;
|
|
let mut wrong_swap_account = old_swap_account.clone();
|
|
wrong_swap_account.owner = spl_token::id();
|
|
accounts.swap_account = wrong_swap_account;
|
|
assert_eq!(
|
|
Err(ProgramError::IncorrectProgramId),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
accounts.swap_account = old_swap_account;
|
|
}
|
|
|
|
// wrong nonce for authority_key
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &withdrawer_key, initial_a, initial_b, 0);
|
|
let old_authority = accounts.authority_key;
|
|
let (bad_authority_key, _nonce) = Pubkey::find_program_address(
|
|
&[&accounts.swap_key.to_bytes()[..]],
|
|
&spl_token::id(),
|
|
);
|
|
accounts.authority_key = bad_authority_key;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidProgramAddress.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
accounts.authority_key = old_authority;
|
|
}
|
|
|
|
// not enough pool tokens
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
to_u64(withdraw_amount).unwrap() / 2u64,
|
|
);
|
|
assert_eq!(
|
|
Err(TokenError::InsufficientFunds.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount / 2,
|
|
minimum_token_b_amount / 2,
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong token a / b accounts
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
withdraw_amount.try_into().unwrap(),
|
|
);
|
|
assert_eq!(
|
|
Err(TokenError::MintMismatch.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong pool token account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
withdraw_amount.try_into().unwrap(),
|
|
);
|
|
let (
|
|
wrong_token_a_key,
|
|
mut wrong_token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
withdraw_amount.try_into().unwrap(),
|
|
initial_b,
|
|
withdraw_amount.try_into().unwrap(),
|
|
);
|
|
assert_eq!(
|
|
Err(TokenError::MintMismatch.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&wrong_token_a_key,
|
|
&mut wrong_token_a_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong pool fee account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
wrong_pool_key,
|
|
wrong_pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
withdraw_amount.try_into().unwrap(),
|
|
);
|
|
let (
|
|
_token_a_key,
|
|
_token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
withdraw_amount.try_into().unwrap(),
|
|
);
|
|
let old_pool_fee_account = accounts.pool_fee_account;
|
|
let old_pool_fee_key = accounts.pool_fee_key;
|
|
accounts.pool_fee_account = wrong_pool_account;
|
|
accounts.pool_fee_key = wrong_pool_key;
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectFeeAccount.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
),
|
|
);
|
|
accounts.pool_fee_account = old_pool_fee_account;
|
|
accounts.pool_fee_key = old_pool_fee_key;
|
|
}
|
|
|
|
// no approval
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
0,
|
|
0,
|
|
withdraw_amount.try_into().unwrap(),
|
|
);
|
|
let user_transfer_authority_key = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(TokenError::OwnerMismatch.into()),
|
|
do_process_instruction(
|
|
withdraw_all_token_types(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&user_transfer_authority_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
&pool_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&token_a_key,
|
|
&token_b_key,
|
|
WithdrawAllTokenTypes {
|
|
pool_token_amount: withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
}
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut accounts.pool_mint_account,
|
|
&mut pool_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut token_a_account,
|
|
&mut token_b_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong token program id
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
withdraw_amount.try_into().unwrap(),
|
|
);
|
|
let wrong_key = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectTokenProgramId.into()),
|
|
do_process_instruction(
|
|
withdraw_all_token_types(
|
|
&SWAP_PROGRAM_ID,
|
|
&wrong_key,
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.authority_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
&pool_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&token_a_key,
|
|
&token_b_key,
|
|
WithdrawAllTokenTypes {
|
|
pool_token_amount: withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut accounts.pool_mint_account,
|
|
&mut pool_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut token_a_account,
|
|
&mut token_b_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong swap token accounts
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
initial_pool.try_into().unwrap(),
|
|
);
|
|
|
|
let old_a_key = accounts.token_a_key;
|
|
let old_a_account = accounts.token_a_account;
|
|
|
|
accounts.token_a_key = token_a_key;
|
|
accounts.token_a_account = token_a_account.clone();
|
|
|
|
// wrong swap token a account
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectSwapAccount.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
|
|
accounts.token_a_key = old_a_key;
|
|
accounts.token_a_account = old_a_account;
|
|
|
|
let old_b_key = accounts.token_b_key;
|
|
let old_b_account = accounts.token_b_account;
|
|
|
|
accounts.token_b_key = token_b_key;
|
|
accounts.token_b_account = token_b_account.clone();
|
|
|
|
// wrong swap token b account
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectSwapAccount.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
|
|
accounts.token_b_key = old_b_key;
|
|
accounts.token_b_account = old_b_account;
|
|
}
|
|
|
|
// wrong mint
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
initial_pool.try_into().unwrap(),
|
|
);
|
|
let (pool_mint_key, pool_mint_account) =
|
|
create_mint(&spl_token::id(), &accounts.authority_key, None);
|
|
let old_pool_key = accounts.pool_mint_key;
|
|
let old_pool_account = accounts.pool_mint_account;
|
|
accounts.pool_mint_key = pool_mint_key;
|
|
accounts.pool_mint_account = pool_mint_account;
|
|
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectPoolMint.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
|
|
accounts.pool_mint_key = old_pool_key;
|
|
accounts.pool_mint_account = old_pool_account;
|
|
}
|
|
|
|
// withdrawing 1 pool token fails because it equates to 0 output tokens
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
initial_pool.try_into().unwrap(),
|
|
);
|
|
assert_eq!(
|
|
Err(SwapError::ZeroTradingTokens.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
1,
|
|
0,
|
|
0,
|
|
)
|
|
);
|
|
}
|
|
|
|
// slippage exceeded
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
initial_pool.try_into().unwrap(),
|
|
);
|
|
// minimum A amount out too high
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount * 10,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
// minimum B amount out too high
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount * 10,
|
|
)
|
|
);
|
|
}
|
|
|
|
// invalid input: can't use swap pool tokens as destination
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
initial_pool.try_into().unwrap(),
|
|
);
|
|
let swap_token_a_key = accounts.token_a_key;
|
|
let mut swap_token_a_account = accounts.get_token_account(&swap_token_a_key).clone();
|
|
assert_eq!(
|
|
Err(SwapError::InvalidInput.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&swap_token_a_key,
|
|
&mut swap_token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
let swap_token_b_key = accounts.token_b_key;
|
|
let mut swap_token_b_account = accounts.get_token_account(&swap_token_b_key).clone();
|
|
assert_eq!(
|
|
Err(SwapError::InvalidInput.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_b_key,
|
|
&mut swap_token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// correct withdrawal
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
initial_pool.try_into().unwrap(),
|
|
);
|
|
|
|
accounts
|
|
.withdraw_all_token_types(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
withdraw_amount.try_into().unwrap(),
|
|
minimum_token_a_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
.unwrap();
|
|
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
let swap_token_b =
|
|
spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap();
|
|
let pool_mint =
|
|
spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap();
|
|
let withdraw_fee = accounts.fees.owner_withdraw_fee(withdraw_amount).unwrap();
|
|
let results = accounts
|
|
.swap_curve
|
|
.calculator
|
|
.pool_tokens_to_trading_tokens(
|
|
withdraw_amount - withdraw_fee,
|
|
pool_mint.supply.try_into().unwrap(),
|
|
swap_token_a.amount.try_into().unwrap(),
|
|
swap_token_b.amount.try_into().unwrap(),
|
|
RoundDirection::Floor,
|
|
)
|
|
.unwrap();
|
|
assert_eq!(
|
|
swap_token_a.amount,
|
|
token_a_amount - to_u64(results.token_a_amount).unwrap()
|
|
);
|
|
assert_eq!(
|
|
swap_token_b.amount,
|
|
token_b_amount - to_u64(results.token_b_amount).unwrap()
|
|
);
|
|
let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap();
|
|
assert_eq!(
|
|
token_a.amount,
|
|
initial_a + to_u64(results.token_a_amount).unwrap()
|
|
);
|
|
let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap();
|
|
assert_eq!(
|
|
token_b.amount,
|
|
initial_b + to_u64(results.token_b_amount).unwrap()
|
|
);
|
|
let pool_account = spl_token::state::Account::unpack(&pool_account.data).unwrap();
|
|
assert_eq!(
|
|
pool_account.amount,
|
|
to_u64(initial_pool - withdraw_amount).unwrap()
|
|
);
|
|
let fee_account =
|
|
spl_token::state::Account::unpack(&accounts.pool_fee_account.data).unwrap();
|
|
assert_eq!(
|
|
fee_account.amount,
|
|
TryInto::<u64>::try_into(withdraw_fee).unwrap()
|
|
);
|
|
}
|
|
|
|
// correct withdrawal from fee account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
mut _pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &withdrawer_key, 0, 0, 0);
|
|
|
|
let pool_fee_key = accounts.pool_fee_key;
|
|
let mut pool_fee_account = accounts.pool_fee_account.clone();
|
|
let fee_account = spl_token::state::Account::unpack(&pool_fee_account.data).unwrap();
|
|
let pool_fee_amount = fee_account.amount;
|
|
|
|
accounts
|
|
.withdraw_all_token_types(
|
|
&user_key,
|
|
&pool_fee_key,
|
|
&mut pool_fee_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
pool_fee_amount,
|
|
0,
|
|
0,
|
|
)
|
|
.unwrap();
|
|
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
let swap_token_b =
|
|
spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap();
|
|
let pool_mint =
|
|
spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap();
|
|
let results = accounts
|
|
.swap_curve
|
|
.calculator
|
|
.pool_tokens_to_trading_tokens(
|
|
pool_fee_amount.try_into().unwrap(),
|
|
pool_mint.supply.try_into().unwrap(),
|
|
swap_token_a.amount.try_into().unwrap(),
|
|
swap_token_b.amount.try_into().unwrap(),
|
|
RoundDirection::Floor,
|
|
)
|
|
.unwrap();
|
|
let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap();
|
|
assert_eq!(
|
|
token_a.amount,
|
|
TryInto::<u64>::try_into(results.token_a_amount).unwrap()
|
|
);
|
|
let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap();
|
|
assert_eq!(
|
|
token_b.amount,
|
|
TryInto::<u64>::try_into(results.token_b_amount).unwrap()
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_deposit_one_exact_in() {
|
|
let user_key = Pubkey::new_unique();
|
|
let depositor_key = Pubkey::new_unique();
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 2;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 10;
|
|
let owner_withdraw_fee_numerator = 1;
|
|
let owner_withdraw_fee_denominator = 5;
|
|
let host_fee_numerator = 20;
|
|
let host_fee_denominator = 100;
|
|
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let token_a_amount = 1000;
|
|
let token_b_amount = 9000;
|
|
let curve_type = CurveType::ConstantProduct;
|
|
let swap_curve = SwapCurve {
|
|
curve_type,
|
|
calculator: Box::new(ConstantProductCurve {}),
|
|
};
|
|
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
|
|
let deposit_a = token_a_amount / 10;
|
|
let deposit_b = token_b_amount / 10;
|
|
let pool_amount = to_u64(INITIAL_SWAP_POOL_AMOUNT / 100).unwrap();
|
|
|
|
// swap not initialized
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
assert_eq!(
|
|
Err(ProgramError::UninitializedAccount),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_a,
|
|
pool_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
accounts.initialize_swap().unwrap();
|
|
|
|
// wrong owner for swap account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let old_swap_account = accounts.swap_account;
|
|
let mut wrong_swap_account = old_swap_account.clone();
|
|
wrong_swap_account.owner = spl_token::id();
|
|
accounts.swap_account = wrong_swap_account;
|
|
assert_eq!(
|
|
Err(ProgramError::IncorrectProgramId),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_a,
|
|
pool_amount,
|
|
)
|
|
);
|
|
accounts.swap_account = old_swap_account;
|
|
}
|
|
|
|
// wrong nonce for authority_key
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let old_authority = accounts.authority_key;
|
|
let (bad_authority_key, _nonce) = Pubkey::find_program_address(
|
|
&[&accounts.swap_key.to_bytes()[..]],
|
|
&spl_token::id(),
|
|
);
|
|
accounts.authority_key = bad_authority_key;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidProgramAddress.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_a,
|
|
pool_amount,
|
|
)
|
|
);
|
|
accounts.authority_key = old_authority;
|
|
}
|
|
|
|
// not enough token A / B
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&depositor_key,
|
|
deposit_a / 2,
|
|
deposit_b / 2,
|
|
0,
|
|
);
|
|
assert_eq!(
|
|
Err(TokenError::InsufficientFunds.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_a,
|
|
0,
|
|
)
|
|
);
|
|
assert_eq!(
|
|
Err(TokenError::InsufficientFunds.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_b,
|
|
0,
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong pool token account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
mut _pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
assert_eq!(
|
|
Err(TokenError::MintMismatch.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
deposit_a,
|
|
pool_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// no approval
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let user_transfer_authority_key = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(TokenError::OwnerMismatch.into()),
|
|
do_process_instruction(
|
|
deposit_single_token_type_exact_amount_in(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&user_transfer_authority_key,
|
|
&token_a_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&pool_key,
|
|
DepositSingleTokenTypeExactAmountIn {
|
|
source_token_amount: deposit_a,
|
|
minimum_pool_token_amount: pool_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut token_a_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut pool_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong token program id
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let wrong_key = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectTokenProgramId.into()),
|
|
do_process_instruction(
|
|
deposit_single_token_type_exact_amount_in(
|
|
&SWAP_PROGRAM_ID,
|
|
&wrong_key,
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.authority_key,
|
|
&token_a_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&pool_key,
|
|
DepositSingleTokenTypeExactAmountIn {
|
|
source_token_amount: deposit_a,
|
|
minimum_pool_token_amount: pool_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut token_a_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut pool_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong swap token accounts
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
|
|
let old_a_key = accounts.token_a_key;
|
|
let old_a_account = accounts.token_a_account;
|
|
|
|
accounts.token_a_key = token_a_key;
|
|
accounts.token_a_account = token_a_account.clone();
|
|
|
|
// wrong swap token a account
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectSwapAccount.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_a,
|
|
pool_amount,
|
|
)
|
|
);
|
|
|
|
accounts.token_a_key = old_a_key;
|
|
accounts.token_a_account = old_a_account;
|
|
|
|
let old_b_key = accounts.token_b_key;
|
|
let old_b_account = accounts.token_b_account;
|
|
|
|
accounts.token_b_key = token_b_key;
|
|
accounts.token_b_account = token_b_account;
|
|
|
|
// wrong swap token b account
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectSwapAccount.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_a,
|
|
pool_amount,
|
|
)
|
|
);
|
|
|
|
accounts.token_b_key = old_b_key;
|
|
accounts.token_b_account = old_b_account;
|
|
}
|
|
|
|
// wrong mint
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let (pool_mint_key, pool_mint_account) =
|
|
create_mint(&spl_token::id(), &accounts.authority_key, None);
|
|
let old_pool_key = accounts.pool_mint_key;
|
|
let old_pool_account = accounts.pool_mint_account;
|
|
accounts.pool_mint_key = pool_mint_key;
|
|
accounts.pool_mint_account = pool_mint_account;
|
|
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectPoolMint.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_a,
|
|
pool_amount,
|
|
)
|
|
);
|
|
|
|
accounts.pool_mint_key = old_pool_key;
|
|
accounts.pool_mint_account = old_pool_account;
|
|
}
|
|
|
|
// slippage exceeded
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
// minimum pool amount too high
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_a / 10,
|
|
pool_amount,
|
|
)
|
|
);
|
|
// minimum pool amount too high
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_b / 10,
|
|
pool_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// invalid input: can't use swap pool tokens as source
|
|
{
|
|
let (
|
|
_token_a_key,
|
|
_token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
let swap_token_a_key = accounts.token_a_key;
|
|
let mut swap_token_a_account = accounts.get_token_account(&swap_token_a_key).clone();
|
|
let swap_token_b_key = accounts.token_b_key;
|
|
let mut swap_token_b_account = accounts.get_token_account(&swap_token_b_key).clone();
|
|
let authority_key = accounts.authority_key;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidInput.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&authority_key,
|
|
&swap_token_a_key,
|
|
&mut swap_token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_a,
|
|
pool_amount,
|
|
)
|
|
);
|
|
assert_eq!(
|
|
Err(SwapError::InvalidInput.into()),
|
|
accounts.deposit_single_token_type_exact_amount_in(
|
|
&authority_key,
|
|
&swap_token_b_key,
|
|
&mut swap_token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_b,
|
|
pool_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// correctly deposit
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &depositor_key, deposit_a, deposit_b, 0);
|
|
accounts
|
|
.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_a,
|
|
pool_amount,
|
|
)
|
|
.unwrap();
|
|
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
assert_eq!(swap_token_a.amount, deposit_a + token_a_amount);
|
|
|
|
let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap();
|
|
assert_eq!(token_a.amount, 0);
|
|
|
|
accounts
|
|
.deposit_single_token_type_exact_amount_in(
|
|
&depositor_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
deposit_b,
|
|
pool_amount,
|
|
)
|
|
.unwrap();
|
|
let swap_token_b =
|
|
spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap();
|
|
assert_eq!(swap_token_b.amount, deposit_b + token_b_amount);
|
|
|
|
let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap();
|
|
assert_eq!(token_b.amount, 0);
|
|
|
|
let pool_account = spl_token::state::Account::unpack(&pool_account.data).unwrap();
|
|
let swap_pool_account =
|
|
spl_token::state::Account::unpack(&accounts.pool_token_account.data).unwrap();
|
|
let pool_mint =
|
|
spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap();
|
|
assert_eq!(
|
|
pool_mint.supply,
|
|
pool_account.amount + swap_pool_account.amount
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_withdraw_one_exact_out() {
|
|
let user_key = Pubkey::new_unique();
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 2;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 10;
|
|
let owner_withdraw_fee_numerator = 1;
|
|
let owner_withdraw_fee_denominator = 5;
|
|
let host_fee_numerator = 7;
|
|
let host_fee_denominator = 100;
|
|
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let token_a_amount = 100_000;
|
|
let token_b_amount = 200_000;
|
|
let curve_type = CurveType::ConstantProduct;
|
|
let swap_curve = SwapCurve {
|
|
curve_type,
|
|
calculator: Box::new(ConstantProductCurve {}),
|
|
};
|
|
|
|
let withdrawer_key = Pubkey::new_unique();
|
|
let initial_a = token_a_amount / 10;
|
|
let initial_b = token_b_amount / 10;
|
|
let initial_pool = swap_curve.calculator.new_pool_supply() / 10;
|
|
let maximum_pool_token_amount = to_u64(initial_pool / 4).unwrap();
|
|
let destination_a_amount = initial_a / 40;
|
|
let destination_b_amount = initial_b / 40;
|
|
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
|
|
// swap not initialized
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &withdrawer_key, initial_a, initial_b, 0);
|
|
assert_eq!(
|
|
Err(ProgramError::UninitializedAccount),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
destination_a_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
accounts.initialize_swap().unwrap();
|
|
|
|
// wrong owner for swap account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &withdrawer_key, initial_a, initial_b, 0);
|
|
let old_swap_account = accounts.swap_account;
|
|
let mut wrong_swap_account = old_swap_account.clone();
|
|
wrong_swap_account.owner = spl_token::id();
|
|
accounts.swap_account = wrong_swap_account;
|
|
assert_eq!(
|
|
Err(ProgramError::IncorrectProgramId),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
destination_a_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
accounts.swap_account = old_swap_account;
|
|
}
|
|
|
|
// wrong nonce for authority_key
|
|
{
|
|
let (
|
|
_token_a_key,
|
|
_token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &withdrawer_key, initial_a, initial_b, 0);
|
|
let old_authority = accounts.authority_key;
|
|
let (bad_authority_key, _nonce) = Pubkey::find_program_address(
|
|
&[&accounts.swap_key.to_bytes()[..]],
|
|
&spl_token::id(),
|
|
);
|
|
accounts.authority_key = bad_authority_key;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidProgramAddress.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
destination_b_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
accounts.authority_key = old_authority;
|
|
}
|
|
|
|
// not enough pool tokens
|
|
{
|
|
let (
|
|
_token_a_key,
|
|
_token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
maximum_pool_token_amount / 1000,
|
|
);
|
|
assert_eq!(
|
|
Err(TokenError::InsufficientFunds.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
destination_b_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong pool token account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
maximum_pool_token_amount,
|
|
initial_b,
|
|
maximum_pool_token_amount,
|
|
);
|
|
assert_eq!(
|
|
Err(TokenError::MintMismatch.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
destination_b_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong pool fee account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
wrong_pool_key,
|
|
wrong_pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
maximum_pool_token_amount,
|
|
);
|
|
let (
|
|
_token_a_key,
|
|
_token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
maximum_pool_token_amount,
|
|
);
|
|
let old_pool_fee_account = accounts.pool_fee_account;
|
|
let old_pool_fee_key = accounts.pool_fee_key;
|
|
accounts.pool_fee_account = wrong_pool_account;
|
|
accounts.pool_fee_key = wrong_pool_key;
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectFeeAccount.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
destination_a_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
accounts.pool_fee_account = old_pool_fee_account;
|
|
accounts.pool_fee_key = old_pool_fee_key;
|
|
}
|
|
|
|
// no approval
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
0,
|
|
0,
|
|
maximum_pool_token_amount,
|
|
);
|
|
let user_transfer_authority_key = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(TokenError::OwnerMismatch.into()),
|
|
do_process_instruction(
|
|
withdraw_single_token_type_exact_amount_out(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&user_transfer_authority_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
&pool_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&token_a_key,
|
|
WithdrawSingleTokenTypeExactAmountOut {
|
|
destination_token_amount: destination_a_amount,
|
|
maximum_pool_token_amount,
|
|
}
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut accounts.pool_mint_account,
|
|
&mut pool_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut token_a_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong token program id
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
maximum_pool_token_amount,
|
|
);
|
|
let wrong_key = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectTokenProgramId.into()),
|
|
do_process_instruction(
|
|
withdraw_single_token_type_exact_amount_out(
|
|
&SWAP_PROGRAM_ID,
|
|
&wrong_key,
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.authority_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
&pool_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&token_a_key,
|
|
WithdrawSingleTokenTypeExactAmountOut {
|
|
destination_token_amount: destination_a_amount,
|
|
maximum_pool_token_amount,
|
|
}
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut accounts.pool_mint_account,
|
|
&mut pool_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut token_a_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong swap token accounts
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
initial_pool.try_into().unwrap(),
|
|
);
|
|
|
|
let old_a_key = accounts.token_a_key;
|
|
let old_a_account = accounts.token_a_account;
|
|
|
|
accounts.token_a_key = token_a_key;
|
|
accounts.token_a_account = token_a_account.clone();
|
|
|
|
// wrong swap token a account
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectSwapAccount.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
destination_a_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
|
|
accounts.token_a_key = old_a_key;
|
|
accounts.token_a_account = old_a_account;
|
|
|
|
let old_b_key = accounts.token_b_key;
|
|
let old_b_account = accounts.token_b_account;
|
|
|
|
accounts.token_b_key = token_b_key;
|
|
accounts.token_b_account = token_b_account.clone();
|
|
|
|
// wrong swap token b account
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectSwapAccount.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
destination_b_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
|
|
accounts.token_b_key = old_b_key;
|
|
accounts.token_b_account = old_b_account;
|
|
}
|
|
|
|
// wrong mint
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
initial_pool.try_into().unwrap(),
|
|
);
|
|
let (pool_mint_key, pool_mint_account) =
|
|
create_mint(&spl_token::id(), &accounts.authority_key, None);
|
|
let old_pool_key = accounts.pool_mint_key;
|
|
let old_pool_account = accounts.pool_mint_account;
|
|
accounts.pool_mint_key = pool_mint_key;
|
|
accounts.pool_mint_account = pool_mint_account;
|
|
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectPoolMint.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
destination_a_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
|
|
accounts.pool_mint_key = old_pool_key;
|
|
accounts.pool_mint_account = old_pool_account;
|
|
}
|
|
|
|
// slippage exceeded
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
maximum_pool_token_amount,
|
|
);
|
|
|
|
// maximum pool token amount too low
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
destination_a_amount,
|
|
maximum_pool_token_amount / 1000,
|
|
)
|
|
);
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
destination_b_amount,
|
|
maximum_pool_token_amount / 1000,
|
|
)
|
|
);
|
|
}
|
|
|
|
// invalid input: can't use swap pool tokens as destination
|
|
{
|
|
let (
|
|
_token_a_key,
|
|
_token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
maximum_pool_token_amount,
|
|
);
|
|
let swap_token_a_key = accounts.token_a_key;
|
|
let mut swap_token_a_account = accounts.get_token_account(&swap_token_a_key).clone();
|
|
assert_eq!(
|
|
Err(SwapError::InvalidInput.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&swap_token_a_key,
|
|
&mut swap_token_a_account,
|
|
destination_a_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
let swap_token_b_key = accounts.token_b_key;
|
|
let mut swap_token_b_account = accounts.get_token_account(&swap_token_b_key).clone();
|
|
assert_eq!(
|
|
Err(SwapError::InvalidInput.into()),
|
|
accounts.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&swap_token_b_key,
|
|
&mut swap_token_b_account,
|
|
destination_b_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// correct withdrawal
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
initial_a,
|
|
initial_b,
|
|
initial_pool.try_into().unwrap(),
|
|
);
|
|
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
let swap_token_b =
|
|
spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap();
|
|
let pool_mint =
|
|
spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap();
|
|
|
|
let pool_token_amount = accounts
|
|
.swap_curve
|
|
.withdraw_single_token_type_exact_out(
|
|
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,
|
|
&accounts.fees,
|
|
)
|
|
.unwrap();
|
|
let withdraw_fee = accounts.fees.owner_withdraw_fee(pool_token_amount).unwrap();
|
|
|
|
accounts
|
|
.withdraw_single_token_type_exact_amount_out(
|
|
&withdrawer_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
destination_a_amount,
|
|
maximum_pool_token_amount,
|
|
)
|
|
.unwrap();
|
|
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
|
|
assert_eq!(swap_token_a.amount, token_a_amount - destination_a_amount);
|
|
let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap();
|
|
assert_eq!(token_a.amount, initial_a + destination_a_amount);
|
|
|
|
let pool_account = spl_token::state::Account::unpack(&pool_account.data).unwrap();
|
|
assert_eq!(
|
|
pool_account.amount,
|
|
to_u64(initial_pool - pool_token_amount - withdraw_fee).unwrap()
|
|
);
|
|
let fee_account =
|
|
spl_token::state::Account::unpack(&accounts.pool_fee_account.data).unwrap();
|
|
assert_eq!(fee_account.amount, to_u64(withdraw_fee).unwrap());
|
|
}
|
|
|
|
// correct withdrawal from fee account
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &withdrawer_key, initial_a, initial_b, 0);
|
|
|
|
let fee_a_amount = 2;
|
|
let pool_fee_key = accounts.pool_fee_key;
|
|
let mut pool_fee_account = accounts.pool_fee_account.clone();
|
|
let fee_account = spl_token::state::Account::unpack(&pool_fee_account.data).unwrap();
|
|
let pool_fee_amount = fee_account.amount;
|
|
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
|
|
let token_a_amount = swap_token_a.amount;
|
|
accounts
|
|
.withdraw_single_token_type_exact_amount_out(
|
|
&user_key,
|
|
&pool_fee_key,
|
|
&mut pool_fee_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
fee_a_amount,
|
|
pool_fee_amount,
|
|
)
|
|
.unwrap();
|
|
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
|
|
assert_eq!(swap_token_a.amount, token_a_amount - fee_a_amount);
|
|
let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap();
|
|
assert_eq!(token_a.amount, initial_a + fee_a_amount);
|
|
}
|
|
}
|
|
|
|
fn check_valid_swap_curve(
|
|
fees: Fees,
|
|
curve_type: CurveType,
|
|
calculator: Box<dyn CurveCalculator>,
|
|
token_a_amount: u64,
|
|
token_b_amount: u64,
|
|
) {
|
|
let user_key = Pubkey::new_unique();
|
|
let swapper_key = Pubkey::new_unique();
|
|
|
|
let swap_curve = SwapCurve {
|
|
curve_type,
|
|
calculator,
|
|
};
|
|
|
|
let mut accounts = SwapAccountInfo::new(
|
|
&user_key,
|
|
fees.clone(),
|
|
swap_curve.clone(),
|
|
token_a_amount,
|
|
token_b_amount,
|
|
);
|
|
let initial_a = token_a_amount / 5;
|
|
let initial_b = token_b_amount / 5;
|
|
accounts.initialize_swap().unwrap();
|
|
|
|
let swap_token_a_key = accounts.token_a_key;
|
|
let swap_token_b_key = accounts.token_b_key;
|
|
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
// swap one way
|
|
let a_to_b_amount = initial_a / 10;
|
|
let minimum_token_b_amount = 0;
|
|
let pool_mint = spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap();
|
|
let initial_supply = pool_mint.supply;
|
|
accounts
|
|
.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
a_to_b_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
.unwrap();
|
|
|
|
let results = swap_curve
|
|
.swap(
|
|
a_to_b_amount.try_into().unwrap(),
|
|
token_a_amount.try_into().unwrap(),
|
|
token_b_amount.try_into().unwrap(),
|
|
TradeDirection::AtoB,
|
|
&fees,
|
|
)
|
|
.unwrap();
|
|
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
let token_a_amount = swap_token_a.amount;
|
|
assert_eq!(
|
|
token_a_amount,
|
|
TryInto::<u64>::try_into(results.new_swap_source_amount).unwrap()
|
|
);
|
|
let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap();
|
|
assert_eq!(token_a.amount, initial_a - a_to_b_amount);
|
|
|
|
let swap_token_b =
|
|
spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap();
|
|
let token_b_amount = swap_token_b.amount;
|
|
assert_eq!(
|
|
token_b_amount,
|
|
TryInto::<u64>::try_into(results.new_swap_destination_amount).unwrap()
|
|
);
|
|
let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap();
|
|
assert_eq!(
|
|
token_b.amount,
|
|
initial_b + to_u64(results.destination_amount_swapped).unwrap()
|
|
);
|
|
|
|
let first_fee = swap_curve
|
|
.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,
|
|
&fees,
|
|
)
|
|
.unwrap();
|
|
let fee_account =
|
|
spl_token::state::Account::unpack(&accounts.pool_fee_account.data).unwrap();
|
|
assert_eq!(
|
|
fee_account.amount,
|
|
TryInto::<u64>::try_into(first_fee).unwrap()
|
|
);
|
|
|
|
let first_swap_amount = results.destination_amount_swapped;
|
|
|
|
// swap the other way
|
|
let pool_mint = spl_token::state::Mint::unpack(&accounts.pool_mint_account.data).unwrap();
|
|
let initial_supply = pool_mint.supply;
|
|
|
|
let b_to_a_amount = initial_b / 10;
|
|
let minimum_a_amount = 0;
|
|
accounts
|
|
.swap(
|
|
&swapper_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&swap_token_b_key,
|
|
&swap_token_a_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
b_to_a_amount,
|
|
minimum_a_amount,
|
|
)
|
|
.unwrap();
|
|
|
|
let results = swap_curve
|
|
.swap(
|
|
b_to_a_amount.try_into().unwrap(),
|
|
token_b_amount.try_into().unwrap(),
|
|
token_a_amount.try_into().unwrap(),
|
|
TradeDirection::BtoA,
|
|
&fees,
|
|
)
|
|
.unwrap();
|
|
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
let token_a_amount = swap_token_a.amount;
|
|
assert_eq!(
|
|
token_a_amount,
|
|
TryInto::<u64>::try_into(results.new_swap_destination_amount).unwrap()
|
|
);
|
|
let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap();
|
|
assert_eq!(
|
|
token_a.amount,
|
|
initial_a - a_to_b_amount + to_u64(results.destination_amount_swapped).unwrap()
|
|
);
|
|
|
|
let swap_token_b =
|
|
spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap();
|
|
let token_b_amount = swap_token_b.amount;
|
|
assert_eq!(
|
|
token_b_amount,
|
|
TryInto::<u64>::try_into(results.new_swap_source_amount).unwrap()
|
|
);
|
|
let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap();
|
|
assert_eq!(
|
|
token_b.amount,
|
|
initial_b + to_u64(first_swap_amount).unwrap()
|
|
- to_u64(results.source_amount_swapped).unwrap()
|
|
);
|
|
|
|
let second_fee = swap_curve
|
|
.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,
|
|
&fees,
|
|
)
|
|
.unwrap();
|
|
let fee_account =
|
|
spl_token::state::Account::unpack(&accounts.pool_fee_account.data).unwrap();
|
|
assert_eq!(fee_account.amount, to_u64(first_fee + second_fee).unwrap());
|
|
}
|
|
|
|
#[test]
|
|
fn test_valid_swap_curves_all_fees() {
|
|
// All fees
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 10;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 30;
|
|
let owner_withdraw_fee_numerator = 1;
|
|
let owner_withdraw_fee_denominator = 30;
|
|
let host_fee_numerator = 20;
|
|
let host_fee_denominator = 100;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let token_a_amount = 10_000_000_000;
|
|
let token_b_amount = 50_000_000_000;
|
|
|
|
check_valid_swap_curve(
|
|
fees.clone(),
|
|
CurveType::ConstantProduct,
|
|
Box::new(ConstantProductCurve {}),
|
|
token_a_amount,
|
|
token_b_amount,
|
|
);
|
|
let token_b_price = 1;
|
|
check_valid_swap_curve(
|
|
fees.clone(),
|
|
CurveType::ConstantPrice,
|
|
Box::new(ConstantPriceCurve { token_b_price }),
|
|
token_a_amount,
|
|
token_b_amount,
|
|
);
|
|
let token_b_offset = 10_000_000_000;
|
|
check_valid_swap_curve(
|
|
fees,
|
|
CurveType::Offset,
|
|
Box::new(OffsetCurve { token_b_offset }),
|
|
token_a_amount,
|
|
token_b_amount,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_valid_swap_curves_trade_fee_only() {
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 10;
|
|
let owner_trade_fee_numerator = 0;
|
|
let owner_trade_fee_denominator = 0;
|
|
let owner_withdraw_fee_numerator = 0;
|
|
let owner_withdraw_fee_denominator = 0;
|
|
let host_fee_numerator = 0;
|
|
let host_fee_denominator = 0;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let token_a_amount = 10_000_000_000;
|
|
let token_b_amount = 50_000_000_000;
|
|
|
|
check_valid_swap_curve(
|
|
fees.clone(),
|
|
CurveType::ConstantProduct,
|
|
Box::new(ConstantProductCurve {}),
|
|
token_a_amount,
|
|
token_b_amount,
|
|
);
|
|
let token_b_price = 10_000;
|
|
check_valid_swap_curve(
|
|
fees.clone(),
|
|
CurveType::ConstantPrice,
|
|
Box::new(ConstantPriceCurve { token_b_price }),
|
|
token_a_amount,
|
|
token_b_amount / token_b_price,
|
|
);
|
|
let token_b_offset = 1;
|
|
check_valid_swap_curve(
|
|
fees,
|
|
CurveType::Offset,
|
|
Box::new(OffsetCurve { token_b_offset }),
|
|
token_a_amount,
|
|
token_b_amount,
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_valid_swap_with_fee_constraints() {
|
|
let owner_key = Pubkey::new_unique();
|
|
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 10;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 30;
|
|
let owner_withdraw_fee_numerator = 1;
|
|
let owner_withdraw_fee_denominator = 30;
|
|
let host_fee_numerator = 10;
|
|
let host_fee_denominator = 100;
|
|
|
|
let token_a_amount = 1_000_000;
|
|
let token_b_amount = 5_000_000;
|
|
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let curve = ConstantProductCurve {};
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::ConstantProduct,
|
|
calculator: Box::new(curve),
|
|
};
|
|
|
|
let owner_key_str = &owner_key.to_string();
|
|
let valid_curve_types = &[CurveType::ConstantProduct];
|
|
let constraints = Some(SwapConstraints {
|
|
owner_key: owner_key_str,
|
|
valid_curve_types,
|
|
fees: &fees,
|
|
});
|
|
let mut accounts = SwapAccountInfo::new(
|
|
&owner_key,
|
|
fees.clone(),
|
|
swap_curve,
|
|
token_a_amount,
|
|
token_b_amount,
|
|
);
|
|
|
|
// initialize swap
|
|
do_process_instruction_with_fee_constraints(
|
|
initialize(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
&accounts.pool_token_key,
|
|
accounts.nonce,
|
|
accounts.fees.clone(),
|
|
accounts.swap_curve.clone(),
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut accounts.pool_token_account,
|
|
&mut Account::default(),
|
|
],
|
|
&constraints,
|
|
)
|
|
.unwrap();
|
|
|
|
let authority_key = accounts.authority_key;
|
|
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&owner_key,
|
|
&authority_key,
|
|
token_a_amount,
|
|
token_b_amount,
|
|
0,
|
|
);
|
|
|
|
let amount_in = token_a_amount / 2;
|
|
let minimum_amount_out = 0;
|
|
|
|
// perform the swap
|
|
do_process_instruction_with_fee_constraints(
|
|
swap(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.authority_key,
|
|
&token_a_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
Some(&pool_key),
|
|
Swap {
|
|
amount_in,
|
|
minimum_amount_out,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut token_a_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut Account::default(),
|
|
&mut pool_account,
|
|
],
|
|
&constraints,
|
|
)
|
|
.unwrap();
|
|
|
|
// check that fees were taken in the host fee account
|
|
let host_fee_account = spl_token::state::Account::unpack(&pool_account.data).unwrap();
|
|
let owner_fee_account =
|
|
spl_token::state::Account::unpack(&accounts.pool_fee_account.data).unwrap();
|
|
let total_fee = owner_fee_account.amount * host_fee_denominator
|
|
/ (host_fee_denominator - host_fee_numerator);
|
|
assert_eq!(
|
|
total_fee,
|
|
host_fee_account.amount + owner_fee_account.amount
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_invalid_swap() {
|
|
let user_key = Pubkey::new_unique();
|
|
let swapper_key = Pubkey::new_unique();
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 4;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 10;
|
|
let owner_withdraw_fee_numerator = 1;
|
|
let owner_withdraw_fee_denominator = 5;
|
|
let host_fee_numerator = 9;
|
|
let host_fee_denominator = 100;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let token_a_amount = 1000;
|
|
let token_b_amount = 5000;
|
|
let curve_type = CurveType::ConstantProduct;
|
|
let swap_curve = SwapCurve {
|
|
curve_type,
|
|
calculator: Box::new(ConstantProductCurve {}),
|
|
};
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
|
|
let initial_a = token_a_amount / 5;
|
|
let initial_b = token_b_amount / 5;
|
|
let minimum_token_b_amount = initial_b / 2;
|
|
|
|
let swap_token_a_key = accounts.token_a_key;
|
|
let swap_token_b_key = accounts.token_b_key;
|
|
|
|
// swap not initialized
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
assert_eq!(
|
|
Err(ProgramError::UninitializedAccount),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
initial_a,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
accounts.initialize_swap().unwrap();
|
|
|
|
// wrong swap account program id
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
let old_swap_account = accounts.swap_account;
|
|
let mut wrong_swap_account = old_swap_account.clone();
|
|
wrong_swap_account.owner = spl_token::id();
|
|
accounts.swap_account = wrong_swap_account;
|
|
assert_eq!(
|
|
Err(ProgramError::IncorrectProgramId),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
initial_a,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
accounts.swap_account = old_swap_account;
|
|
}
|
|
|
|
// wrong nonce
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
let old_authority = accounts.authority_key;
|
|
let (bad_authority_key, _nonce) = Pubkey::find_program_address(
|
|
&[&accounts.swap_key.to_bytes()[..]],
|
|
&spl_token::id(),
|
|
);
|
|
accounts.authority_key = bad_authority_key;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidProgramAddress.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
initial_a,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
accounts.authority_key = old_authority;
|
|
}
|
|
|
|
// wrong token program id
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
let wrong_program_id = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectTokenProgramId.into()),
|
|
do_process_instruction(
|
|
swap(
|
|
&SWAP_PROGRAM_ID,
|
|
&wrong_program_id,
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.authority_key,
|
|
&token_a_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
None,
|
|
Swap {
|
|
amount_in: initial_a,
|
|
minimum_amount_out: minimum_token_b_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut token_a_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// not enough token a to swap
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
assert_eq!(
|
|
Err(TokenError::InsufficientFunds.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
initial_a * 2,
|
|
minimum_token_b_amount * 2,
|
|
)
|
|
);
|
|
}
|
|
|
|
// wrong swap token A / B accounts
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
let user_transfer_key = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectSwapAccount.into()),
|
|
do_process_instruction(
|
|
swap(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&user_transfer_key,
|
|
&token_a_key,
|
|
&token_a_key,
|
|
&token_b_key,
|
|
&token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
None,
|
|
Swap {
|
|
amount_in: initial_a,
|
|
minimum_amount_out: minimum_token_b_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut token_a_account.clone(),
|
|
&mut token_a_account,
|
|
&mut token_b_account.clone(),
|
|
&mut token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// wrong user token A / B accounts
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
assert_eq!(
|
|
Err(TokenError::MintMismatch.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
initial_a,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// swap from a to a
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
_token_b_key,
|
|
_token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
assert_eq!(
|
|
Err(SwapError::InvalidInput.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account.clone(),
|
|
&swap_token_a_key,
|
|
&swap_token_a_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
initial_a,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// incorrect mint provided
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
let (pool_mint_key, pool_mint_account) =
|
|
create_mint(&spl_token::id(), &accounts.authority_key, None);
|
|
let old_pool_key = accounts.pool_mint_key;
|
|
let old_pool_account = accounts.pool_mint_account;
|
|
accounts.pool_mint_key = pool_mint_key;
|
|
accounts.pool_mint_account = pool_mint_account;
|
|
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectPoolMint.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
initial_a,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
|
|
accounts.pool_mint_key = old_pool_key;
|
|
accounts.pool_mint_account = old_pool_account;
|
|
}
|
|
|
|
// incorrect fee account provided
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
wrong_pool_key,
|
|
wrong_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
let old_pool_fee_account = accounts.pool_fee_account;
|
|
let old_pool_fee_key = accounts.pool_fee_key;
|
|
accounts.pool_fee_account = wrong_pool_account;
|
|
accounts.pool_fee_key = wrong_pool_key;
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectFeeAccount.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
initial_a,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
accounts.pool_fee_account = old_pool_fee_account;
|
|
accounts.pool_fee_key = old_pool_fee_key;
|
|
}
|
|
|
|
// no approval
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
let user_transfer_key = Pubkey::new_unique();
|
|
assert_eq!(
|
|
Err(TokenError::OwnerMismatch.into()),
|
|
do_process_instruction(
|
|
swap(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&user_transfer_key,
|
|
&token_a_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
None,
|
|
Swap {
|
|
amount_in: initial_a,
|
|
minimum_amount_out: minimum_token_b_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut token_a_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// output token value 0
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
assert_eq!(
|
|
Err(SwapError::ZeroTradingTokens.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&swap_token_b_key,
|
|
&swap_token_a_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
1,
|
|
1,
|
|
)
|
|
);
|
|
}
|
|
|
|
// slippage exceeded: minimum out amount too high
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
initial_a,
|
|
minimum_token_b_amount * 2,
|
|
)
|
|
);
|
|
}
|
|
|
|
// invalid input: can't use swap pool as user source / dest
|
|
{
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
let mut swap_token_a_account = accounts.get_token_account(&swap_token_a_key).clone();
|
|
let authority_key = accounts.authority_key;
|
|
assert_eq!(
|
|
Err(SwapError::InvalidInput.into()),
|
|
accounts.swap(
|
|
&authority_key,
|
|
&swap_token_a_key,
|
|
&mut swap_token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
initial_a,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
let mut swap_token_b_account = accounts.get_token_account(&swap_token_b_key).clone();
|
|
assert_eq!(
|
|
Err(SwapError::InvalidInput.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&swap_token_b_key,
|
|
&mut swap_token_b_account,
|
|
initial_a,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
}
|
|
|
|
// still correct: constraint specified, no host fee account
|
|
{
|
|
let authority_key = accounts.authority_key;
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &authority_key, initial_a, initial_b, 0);
|
|
let owner_key = &swapper_key.to_string();
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
let constraints = Some(SwapConstraints {
|
|
owner_key,
|
|
valid_curve_types: &[],
|
|
fees: &fees,
|
|
});
|
|
do_process_instruction_with_fee_constraints(
|
|
swap(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.authority_key,
|
|
&token_a_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
None,
|
|
Swap {
|
|
amount_in: initial_a,
|
|
minimum_amount_out: minimum_token_b_amount,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut token_a_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut Account::default(),
|
|
],
|
|
&constraints,
|
|
)
|
|
.unwrap();
|
|
}
|
|
|
|
// invalid mint for host fee account
|
|
{
|
|
let authority_key = accounts.authority_key;
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &authority_key, initial_a, initial_b, 0);
|
|
let (
|
|
bad_token_a_key,
|
|
mut bad_token_a_account,
|
|
_token_b_key,
|
|
mut _token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &authority_key, initial_a, initial_b, 0);
|
|
let owner_key = &swapper_key.to_string();
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
let constraints = Some(SwapConstraints {
|
|
owner_key,
|
|
valid_curve_types: &[],
|
|
fees: &fees,
|
|
});
|
|
assert_eq!(
|
|
Err(SwapError::IncorrectPoolMint.into()),
|
|
do_process_instruction_with_fee_constraints(
|
|
swap(
|
|
&SWAP_PROGRAM_ID,
|
|
&spl_token::id(),
|
|
&accounts.swap_key,
|
|
&accounts.authority_key,
|
|
&accounts.authority_key,
|
|
&token_a_key,
|
|
&accounts.token_a_key,
|
|
&accounts.token_b_key,
|
|
&token_b_key,
|
|
&accounts.pool_mint_key,
|
|
&accounts.pool_fee_key,
|
|
Some(&bad_token_a_key),
|
|
Swap {
|
|
amount_in: initial_a,
|
|
minimum_amount_out: 0,
|
|
},
|
|
)
|
|
.unwrap(),
|
|
vec![
|
|
&mut accounts.swap_account,
|
|
&mut Account::default(),
|
|
&mut Account::default(),
|
|
&mut token_a_account,
|
|
&mut accounts.token_a_account,
|
|
&mut accounts.token_b_account,
|
|
&mut token_b_account,
|
|
&mut accounts.pool_mint_account,
|
|
&mut accounts.pool_fee_account,
|
|
&mut Account::default(),
|
|
&mut bad_token_a_account,
|
|
],
|
|
&constraints,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_overdraw_offset_curve() {
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 10;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 30;
|
|
let owner_withdraw_fee_numerator = 1;
|
|
let owner_withdraw_fee_denominator = 30;
|
|
let host_fee_numerator = 10;
|
|
let host_fee_denominator = 100;
|
|
|
|
let token_a_amount = 1_000_000_000;
|
|
let token_b_amount = 0;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let token_b_offset = 2_000_000;
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::Offset,
|
|
calculator: Box::new(OffsetCurve { token_b_offset }),
|
|
};
|
|
let user_key = Pubkey::new_unique();
|
|
let swapper_key = Pubkey::new_unique();
|
|
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
|
|
accounts.initialize_swap().unwrap();
|
|
|
|
let swap_token_a_key = accounts.token_a_key;
|
|
let swap_token_b_key = accounts.token_b_key;
|
|
let initial_a = 500_000;
|
|
let initial_b = 1_000;
|
|
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
|
|
// swap a to b way, fails, there's no liquidity
|
|
let a_to_b_amount = initial_a;
|
|
let minimum_token_b_amount = 0;
|
|
|
|
assert_eq!(
|
|
Err(SwapError::ZeroTradingTokens.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
a_to_b_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
|
|
// swap b to a, succeeds at offset price
|
|
let b_to_a_amount = initial_b;
|
|
let minimum_token_a_amount = 0;
|
|
accounts
|
|
.swap(
|
|
&swapper_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&swap_token_b_key,
|
|
&swap_token_a_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
b_to_a_amount,
|
|
minimum_token_a_amount,
|
|
)
|
|
.unwrap();
|
|
|
|
// try a to b again, succeeds due to new liquidity
|
|
accounts
|
|
.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
a_to_b_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
.unwrap();
|
|
|
|
// try a to b again, fails due to no more liquidity
|
|
assert_eq!(
|
|
Err(SwapError::ZeroTradingTokens.into()),
|
|
accounts.swap(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&swap_token_a_key,
|
|
&swap_token_b_key,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
a_to_b_amount,
|
|
minimum_token_b_amount,
|
|
)
|
|
);
|
|
|
|
// Try to deposit, fails because deposits are not allowed for offset
|
|
// curve swaps
|
|
{
|
|
let initial_a = 100;
|
|
let initial_b = 100;
|
|
let pool_amount = 100;
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &swapper_key, initial_a, initial_b, 0);
|
|
assert_eq!(
|
|
Err(SwapError::UnsupportedCurveOperation.into()),
|
|
accounts.deposit_all_token_types(
|
|
&swapper_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
pool_amount,
|
|
initial_a,
|
|
initial_b,
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_withdraw_all_offset_curve() {
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 10;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 30;
|
|
let owner_withdraw_fee_numerator = 0;
|
|
let owner_withdraw_fee_denominator = 30;
|
|
let host_fee_numerator = 10;
|
|
let host_fee_denominator = 100;
|
|
|
|
let token_a_amount = 1_000_000_000;
|
|
let token_b_amount = 10;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let token_b_offset = 2_000_000;
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::Offset,
|
|
calculator: Box::new(OffsetCurve { token_b_offset }),
|
|
};
|
|
let total_pool = swap_curve.calculator.new_pool_supply();
|
|
let user_key = Pubkey::new_unique();
|
|
let withdrawer_key = Pubkey::new_unique();
|
|
|
|
let mut accounts =
|
|
SwapAccountInfo::new(&user_key, fees, swap_curve, token_a_amount, token_b_amount);
|
|
|
|
accounts.initialize_swap().unwrap();
|
|
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &withdrawer_key, 0, 0, 0);
|
|
|
|
let pool_key = accounts.pool_token_key;
|
|
let mut pool_account = accounts.pool_token_account.clone();
|
|
|
|
// WithdrawAllTokenTypes takes all tokens for A and B.
|
|
// The curve's calculation for token B will say to transfer
|
|
// `token_b_offset + token_b_amount`, but only `token_b_amount` will be
|
|
// moved.
|
|
accounts
|
|
.withdraw_all_token_types(
|
|
&user_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
total_pool.try_into().unwrap(),
|
|
0,
|
|
0,
|
|
)
|
|
.unwrap();
|
|
|
|
let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap();
|
|
assert_eq!(token_a.amount, token_a_amount);
|
|
let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap();
|
|
assert_eq!(token_b.amount, token_b_amount);
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
assert_eq!(swap_token_a.amount, 0);
|
|
let swap_token_b =
|
|
spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap();
|
|
assert_eq!(swap_token_b.amount, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_withdraw_all_constant_price_curve() {
|
|
let trade_fee_numerator = 1;
|
|
let trade_fee_denominator = 10;
|
|
let owner_trade_fee_numerator = 1;
|
|
let owner_trade_fee_denominator = 30;
|
|
let owner_withdraw_fee_numerator = 0;
|
|
let owner_withdraw_fee_denominator = 30;
|
|
let host_fee_numerator = 10;
|
|
let host_fee_denominator = 100;
|
|
|
|
// initialize "unbalanced", so that withdrawing all will have some issues
|
|
// A: 1_000_000_000
|
|
// B: 2_000_000_000 (1_000 * 2_000_000)
|
|
let swap_token_a_amount = 1_000_000_000;
|
|
let swap_token_b_amount = 1_000;
|
|
let token_b_price = 2_000_000;
|
|
let fees = Fees {
|
|
trade_fee_numerator,
|
|
trade_fee_denominator,
|
|
owner_trade_fee_numerator,
|
|
owner_trade_fee_denominator,
|
|
owner_withdraw_fee_numerator,
|
|
owner_withdraw_fee_denominator,
|
|
host_fee_numerator,
|
|
host_fee_denominator,
|
|
};
|
|
|
|
let swap_curve = SwapCurve {
|
|
curve_type: CurveType::ConstantPrice,
|
|
calculator: Box::new(ConstantPriceCurve { token_b_price }),
|
|
};
|
|
let total_pool = swap_curve.calculator.new_pool_supply();
|
|
let user_key = Pubkey::new_unique();
|
|
let withdrawer_key = Pubkey::new_unique();
|
|
|
|
let mut accounts = SwapAccountInfo::new(
|
|
&user_key,
|
|
fees,
|
|
swap_curve,
|
|
swap_token_a_amount,
|
|
swap_token_b_amount,
|
|
);
|
|
|
|
accounts.initialize_swap().unwrap();
|
|
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
_pool_key,
|
|
_pool_account,
|
|
) = accounts.setup_token_accounts(&user_key, &withdrawer_key, 0, 0, 0);
|
|
|
|
let pool_key = accounts.pool_token_key;
|
|
let mut pool_account = accounts.pool_token_account.clone();
|
|
|
|
// WithdrawAllTokenTypes will not take all token A and B, since their
|
|
// ratio is unbalanced. It will try to take 1_500_000_000 worth of
|
|
// each token, which means 1_500_000_000 token A, and 750 token B.
|
|
// With no slippage, this will leave 250 token B in the pool.
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.withdraw_all_token_types(
|
|
&user_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
total_pool.try_into().unwrap(),
|
|
swap_token_a_amount,
|
|
swap_token_b_amount,
|
|
)
|
|
);
|
|
|
|
accounts
|
|
.withdraw_all_token_types(
|
|
&user_key,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
total_pool.try_into().unwrap(),
|
|
0,
|
|
0,
|
|
)
|
|
.unwrap();
|
|
|
|
let token_a = spl_token::state::Account::unpack(&token_a_account.data).unwrap();
|
|
assert_eq!(token_a.amount, swap_token_a_amount);
|
|
let token_b = spl_token::state::Account::unpack(&token_b_account.data).unwrap();
|
|
assert_eq!(token_b.amount, 750);
|
|
let swap_token_a =
|
|
spl_token::state::Account::unpack(&accounts.token_a_account.data).unwrap();
|
|
assert_eq!(swap_token_a.amount, 0);
|
|
let swap_token_b =
|
|
spl_token::state::Account::unpack(&accounts.token_b_account.data).unwrap();
|
|
assert_eq!(swap_token_b.amount, 250);
|
|
|
|
// deposit now, not enough to cover the tokens already in there
|
|
let token_b_amount = 10;
|
|
let token_a_amount = token_b_amount * token_b_price;
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
token_a_amount,
|
|
token_b_amount,
|
|
0,
|
|
);
|
|
|
|
assert_eq!(
|
|
Err(SwapError::ExceededSlippage.into()),
|
|
accounts.deposit_all_token_types(
|
|
&withdrawer_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
1, // doesn't matter
|
|
token_a_amount,
|
|
token_b_amount,
|
|
)
|
|
);
|
|
|
|
// deposit enough tokens, success!
|
|
let token_b_amount = 125;
|
|
let token_a_amount = token_b_amount * token_b_price;
|
|
let (
|
|
token_a_key,
|
|
mut token_a_account,
|
|
token_b_key,
|
|
mut token_b_account,
|
|
pool_key,
|
|
mut pool_account,
|
|
) = accounts.setup_token_accounts(
|
|
&user_key,
|
|
&withdrawer_key,
|
|
token_a_amount,
|
|
token_b_amount,
|
|
0,
|
|
);
|
|
|
|
accounts
|
|
.deposit_all_token_types(
|
|
&withdrawer_key,
|
|
&token_a_key,
|
|
&mut token_a_account,
|
|
&token_b_key,
|
|
&mut token_b_account,
|
|
&pool_key,
|
|
&mut pool_account,
|
|
1, // doesn't matter
|
|
token_a_amount,
|
|
token_b_amount,
|
|
)
|
|
.unwrap();
|
|
}
|
|
}
|