Fix deposit instruction (#399)

* Fix deposit instruction

* Refactor tests for easier composibility
* Add deposit instruction and test
* Deposit JS test works again

* Fix js lint
This commit is contained in:
Jon Cinque 2020-09-09 10:59:27 +02:00 committed by GitHub
parent 85b28cbe82
commit db729af089
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 304 additions and 121 deletions

View File

@ -189,6 +189,7 @@ export async function createTokenSwap(): Promise<void> {
}
export async function deposit(): Promise<void> {
console.log('Creating depositor token a account');
let userAccountA = await mintA.createAccount(owner.publicKey);
await mintA.mintTo(userAccountA, owner, [], USER_AMOUNT);
await mintA.approve(
@ -198,6 +199,7 @@ export async function deposit(): Promise<void> {
[],
USER_AMOUNT,
);
console.log('Creating depositor token b account');
let userAccountB = await mintB.createAccount(owner.publicKey);
await mintB.mintTo(userAccountB, owner, [], USER_AMOUNT);
await mintB.approve(
@ -207,9 +209,11 @@ export async function deposit(): Promise<void> {
[],
USER_AMOUNT,
);
console.log('Creating depositor pool token account');
let newAccountPool = await tokenPool.createAccount(owner.publicKey);
const [tokenProgramId,] = await GetPrograms(connection);
console.log('Depositing into swap');
await tokenSwap.deposit(
authority,
userAccountA,
@ -224,20 +228,14 @@ export async function deposit(): Promise<void> {
let info;
info = await mintA.getAccountInfo(userAccountA);
console.log('userAccountA', info.amount.toNumber());
assert(info.amount.toNumber() == 0);
assert(info.amount.toNumber() == 0);
info = await mintB.getAccountInfo(userAccountB);
console.log('userAccountB', info.amount.toNumber());
assert(info.amount.toNumber() == 0);
info = await mintA.getAccountInfo(tokenAccountA);
console.log('tokenAccountA', info.amount.toNumber());
assert(info.amount.toNumber() == BASE_AMOUNT + USER_AMOUNT);
info = await mintB.getAccountInfo(tokenAccountB);
console.log('tokenAccountB', info.amount.toNumber());
assert(info.amount.toNumber() == BASE_AMOUNT + USER_AMOUNT);
info = await tokenPool.getAccountInfo(newAccountPool);
console.log('newAccountPool', info.amount.toNumber());
assert(info.amount.toNumber() == USER_AMOUNT);
}

View File

@ -407,6 +407,7 @@ export class TokenSwap {
this.payer,
);
}
depositInstruction(
authority: PublicKey,
sourceA: PublicKey,

View File

@ -65,12 +65,12 @@ pub enum SwapInstruction {
/// 0. `[]` Token-swap
/// 1. `[]` $authority
/// 2. `[writable]` token_a $authority can transfer amount,
/// 4. `[writable]` token_b $authority can transfer amount,
/// 6. `[writable]` token_a Base Account to deposit into.
/// 7. `[writable]` token_b Base Account to deposit into.
/// 8. `[writable]` Pool MINT account, $authority is the owner.
/// 9. `[writable]` Pool Account to deposit the generated tokens, user is the owner.
/// 10. '[]` Token program id
/// 3. `[writable]` token_b $authority can transfer amount,
/// 4. `[writable]` token_a Base Account to deposit into.
/// 5. `[writable]` token_b Base Account to deposit into.
/// 6. `[writable]` Pool MINT account, $authority is the owner.
/// 7. `[writable]` Pool Account to deposit the generated tokens, user is the owner.
/// 8. '[]` Token program id
/// userdata: token_a amount to transfer. token_b amount is set by the current exchange rate.
Deposit(u64),
@ -187,6 +187,41 @@ pub fn initialize(
})
}
/// Creates a 'deposit' instruction.
pub fn deposit(
program_id: &Pubkey,
token_program_id: &Pubkey,
swap_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
deposit_token_a_pubkey: &Pubkey,
deposit_token_b_pubkey: &Pubkey,
swap_token_a_pubkey: &Pubkey,
swap_token_b_pubkey: &Pubkey,
pool_mint_pubkey: &Pubkey,
user_output_pubkey: &Pubkey,
amount: u64,
) -> Result<Instruction, ProgramError> {
let data = SwapInstruction::Deposit(amount).serialize()?;
let accounts = vec![
AccountMeta::new(*swap_pubkey, false),
AccountMeta::new(*authority_pubkey, false),
AccountMeta::new(*deposit_token_a_pubkey, false),
AccountMeta::new(*deposit_token_b_pubkey, false),
AccountMeta::new(*swap_token_a_pubkey, false),
AccountMeta::new(*swap_token_b_pubkey, false),
AccountMeta::new(*pool_mint_pubkey, false),
AccountMeta::new(*user_output_pubkey, false),
AccountMeta::new(*token_program_id, false),
];
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Unpacks a reference from a bytes buffer.
/// TODO actually pack / unpack instead of relying on normal memory layout.
pub fn unpack<T>(input: &[u8]) -> Result<&T, ProgramError> {

View File

@ -34,16 +34,13 @@ const TOKEN_PROGRAM_ID: Pubkey = Pubkey::new_from_array([1u8; 32]);
pub struct Processor {}
impl Processor {
/// Deserializes a spl_token `Account`.
pub fn token_account_deserialize(
info: &AccountInfo,
) -> Result<spl_token::state::Account, Error> {
spl_token::state::Account::unpack(&info.data.borrow_mut())
.map_err(|_| Error::ExpectedAccount)
pub fn token_account_deserialize(data: &[u8]) -> Result<spl_token::state::Account, Error> {
spl_token::state::Account::unpack(data).map_err(|_| Error::ExpectedAccount)
}
/// Deserializes a spl_token `Mint`.
pub fn mint_deserialize(info: &AccountInfo) -> Result<spl_token::state::Mint, Error> {
spl_token::state::Mint::unpack(&info.data.borrow_mut()).map_err(|_| Error::ExpectedAccount)
pub fn mint_deserialize(data: &[u8]) -> Result<spl_token::state::Mint, Error> {
spl_token::state::Mint::unpack(data).map_err(|_| Error::ExpectedAccount)
}
/// Calculates the authority id by generating a program address.
@ -100,26 +97,31 @@ impl Processor {
}
/// Issue a spl_token `Transfer` instruction.
pub fn token_transfer(
accounts: &[AccountInfo],
token_program_id: &Pubkey,
pub fn token_transfer<'a>(
swap: &Pubkey,
source: &Pubkey,
destination: &Pubkey,
authority: &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 signers = &[&[&swap_bytes[..32]][..]];
let authority_signature_seeds = [&swap_bytes[..32], &[nonce]];
let signers = &[&authority_signature_seeds[..]];
let ix = spl_token::instruction::transfer(
token_program_id,
source,
destination,
authority,
token_program.key,
source.key,
destination.key,
authority.key,
&[],
amount,
)?;
invoke_signed(&ix, accounts, signers)
invoke_signed(
&ix,
&[source, destination, authority, token_program],
signers,
)
}
/// Processes an [Initialize](enum.Instruction.html).
@ -145,9 +147,9 @@ impl Processor {
if *authority_info.key != Self::authority_id(program_id, swap_info.key, nonce)? {
return Err(Error::InvalidProgramAddress.into());
}
let token_a = Self::token_account_deserialize(token_a_info)?;
let token_b = Self::token_account_deserialize(token_b_info)?;
let pool_mint = Self::mint_deserialize(pool_info)?;
let token_a = Self::token_account_deserialize(&token_a_info.data.borrow())?;
let token_b = Self::token_account_deserialize(&token_b_info.data.borrow())?;
let pool_mint = Self::mint_deserialize(&pool_info.data.borrow())?;
if *authority_info.key != token_a.owner {
return Err(Error::InvalidOwner.into());
}
@ -222,8 +224,8 @@ impl Processor {
if *into_info.key == *from_info.key {
return Err(Error::InvalidInput.into());
}
let into_token = Self::token_account_deserialize(into_info)?;
let from_token = Self::token_account_deserialize(from_info)?;
let into_token = Self::token_account_deserialize(&into_info.data.borrow())?;
let from_token = Self::token_account_deserialize(&from_info.data.borrow())?;
let mut invariant = Invariant {
token_a: into_token.amount,
token_b: from_token.amount,
@ -231,21 +233,21 @@ impl Processor {
};
let output = invariant.swap(amount).ok_or(Error::CalculationFailure)?;
Self::token_transfer(
accounts,
token_program_info.key,
swap_info.key,
source_info.key,
into_info.key,
authority_info.key,
swap_info.clone(),
source_info.clone(),
into_info.clone(),
authority_info.clone(),
token_swap.nonce,
amount,
)?;
Self::token_transfer(
accounts,
token_program_info.key,
swap_info.key,
from_info.key,
dest_info.key,
authority_info.key,
swap_info.clone(),
from_info.clone(),
dest_info.clone(),
authority_info.clone(),
token_swap.nonce,
output,
)?;
Ok(())
@ -280,8 +282,8 @@ impl Processor {
if *pool_info.key != token_swap.pool_mint {
return Err(Error::InvalidInput.into());
}
let token_a = Self::token_account_deserialize(token_a_info)?;
let token_b = Self::token_account_deserialize(token_b_info)?;
let token_a = Self::token_account_deserialize(&token_a_info.data.borrow())?;
let token_b = Self::token_account_deserialize(&token_b_info.data.borrow())?;
let invariant = Invariant {
token_a: token_a.amount,
@ -297,21 +299,21 @@ impl Processor {
let output = a_amount;
Self::token_transfer(
accounts,
token_program_info.key,
swap_info.key,
source_a_info.key,
token_a_info.key,
authority_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(
accounts,
token_program_info.key,
swap_info.key,
source_b_info.key,
token_b_info.key,
authority_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(
@ -354,8 +356,8 @@ impl Processor {
return Err(Error::InvalidInput.into());
}
let token_a = Self::token_account_deserialize(token_a_info)?;
let token_b = Self::token_account_deserialize(token_b_info)?;
let token_a = Self::token_account_deserialize(&token_a_info.data.borrow())?;
let token_b = Self::token_account_deserialize(&token_b_info.data.borrow())?;
let invariant = Invariant {
token_a: token_a.amount,
@ -369,21 +371,21 @@ impl Processor {
.ok_or(Error::CalculationFailure)?;
Self::token_transfer(
accounts,
token_program_info.key,
swap_info.key,
token_a_info.key,
dest_token_a_info.key,
authority_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(
accounts,
token_program_info.key,
swap_info.key,
token_b_info.key,
dest_token_b_info.key,
authority_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(
@ -489,7 +491,7 @@ solana_sdk::program_stubs!();
#[cfg(test)]
mod tests {
use super::*;
use crate::instruction::initialize;
use crate::instruction::{deposit, initialize};
use solana_sdk::{
account::Account, account_info::create_is_signer_account_infos, instruction::Instruction,
rent::Rent, sysvar::rent,
@ -502,12 +504,23 @@ mod tests {
};
use std::mem::size_of;
const SWAP_INDEX: usize = 0;
const TOKEN_A_INDEX: usize = 1;
const TOKEN_B_INDEX: usize = 2;
const POOL_MINT_INDEX: usize = 3;
const POOL_INDEX: usize = 4;
const AUTHORITY_INDEX: usize = 5;
struct SwapAccountInfo {
nonce: u8,
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,
}
fn mint_minimum_balance() -> u64 {
Rent::default().minimum_balance(SplMint::get_packed_len())
@ -542,38 +555,26 @@ mod tests {
fn mint_token(
program_id: &Pubkey,
mint_key: &Pubkey,
mut mint_account: &mut Account,
authority_key: &Pubkey,
amount: u64,
) -> ((Pubkey, Account), (Pubkey, Account)) {
let token_key = pubkey_rand();
let mut token_account = Account::new(
mint_minimum_balance(),
SplMint::get_packed_len(),
&program_id,
);
) -> (Pubkey, Account) {
let account_key = pubkey_rand();
let mut account_account = Account::new(
account_minimum_balance(),
SplAccount::get_packed_len(),
&program_id,
);
let mut rent_sysvar_account = rent::create_account(1, &Rent::free());
// create token mint
do_process_instruction(
initialize_mint(&program_id, &token_key, authority_key, None, 2).unwrap(),
vec![&mut token_account, &mut rent_sysvar_account],
)
.unwrap();
let mut authority_account = Account::default();
let mut rent_sysvar_account = rent::create_account(1, &Rent::free());
// create account
do_process_instruction(
initialize_account(&program_id, &account_key, &token_key, authority_key).unwrap(),
initialize_account(&program_id, &account_key, &mint_key, authority_key).unwrap(),
vec![
&mut account_account,
&mut token_account,
&mut mint_account,
&mut authority_account,
&mut rent_sysvar_account,
],
@ -583,7 +584,7 @@ mod tests {
do_process_instruction(
mint_to(
&program_id,
&token_key,
&mint_key,
&account_key,
&authority_key,
&[],
@ -591,14 +592,33 @@ mod tests {
)
.unwrap(),
vec![
&mut token_account,
&mut mint_account,
&mut account_account,
&mut authority_account,
],
)
.unwrap();
return ((token_key, token_account), (account_key, account_account));
(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());
// create token mint
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]
@ -630,19 +650,43 @@ mod tests {
assert_eq!(err, ProgramError::InvalidAccountData);
}
fn initialize_swap(numerator: u64, denominator: u64) -> (u8, Vec<(Pubkey, Account)>) {
let mut account_infos = vec![];
fn initialize_swap<'a>(
numerator: u64,
denominator: u64,
token_a_amount: u64,
token_b_amount: u64,
) -> SwapAccountInfo {
let swap_key = pubkey_rand();
let mut swap_account = Account::new(0, size_of::<State>(), &SWAP_PROGRAM_ID);
let (authority_key, nonce) =
Pubkey::find_program_address(&[&swap_key.to_bytes()[..]], &SWAP_PROGRAM_ID);
let ((pool_key, mut pool_account), (pool_token_key, mut pool_token_account)) =
mint_token(&TOKEN_PROGRAM_ID, &authority_key, 0);
let ((_token_a_mint_key, mut _token_a_mint_account), (token_a_key, mut token_a_account)) =
mint_token(&TOKEN_PROGRAM_ID, &authority_key, 1000);
let ((_token_b_mint_key, mut _token_b_mint_account), (token_b_key, mut token_b_account)) =
mint_token(&TOKEN_PROGRAM_ID, &authority_key, 1000);
let (pool_mint_key, mut pool_mint_account) = create_mint(&TOKEN_PROGRAM_ID, &authority_key);
let (pool_token_key, mut pool_token_account) = mint_token(
&TOKEN_PROGRAM_ID,
&pool_mint_key,
&mut pool_mint_account,
&authority_key,
0,
);
let (token_a_mint_key, mut token_a_mint_account) =
create_mint(&TOKEN_PROGRAM_ID, &authority_key);
let (token_a_key, mut token_a_account) = mint_token(
&TOKEN_PROGRAM_ID,
&token_a_mint_key,
&mut token_a_mint_account,
&authority_key,
token_a_amount,
);
let (token_b_mint_key, mut token_b_mint_account) =
create_mint(&TOKEN_PROGRAM_ID, &authority_key);
let (token_b_key, mut token_b_account) = mint_token(
&TOKEN_PROGRAM_ID,
&token_b_mint_key,
&mut token_b_mint_account,
&authority_key,
token_b_amount,
);
// Swap Init
let mut authority_account = Account::default();
@ -654,7 +698,7 @@ mod tests {
&authority_key,
&token_a_key,
&token_b_key,
&pool_key,
&pool_mint_key,
&pool_token_key,
nonce,
Fee {
@ -668,33 +712,45 @@ mod tests {
&mut authority_account,
&mut token_a_account,
&mut token_b_account,
&mut pool_account,
&mut pool_mint_account,
&mut pool_token_account,
&mut Account::default(),
],
)
.unwrap();
account_infos.insert(SWAP_INDEX, (swap_key, swap_account));
account_infos.insert(TOKEN_A_INDEX, (token_a_key, token_a_account));
account_infos.insert(TOKEN_B_INDEX, (token_b_key, token_b_account));
account_infos.insert(POOL_MINT_INDEX, (pool_key, pool_account));
account_infos.insert(POOL_INDEX, (pool_token_key, pool_token_account));
account_infos.insert(AUTHORITY_INDEX, (authority_key, authority_account));
(nonce, account_infos)
SwapAccountInfo {
nonce,
swap_key,
swap_account,
pool_mint_key,
pool_mint_account,
_pool_token_key: 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,
}
}
#[test]
fn test_initialize() {
let numerator = 1;
let denominator = 2;
let (nonce, account_infos) = initialize_swap(numerator, denominator);
let state = State::deserialize(&account_infos[SWAP_INDEX].1.data).unwrap();
let token_a_amount = 1000;
let token_b_amount = 2000;
let swap_accounts = initialize_swap(numerator, denominator, token_a_amount, token_b_amount);
let state = State::deserialize(&swap_accounts.swap_account.data).unwrap();
match state {
State::Init(swap_info) => {
assert_eq!(swap_info.nonce, nonce);
assert_eq!(swap_info.token_a, account_infos[TOKEN_A_INDEX].0);
assert_eq!(swap_info.token_b, account_infos[TOKEN_B_INDEX].0);
assert_eq!(swap_info.pool_mint, account_infos[POOL_MINT_INDEX].0);
assert_eq!(swap_info.nonce, swap_accounts.nonce);
assert_eq!(swap_info.token_a, swap_accounts.token_a_key);
assert_eq!(swap_info.token_b, swap_accounts.token_b_key);
assert_eq!(swap_info.pool_mint, swap_accounts.pool_mint_key);
assert_eq!(swap_info.fee.denominator, denominator);
assert_eq!(swap_info.fee.numerator, numerator);
}
@ -702,5 +758,98 @@ mod tests {
panic!("Incorrect state");
}
}
let token_a =
Processor::token_account_deserialize(&swap_accounts.token_a_account.data).unwrap();
assert_eq!(token_a.amount, token_a_amount);
let token_b =
Processor::token_account_deserialize(&swap_accounts.token_b_account.data).unwrap();
assert_eq!(token_b.amount, token_b_amount);
let pool_account =
Processor::token_account_deserialize(&swap_accounts.pool_token_account.data).unwrap();
let pool_mint = Processor::mint_deserialize(&swap_accounts.pool_mint_account.data).unwrap();
assert_eq!(pool_mint.supply, pool_account.amount);
}
#[test]
fn test_deposit() {
let numerator = 1;
let denominator = 2;
let token_a_amount = 1000;
let token_b_amount = 2000;
let mut accounts = initialize_swap(numerator, denominator, token_a_amount, token_b_amount);
let seeds = [&accounts.swap_key.to_bytes()[..32], &[accounts.nonce]];
let authority_key = Pubkey::create_program_address(&seeds, &SWAP_PROGRAM_ID).unwrap();
let deposit_a = token_a_amount / 10;
let (depositor_token_a_key, mut depositor_token_a_account) = mint_token(
&TOKEN_PROGRAM_ID,
&accounts.token_a_mint_key,
&mut accounts.token_a_mint_account,
&authority_key,
deposit_a,
);
let deposit_b = token_b_amount / 10;
let (depositor_token_b_key, mut depositor_token_b_account) = mint_token(
&TOKEN_PROGRAM_ID,
&accounts.token_b_mint_key,
&mut accounts.token_b_mint_account,
&authority_key,
deposit_b,
);
let initial_pool = 10;
let (depositor_pool_key, mut depositor_pool_account) = mint_token(
&TOKEN_PROGRAM_ID,
&accounts.pool_mint_key,
&mut accounts.pool_mint_account,
&authority_key,
initial_pool,
);
do_process_instruction(
deposit(
&SWAP_PROGRAM_ID,
&TOKEN_PROGRAM_ID,
&accounts.swap_key,
&authority_key,
&depositor_token_a_key,
&depositor_token_b_key,
&accounts.token_a_key,
&accounts.token_b_key,
&accounts.pool_mint_key,
&depositor_pool_key,
deposit_a,
)
.unwrap(),
vec![
&mut accounts.swap_account,
&mut Account::default(),
&mut depositor_token_a_account,
&mut depositor_token_b_account,
&mut accounts.token_a_account,
&mut accounts.token_b_account,
&mut accounts.pool_mint_account,
&mut depositor_pool_account,
&mut Account::default(),
],
)
.unwrap();
let token_a = Processor::token_account_deserialize(&accounts.token_a_account.data).unwrap();
assert_eq!(token_a.amount, deposit_a + token_a_amount);
let token_b = Processor::token_account_deserialize(&accounts.token_b_account.data).unwrap();
assert_eq!(token_b.amount, deposit_b + token_b_amount);
let depositor_token_a =
Processor::token_account_deserialize(&depositor_token_a_account.data).unwrap();
assert_eq!(depositor_token_a.amount, 0);
let depositor_token_b =
Processor::token_account_deserialize(&depositor_token_b_account.data).unwrap();
assert_eq!(depositor_token_b.amount, 0);
let depositor_pool_account =
Processor::token_account_deserialize(&depositor_pool_account.data).unwrap();
let pool_account =
Processor::token_account_deserialize(&accounts.pool_token_account.data).unwrap();
let pool_mint = Processor::mint_deserialize(&accounts.pool_mint_account.data).unwrap();
assert_eq!(
pool_mint.supply,
pool_account.amount + depositor_pool_account.amount
);
}
}