solana-program-library/token-swap/program/src/processor.rs

2732 lines
93 KiB
Rust
Raw Normal View History

//! Program state processor
2020-07-08 20:54:52 -07:00
#![cfg(feature = "program")]
use crate::{
curve::{ConstantProduct, PoolTokenConverter},
error::SwapError,
instruction::SwapInstruction,
state::SwapInfo,
};
use num_traits::FromPrimitive;
#[cfg(not(target_arch = "bpf"))]
use solana_sdk::instruction::Instruction;
#[cfg(target_arch = "bpf")]
use solana_sdk::program::invoke_signed;
2020-07-08 20:54:52 -07:00
use solana_sdk::{
account_info::{next_account_info, AccountInfo},
decode_error::DecodeError,
entrypoint::ProgramResult,
info,
program_error::PrintProgramError,
program_error::ProgramError,
2020-09-21 14:56:19 -07:00
program_option::COption,
program_pack::Pack,
pubkey::Pubkey,
2020-07-08 20:54:52 -07:00
};
// Test program id for the swap program.
#[cfg(not(target_arch = "bpf"))]
const SWAP_PROGRAM_ID: Pubkey = Pubkey::new_from_array([2u8; 32]);
// Test program id for the token program.
#[cfg(not(target_arch = "bpf"))]
const TOKEN_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
/// Program state handler.
pub struct Processor {}
impl Processor {
/// Unpacks a spl_token `Account`.
pub fn unpack_token_account(data: &[u8]) -> Result<spl_token::state::Account, SwapError> {
spl_token::state::Account::unpack(data).map_err(|_| SwapError::ExpectedAccount)
}
/// Unpacks a spl_token `Mint`.
pub fn unpack_mint(data: &[u8]) -> Result<spl_token::state::Mint, SwapError> {
spl_token::state::Mint::unpack(data).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> {
2020-07-31 20:55:45 -07:00
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> {
2020-07-31 20:55:45 -07:00
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> {
2020-07-31 20:55:45 -07:00
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,
)
}
/// Processes an [Initialize](enum.Instruction.html).
pub fn process_initialize(
program_id: &Pubkey,
nonce: u8,
fee_numerator: u64,
fee_denominator: 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 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 destination_info = next_account_info(account_info_iter)?;
let token_program_info = next_account_info(account_info_iter)?;
let token_swap = SwapInfo::unpack_unchecked(&swap_info.data.borrow())?;
if token_swap.is_initialized {
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.data.borrow())?;
let token_b = Self::unpack_token_account(&token_b_info.data.borrow())?;
let destination = Self::unpack_token_account(&destination_info.data.borrow())?;
let pool_mint = Self::unpack_mint(&pool_mint_info.data.borrow())?;
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 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());
}
if token_b.amount == 0 {
return Err(SwapError::EmptySupply.into());
}
if token_a.amount == 0 {
return Err(SwapError::EmptySupply.into());
}
if token_a.delegate.is_some() {
return Err(SwapError::InvalidDelegate.into());
}
if token_b.delegate.is_some() {
return Err(SwapError::InvalidDelegate.into());
}
if pool_mint.supply != 0 {
return Err(SwapError::InvalidSupply.into());
}
let converter = PoolTokenConverter::new_pool(token_a.amount, token_b.amount);
let initial_amount = converter.supply;
Self::token_mint_to(
swap_info.key,
token_program_info.clone(),
pool_mint_info.clone(),
destination_info.clone(),
authority_info.clone(),
nonce,
initial_amount,
)?;
let obj = SwapInfo {
is_initialized: true,
nonce,
token_a: *token_a_info.key,
token_b: *token_b_info.key,
pool_mint: *pool_mint_info.key,
fee_numerator,
fee_denominator,
};
SwapInfo::pack(obj, &mut swap_info.data.borrow_mut())?;
Ok(())
}
/// Processes an [Swap](enum.Instruction.html).
pub fn process_swap(
program_id: &Pubkey,
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 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 token_program_info = next_account_info(account_info_iter)?;
let token_swap = SwapInfo::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
|| *swap_source_info.key == token_swap.token_b)
{
return Err(SwapError::IncorrectSwapAccount.into());
}
if !(*swap_destination_info.key == token_swap.token_a
|| *swap_destination_info.key == token_swap.token_b)
{
return Err(SwapError::IncorrectSwapAccount.into());
}
if *swap_source_info.key == *swap_destination_info.key {
return Err(SwapError::InvalidInput.into());
}
let source_account = Self::unpack_token_account(&swap_source_info.data.borrow())?;
let dest_account = Self::unpack_token_account(&swap_destination_info.data.borrow())?;
let output = if *swap_source_info.key == token_swap.token_a {
let mut invariant = ConstantProduct {
token_a: source_account.amount,
token_b: dest_account.amount,
fee_numerator: token_swap.fee_numerator,
fee_denominator: token_swap.fee_denominator,
};
invariant
.swap_a_to_b(amount)
.ok_or(SwapError::CalculationFailure)?
} else {
let mut invariant = ConstantProduct {
token_a: dest_account.amount,
token_b: source_account.amount,
fee_numerator: token_swap.fee_numerator,
fee_denominator: token_swap.fee_denominator,
};
invariant
.swap_b_to_a(amount)
.ok_or(SwapError::CalculationFailure)?
};
Self::token_transfer(
swap_info.key,
token_program_info.clone(),
source_info.clone(),
swap_source_info.clone(),
authority_info.clone(),
token_swap.nonce,
amount,
)?;
Self::token_transfer(
swap_info.key,
token_program_info.clone(),
swap_destination_info.clone(),
destination_info.clone(),
authority_info.clone(),
token_swap.nonce,
output,
)?;
Ok(())
}
/// Processes an [Deposit](enum.Instruction.html).
pub fn process_deposit(
program_id: &Pubkey,
pool_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 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 = SwapInfo::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 *token_a_info.key != token_swap.token_a {
return Err(SwapError::IncorrectSwapAccount.into());
}
if *token_b_info.key != token_swap.token_b {
return Err(SwapError::IncorrectSwapAccount.into());
}
if *pool_mint_info.key != token_swap.pool_mint {
return Err(SwapError::IncorrectPoolMint.into());
}
let token_a = Self::unpack_token_account(&token_a_info.data.borrow())?;
let token_b = Self::unpack_token_account(&token_b_info.data.borrow())?;
let pool_mint = Self::unpack_mint(&pool_mint_info.data.borrow())?;
let converter =
PoolTokenConverter::new_existing(pool_mint.supply, token_a.amount, token_b.amount);
let a_amount = converter
.token_a_rate(pool_amount)
.ok_or(SwapError::CalculationFailure)?;
let b_amount = converter
.token_b_rate(pool_amount)
.ok_or(SwapError::CalculationFailure)?;
Self::token_transfer(
swap_info.key,
token_program_info.clone(),
source_a_info.clone(),
token_a_info.clone(),
authority_info.clone(),
token_swap.nonce,
a_amount,
)?;
Self::token_transfer(
swap_info.key,
token_program_info.clone(),
source_b_info.clone(),
token_b_info.clone(),
authority_info.clone(),
token_swap.nonce,
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_amount,
)?;
2020-07-08 20:54:52 -07:00
Ok(())
}
/// Processes an [Withdraw](enum.Instruction.html).
pub fn process_withdraw(
program_id: &Pubkey,
pool_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 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 token_program_info = next_account_info(account_info_iter)?;
let token_swap = SwapInfo::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 *token_a_info.key != token_swap.token_a {
return Err(SwapError::IncorrectSwapAccount.into());
}
if *token_b_info.key != token_swap.token_b {
return Err(SwapError::IncorrectSwapAccount.into());
}
if *pool_mint_info.key != token_swap.pool_mint {
return Err(SwapError::IncorrectPoolMint.into());
}
let token_a = Self::unpack_token_account(&token_a_info.data.borrow())?;
let token_b = Self::unpack_token_account(&token_b_info.data.borrow())?;
let pool_mint = Self::unpack_mint(&pool_mint_info.data.borrow())?;
let converter =
PoolTokenConverter::new_existing(pool_mint.supply, token_a.amount, token_b.amount);
let a_amount = converter
.token_a_rate(pool_amount)
.ok_or(SwapError::CalculationFailure)?;
let b_amount = converter
.token_b_rate(pool_amount)
.ok_or(SwapError::CalculationFailure)?;
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,
a_amount,
)?;
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,
b_amount,
)?;
Self::token_burn(
swap_info.key,
token_program_info.clone(),
source_info.clone(),
pool_mint_info.clone(),
authority_info.clone(),
token_swap.nonce,
pool_amount,
)?;
Ok(())
}
/// Processes an [Instruction](enum.Instruction.html).
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let instruction = SwapInstruction::unpack(input)?;
match instruction {
SwapInstruction::Initialize {
fee_numerator,
fee_denominator,
nonce,
} => {
info!("Instruction: Init");
Self::process_initialize(
program_id,
nonce,
fee_numerator,
fee_denominator,
accounts,
)
}
SwapInstruction::Swap { amount } => {
info!("Instruction: Swap");
Self::process_swap(program_id, amount, accounts)
}
SwapInstruction::Deposit { amount } => {
info!("Instruction: Deposit");
Self::process_deposit(program_id, amount, accounts)
}
SwapInstruction::Withdraw { amount } => {
info!("Instruction: Withdraw");
Self::process_withdraw(program_id, amount, accounts)
}
}
}
}
/// Routes invokes to the token program, used for testing.
#[cfg(not(target_arch = "bpf"))]
pub fn invoke_signed<'a>(
instruction: &Instruction,
account_infos: &[AccountInfo<'a>],
2020-07-31 20:55:45 -07:00
signers_seeds: &[&[&[u8]]],
2020-07-08 20:54:52 -07:00
) -> ProgramResult {
let mut new_account_infos = vec![];
// mimic check for token program in accounts
if !account_infos.iter().any(|x| *x.key == TOKEN_PROGRAM_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,
)
}
impl PrintProgramError for SwapError {
fn print<E>(&self)
where
E: 'static + std::error::Error + DecodeError<E> + PrintProgramError + FromPrimitive,
{
match self {
SwapError::AlreadyInUse => info!("Error: Swap account already in use"),
SwapError::InvalidProgramAddress => {
info!("Error: Invalid program address generated from nonce and key")
}
SwapError::InvalidOwner => {
info!("Error: The input account owner is not the program address")
}
SwapError::InvalidOutputOwner => {
info!("Error: Output pool account owner cannot be the program address")
}
SwapError::ExpectedMint => {
info!("Error: Deserialized account is not an SPL Token mint")
}
SwapError::ExpectedAccount => {
info!("Error: Deserialized account is not an SPL Token account")
}
SwapError::EmptySupply => info!("Error: Input token account empty"),
SwapError::InvalidSupply => info!("Error: Pool token mint has a non-zero supply"),
SwapError::RepeatedMint => info!("Error: Swap input token accounts have the same mint"),
SwapError::InvalidDelegate => info!("Error: Token account has a delegate"),
SwapError::InvalidInput => info!("Error: InvalidInput"),
SwapError::IncorrectSwapAccount => {
info!("Error: Address of the provided swap token account is incorrect")
}
SwapError::IncorrectPoolMint => {
info!("Error: Address of the provided pool token mint is incorrect")
}
SwapError::InvalidOutput => info!("Error: InvalidOutput"),
SwapError::CalculationFailure => info!("Error: CalculationFailure"),
SwapError::InvalidInstruction => info!("Error: InvalidInstruction"),
}
}
}
// Pull in syscall stubs when building for non-BPF targets
#[cfg(not(target_arch = "bpf"))]
solana_sdk::program_stubs!();
#[cfg(test)]
mod tests {
use super::*;
use crate::{
curve::{SwapResult, INITIAL_SWAP_POOL_AMOUNT},
instruction::{deposit, initialize, swap, withdraw},
};
use solana_sdk::{
account::Account, account_info::create_is_signer_account_infos, instruction::Instruction,
2020-09-03 19:15:03 -07:00
rent::Rent, sysvar::rent,
};
use spl_token::{
error::TokenError,
instruction::{approve, initialize_account, initialize_mint, mint_to, revoke},
processor::Processor as SplProcessor,
state::{Account as SplAccount, Mint as SplMint},
};
struct SwapAccountInfo {
nonce: u8,
authority_key: Pubkey,
fee_numerator: u64,
fee_denominator: u64,
swap_key: Pubkey,
swap_account: Account,
pool_mint_key: Pubkey,
pool_mint_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,
fee_numerator: u64,
fee_denominator: u64,
token_a_amount: u64,
token_b_amount: u64,
) -> Self {
let swap_key = pubkey_rand();
let swap_account = Account::new(0, SwapInfo::get_packed_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(&TOKEN_PROGRAM_ID, &authority_key);
let (pool_token_key, pool_token_account) = mint_token(
&TOKEN_PROGRAM_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(&TOKEN_PROGRAM_ID, &user_key);
let (token_a_key, token_a_account) = mint_token(
&TOKEN_PROGRAM_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(&TOKEN_PROGRAM_ID, &user_key);
let (token_b_key, token_b_account) = mint_token(
&TOKEN_PROGRAM_ID,
&token_b_mint_key,
&mut token_b_mint_account,
&user_key,
&authority_key,
token_b_amount,
);
SwapAccountInfo {
nonce,
authority_key,
fee_numerator,
fee_denominator,
swap_key,
swap_account,
pool_mint_key,
pool_mint_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,
&TOKEN_PROGRAM_ID,
&self.swap_key,
&self.authority_key,
&self.token_a_key,
&self.token_b_key,
&self.pool_mint_key,
&self.pool_token_key,
self.nonce,
self.fee_numerator,
self.fee_denominator,
)
.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_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(
&TOKEN_PROGRAM_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(
&TOKEN_PROGRAM_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(
&TOKEN_PROGRAM_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");
}
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: u64,
) -> ProgramResult {
// approve moving from user source account
do_process_instruction(
approve(
&TOKEN_PROGRAM_ID,
&user_source_key,
&self.authority_key,
&user_key,
&[],
amount,
)
.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,
&TOKEN_PROGRAM_ID,
&self.swap_key,
&self.authority_key,
&user_source_key,
&swap_source_key,
&swap_destination_key,
&user_destination_key,
amount,
)
.unwrap(),
vec![
&mut self.swap_account,
&mut Account::default(),
&mut user_source_account,
&mut swap_source_account,
&mut swap_destination_account,
&mut user_destination_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(())
}
pub fn deposit(
&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_amount: u64,
amount_a: u64,
amount_b: u64,
) -> ProgramResult {
do_process_instruction(
approve(
&TOKEN_PROGRAM_ID,
&depositor_token_a_key,
&self.authority_key,
&depositor_key,
&[],
amount_a,
)
.unwrap(),
vec![
&mut depositor_token_a_account,
&mut Account::default(),
&mut Account::default(),
],
)
.unwrap();
do_process_instruction(
approve(
&TOKEN_PROGRAM_ID,
&depositor_token_b_key,
&self.authority_key,
&depositor_key,
&[],
amount_b,
)
.unwrap(),
vec![
&mut depositor_token_b_account,
&mut Account::default(),
&mut Account::default(),
],
)
.unwrap();
do_process_instruction(
deposit(
&SWAP_PROGRAM_ID,
&TOKEN_PROGRAM_ID,
&self.swap_key,
&self.authority_key,
&depositor_token_a_key,
&depositor_token_b_key,
&self.token_a_key,
&self.token_b_key,
&self.pool_mint_key,
&depositor_pool_key,
pool_amount,
)
.unwrap(),
vec![
&mut self.swap_account,
&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(),
],
)
}
pub fn withdraw(
&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_amount: u64,
) -> ProgramResult {
// approve swap program to take out pool tokens
do_process_instruction(
approve(
&TOKEN_PROGRAM_ID,
&pool_key,
&self.authority_key,
&user_key,
&[],
pool_amount,
)
.unwrap(),
vec![
&mut pool_account,
&mut Account::default(),
&mut Account::default(),
],
)
.unwrap();
// withraw token a and b correctly
do_process_instruction(
withdraw(
&SWAP_PROGRAM_ID,
&TOKEN_PROGRAM_ID,
&self.swap_key,
&self.authority_key,
&self.pool_mint_key,
&pool_key,
&self.token_a_key,
&self.token_b_key,
&token_a_key,
&token_b_key,
pool_amount,
)
.unwrap(),
vec![
&mut self.swap_account,
&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 Account::default(),
],
)
}
}
fn mint_minimum_balance() -> u64 {
Rent::default().minimum_balance(SplMint::get_packed_len())
}
fn account_minimum_balance() -> u64 {
Rent::default().minimum_balance(SplAccount::get_packed_len())
}
fn pubkey_rand() -> Pubkey {
Pubkey::new(&rand::random::<[u8; 32]>())
}
fn do_process_instruction(
instruction: Instruction,
accounts: Vec<&mut Account>,
) -> ProgramResult {
// 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(&instruction.program_id, &account_infos, &instruction.data)
} else {
SplProcessor::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 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_rand();
let mut account_account = Account::new(
account_minimum_balance(),
SplAccount::get_packed_len(),
&program_id,
);
let mut mint_authority_account = Account::default();
let mut rent_sysvar_account = rent::create_account(1, &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,
2020-09-03 19:15:03 -07:00
&mut rent_sysvar_account,
],
)
.unwrap();
2020-08-28 12:03:10 -07:00
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) -> (Pubkey, Account) {
let mint_key = pubkey_rand();
let mut mint_account = Account::new(
mint_minimum_balance(),
SplMint::get_packed_len(),
&program_id,
);
let mut rent_sysvar_account = rent::create_account(1, &Rent::free());
do_process_instruction(
initialize_mint(&program_id, &mint_key, authority_key, None, 2).unwrap(),
vec![&mut mint_account, &mut rent_sysvar_account],
)
.unwrap();
(mint_key, mint_account)
}
#[test]
fn test_token_program_id_error() {
let swap_key = pubkey_rand();
let mut mint = (pubkey_rand(), Account::default());
let mut destination = (pubkey_rand(), Account::default());
let token_program = (TOKEN_PROGRAM_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_rand();
let fee_numerator = 1;
let fee_denominator = 2;
let token_a_amount = 1000;
let token_b_amount = 2000;
let pool_token_amount = 10;
let mut accounts = SwapAccountInfo::new(
&user_key,
fee_numerator,
fee_denominator,
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::default();
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::default();
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::default();
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(
&TOKEN_PROGRAM_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(
&TOKEN_PROGRAM_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(
&TOKEN_PROGRAM_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 mint authority is not swap authority
{
let (_pool_mint_key, pool_mint_account) = create_mint(&TOKEN_PROGRAM_ID, &user_key);
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;
}
// empty token A account
{
let (_token_a_key, token_a_account) = mint_token(
&TOKEN_PROGRAM_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(
&TOKEN_PROGRAM_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(&TOKEN_PROGRAM_ID, &accounts.authority_key);
accounts.pool_mint_account = pool_mint_account;
let (_empty_pool_token_key, empty_pool_token_account) = mint_token(
&TOKEN_PROGRAM_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(
&TOKEN_PROGRAM_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;
}
// token A account is delegated
{
do_process_instruction(
approve(
&TOKEN_PROGRAM_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(
&TOKEN_PROGRAM_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(
&TOKEN_PROGRAM_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(
&TOKEN_PROGRAM_ID,
&accounts.token_b_key,
&accounts.authority_key,
&[],
)
.unwrap(),
vec![&mut accounts.token_b_account, &mut Account::default()],
)
.unwrap();
}
// wrong token program id
{
let wrong_program_id = pubkey_rand();
assert_eq!(
Err(ProgramError::InvalidAccountData),
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_token_key,
accounts.nonce,
accounts.fee_numerator,
accounts.fee_denominator,
)
.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_token_account,
&mut Account::default(),
],
)
);
}
// create swap with same token A and B
{
let (_token_a_repeat_key, token_a_repeat_account) = mint_token(
&TOKEN_PROGRAM_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 again
{
assert_eq!(
Err(SwapError::AlreadyInUse.into()),
accounts.initialize_swap()
);
}
let swap_info = SwapInfo::unpack(&accounts.swap_account.data).unwrap();
assert_eq!(swap_info.is_initialized, true);
assert_eq!(swap_info.nonce, accounts.nonce);
assert_eq!(swap_info.token_a, accounts.token_a_key);
assert_eq!(swap_info.token_b, accounts.token_b_key);
assert_eq!(swap_info.pool_mint, accounts.pool_mint_key);
assert_eq!(swap_info.fee_denominator, fee_denominator);
assert_eq!(swap_info.fee_numerator, fee_numerator);
let token_a = Processor::unpack_token_account(&accounts.token_a_account.data).unwrap();
assert_eq!(token_a.amount, token_a_amount);
let token_b = Processor::unpack_token_account(&accounts.token_b_account.data).unwrap();
assert_eq!(token_b.amount, token_b_amount);
let pool_account =
Processor::unpack_token_account(&accounts.pool_token_account.data).unwrap();
let pool_mint = Processor::unpack_mint(&accounts.pool_mint_account.data).unwrap();
assert_eq!(pool_mint.supply, pool_account.amount);
}
#[test]
fn test_deposit() {
let user_key = pubkey_rand();
let depositor_key = pubkey_rand();
let fee_numerator = 1;
let fee_denominator = 2;
let token_a_amount = 1000;
let token_b_amount = 9000;
let mut accounts = SwapAccountInfo::new(
&user_key,
fee_numerator,
fee_denominator,
token_a_amount,
token_b_amount,
);
let deposit_a = token_a_amount / 10;
let deposit_b = token_b_amount / 10;
let pool_amount = INITIAL_SWAP_POOL_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(
&depositor_key,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
&pool_key,
&mut pool_account,
pool_amount,
deposit_a,
deposit_b,
)
);
}
accounts.initialize_swap().unwrap();
// 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()[..]],
&TOKEN_PROGRAM_ID,
);
accounts.authority_key = bad_authority_key;
assert_eq!(
Err(SwapError::InvalidProgramAddress.into()),
accounts.deposit(
&depositor_key,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
&pool_key,
&mut pool_account,
pool_amount,
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(
&depositor_key,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
&pool_key,
&mut pool_account,
pool_amount,
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(
&depositor_key,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
&pool_key,
&mut pool_account,
pool_amount,
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(
&depositor_key,
&token_b_key,
&mut token_b_account,
&token_a_key,
&mut token_a_account,
&pool_key,
&mut pool_account,
pool_amount,
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(
&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,
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);
assert_eq!(
Err(TokenError::OwnerMismatch.into()),
do_process_instruction(
deposit(
&SWAP_PROGRAM_ID,
&TOKEN_PROGRAM_ID,
&accounts.swap_key,
&accounts.authority_key,
&token_a_key,
&token_b_key,
&accounts.token_a_key,
&accounts.token_b_key,
&accounts.pool_mint_key,
&pool_key,
pool_amount,
)
.unwrap(),
vec![
&mut accounts.swap_account,
&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_rand();
assert_eq!(
Err(ProgramError::InvalidAccountData),
do_process_instruction(
deposit(
&SWAP_PROGRAM_ID,
&wrong_key,
&accounts.swap_key,
&accounts.authority_key,
&token_a_key,
&token_b_key,
&accounts.token_a_key,
&accounts.token_b_key,
&accounts.pool_mint_key,
&pool_key,
pool_amount,
)
.unwrap(),
vec![
&mut accounts.swap_account,
&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.clone();
accounts.token_a_account = token_a_account.clone();
// wrong swap token a account
assert_eq!(
Err(SwapError::IncorrectSwapAccount.into()),
accounts.deposit(
&depositor_key,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
&pool_key,
&mut pool_account,
pool_amount,
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.clone();
accounts.token_b_account = token_b_account.clone();
// wrong swap token b account
assert_eq!(
Err(SwapError::IncorrectSwapAccount.into()),
accounts.deposit(
&depositor_key,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
&pool_key,
&mut pool_account,
pool_amount,
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(&TOKEN_PROGRAM_ID, &accounts.authority_key);
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(
&depositor_key,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
&pool_key,
&mut pool_account,
pool_amount,
deposit_a,
deposit_b,
)
);
accounts.pool_mint_key = old_pool_key;
accounts.pool_mint_account = old_pool_account;
}
// 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(
&depositor_key,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
&pool_key,
&mut pool_account,
pool_amount,
deposit_a,
deposit_b,
)
.unwrap();
let swap_token_a =
Processor::unpack_token_account(&accounts.token_a_account.data).unwrap();
assert_eq!(swap_token_a.amount, deposit_a + token_a_amount);
let swap_token_b =
Processor::unpack_token_account(&accounts.token_b_account.data).unwrap();
assert_eq!(swap_token_b.amount, deposit_b + token_b_amount);
let token_a = Processor::unpack_token_account(&token_a_account.data).unwrap();
assert_eq!(token_a.amount, 0);
let token_b = Processor::unpack_token_account(&token_b_account.data).unwrap();
assert_eq!(token_b.amount, 0);
let pool_account = Processor::unpack_token_account(&pool_account.data).unwrap();
let swap_pool_account =
Processor::unpack_token_account(&accounts.pool_token_account.data).unwrap();
let pool_mint = Processor::unpack_mint(&accounts.pool_mint_account.data).unwrap();
assert_eq!(
pool_mint.supply,
pool_account.amount + swap_pool_account.amount
);
}
2020-07-08 20:54:52 -07:00
}
#[test]
fn test_withdraw() {
let user_key = pubkey_rand();
let fee_numerator = 1;
let fee_denominator = 2;
let token_a_amount = 1000;
let token_b_amount = 2000;
let mut accounts = SwapAccountInfo::new(
&user_key,
fee_numerator,
fee_denominator,
token_a_amount,
token_b_amount,
);
let withdrawer_key = pubkey_rand();
let pool_converter = PoolTokenConverter::new_pool(token_a_amount, token_b_amount);
let initial_a = token_a_amount / 10;
let initial_b = token_b_amount / 10;
let initial_pool = pool_converter.supply / 10;
let withdraw_amount = initial_pool / 4;
// 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(
&withdrawer_key,
&pool_key,
&mut pool_account,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
withdraw_amount,
)
);
}
accounts.initialize_swap().unwrap();
// 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()[..]],
&TOKEN_PROGRAM_ID,
);
accounts.authority_key = bad_authority_key;
assert_eq!(
Err(SwapError::InvalidProgramAddress.into()),
accounts.withdraw(
&withdrawer_key,
&pool_key,
&mut pool_account,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
withdraw_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,
withdraw_amount / 2,
);
assert_eq!(
Err(TokenError::InsufficientFunds.into()),
accounts.withdraw(
&withdrawer_key,
&pool_key,
&mut pool_account,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
withdraw_amount,
)
);
}
// 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,
);
assert_eq!(
Err(TokenError::MintMismatch.into()),
accounts.withdraw(
&withdrawer_key,
&pool_key,
&mut pool_account,
&token_b_key,
&mut token_b_account,
&token_a_key,
&mut token_a_account,
withdraw_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,
);
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,
initial_b,
withdraw_amount,
);
assert_eq!(
Err(TokenError::MintMismatch.into()),
accounts.withdraw(
&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,
)
);
}
// 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);
assert_eq!(
Err(TokenError::OwnerMismatch.into()),
do_process_instruction(
withdraw(
&SWAP_PROGRAM_ID,
&TOKEN_PROGRAM_ID,
&accounts.swap_key,
&accounts.authority_key,
&accounts.pool_mint_key,
&pool_key,
&accounts.token_a_key,
&accounts.token_b_key,
&token_a_key,
&token_b_key,
withdraw_amount,
)
.unwrap(),
vec![
&mut accounts.swap_account,
&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 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,
);
let wrong_key = pubkey_rand();
assert_eq!(
Err(ProgramError::InvalidAccountData),
do_process_instruction(
withdraw(
&SWAP_PROGRAM_ID,
&wrong_key,
&accounts.swap_key,
&accounts.authority_key,
&accounts.pool_mint_key,
&pool_key,
&accounts.token_a_key,
&accounts.token_b_key,
&token_a_key,
&token_b_key,
withdraw_amount,
)
.unwrap(),
vec![
&mut accounts.swap_account,
&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 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,
);
let old_a_key = accounts.token_a_key;
let old_a_account = accounts.token_a_account;
accounts.token_a_key = token_a_key.clone();
accounts.token_a_account = token_a_account.clone();
// wrong swap token a account
assert_eq!(
Err(SwapError::IncorrectSwapAccount.into()),
accounts.withdraw(
&withdrawer_key,
&pool_key,
&mut pool_account,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
withdraw_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.clone();
accounts.token_b_account = token_b_account.clone();
// wrong swap token b account
assert_eq!(
Err(SwapError::IncorrectSwapAccount.into()),
accounts.withdraw(
&withdrawer_key,
&pool_key,
&mut pool_account,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
withdraw_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,
);
let (pool_mint_key, pool_mint_account) =
create_mint(&TOKEN_PROGRAM_ID, &accounts.authority_key);
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(
&withdrawer_key,
&pool_key,
&mut pool_account,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
withdraw_amount,
)
);
accounts.pool_mint_key = old_pool_key;
accounts.pool_mint_account = old_pool_account;
}
// 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,
);
accounts
.withdraw(
&withdrawer_key,
&pool_key,
&mut pool_account,
&token_a_key,
&mut token_a_account,
&token_b_key,
&mut token_b_account,
withdraw_amount,
)
.unwrap();
let swap_token_a =
Processor::unpack_token_account(&accounts.token_a_account.data).unwrap();
let swap_token_b =
Processor::unpack_token_account(&accounts.token_b_account.data).unwrap();
let pool_mint = Processor::unpack_mint(&accounts.pool_mint_account.data).unwrap();
let pool_converter = PoolTokenConverter::new_existing(
pool_mint.supply,
swap_token_a.amount,
swap_token_b.amount,
);
let withdrawn_a = pool_converter.token_a_rate(withdraw_amount).unwrap();
assert_eq!(swap_token_a.amount, token_a_amount - withdrawn_a);
let withdrawn_b = pool_converter.token_b_rate(withdraw_amount).unwrap();
assert_eq!(swap_token_b.amount, token_b_amount - withdrawn_b);
let token_a = Processor::unpack_token_account(&token_a_account.data).unwrap();
assert_eq!(token_a.amount, initial_a + withdrawn_a);
let token_b = Processor::unpack_token_account(&token_b_account.data).unwrap();
assert_eq!(token_b.amount, initial_b + withdrawn_b);
let pool_account = Processor::unpack_token_account(&pool_account.data).unwrap();
assert_eq!(pool_account.amount, initial_pool - withdraw_amount);
}
}
#[test]
fn test_swap() {
let user_key = pubkey_rand();
let swapper_key = pubkey_rand();
let fee_numerator = 1;
let fee_denominator = 10;
let token_a_amount = 1000;
let token_b_amount = 5000;
let mut accounts = SwapAccountInfo::new(
&user_key,
fee_numerator,
fee_denominator,
token_a_amount,
token_b_amount,
);
let initial_a = token_a_amount / 5;
let initial_b = token_b_amount / 5;
let swap_token_a_key = accounts.token_a_key.clone();
let swap_token_b_key = accounts.token_b_key.clone();
// 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,
)
);
}
accounts.initialize_swap().unwrap();
// 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()[..]],
&TOKEN_PROGRAM_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,
)
);
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_rand();
assert_eq!(
Err(ProgramError::InvalidAccountData),
do_process_instruction(
swap(
&SWAP_PROGRAM_ID,
&wrong_program_id,
&accounts.swap_key,
&accounts.authority_key,
&token_a_key,
&accounts.token_a_key,
&accounts.token_b_key,
&token_b_key,
initial_a,
)
.unwrap(),
vec![
&mut accounts.swap_account,
&mut Account::default(),
&mut token_a_account,
&mut accounts.token_a_account,
&mut accounts.token_b_account,
&mut token_b_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,
)
);
}
// 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);
assert_eq!(
Err(SwapError::IncorrectSwapAccount.into()),
do_process_instruction(
swap(
&SWAP_PROGRAM_ID,
&TOKEN_PROGRAM_ID,
&accounts.swap_key,
&accounts.authority_key,
&token_a_key,
&token_a_key,
&token_b_key,
&token_b_key,
initial_a,
)
.unwrap(),
vec![
&mut accounts.swap_account,
&mut Account::default(),
&mut token_a_account.clone(),
&mut token_a_account,
&mut token_b_account.clone(),
&mut token_b_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,
)
);
}
// 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,
)
);
}
// 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);
assert_eq!(
Err(TokenError::OwnerMismatch.into()),
do_process_instruction(
swap(
&SWAP_PROGRAM_ID,
&TOKEN_PROGRAM_ID,
&accounts.swap_key,
&accounts.authority_key,
&token_a_key,
&accounts.token_a_key,
&accounts.token_b_key,
&token_b_key,
initial_a,
)
.unwrap(),
vec![
&mut accounts.swap_account,
&mut Account::default(),
&mut token_a_account,
&mut accounts.token_a_account,
&mut accounts.token_b_account,
&mut token_b_account,
&mut Account::default(),
],
),
);
}
// correct 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);
// swap one way
let a_to_b_amount = initial_a / 10;
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,
)
.unwrap();
let results = SwapResult::swap_to(
a_to_b_amount,
token_a_amount,
token_b_amount,
fee_numerator,
fee_denominator,
)
.unwrap();
let swap_token_a =
Processor::unpack_token_account(&accounts.token_a_account.data).unwrap();
let token_a_amount = swap_token_a.amount;
assert_eq!(token_a_amount, results.new_source_amount);
let token_a = Processor::unpack_token_account(&token_a_account.data).unwrap();
assert_eq!(token_a.amount, initial_a - a_to_b_amount);
let swap_token_b =
Processor::unpack_token_account(&accounts.token_b_account.data).unwrap();
let token_b_amount = swap_token_b.amount;
assert_eq!(token_b_amount, results.new_destination_amount);
let token_b = Processor::unpack_token_account(&token_b_account.data).unwrap();
assert_eq!(token_b.amount, initial_b + results.amount_swapped);
let first_swap_amount = results.amount_swapped;
// swap the other way
let b_to_a_amount = initial_b / 10;
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,
)
.unwrap();
let results = SwapResult::swap_to(
b_to_a_amount,
token_b_amount,
token_a_amount,
fee_numerator,
fee_denominator,
)
.unwrap();
let swap_token_a =
Processor::unpack_token_account(&accounts.token_a_account.data).unwrap();
assert_eq!(swap_token_a.amount, results.new_destination_amount);
let token_a = Processor::unpack_token_account(&token_a_account.data).unwrap();
assert_eq!(
token_a.amount,
initial_a - a_to_b_amount + results.amount_swapped
);
let swap_token_b =
Processor::unpack_token_account(&accounts.token_b_account.data).unwrap();
assert_eq!(swap_token_b.amount, results.new_source_amount);
let token_b = Processor::unpack_token_account(&token_b_account.data).unwrap();
assert_eq!(
token_b.amount,
initial_b + first_swap_amount - b_to_a_amount
);
}
}
2020-07-08 20:54:52 -07:00
}