Skip to content

Commit f4ed44c

Browse files
committed
token-swap-native: Add withdraw_liquidity
1 parent f3c1559 commit f4ed44c

12 files changed

+336
-15
lines changed

tokens/token-swap/native/program/src/instructions/mod.rs

+3
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,21 @@ pub mod create_amm;
44
pub mod create_pool;
55
pub mod deposit_liquidity;
66
pub mod swap_exact_tokens_for_tokens;
7+
pub mod withdraw_liquidity;
78

89
pub use create_amm::{process_create_amm, CreateAmmArgs};
910
pub use create_pool::{process_create_pool, CreatePoolArgs};
1011
pub use deposit_liquidity::{process_deposit_liquidity, DepositLiquidityArgs};
1112
pub use swap_exact_tokens_for_tokens::{
1213
process_swap_exact_tokens_for_tokens, SwapExactTokensForTokensArgs,
1314
};
15+
pub use withdraw_liquidity::{process_withdraw_liquidity, WithdrawLiquidityArgs};
1416

1517
#[derive(BorshSerialize, BorshDeserialize, Debug)]
1618
pub enum AmmInstruction {
1719
CreateAmm(CreateAmmArgs),
1820
CreatePool(CreatePoolArgs),
1921
DepositLiquidity(DepositLiquidityArgs),
2022
SwapExactTokensForToken(SwapExactTokensForTokensArgs),
23+
WithdrawLiquidity(WithdrawLiquidityArgs),
2124
}

tokens/token-swap/native/program/src/instructions/swap_exact_tokens_for_tokens.rs

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ use fixed::types::I64F64;
33
use solana_program::{
44
account_info::{next_account_info, AccountInfo},
55
entrypoint::ProgramResult,
6-
msg,
76
program::{invoke, invoke_signed},
87
program_error::ProgramError,
98
program_pack::Pack,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
use crate::{constants::MINIMUM_LIQUIDITY, errors::AmmError, state::Pool};
2+
use borsh::{BorshDeserialize, BorshSerialize};
3+
use fixed::types::I64F64;
4+
use solana_program::{
5+
account_info::{next_account_info, AccountInfo},
6+
entrypoint::ProgramResult,
7+
program::{invoke, invoke_signed},
8+
program_error::ProgramError,
9+
program_pack::Pack,
10+
pubkey::Pubkey,
11+
};
12+
use spl_token::{
13+
instruction::{burn, transfer},
14+
state::{Account as TokenAccount, Mint},
15+
};
16+
17+
#[derive(BorshDeserialize, BorshSerialize, Debug)]
18+
pub struct WithdrawLiquidityArgs {
19+
amount: u64,
20+
}
21+
22+
pub fn process_withdraw_liquidity(
23+
program_id: &Pubkey,
24+
accounts: &[AccountInfo],
25+
args: WithdrawLiquidityArgs,
26+
) -> ProgramResult {
27+
let accounts_iter = &mut accounts.iter();
28+
29+
let pool = next_account_info(accounts_iter)?;
30+
let pool_authority = next_account_info(accounts_iter)?;
31+
let depositor = next_account_info(accounts_iter)?;
32+
let mint_liquidity = next_account_info(accounts_iter)?;
33+
let mint_a = next_account_info(accounts_iter)?;
34+
let mint_b = next_account_info(accounts_iter)?;
35+
let pool_account_a = next_account_info(accounts_iter)?;
36+
let pool_account_b = next_account_info(accounts_iter)?;
37+
let depositor_account_liquidity = next_account_info(accounts_iter)?;
38+
let depositor_account_a = next_account_info(accounts_iter)?;
39+
let depositor_account_b = next_account_info(accounts_iter)?;
40+
let token_program = next_account_info(accounts_iter)?;
41+
42+
// Check that the pool corresponds to the target mints
43+
let pool_data = Pool::try_from_slice(&pool.data.borrow())?;
44+
if &pool_data.mint_a != mint_a.key || &pool_data.mint_b != mint_b.key {
45+
return Err(ProgramError::InvalidAccountData);
46+
}
47+
48+
// Verify pool_authority PDA
49+
let pool_authority_seeds = &[
50+
Pool::AUTHORITY_PREFIX.as_ref(),
51+
pool_data.amm.as_ref(),
52+
mint_a.key.as_ref(),
53+
mint_b.key.as_ref(),
54+
];
55+
let (pool_authority_pda, pool_authority_bump) =
56+
Pubkey::find_program_address(pool_authority_seeds, program_id);
57+
if pool_authority.key != &pool_authority_pda {
58+
return Err(AmmError::InvalidAuthority.into());
59+
}
60+
61+
// Transfer tokens from the pool
62+
let pool_token_account_data_a = TokenAccount::unpack(&pool_account_a.data.borrow())?;
63+
let pool_token_account_data_b = TokenAccount::unpack(&pool_account_b.data.borrow())?;
64+
let mint_liquidity_data = Mint::unpack(&mint_liquidity.data.borrow())?;
65+
let amount_a = I64F64::from_num(args.amount)
66+
.checked_mul(I64F64::from_num(pool_token_account_data_a.amount))
67+
.unwrap()
68+
.checked_div(I64F64::from_num(
69+
mint_liquidity_data.supply + MINIMUM_LIQUIDITY,
70+
))
71+
.unwrap()
72+
.floor()
73+
.to_num::<u64>();
74+
75+
invoke_signed(
76+
&transfer(
77+
token_program.key,
78+
pool_account_a.key,
79+
depositor_account_a.key,
80+
pool_authority.key,
81+
&[],
82+
amount_a,
83+
)?,
84+
&[
85+
pool_account_a.clone(),
86+
depositor_account_a.clone(),
87+
pool_authority.clone(),
88+
token_program.clone(),
89+
],
90+
&[&[
91+
Pool::AUTHORITY_PREFIX.as_ref(),
92+
pool_data.amm.as_ref(),
93+
mint_a.key.as_ref(),
94+
mint_b.key.as_ref(),
95+
&[pool_authority_bump],
96+
]],
97+
)?;
98+
99+
let amount_b = I64F64::from_num(args.amount)
100+
.checked_mul(I64F64::from_num(pool_token_account_data_b.amount))
101+
.unwrap()
102+
.checked_div(I64F64::from_num(
103+
mint_liquidity_data.supply + MINIMUM_LIQUIDITY,
104+
))
105+
.unwrap()
106+
.floor()
107+
.to_num::<u64>();
108+
109+
invoke_signed(
110+
&transfer(
111+
token_program.key,
112+
pool_account_b.key,
113+
depositor_account_b.key,
114+
pool_authority.key,
115+
&[],
116+
amount_b,
117+
)?,
118+
&[
119+
pool_account_b.clone(),
120+
depositor_account_b.clone(),
121+
pool_authority.clone(),
122+
token_program.clone(),
123+
],
124+
&[&[
125+
Pool::AUTHORITY_PREFIX.as_ref(),
126+
pool_data.amm.as_ref(),
127+
mint_a.key.as_ref(),
128+
mint_b.key.as_ref(),
129+
&[pool_authority_bump],
130+
]],
131+
)?;
132+
133+
// Burn the liquidity tokens
134+
// It will fail if the amount is invalid
135+
invoke(
136+
&burn(
137+
token_program.key,
138+
depositor_account_liquidity.key,
139+
mint_liquidity.key,
140+
depositor.key,
141+
&[],
142+
args.amount,
143+
)?,
144+
&[
145+
depositor.clone(),
146+
depositor_account_liquidity.clone(),
147+
mint_liquidity.clone(),
148+
token_program.clone(),
149+
],
150+
)?;
151+
152+
Ok(())
153+
}

tokens/token-swap/native/program/src/lib.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use solana_program::{
1010

1111
use crate::instructions::{
1212
process_create_amm, process_create_pool, process_deposit_liquidity,
13-
process_swap_exact_tokens_for_tokens, AmmInstruction,
13+
process_swap_exact_tokens_for_tokens, process_withdraw_liquidity, AmmInstruction,
1414
};
1515

1616
declare_id!("5tS77fBNSDtMSuyBfizp3bdBCcgmVPuLTKzYpZjgoMjq");
@@ -31,5 +31,8 @@ pub fn process_instruction(
3131
AmmInstruction::SwapExactTokensForToken(args) => {
3232
process_swap_exact_tokens_for_tokens(program_id, accounts, args)
3333
}
34+
AmmInstruction::WithdrawLiquidity(args) => {
35+
process_withdraw_liquidity(program_id, accounts, args)
36+
}
3437
}
3538
}

tokens/token-swap/native/tests/create_pool.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { Connection, Keypair, PublicKey, Transaction } from "@solana/web3.js";
22
import { it, beforeEach, describe } from "mocha";
33
import { start } from "solana-bankrun";
4-
import { createCreateAmmInstruction } from "./ts/instructions";
4+
import { createCreateAmmInstruction, createCreatePoolInstruction } from "./ts/instructions";
55
import { createMint } from "spl-token-bankrun";
6-
import { createCreatePoolInstruction } from "./ts/instructions/create_pool";
76
import { Pool } from "./ts/state";
87
import { expect } from "chai";
98
import { getAssociatedTokenAddressSync } from "@solana/spl-token";

tokens/token-swap/native/tests/deposit_liquidity.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
import { Keypair, PublicKey, Transaction } from "@solana/web3.js";
22
import { describe } from "mocha";
33
import { start } from "solana-bankrun";
4-
import { createCreateAmmInstruction } from "./ts/instructions";
4+
import { createCreateAmmInstruction, createCreatePoolInstruction, createDepositLiquidityInstruction } from "./ts/instructions";
55
import { createMint, createAssociatedTokenAccount, mintTo } from "spl-token-bankrun";
66
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
7-
import { createCreatePoolInstruction } from "./ts/instructions/create_pool";
8-
import { createDepositLiquidityInstruction } from "./ts/instructions/deposit_liquidity";
97
import { expect } from "chai";
108
import { getTokenBalance } from "./utils";
119

tokens/token-swap/native/tests/swap_exact_tokens_for_tokens.ts

+1-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import { Keypair, PublicKey, Transaction } from "@solana/web3.js";
22
import { describe } from "mocha";
33
import { start } from "solana-bankrun";
4-
import { createCreateAmmInstruction } from "./ts/instructions";
4+
import { createCreateAmmInstruction, createCreatePoolInstruction, createDepositLiquidityInstruction, createSwapExactTokensForTokensInstruction } from "./ts/instructions";
55
import { createMint, createAssociatedTokenAccount, mintTo } from "spl-token-bankrun";
66
import { getAssociatedTokenAddressSync } from "@solana/spl-token";
7-
import { createCreatePoolInstruction } from "./ts/instructions/create_pool";
8-
import { createDepositLiquidityInstruction } from "./ts/instructions/deposit_liquidity";
9-
import { createSwapExactTokensForTokensInstruction } from "./ts/instructions/swap_exact_tokens_for_tokens";
107
import { getTokenBalance } from "./utils";
118
import { expect } from "chai";
129

tokens/token-swap/native/tests/ts/instructions/create_pool.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ export class CreatePoolArgs {
1313
}
1414

1515
toBuffer() {
16-
return Buffer.from(borsh.serialize(CreateAmmArgsSchema, this));
16+
return Buffer.from(borsh.serialize(CreatePoolArgsSchema, this));
1717
}
1818

1919
static fromBuffer(buffer: Buffer) {
20-
return borsh.deserialize(CreateAmmArgsSchema, CreatePoolArgs, buffer);
20+
return borsh.deserialize(CreatePoolArgsSchema, CreatePoolArgs, buffer);
2121
}
2222

2323
}
2424

25-
export const CreateAmmArgsSchema = new Map([
25+
export const CreatePoolArgsSchema = new Map([
2626
[
2727
CreatePoolArgs,
2828
{

tokens/token-swap/native/tests/ts/instructions/deposit_liquidity.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { SystemProgram, TransactionInstruction } from "@solana/web3.js";
1+
import { TransactionInstruction } from "@solana/web3.js";
22
import { AmmInstruction } from ".";
33
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
44
import * as borsh from 'borsh';
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
export * from './create_amm';
2+
export * from './create_pool';
3+
export * from './deposit_liquidity';
4+
export * from './swap_exact_tokens_for_tokens';
5+
export * from './withdraw_liquidity';
26

37
export enum AmmInstruction {
48
CreateAmm = 0,
59
CreatePool = 1,
610
DepositLiquidity = 2,
711
SwapExactTokensForTokens = 3,
12+
WithdrawLiquidity = 4,
813
}
914

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import { TransactionInstruction } from "@solana/web3.js";
2+
import { AmmInstruction } from ".";
3+
import { TOKEN_PROGRAM_ID } from "@solana/spl-token";
4+
import * as borsh from 'borsh';
5+
6+
export class WithdrawLiquidityArgs {
7+
instruction: AmmInstruction;
8+
amount: number; // u64
9+
10+
constructor(props: { instruction: AmmInstruction; amount: number }) {
11+
this.instruction = props.instruction;
12+
this.amount = props.amount;
13+
}
14+
15+
toBuffer() {
16+
return Buffer.from(borsh.serialize(WithdrawLiquidityArgsSchema, this));
17+
}
18+
}
19+
20+
export const WithdrawLiquidityArgsSchema = new Map([
21+
[
22+
WithdrawLiquidityArgs,
23+
{
24+
kind: 'struct',
25+
fields: [
26+
['instruction', 'u8'],
27+
['amount', 'u64'],
28+
]
29+
}
30+
]
31+
]);
32+
33+
export function createWithdrawLiquidityInstruction(pool, poolAuthority, depositor, mintLiquidity, mintA, mintB, poolAccountA, poolAccountB, depositorAccountLiquidity, depositorAccountA, depositorAccountB, amount, programId) {
34+
const instructionObject = new WithdrawLiquidityArgs({
35+
instruction: AmmInstruction.WithdrawLiquidity,
36+
amount: amount,
37+
});
38+
39+
const ix = new TransactionInstruction({
40+
keys: [
41+
{ pubkey: pool, isSigner: false, isWritable: false },
42+
{ pubkey: poolAuthority, isSigner: false, isWritable: false },
43+
{ pubkey: depositor, isSigner: true, isWritable: false },
44+
{ pubkey: mintLiquidity, isSigner: false, isWritable: true },
45+
{ pubkey: mintA, isSigner: false, isWritable: false },
46+
{ pubkey: mintB, isSigner: false, isWritable: false },
47+
{ pubkey: poolAccountA, isSigner: false, isWritable: true },
48+
{ pubkey: poolAccountB, isSigner: false, isWritable: true },
49+
{ pubkey: depositorAccountLiquidity, isSigner: false, isWritable: true },
50+
{ pubkey: depositorAccountA, isSigner: false, isWritable: true },
51+
{ pubkey: depositorAccountB, isSigner: false, isWritable: true },
52+
{ pubkey: TOKEN_PROGRAM_ID, isSigner: false, isWritable: false },
53+
],
54+
programId: programId,
55+
data: instructionObject.toBuffer(),
56+
})
57+
58+
return ix;
59+
}

0 commit comments

Comments
 (0)