token-swap: Fix withdraw instruction (#405)

* Include pool mint in instruction, required for burning
* Add simple Rust test
* Fix js withdraw test
This commit is contained in:
Jon Cinque 2020-09-09 18:00:03 +02:00 committed by GitHub
parent b8066f2e08
commit f6fc53c311
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 156 additions and 33 deletions

View File

@ -240,8 +240,12 @@ export async function deposit(): Promise<void> {
}
export async function withdraw(): Promise<void> {
console.log('Creating withdraw token A account');
let userAccountA = await mintA.createAccount(owner.publicKey);
console.log('Creating withdraw token B account');
let userAccountB = await mintB.createAccount(owner.publicKey);
console.log('Approving withdrawal from pool account');
await tokenPool.approve(
tokenAccountPool,
authority,
@ -251,8 +255,10 @@ export async function withdraw(): Promise<void> {
);
const [tokenProgramId,] = await GetPrograms(connection);
console.log('Withdrawing pool tokens for A and B tokens');
await tokenSwap.withdraw(
authority,
tokenPool.publicKey,
tokenAccountPool,
tokenAccountA,
tokenAccountB,
@ -262,21 +268,15 @@ export async function withdraw(): Promise<void> {
USER_AMOUNT
);
let info;
info = await tokenPool.getAccountInfo(tokenAccountPool);
console.log('tokenAccountPool', info.amount.toNumber());
let info = await tokenPool.getAccountInfo(tokenAccountPool);
assert(info.amount.toNumber() == BASE_AMOUNT - USER_AMOUNT);
info = await mintA.getAccountInfo(tokenAccountA);
console.log('tokenAccountA', info.amount.toNumber());
assert(info.amount.toNumber() == BASE_AMOUNT);
info = await mintB.getAccountInfo(tokenAccountB);
console.log('tokenAccountB', info.amount.toNumber());
assert(info.amount.toNumber() == BASE_AMOUNT);
info = await mintA.getAccountInfo(userAccountA);
console.log('userAccountA', info.amount.toNumber());
assert(info.amount.toNumber() == USER_AMOUNT);
info = await mintB.getAccountInfo(userAccountB);
console.log('userAccountB', info.amount.toNumber());
assert(info.amount.toNumber() == USER_AMOUNT);
}

View File

@ -467,6 +467,7 @@ export class TokenSwap {
*/
async withdraw(
authority: PublicKey,
poolMint: PublicKey,
sourcePoolAccount: PublicKey,
fromA: PublicKey,
fromB: PublicKey,
@ -481,6 +482,7 @@ export class TokenSwap {
new Transaction().add(
this.withdrawInstruction(
authority,
poolMint,
sourcePoolAccount,
fromA,
fromB,
@ -493,8 +495,10 @@ export class TokenSwap {
this.payer,
);
}
withdrawInstruction(
authority: PublicKey,
poolMint: PublicKey,
sourcePoolAccount: PublicKey,
fromA: PublicKey,
fromB: PublicKey,
@ -520,6 +524,7 @@ export class TokenSwap {
const keys = [
{pubkey: this.tokenSwap, isSigner: false, isWritable: false},
{pubkey: authority, isSigner: false, isWritable: false},
{pubkey: poolMint, isSigner: false, isWritable: true},
{pubkey: sourcePoolAccount, isSigner: false, isWritable: true},
{pubkey: fromA, isSigner: false, isWritable: true},
{pubkey: fromB, isSigner: false, isWritable: true},

View File

@ -78,12 +78,13 @@ pub enum SwapInstruction {
///
/// 0. `[]` Token-swap
/// 1. `[]` $authority
/// 2. `[writable]` SOURCE Pool account, amount is transferable by $authority.
/// 5. `[writable]` token_a Account to withdraw FROM.
/// 6. `[writable]` token_b Account to withdraw FROM.
/// 7. `[writable]` token_a user Account.
/// 8. `[writable]` token_b user Account.
/// 9. '[]` Token program id
/// 2. `[writable]` Pool mint account, $authority is the owner
/// 3. `[writable]` SOURCE Pool account, amount is transferable by $authority.
/// 4. `[writable]` token_a Swap Account to withdraw FROM.
/// 5. `[writable]` token_b Swap Account to withdraw FROM.
/// 6. `[writable]` token_a user Account to credit.
/// 7. `[writable]` token_b user Account to credit.
/// 8. '[]` Token program id
/// userdata: SOURCE amount of pool tokens to transfer. User receives an output based on the
/// percentage of the pool tokens that are returned.
Withdraw(u64),
@ -222,6 +223,41 @@ pub fn deposit(
})
}
/// Creates a 'withdraw' instruction.
pub fn withdraw(
program_id: &Pubkey,
token_program_id: &Pubkey,
swap_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
pool_mint_pubkey: &Pubkey,
source_pubkey: &Pubkey,
swap_token_a_pubkey: &Pubkey,
swap_token_b_pubkey: &Pubkey,
output_token_a_pubkey: &Pubkey,
output_token_b_pubkey: &Pubkey,
amount: u64,
) -> Result<Instruction, ProgramError> {
let data = SwapInstruction::Withdraw(amount).serialize()?;
let accounts = vec![
AccountMeta::new(*swap_pubkey, false),
AccountMeta::new(*authority_pubkey, false),
AccountMeta::new(*pool_mint_pubkey, false),
AccountMeta::new(*source_pubkey, false),
AccountMeta::new(*swap_token_a_pubkey, false),
AccountMeta::new(*swap_token_b_pubkey, false),
AccountMeta::new(*output_token_a_pubkey, false),
AccountMeta::new(*output_token_b_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

@ -48,27 +48,35 @@ impl Processor {
Pubkey::create_program_address(&[&my_info.to_bytes()[..32], &[nonce]], program_id)
.or(Err(Error::InvalidProgramAddress))
}
/// Issue a spl_token `Burn` instruction.
pub fn token_burn(
accounts: &[AccountInfo],
token_program_id: &Pubkey,
pub fn token_burn<'a>(
swap: &Pubkey,
burn_account: &Pubkey,
mint: &Pubkey,
authority: &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 signers = &[&[&swap_bytes[..32]][..]];
let authority_signature_seeds = [&swap_bytes[..32], &[nonce]];
let signers = &[&authority_signature_seeds[..]];
let ix = spl_token::instruction::burn(
token_program_id,
burn_account,
mint,
authority,
token_program.key,
burn_account.key,
mint.key,
authority.key,
&[],
amount,
)?;
invoke_signed(&ix, accounts, signers)
invoke_signed(
&ix,
&[burn_account, mint, authority, token_program],
signers,
)
}
/// Issue a spl_token `MintTo` instruction.
@ -338,6 +346,7 @@ impl Processor {
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)?;
@ -389,16 +398,17 @@ impl Processor {
b_amount,
)?;
Self::token_burn(
accounts,
token_program_info.key,
swap_info.key,
source_info.key,
&token_swap.pool_mint,
authority_info.key,
token_program_info.clone(),
source_info.clone(),
pool_mint_info.clone(),
authority_info.clone(),
token_swap.nonce,
amount,
)?;
Ok(())
}
/// Processes an [Instruction](enum.Instruction.html).
pub fn process(program_id: &Pubkey, accounts: &[AccountInfo], input: &[u8]) -> ProgramResult {
let instruction = SwapInstruction::deserialize(input)?;
@ -491,7 +501,7 @@ solana_sdk::program_stubs!();
#[cfg(test)]
mod tests {
use super::*;
use crate::instruction::{deposit, initialize};
use crate::instruction::{deposit, initialize, withdraw};
use solana_sdk::{
account::Account, account_info::create_is_signer_account_infos, instruction::Instruction,
rent::Rent, sysvar::rent,
@ -510,7 +520,7 @@ mod tests {
swap_account: Account,
pool_mint_key: Pubkey,
pool_mint_account: Account,
_pool_token_key: Pubkey,
pool_token_key: Pubkey,
pool_token_account: Account,
token_a_key: Pubkey,
token_a_account: Account,
@ -724,7 +734,7 @@ mod tests {
swap_account,
pool_mint_key,
pool_mint_account,
_pool_token_key: pool_token_key,
pool_token_key,
pool_token_account,
token_a_key,
token_a_account,
@ -852,4 +862,76 @@ mod tests {
pool_account.amount + depositor_pool_account.amount
);
}
#[test]
fn test_withdraw() {
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 initial_a = token_a_amount / 10;
let (withdraw_token_a_key, mut withdraw_token_a_account) = mint_token(
&TOKEN_PROGRAM_ID,
&accounts.token_a_mint_key,
&mut accounts.token_a_mint_account,
&authority_key,
initial_a,
);
let initial_b = token_b_amount / 10;
let (withdraw_token_b_key, mut withdraw_token_b_account) = mint_token(
&TOKEN_PROGRAM_ID,
&accounts.token_b_mint_key,
&mut accounts.token_b_mint_account,
&authority_key,
initial_b,
);
let withdraw_amount = token_a_amount / 4;
do_process_instruction(
withdraw(
&SWAP_PROGRAM_ID,
&TOKEN_PROGRAM_ID,
&accounts.swap_key,
&authority_key,
&accounts.pool_mint_key,
&accounts.pool_token_key,
&accounts.token_a_key,
&accounts.token_b_key,
&withdraw_token_a_key,
&withdraw_token_b_key,
withdraw_amount,
)
.unwrap(),
vec![
&mut accounts.swap_account,
&mut Account::default(),
&mut accounts.pool_mint_account,
&mut accounts.pool_token_account,
&mut accounts.token_a_account,
&mut accounts.token_b_account,
&mut withdraw_token_a_account,
&mut withdraw_token_b_account,
&mut Account::default(),
],
)
.unwrap();
let token_a = Processor::token_account_deserialize(&accounts.token_a_account.data).unwrap();
assert_eq!(token_a.amount, token_a_amount - withdraw_amount);
let token_b = Processor::token_account_deserialize(&accounts.token_b_account.data).unwrap();
assert_eq!(token_b.amount, token_b_amount - (withdraw_amount * 2));
let withdraw_token_a =
Processor::token_account_deserialize(&withdraw_token_a_account.data).unwrap();
assert_eq!(withdraw_token_a.amount, initial_a + withdraw_amount);
let withdraw_token_b =
Processor::token_account_deserialize(&withdraw_token_b_account.data).unwrap();
assert_eq!(withdraw_token_b.amount, initial_b + (withdraw_amount * 2));
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);
}
}