token-swap: Add instructions to deposit / withdraw one token (#937)

* Add instructions to deposit / withdraw one token

* Run cargo fmt

* Fix clippy issues

* Add JS interface

* Add tests for new instructions

* Run prettier

* Rename deposit and withdraw

* Rename withdraw -> withdraw all in program

* Rename single token instructions
This commit is contained in:
Jon Cinque 2020-12-16 10:02:27 +01:00 committed by GitHub
parent 020b13d80c
commit 7190672a0f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 2380 additions and 132 deletions

View File

@ -9,8 +9,10 @@ import {
createAccountAndSwapAtomic,
createTokenSwap,
swap,
deposit,
withdraw,
depositAllTokenTypes,
withdrawAllTokenTypes,
depositSingleTokenTypeExactAmountIn,
withdrawSingleTokenTypeExactAmountOut,
} from './token-swap-test';
async function main() {
@ -19,14 +21,18 @@ async function main() {
await loadPrograms();
console.log('Run test: createTokenSwap');
await createTokenSwap();
console.log('Run test: deposit');
await deposit();
console.log('Run test: withdraw');
await withdraw();
console.log('Run test: deposit all token types');
await depositAllTokenTypes();
console.log('Run test: withdraw all token types');
await withdrawAllTokenTypes();
console.log('Run test: swap');
await swap();
console.log('Run test: create account, approve, swap all at once');
await createAccountAndSwapAtomic();
console.log('Run test: deposit one exact amount in');
await depositSingleTokenTypeExactAmountIn();
console.log('Run test: withrdaw one exact amount out');
await withdrawSingleTokenTypeExactAmountOut();
console.log('Success\n');
}

View File

@ -287,13 +287,19 @@ export async function createTokenSwap(): Promise<void> {
assert(CURVE_TYPE == fetchedTokenSwap.curveType);
}
export async function deposit(): Promise<void> {
export async function depositAllTokenTypes(): Promise<void> {
const poolMintInfo = await tokenPool.getMintInfo();
const supply = poolMintInfo.supply.toNumber();
const swapTokenA = await mintA.getAccountInfo(tokenAccountA);
const tokenA = Math.floor((swapTokenA.amount.toNumber() * POOL_TOKEN_AMOUNT) / (supply + POOL_TOKEN_AMOUNT));
const tokenA = Math.floor(
(swapTokenA.amount.toNumber() * POOL_TOKEN_AMOUNT) /
(supply + POOL_TOKEN_AMOUNT),
);
const swapTokenB = await mintB.getAccountInfo(tokenAccountB);
const tokenB = Math.floor((swapTokenB.amount.toNumber() * POOL_TOKEN_AMOUNT) / (supply + POOL_TOKEN_AMOUNT));
const tokenB = Math.floor(
(swapTokenB.amount.toNumber() * POOL_TOKEN_AMOUNT) /
(supply + POOL_TOKEN_AMOUNT),
);
console.log('Creating depositor token a account');
const userAccountA = await mintA.createAccount(owner.publicKey);
@ -307,7 +313,7 @@ export async function deposit(): Promise<void> {
const newAccountPool = await tokenPool.createAccount(owner.publicKey);
console.log('Depositing into swap');
await tokenSwap.deposit(
await tokenSwap.depositAllTokenTypes(
userAccountA,
userAccountB,
newAccountPool,
@ -331,7 +337,7 @@ export async function deposit(): Promise<void> {
assert(info.amount.toNumber() == POOL_TOKEN_AMOUNT);
}
export async function withdraw(): Promise<void> {
export async function withdrawAllTokenTypes(): Promise<void> {
const poolMintInfo = await tokenPool.getMintInfo();
const supply = poolMintInfo.supply.toNumber();
let swapTokenA = await mintA.getAccountInfo(tokenAccountA);
@ -366,7 +372,7 @@ export async function withdraw(): Promise<void> {
);
console.log('Withdrawing pool tokens for A and B tokens');
await tokenSwap.withdraw(
await tokenSwap.withdrawAllTokenTypes(
userAccountA,
userAccountB,
tokenAccountPool,
@ -463,6 +469,12 @@ export async function createAccountAndSwapAtomic(): Promise<void> {
owner,
newAccount,
);
let info;
info = await mintA.getAccountInfo(tokenAccountA);
currentSwapTokenA = info.amount.toNumber();
info = await mintB.getAccountInfo(tokenAccountB);
currentSwapTokenB = info.amount.toNumber();
}
export async function swap(): Promise<void> {
@ -515,3 +527,163 @@ export async function swap(): Promise<void> {
assert(info.amount.toNumber() == HOST_SWAP_FEE);
}
}
function tradingTokensToPoolTokens(
sourceAmount: number,
swapSourceAmount: number,
poolAmount: number,
): number {
const tradingFee =
(sourceAmount / 2) * (TRADING_FEE_NUMERATOR / TRADING_FEE_DENOMINATOR);
const sourceAmountPostFee = sourceAmount - tradingFee;
const root = Math.sqrt(sourceAmountPostFee / swapSourceAmount + 1);
return Math.floor(poolAmount * (root - 1));
}
export async function depositSingleTokenTypeExactAmountIn(): Promise<void> {
// Pool token amount to deposit on one side
const depositAmount = 10000;
const poolMintInfo = await tokenPool.getMintInfo();
const supply = poolMintInfo.supply.toNumber();
const swapTokenA = await mintA.getAccountInfo(tokenAccountA);
const poolTokenA = tradingTokensToPoolTokens(
depositAmount,
swapTokenA.amount.toNumber(),
supply,
);
const swapTokenB = await mintB.getAccountInfo(tokenAccountB);
const poolTokenB = tradingTokensToPoolTokens(
depositAmount,
swapTokenB.amount.toNumber(),
supply,
);
console.log('Creating depositor token a account');
const userAccountA = await mintA.createAccount(owner.publicKey);
await mintA.mintTo(userAccountA, owner, [], depositAmount);
await mintA.approve(userAccountA, authority, owner, [], depositAmount);
console.log('Creating depositor token b account');
const userAccountB = await mintB.createAccount(owner.publicKey);
await mintB.mintTo(userAccountB, owner, [], depositAmount);
await mintB.approve(userAccountB, authority, owner, [], depositAmount);
console.log('Creating depositor pool token account');
const newAccountPool = await tokenPool.createAccount(owner.publicKey);
console.log('Depositing token A into swap');
await tokenSwap.depositSingleTokenTypeExactAmountIn(
userAccountA,
newAccountPool,
depositAmount,
poolTokenA,
);
let info;
info = await mintA.getAccountInfo(userAccountA);
assert(info.amount.toNumber() == 0);
info = await mintA.getAccountInfo(tokenAccountA);
assert(info.amount.toNumber() == currentSwapTokenA + depositAmount);
currentSwapTokenA += depositAmount;
console.log('Depositing token B into swap');
await tokenSwap.depositSingleTokenTypeExactAmountIn(
userAccountB,
newAccountPool,
depositAmount,
poolTokenB,
);
info = await mintB.getAccountInfo(userAccountB);
assert(info.amount.toNumber() == 0);
info = await mintB.getAccountInfo(tokenAccountB);
assert(info.amount.toNumber() == currentSwapTokenB + depositAmount);
currentSwapTokenB += depositAmount;
info = await tokenPool.getAccountInfo(newAccountPool);
assert(info.amount.toNumber() >= poolTokenA + poolTokenB);
}
export async function withdrawSingleTokenTypeExactAmountOut(): Promise<void> {
// Pool token amount to withdraw on one side
const withdrawAmount = 50000;
const roundingAmount = 1.0001; // make math a little easier
const poolMintInfo = await tokenPool.getMintInfo();
const supply = poolMintInfo.supply.toNumber();
const swapTokenA = await mintA.getAccountInfo(tokenAccountA);
const swapTokenAPost = swapTokenA.amount.toNumber() - withdrawAmount;
const poolTokenA = tradingTokensToPoolTokens(
withdrawAmount,
swapTokenAPost,
supply,
);
let adjustedPoolTokenA = poolTokenA * roundingAmount;
if (OWNER_WITHDRAW_FEE_NUMERATOR !== 0) {
adjustedPoolTokenA *=
1 + OWNER_WITHDRAW_FEE_NUMERATOR / OWNER_WITHDRAW_FEE_DENOMINATOR;
}
const swapTokenB = await mintB.getAccountInfo(tokenAccountB);
const swapTokenBPost = swapTokenB.amount.toNumber() - withdrawAmount;
const poolTokenB = tradingTokensToPoolTokens(
withdrawAmount,
swapTokenBPost,
supply,
);
let adjustedPoolTokenB = poolTokenB * roundingAmount;
if (OWNER_WITHDRAW_FEE_NUMERATOR !== 0) {
adjustedPoolTokenB *=
1 + OWNER_WITHDRAW_FEE_NUMERATOR / OWNER_WITHDRAW_FEE_DENOMINATOR;
}
console.log('Creating withdraw token a account');
const userAccountA = await mintA.createAccount(owner.publicKey);
console.log('Creating withdraw token b account');
const userAccountB = await mintB.createAccount(owner.publicKey);
console.log('Creating withdraw pool token account');
const poolAccount = await tokenPool.getAccountInfo(tokenAccountPool);
const poolTokenAmount = poolAccount.amount.toNumber();
await tokenPool.approve(
tokenAccountPool,
authority,
owner,
[],
adjustedPoolTokenA + adjustedPoolTokenB,
);
console.log('Withdrawing token A only');
await tokenSwap.withdrawSingleTokenTypeExactAmountOut(
userAccountA,
tokenAccountPool,
withdrawAmount,
adjustedPoolTokenA,
);
let info;
info = await mintA.getAccountInfo(userAccountA);
assert(info.amount.toNumber() == withdrawAmount);
info = await mintA.getAccountInfo(tokenAccountA);
assert(info.amount.toNumber() == currentSwapTokenA - withdrawAmount);
currentSwapTokenA += withdrawAmount;
info = await tokenPool.getAccountInfo(tokenAccountPool);
assert(info.amount.toNumber() >= poolTokenAmount - adjustedPoolTokenA);
console.log('Withdrawing token B only');
await tokenSwap.withdrawSingleTokenTypeExactAmountOut(
userAccountB,
tokenAccountPool,
withdrawAmount,
adjustedPoolTokenB,
);
info = await mintB.getAccountInfo(userAccountB);
assert(info.amount.toNumber() == withdrawAmount);
info = await mintB.getAccountInfo(tokenAccountB);
assert(info.amount.toNumber() == currentSwapTokenB - withdrawAmount);
currentSwapTokenB += withdrawAmount;
info = await tokenPool.getAccountInfo(tokenAccountPool);
assert(
info.amount.toNumber() >=
poolTokenAmount - adjustedPoolTokenA - adjustedPoolTokenB,
);
}

View File

@ -638,7 +638,7 @@ export class TokenSwap {
* @param maximumTokenA The maximum amount of token A to deposit
* @param maximumTokenB The maximum amount of token B to deposit
*/
async deposit(
async depositAllTokenTypes(
userAccountA: PublicKey,
userAccountB: PublicKey,
poolAccount: PublicKey,
@ -647,10 +647,10 @@ export class TokenSwap {
maximumTokenB: number | Numberu64,
): Promise<TransactionSignature> {
return await sendAndConfirmTransaction(
'deposit',
'depositAllTokenTypes',
this.connection,
new Transaction().add(
TokenSwap.depositInstruction(
TokenSwap.depositAllTokenTypesInstruction(
this.tokenSwap,
this.authority,
userAccountA,
@ -670,7 +670,7 @@ export class TokenSwap {
);
}
static depositInstruction(
static depositAllTokenTypesInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
sourceA: PublicKey,
@ -731,7 +731,7 @@ export class TokenSwap {
* @param minimumTokenA The minimum amount of token A to withdraw
* @param minimumTokenB The minimum amount of token B to withdraw
*/
async withdraw(
async withdrawAllTokenTypes(
userAccountA: PublicKey,
userAccountB: PublicKey,
poolAccount: PublicKey,
@ -743,7 +743,7 @@ export class TokenSwap {
'withdraw',
this.connection,
new Transaction().add(
TokenSwap.withdrawInstruction(
TokenSwap.withdrawAllTokenTypesInstruction(
this.tokenSwap,
this.authority,
this.poolToken,
@ -764,7 +764,7 @@ export class TokenSwap {
);
}
static withdrawInstruction(
static withdrawAllTokenTypesInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
poolMint: PublicKey,
@ -816,4 +816,176 @@ export class TokenSwap {
data,
});
}
/**
* Deposit one side of tokens into the pool
* @param userAccount User account to deposit token A or B
* @param poolAccount User account to receive pool tokens
* @param sourceTokenAmount The amount of token A or B to deposit
* @param minimumPoolTokenAmount Minimum amount of pool tokens to mint
*/
async depositSingleTokenTypeExactAmountIn(
userAccount: PublicKey,
poolAccount: PublicKey,
sourceTokenAmount: number | Numberu64,
minimumPoolTokenAmount: number | Numberu64,
): Promise<TransactionSignature> {
return await sendAndConfirmTransaction(
'depositSingleTokenTypeExactAmountIn',
this.connection,
new Transaction().add(
TokenSwap.depositSingleTokenTypeExactAmountInInstruction(
this.tokenSwap,
this.authority,
userAccount,
this.tokenAccountA,
this.tokenAccountB,
this.poolToken,
poolAccount,
this.swapProgramId,
this.tokenProgramId,
sourceTokenAmount,
minimumPoolTokenAmount,
),
),
this.payer,
);
}
static depositSingleTokenTypeExactAmountInInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
source: PublicKey,
intoA: PublicKey,
intoB: PublicKey,
poolToken: PublicKey,
poolAccount: PublicKey,
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
sourceTokenAmount: number | Numberu64,
minimumPoolTokenAmount: number | Numberu64,
): TransactionInstruction {
const dataLayout = BufferLayout.struct([
BufferLayout.u8('instruction'),
Layout.uint64('sourceTokenAmount'),
Layout.uint64('minimumPoolTokenAmount'),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: 4, // depositSingleTokenTypeExactAmountIn instruction
sourceTokenAmount: new Numberu64(sourceTokenAmount).toBuffer(),
minimumPoolTokenAmount: new Numberu64(
minimumPoolTokenAmount,
).toBuffer(),
},
data,
);
const keys = [
{pubkey: tokenSwap, isSigner: false, isWritable: false},
{pubkey: authority, isSigner: false, isWritable: false},
{pubkey: source, isSigner: false, isWritable: true},
{pubkey: intoA, isSigner: false, isWritable: true},
{pubkey: intoB, isSigner: false, isWritable: true},
{pubkey: poolToken, isSigner: false, isWritable: true},
{pubkey: poolAccount, isSigner: false, isWritable: true},
{pubkey: tokenProgramId, isSigner: false, isWritable: false},
];
return new TransactionInstruction({
keys,
programId: swapProgramId,
data,
});
}
/**
* Withdraw tokens from the pool
*
* @param userAccount User account to receive token A or B
* @param poolAccount User account to burn pool token
* @param destinationTokenAmount The amount of token A or B to withdraw
* @param maximumPoolTokenAmount Maximum amount of pool tokens to burn
*/
async withdrawSingleTokenTypeExactAmountOut(
userAccount: PublicKey,
poolAccount: PublicKey,
destinationTokenAmount: number | Numberu64,
maximumPoolTokenAmount: number | Numberu64,
): Promise<TransactionSignature> {
return await sendAndConfirmTransaction(
'withdrawSingleTokenTypeExactAmountOut',
this.connection,
new Transaction().add(
TokenSwap.withdrawSingleTokenTypeExactAmountOutInstruction(
this.tokenSwap,
this.authority,
this.poolToken,
this.feeAccount,
poolAccount,
this.tokenAccountA,
this.tokenAccountB,
userAccount,
this.swapProgramId,
this.tokenProgramId,
destinationTokenAmount,
maximumPoolTokenAmount,
),
),
this.payer,
);
}
static withdrawSingleTokenTypeExactAmountOutInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
poolMint: PublicKey,
feeAccount: PublicKey,
sourcePoolAccount: PublicKey,
fromA: PublicKey,
fromB: PublicKey,
userAccount: PublicKey,
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
destinationTokenAmount: number | Numberu64,
maximumPoolTokenAmount: number | Numberu64,
): TransactionInstruction {
const dataLayout = BufferLayout.struct([
BufferLayout.u8('instruction'),
Layout.uint64('destinationTokenAmount'),
Layout.uint64('maximumPoolTokenAmount'),
]);
const data = Buffer.alloc(dataLayout.span);
dataLayout.encode(
{
instruction: 5, // withdrawSingleTokenTypeExactAmountOut instruction
destinationTokenAmount: new Numberu64(
destinationTokenAmount,
).toBuffer(),
maximumPoolTokenAmount: new Numberu64(
maximumPoolTokenAmount,
).toBuffer(),
},
data,
);
const keys = [
{pubkey: 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},
{pubkey: userAccount, isSigner: false, isWritable: true},
{pubkey: feeAccount, isSigner: false, isWritable: true},
{pubkey: tokenProgramId, isSigner: false, isWritable: false},
];
return new TransactionInstruction({
keys,
programId: swapProgramId,
data,
});
}
}

View File

@ -129,7 +129,7 @@ declare module '@solana/spl-token-swap' {
minimumAmountOut: number | Numberu64,
): TransactionInstruction;
deposit(
depositAllTokenTypes(
userAccountA: PublicKey,
userAccountB: PublicKey,
poolAccount: PublicKey,
@ -138,7 +138,7 @@ declare module '@solana/spl-token-swap' {
maximumTokenB: number | Numberu64,
): Promise<TransactionSignature>;
static depositInstruction(
static depositAllTokenTypesInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
sourceA: PublicKey,
@ -154,7 +154,7 @@ declare module '@solana/spl-token-swap' {
maximumTokenB: number | Numberu64,
): TransactionInstruction;
withdraw(
withdrawAllTokenTypes(
userAccountA: PublicKey,
userAccountB: PublicKey,
poolTokenAmount: number | Numberu64,
@ -162,7 +162,7 @@ declare module '@solana/spl-token-swap' {
minimumTokenB: number | Numberu64,
): Promise<TransactionSignature>;
static withdrawInstruction(
static withdrawAllTokenTypesInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
poolMint: PublicKey,
@ -178,5 +178,48 @@ declare module '@solana/spl-token-swap' {
minimumTokenA: number | Numberu64,
minimumTokenB: number | Numberu64,
): TransactionInstruction;
depositSingleTokenTypeExactAmountIn(
userAccount: PublicKey,
poolAccount: PublicKey,
sourceTokenAmount: number | Numberu64,
minimumPoolTokenAmount: number | Numberu64,
): Promise<TransactionSignature>;
static depositSingleTokenTypeExactAmountInInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
source: PublicKey,
intoA: PublicKey,
intoB: PublicKey,
poolToken: PublicKey,
poolAccount: PublicKey,
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
sourceTokenAmount: number | Numberu64,
minimumPoolTokenAmount: number | Numberu64,
): TransactionInstruction;
withdrawSingleTokenTypeExactAmountOut(
userAccount: PublicKey,
poolAccount: PublicKey,
destinationTokenAmount: number | Numberu64,
maximumPoolTokenAmount: number | Numberu64,
): Promise<TransactionSignature>;
static withdrawSingleTokenTypeExactAmountOutInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
poolMint: PublicKey,
feeAccount: PublicKey,
sourcePoolAccount: PublicKey,
fromA: PublicKey,
fromB: PublicKey,
userAccount: PublicKey,
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
destinationTokenAmount: number | Numberu64,
maximumPoolTokenAmount: number | Numberu64,
): TransactionInstruction;
}
}

View File

@ -126,7 +126,7 @@ declare module '@solana/spl-token-swap' {
minimumAmountOut: number | Numberu64,
): TransactionInstruction;
deposit(
depositAllTokenTypes(
userAccountA: PublicKey,
userAccountB: PublicKey,
poolAccount: PublicKey,
@ -135,7 +135,7 @@ declare module '@solana/spl-token-swap' {
maximumTokenB: number | Numberu64,
): Promise<TransactionSignature>;
static depositInstruction(
static depositAllTokenTypesInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
sourceA: PublicKey,
@ -151,7 +151,7 @@ declare module '@solana/spl-token-swap' {
maximumTokenB: number | Numberu64,
): TransactionInstruction;
withdraw(
withdrawAllTokenTypes(
userAccountA: PublicKey,
userAccountB: PublicKey,
poolAccount: PublicKey,
@ -160,7 +160,7 @@ declare module '@solana/spl-token-swap' {
minimumTokenB: number | Numberu64,
): Promise<TransactionSignature>;
static withdrawInstruction(
static withdrawAllTokenTypesInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
poolMint: PublicKey,
@ -176,5 +176,48 @@ declare module '@solana/spl-token-swap' {
minimumTokenA: number | Numberu64,
minimumTokenB: number | Numberu64,
): TransactionInstruction;
depositSingleTokenTypeExactAmountIn(
userAccount: PublicKey,
poolAccount: PublicKey,
sourceTokenAmount: number | Numberu64,
minimumPoolTokenAmount: number | Numberu64,
): Promise<TransactionSignature>;
static depositSingleTokenTypeExactAmountInInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
source: PublicKey,
intoA: PublicKey,
intoB: PublicKey,
poolToken: PublicKey,
poolAccount: PublicKey,
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
sourceTokenAmount: number | Numberu64,
minimumPoolTokenAmount: number | Numberu64,
): TransactionInstruction;
withdrawSingleTokenTypeExactAmountOut(
userAccount: PublicKey,
poolAccount: PublicKey,
destinationTokenAmount: number | Numberu64,
maximumPoolTokenAmount: number | Numberu64,
): Promise<TransactionSignature>;
static withdrawSingleTokenTypeExactAmountOutInstruction(
tokenSwap: PublicKey,
authority: PublicKey,
poolMint: PublicKey,
feeAccount: PublicKey,
sourcePoolAccount: PublicKey,
fromA: PublicKey,
fromB: PublicKey,
userAccount: PublicKey,
swapProgramId: PublicKey,
tokenProgramId: PublicKey,
destinationTokenAmount: number | Numberu64,
maximumPoolTokenAmount: number | Numberu64,
): TransactionInstruction;
}
}

View File

@ -10,7 +10,7 @@ use spl_token_swap::{
fees::Fees,
},
error::SwapError,
instruction::{Deposit, Swap, Withdraw},
instruction::{DepositAllTokenTypes, Swap, WithdrawAllTokenTypes},
};
use spl_token::error::TokenError;
@ -28,17 +28,17 @@ enum FuzzInstruction {
trade_direction: TradeDirection,
instruction: Swap,
},
Deposit {
DepositAllTokenTypes {
token_a_id: AccountId,
token_b_id: AccountId,
pool_token_id: AccountId,
instruction: Deposit,
instruction: DepositAllTokenTypes,
},
Withdraw {
WithdrawAllTokenTypes {
token_a_id: AccountId,
token_b_id: AccountId,
pool_token_id: AccountId,
instruction: Withdraw,
instruction: WithdrawAllTokenTypes,
},
}
@ -206,7 +206,7 @@ fn run_fuzz_instruction(
}
}
}
FuzzInstruction::Deposit {
FuzzInstruction::DepositAllTokenTypes {
token_a_id,
token_b_id,
pool_token_id,
@ -228,7 +228,7 @@ fn run_fuzz_instruction(
instruction,
)
}
FuzzInstruction::Withdraw {
FuzzInstruction::WithdrawAllTokenTypes {
token_a_id,
token_b_id,
pool_token_id,
@ -271,8 +271,12 @@ fn get_total_token_a_amount(fuzz_instructions: &[FuzzInstruction]) -> u64 {
for fuzz_instruction in fuzz_instructions.iter() {
match fuzz_instruction {
FuzzInstruction::Swap { token_a_id, .. } => token_a_ids.insert(token_a_id),
FuzzInstruction::Deposit { token_a_id, .. } => token_a_ids.insert(token_a_id),
FuzzInstruction::Withdraw { token_a_id, .. } => token_a_ids.insert(token_a_id),
FuzzInstruction::DepositAllTokenTypes { token_a_id, .. } => {
token_a_ids.insert(token_a_id)
}
FuzzInstruction::WithdrawAllTokenTypes { token_a_id, .. } => {
token_a_ids.insert(token_a_id)
}
};
}
(token_a_ids.len() as u64) * INITIAL_USER_TOKEN_A_AMOUNT
@ -283,8 +287,12 @@ fn get_total_token_b_amount(fuzz_instructions: &[FuzzInstruction]) -> u64 {
for fuzz_instruction in fuzz_instructions.iter() {
match fuzz_instruction {
FuzzInstruction::Swap { token_b_id, .. } => token_b_ids.insert(token_b_id),
FuzzInstruction::Deposit { token_b_id, .. } => token_b_ids.insert(token_b_id),
FuzzInstruction::Withdraw { token_b_id, .. } => token_b_ids.insert(token_b_id),
FuzzInstruction::DepositAllTokenTypes { token_b_id, .. } => {
token_b_ids.insert(token_b_id)
}
FuzzInstruction::WithdrawAllTokenTypes { token_b_id, .. } => {
token_b_ids.insert(token_b_id)
}
};
}
(token_b_ids.len() as u64) * INITIAL_USER_TOKEN_B_AMOUNT

View File

@ -6,7 +6,7 @@ use crate::native_token;
use spl_token_swap::{
curve::{base::SwapCurve, fees::Fees},
instruction::{self, Deposit, Swap, Withdraw},
instruction::{self, DepositAllTokenTypes, Swap, WithdrawAllTokenTypes},
state::SwapInfo,
};
@ -261,7 +261,7 @@ impl NativeTokenSwap {
token_a_account: &mut NativeAccountData,
token_b_account: &mut NativeAccountData,
pool_account: &mut NativeAccountData,
mut instruction: Deposit,
mut instruction: DepositAllTokenTypes,
) -> ProgramResult {
do_process_instruction(
approve(
@ -341,7 +341,7 @@ impl NativeTokenSwap {
pool_account: &mut NativeAccountData,
token_a_account: &mut NativeAccountData,
token_b_account: &mut NativeAccountData,
mut instruction: Withdraw,
mut instruction: WithdrawAllTokenTypes,
) -> ProgramResult {
let pool_token_amount = native_token::get_token_balance(&pool_account);
// special logic to avoid withdrawing down to 1 pool token, which
@ -408,7 +408,7 @@ impl NativeTokenSwap {
) -> ProgramResult {
let pool_token_amount = native_token::get_token_balance(&pool_account);
if pool_token_amount > 0 {
let instruction = Withdraw {
let instruction = WithdrawAllTokenTypes {
pool_token_amount,
minimum_token_a_amount: 0,
minimum_token_b_amount: 0,

View File

@ -40,11 +40,11 @@ pub struct Swap {
pub minimum_amount_out: u64,
}
/// Deposit instruction data
/// DepositAllTokenTypes instruction data
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub struct Deposit {
pub struct DepositAllTokenTypes {
/// Pool token amount to transfer. token_a and token_b amount are set by
/// the current exchange rate and size of the pool
pub pool_token_amount: u64,
@ -54,11 +54,11 @@ pub struct Deposit {
pub maximum_token_b_amount: u64,
}
/// Withdraw instruction data
/// WithdrawAllTokenTypes instruction data
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub struct Withdraw {
pub struct WithdrawAllTokenTypes {
/// Amount of pool tokens to burn. User receives an output of token a
/// and b based on the percentage of the pool tokens that are returned.
pub pool_token_amount: u64,
@ -68,6 +68,30 @@ pub struct Withdraw {
pub minimum_token_b_amount: u64,
}
/// Deposit one token type, exact amount in instruction data
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub struct DepositSingleTokenTypeExactAmountIn {
/// Token amount to deposit
pub source_token_amount: u64,
/// Pool token amount to receive in exchange. The amount is set by
/// the current exchange rate and size of the pool
pub minimum_pool_token_amount: u64,
}
/// WithdrawAllTokenTypes instruction data
#[cfg_attr(feature = "fuzz", derive(Arbitrary))]
#[repr(C)]
#[derive(Clone, Debug, PartialEq)]
pub struct WithdrawSingleTokenTypeExactAmountOut {
/// Amount of token A or B to receive
pub destination_token_amount: u64,
/// Maximum amount of pool tokens to burn. User receives an output of token A
/// or B based on the percentage of the pool tokens that are returned.
pub maximum_pool_token_amount: u64,
}
/// Instructions supported by the SwapInfo program.
#[repr(C)]
#[derive(Debug, PartialEq)]
@ -100,8 +124,9 @@ pub enum SwapInstruction {
/// 9. `[optional, writable]` Host fee account to receive additional trading fees
Swap(Swap),
/// Deposit some tokens into the pool. The output is a "pool" token representing ownership
/// into the pool. Inputs are converted to the current ratio.
/// Deposit both types of tokens into the pool. The output is a "pool"
/// token representing ownership in the pool. Inputs are converted to
/// the current ratio.
///
/// 0. `[]` Token-swap
/// 1. `[]` $authority
@ -112,9 +137,11 @@ pub enum SwapInstruction {
/// 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
Deposit(Deposit),
DepositAllTokenTypes(DepositAllTokenTypes),
/// Withdraw the token from the pool at the current ratio.
/// Withdraw both types of tokens from the pool at the current ratio, given
/// pool tokens. The pool tokens are burned in exchange for an equivalent
/// amount of token A and B.
///
/// 0. `[]` Token-swap
/// 1. `[]` $authority
@ -126,7 +153,35 @@ pub enum SwapInstruction {
/// 7. `[writable]` token_b user Account to credit.
/// 8. `[writable]` Fee account, to receive withdrawal fees
/// 9. '[]` Token program id
Withdraw(Withdraw),
WithdrawAllTokenTypes(WithdrawAllTokenTypes),
/// Deposit one type of tokens into the pool. The output is a "pool" token
/// representing ownership into the pool. Input token is converted as if
/// a swap and deposit all token types were performed.
///
/// 0. `[]` Token-swap
/// 1. `[]` $authority
/// 2. `[writable]` token_(A|B) SOURCE Account, amount is transferable by $authority,
/// 3. `[writable]` token_a Swap Account, may deposit INTO.
/// 4. `[writable]` token_b Swap Account, may deposit INTO.
/// 5. `[writable]` Pool MINT account, $authority is the owner.
/// 6. `[writable]` Pool Account to deposit the generated tokens, user is the owner.
/// 7. '[]` Token program id
DepositSingleTokenTypeExactAmountIn(DepositSingleTokenTypeExactAmountIn),
/// Withdraw one token type from the pool at the current ratio given the
/// exact amount out expected.
///
/// 0. `[]` Token-swap
/// 1. `[]` $authority
/// 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 potentially withdraw from.
/// 5. `[writable]` token_b Swap Account to potentially withdraw from.
/// 6. `[writable]` token_(A|B) User Account to credit
/// 7. `[writable]` Fee account, to receive withdrawal fees
/// 8. '[]` Token program id
WithdrawSingleTokenTypeExactAmountOut(WithdrawSingleTokenTypeExactAmountOut),
}
impl SwapInstruction {
@ -161,7 +216,7 @@ impl SwapInstruction {
let (pool_token_amount, rest) = Self::unpack_u64(rest)?;
let (maximum_token_a_amount, rest) = Self::unpack_u64(rest)?;
let (maximum_token_b_amount, _rest) = Self::unpack_u64(rest)?;
Self::Deposit(Deposit {
Self::DepositAllTokenTypes(DepositAllTokenTypes {
pool_token_amount,
maximum_token_a_amount,
maximum_token_b_amount,
@ -171,12 +226,28 @@ impl SwapInstruction {
let (pool_token_amount, rest) = Self::unpack_u64(rest)?;
let (minimum_token_a_amount, rest) = Self::unpack_u64(rest)?;
let (minimum_token_b_amount, _rest) = Self::unpack_u64(rest)?;
Self::Withdraw(Withdraw {
Self::WithdrawAllTokenTypes(WithdrawAllTokenTypes {
pool_token_amount,
minimum_token_a_amount,
minimum_token_b_amount,
})
}
4 => {
let (source_token_amount, rest) = Self::unpack_u64(rest)?;
let (minimum_pool_token_amount, _rest) = Self::unpack_u64(rest)?;
Self::DepositSingleTokenTypeExactAmountIn(DepositSingleTokenTypeExactAmountIn {
source_token_amount,
minimum_pool_token_amount,
})
}
5 => {
let (destination_token_amount, rest) = Self::unpack_u64(rest)?;
let (maximum_pool_token_amount, _rest) = Self::unpack_u64(rest)?;
Self::WithdrawSingleTokenTypeExactAmountOut(WithdrawSingleTokenTypeExactAmountOut {
destination_token_amount,
maximum_pool_token_amount,
})
}
_ => return Err(SwapError::InvalidInstruction.into()),
})
}
@ -221,7 +292,7 @@ impl SwapInstruction {
buf.extend_from_slice(&amount_in.to_le_bytes());
buf.extend_from_slice(&minimum_amount_out.to_le_bytes());
}
Self::Deposit(Deposit {
Self::DepositAllTokenTypes(DepositAllTokenTypes {
pool_token_amount,
maximum_token_a_amount,
maximum_token_b_amount,
@ -231,7 +302,7 @@ impl SwapInstruction {
buf.extend_from_slice(&maximum_token_a_amount.to_le_bytes());
buf.extend_from_slice(&maximum_token_b_amount.to_le_bytes());
}
Self::Withdraw(Withdraw {
Self::WithdrawAllTokenTypes(WithdrawAllTokenTypes {
pool_token_amount,
minimum_token_a_amount,
minimum_token_b_amount,
@ -241,6 +312,24 @@ impl SwapInstruction {
buf.extend_from_slice(&minimum_token_a_amount.to_le_bytes());
buf.extend_from_slice(&minimum_token_b_amount.to_le_bytes());
}
Self::DepositSingleTokenTypeExactAmountIn(DepositSingleTokenTypeExactAmountIn {
source_token_amount,
minimum_pool_token_amount,
}) => {
buf.push(4);
buf.extend_from_slice(&source_token_amount.to_le_bytes());
buf.extend_from_slice(&minimum_pool_token_amount.to_le_bytes());
}
Self::WithdrawSingleTokenTypeExactAmountOut(
WithdrawSingleTokenTypeExactAmountOut {
destination_token_amount,
maximum_pool_token_amount,
},
) => {
buf.push(5);
buf.extend_from_slice(&destination_token_amount.to_le_bytes());
buf.extend_from_slice(&maximum_pool_token_amount.to_le_bytes());
}
}
buf
}
@ -298,9 +387,9 @@ pub fn deposit(
swap_token_b_pubkey: &Pubkey,
pool_mint_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
instruction: Deposit,
instruction: DepositAllTokenTypes,
) -> Result<Instruction, ProgramError> {
let data = SwapInstruction::Deposit(instruction).pack();
let data = SwapInstruction::DepositAllTokenTypes(instruction).pack();
let accounts = vec![
AccountMeta::new_readonly(*swap_pubkey, false),
@ -334,9 +423,9 @@ pub fn withdraw(
swap_token_b_pubkey: &Pubkey,
destination_token_a_pubkey: &Pubkey,
destination_token_b_pubkey: &Pubkey,
instruction: Withdraw,
instruction: WithdrawAllTokenTypes,
) -> Result<Instruction, ProgramError> {
let data = SwapInstruction::Withdraw(instruction).pack();
let data = SwapInstruction::WithdrawAllTokenTypes(instruction).pack();
let accounts = vec![
AccountMeta::new_readonly(*swap_pubkey, false),
@ -358,6 +447,74 @@ pub fn withdraw(
})
}
/// Creates a 'deposit_one_exact_in' instruction.
pub fn deposit_one_exact_in(
program_id: &Pubkey,
token_program_id: &Pubkey,
swap_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
source_token_pubkey: &Pubkey,
swap_token_a_pubkey: &Pubkey,
swap_token_b_pubkey: &Pubkey,
pool_mint_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
instruction: DepositSingleTokenTypeExactAmountIn,
) -> Result<Instruction, ProgramError> {
let data = SwapInstruction::DepositSingleTokenTypeExactAmountIn(instruction).pack();
let accounts = vec![
AccountMeta::new_readonly(*swap_pubkey, false),
AccountMeta::new_readonly(*authority_pubkey, false),
AccountMeta::new(*source_token_pubkey, false),
AccountMeta::new(*swap_token_a_pubkey, false),
AccountMeta::new(*swap_token_b_pubkey, false),
AccountMeta::new(*pool_mint_pubkey, false),
AccountMeta::new(*destination_pubkey, false),
AccountMeta::new_readonly(*token_program_id, false),
];
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates a 'withdraw_one_exact_out' instruction.
pub fn withdraw_one_exact_out(
program_id: &Pubkey,
token_program_id: &Pubkey,
swap_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
pool_mint_pubkey: &Pubkey,
fee_account_pubkey: &Pubkey,
pool_token_source_pubkey: &Pubkey,
swap_token_a_pubkey: &Pubkey,
swap_token_b_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
instruction: WithdrawSingleTokenTypeExactAmountOut,
) -> Result<Instruction, ProgramError> {
let data = SwapInstruction::WithdrawSingleTokenTypeExactAmountOut(instruction).pack();
let accounts = vec![
AccountMeta::new_readonly(*swap_pubkey, false),
AccountMeta::new_readonly(*authority_pubkey, false),
AccountMeta::new(*pool_mint_pubkey, false),
AccountMeta::new(*pool_token_source_pubkey, false),
AccountMeta::new(*swap_token_a_pubkey, false),
AccountMeta::new(*swap_token_b_pubkey, false),
AccountMeta::new(*destination_pubkey, false),
AccountMeta::new(*fee_account_pubkey, false),
AccountMeta::new_readonly(*token_program_id, false),
];
Ok(Instruction {
program_id: *program_id,
accounts,
data,
})
}
/// Creates a 'swap' instruction.
pub fn swap(
program_id: &Pubkey,
@ -415,7 +572,7 @@ mod tests {
use crate::curve::{base::CurveType, stable::StableCurve};
#[test]
fn test_instruction_packing() {
fn pack_intialize() {
let trade_fee_numerator: u64 = 1;
let trade_fee_denominator: u64 = 4;
let owner_trade_fee_numerator: u64 = 2;
@ -465,7 +622,10 @@ mod tests {
assert_eq!(packed, expect);
let unpacked = SwapInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
}
#[test]
fn pack_swap() {
let amount_in: u64 = 2;
let minimum_amount_out: u64 = 10;
let check = SwapInstruction::Swap(Swap {
@ -479,11 +639,14 @@ mod tests {
assert_eq!(packed, expect);
let unpacked = SwapInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
}
#[test]
fn pack_deposit() {
let pool_token_amount: u64 = 5;
let maximum_token_a_amount: u64 = 10;
let maximum_token_b_amount: u64 = 20;
let check = SwapInstruction::Deposit(Deposit {
let check = SwapInstruction::DepositAllTokenTypes(DepositAllTokenTypes {
pool_token_amount,
maximum_token_a_amount,
maximum_token_b_amount,
@ -496,11 +659,14 @@ mod tests {
assert_eq!(packed, expect);
let unpacked = SwapInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
}
#[test]
fn pack_withdraw() {
let pool_token_amount: u64 = 1212438012089;
let minimum_token_a_amount: u64 = 102198761982612;
let minimum_token_b_amount: u64 = 2011239855213;
let check = SwapInstruction::Withdraw(Withdraw {
let check = SwapInstruction::WithdrawAllTokenTypes(WithdrawAllTokenTypes {
pool_token_amount,
minimum_token_a_amount,
minimum_token_b_amount,
@ -514,4 +680,42 @@ mod tests {
let unpacked = SwapInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
}
#[test]
fn pack_deposit_one_exact_in() {
let source_token_amount: u64 = 10;
let minimum_pool_token_amount: u64 = 5;
let check = SwapInstruction::DepositSingleTokenTypeExactAmountIn(
DepositSingleTokenTypeExactAmountIn {
source_token_amount,
minimum_pool_token_amount,
},
);
let packed = check.pack();
let mut expect = vec![4];
expect.extend_from_slice(&source_token_amount.to_le_bytes());
expect.extend_from_slice(&minimum_pool_token_amount.to_le_bytes());
assert_eq!(packed, expect);
let unpacked = SwapInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
}
#[test]
fn pack_withdraw_one_exact_out() {
let destination_token_amount: u64 = 102198761982612;
let maximum_pool_token_amount: u64 = 1212438012089;
let check = SwapInstruction::WithdrawSingleTokenTypeExactAmountOut(
WithdrawSingleTokenTypeExactAmountOut {
destination_token_amount,
maximum_pool_token_amount,
},
);
let packed = check.pack();
let mut expect = vec![5];
expect.extend_from_slice(&destination_token_amount.to_le_bytes());
expect.extend_from_slice(&maximum_pool_token_amount.to_le_bytes());
assert_eq!(packed, expect);
let unpacked = SwapInstruction::unpack(&expect).unwrap();
assert_eq!(unpacked, check);
}
}

File diff suppressed because it is too large Load Diff