Skip to content

Commit a6fcaea

Browse files
authored
solana: Add Rust test cases to handle tokens with transfer fee (#489)
* solana: Add sdk helper methods to use specified token program * solana: Add test cases for locking and burning tokens with transfer fee
1 parent ef20b45 commit a6fcaea

File tree

5 files changed

+284
-19
lines changed

5 files changed

+284
-19
lines changed

solana/programs/example-native-token-transfers/tests/common/setup.rs

+154-6
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,13 @@ use solana_sdk::{
1515
transaction::Transaction,
1616
};
1717
use spl_associated_token_account::get_associated_token_address_with_program_id;
18-
use spl_token::instruction::AuthorityType;
1918
use wormhole_anchor_sdk::wormhole::{BridgeData, FeeCollector};
2019

2120
use crate::sdk::{
2221
accounts::{Governance, Wormhole, NTT},
2322
instructions::{
2423
admin::{register_transceiver, set_peer, RegisterTransceiver, SetPeer},
25-
initialize::{initialize, Initialize},
24+
initialize::{initialize_with_token_program_id, Initialize},
2625
},
2726
transceivers::wormhole::instructions::admin::{set_transceiver_peer, SetTransceiverPeer},
2827
};
@@ -73,10 +72,33 @@ pub async fn setup_with_extra_accounts(
7372
(ctx, test_data)
7473
}
7574

75+
pub async fn setup_with_extra_accounts_with_transfer_fee(
76+
mode: Mode,
77+
accounts: &[(Pubkey, Account)],
78+
) -> (ProgramTestContext, TestData) {
79+
let program_owner = Keypair::new();
80+
let mut program_test = setup_programs(program_owner.pubkey()).await.unwrap();
81+
82+
for (pubkey, account) in accounts {
83+
program_test.add_account(*pubkey, account.clone());
84+
}
85+
86+
let mut ctx = program_test.start_with_context().await;
87+
88+
let test_data = setup_accounts_with_transfer_fee(&mut ctx, program_owner).await;
89+
setup_ntt_with_token_program_id(&mut ctx, &test_data, mode, &spl_token_2022::id()).await;
90+
91+
(ctx, test_data)
92+
}
93+
7694
pub async fn setup(mode: Mode) -> (ProgramTestContext, TestData) {
7795
setup_with_extra_accounts(mode, &[]).await
7896
}
7997

98+
pub async fn setup_with_transfer_fee(mode: Mode) -> (ProgramTestContext, TestData) {
99+
setup_with_extra_accounts_with_transfer_fee(mode, &[]).await
100+
}
101+
80102
fn prefer_bpf() -> bool {
81103
std::env::var("BPF_OUT_DIR").is_ok() || std::env::var("SBF_OUT_DIR").is_ok()
82104
}
@@ -126,13 +148,22 @@ pub async fn setup_programs(program_owner: Pubkey) -> Result<ProgramTest, Error>
126148
/// Set up test accounts, and mint MINT_AMOUNT to the user's token account
127149
/// Set up the program for locking mode, and registers a peer
128150
pub async fn setup_ntt(ctx: &mut ProgramTestContext, test_data: &TestData, mode: Mode) {
151+
setup_ntt_with_token_program_id(ctx, test_data, mode, &Token::id()).await;
152+
}
153+
154+
pub async fn setup_ntt_with_token_program_id(
155+
ctx: &mut ProgramTestContext,
156+
test_data: &TestData,
157+
mode: Mode,
158+
token_program_id: &Pubkey,
159+
) {
129160
if mode == Mode::Burning {
130161
// we set the mint authority to the ntt contract in burn/mint mode
131-
spl_token::instruction::set_authority(
132-
&spl_token::ID,
162+
spl_token_2022::instruction::set_authority(
163+
token_program_id,
133164
&test_data.mint,
134165
Some(&test_data.ntt.token_authority()),
135-
AuthorityType::MintTokens,
166+
spl_token_2022::instruction::AuthorityType::MintTokens,
136167
&test_data.mint_authority.pubkey(),
137168
&[],
138169
)
@@ -142,7 +173,7 @@ pub async fn setup_ntt(ctx: &mut ProgramTestContext, test_data: &TestData, mode:
142173
.unwrap();
143174
}
144175

145-
initialize(
176+
initialize_with_token_program_id(
146177
&test_data.ntt,
147178
Initialize {
148179
payer: ctx.payer.pubkey(),
@@ -155,6 +186,7 @@ pub async fn setup_ntt(ctx: &mut ProgramTestContext, test_data: &TestData, mode:
155186
limit: OUTBOUND_LIMIT,
156187
mode,
157188
},
189+
token_program_id,
158190
)
159191
.submit_with_signers(&[&test_data.program_owner], ctx)
160192
.await
@@ -265,6 +297,71 @@ pub async fn setup_accounts(ctx: &mut ProgramTestContext, program_owner: Keypair
265297
}
266298
}
267299

300+
pub async fn setup_accounts_with_transfer_fee(
301+
ctx: &mut ProgramTestContext,
302+
program_owner: Keypair,
303+
) -> TestData {
304+
// create mint
305+
let mint = Keypair::new();
306+
let mint_authority = Keypair::new();
307+
308+
let user = Keypair::new();
309+
let payer = ctx.payer.pubkey();
310+
311+
create_mint_with_transfer_fee(ctx, &mint, &mint_authority.pubkey(), 9, 500, 5000)
312+
.await
313+
.submit(ctx)
314+
.await
315+
.unwrap();
316+
317+
// create associated token account for user
318+
let user_token_account = get_associated_token_address_with_program_id(
319+
&user.pubkey(),
320+
&mint.pubkey(),
321+
&spl_token_2022::id(),
322+
);
323+
324+
spl_associated_token_account::instruction::create_associated_token_account(
325+
&payer,
326+
&user.pubkey(),
327+
&mint.pubkey(),
328+
&spl_token_2022::id(),
329+
)
330+
.submit(ctx)
331+
.await
332+
.unwrap();
333+
334+
spl_token_2022::instruction::mint_to(
335+
&spl_token_2022::id(),
336+
&mint.pubkey(),
337+
&user_token_account,
338+
&mint_authority.pubkey(),
339+
&[],
340+
MINT_AMOUNT,
341+
)
342+
.unwrap()
343+
.submit_with_signers(&[&mint_authority], ctx)
344+
.await
345+
.unwrap();
346+
347+
TestData {
348+
ntt: NTT {
349+
program: example_native_token_transfers::ID,
350+
wormhole: Wormhole {
351+
program: wormhole_anchor_sdk::wormhole::program::ID,
352+
},
353+
},
354+
governance: Governance {
355+
program: wormhole_governance::ID,
356+
},
357+
program_owner,
358+
mint_authority,
359+
mint: mint.pubkey(),
360+
user,
361+
user_token_account,
362+
}
363+
}
364+
268365
pub async fn create_mint(
269366
ctx: &mut ProgramTestContext,
270367
mint: &Keypair,
@@ -300,6 +397,57 @@ pub async fn create_mint(
300397
)
301398
}
302399

400+
pub async fn create_mint_with_transfer_fee(
401+
ctx: &mut ProgramTestContext,
402+
mint: &Keypair,
403+
mint_authority: &Pubkey,
404+
decimals: u8,
405+
transfer_fee_basis_points: u16,
406+
maximum_fee: u64,
407+
) -> Transaction {
408+
let rent = ctx.banks_client.get_rent().await.unwrap();
409+
let extension_types = vec![spl_token_2022::extension::ExtensionType::TransferFeeConfig];
410+
let space = spl_token_2022::extension::ExtensionType::try_calculate_account_len::<
411+
spl_token_2022::state::Mint,
412+
>(&extension_types)
413+
.unwrap();
414+
let mint_rent = rent.minimum_balance(space);
415+
416+
let blockhash = ctx.banks_client.get_latest_blockhash().await.unwrap();
417+
418+
Transaction::new_signed_with_payer(
419+
&[
420+
system_instruction::create_account(
421+
&ctx.payer.pubkey(),
422+
&mint.pubkey(),
423+
mint_rent,
424+
space as u64,
425+
&spl_token_2022::id(),
426+
),
427+
spl_token_2022::extension::transfer_fee::instruction::initialize_transfer_fee_config(
428+
&spl_token_2022::id(),
429+
&mint.pubkey(),
430+
None,
431+
None,
432+
transfer_fee_basis_points,
433+
maximum_fee,
434+
)
435+
.unwrap(),
436+
spl_token_2022::instruction::initialize_mint2(
437+
&spl_token_2022::id(),
438+
&mint.pubkey(),
439+
mint_authority,
440+
None,
441+
decimals,
442+
)
443+
.unwrap(),
444+
],
445+
Some(&ctx.payer.pubkey()),
446+
&[&ctx.payer, &mint],
447+
blockhash,
448+
)
449+
}
450+
303451
// TODO: upstream this to solana-program-test
304452

305453
/// Add a SBF program to the test environment. (copied from solana_program_test

solana/programs/example-native-token-transfers/tests/sdk/accounts.rs

+9-1
Original file line numberDiff line numberDiff line change
@@ -182,10 +182,18 @@ impl NTT {
182182
}
183183

184184
pub fn custody(&self, mint: &Pubkey) -> Pubkey {
185+
self.custody_with_token_program_id(mint, &spl_token::ID)
186+
}
187+
188+
pub fn custody_with_token_program_id(
189+
&self,
190+
mint: &Pubkey,
191+
token_program_id: &Pubkey,
192+
) -> Pubkey {
185193
anchor_spl::associated_token::get_associated_token_address_with_program_id(
186194
&self.token_authority(),
187195
mint,
188-
&spl_token::ID,
196+
token_program_id,
189197
)
190198
}
191199

solana/programs/example-native-token-transfers/tests/sdk/instructions/initialize.rs

+11-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,15 @@ pub struct Initialize {
1313
}
1414

1515
pub fn initialize(ntt: &NTT, accounts: Initialize, args: InitializeArgs) -> Instruction {
16+
initialize_with_token_program_id(ntt, accounts, args, &Token::id())
17+
}
18+
19+
pub fn initialize_with_token_program_id(
20+
ntt: &NTT,
21+
accounts: Initialize,
22+
args: InitializeArgs,
23+
token_program_id: &Pubkey,
24+
) -> Instruction {
1625
let data = example_native_token_transfers::instruction::Initialize { args };
1726

1827
let bpf_loader_upgradeable_program = BpfLoaderUpgradeable::id();
@@ -24,8 +33,8 @@ pub fn initialize(ntt: &NTT, accounts: Initialize, args: InitializeArgs) -> Inst
2433
mint: accounts.mint,
2534
rate_limit: ntt.outbox_rate_limit(),
2635
token_authority: ntt.token_authority(),
27-
custody: ntt.custody(&accounts.mint),
28-
token_program: Token::id(),
36+
custody: ntt.custody_with_token_program_id(&accounts.mint, token_program_id),
37+
token_program: *token_program_id,
2938
associated_token_program: AssociatedToken::id(),
3039
bpf_loader_upgradeable_program,
3140
system_program: System::id(),

solana/programs/example-native-token-transfers/tests/sdk/instructions/transfer.rs

+50-8
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,38 @@ pub struct Transfer {
1717
}
1818

1919
pub fn transfer(ntt: &NTT, transfer: Transfer, args: TransferArgs, mode: Mode) -> Instruction {
20+
transfer_with_token_program_id(ntt, transfer, args, mode, &Token::id())
21+
}
22+
23+
pub fn transfer_with_token_program_id(
24+
ntt: &NTT,
25+
transfer: Transfer,
26+
args: TransferArgs,
27+
mode: Mode,
28+
token_program_id: &Pubkey,
29+
) -> Instruction {
2030
match mode {
21-
Mode::Burning => transfer_burn(ntt, transfer, args),
22-
Mode::Locking => transfer_lock(ntt, transfer, args),
31+
Mode::Burning => transfer_burn_with_token_program_id(ntt, transfer, args, token_program_id),
32+
Mode::Locking => transfer_lock_with_token_program_id(ntt, transfer, args, token_program_id),
2333
}
2434
}
2535

2636
pub fn transfer_burn(ntt: &NTT, transfer: Transfer, args: TransferArgs) -> Instruction {
37+
transfer_burn_with_token_program_id(ntt, transfer, args, &Token::id())
38+
}
39+
40+
pub fn transfer_burn_with_token_program_id(
41+
ntt: &NTT,
42+
transfer: Transfer,
43+
args: TransferArgs,
44+
token_program_id: &Pubkey,
45+
) -> Instruction {
2746
let chain_id = args.recipient_chain.id;
2847
let session_authority = ntt.session_authority(&transfer.from_authority, &args);
2948
let data = example_native_token_transfers::instruction::TransferBurn { args };
3049

3150
let accounts = example_native_token_transfers::accounts::TransferBurn {
32-
common: common(ntt, &transfer),
51+
common: common_with_token_program_id(ntt, &transfer, token_program_id),
3352
inbox_rate_limit: ntt.inbox_rate_limit(chain_id),
3453
peer: transfer.peer,
3554
session_authority,
@@ -44,12 +63,21 @@ pub fn transfer_burn(ntt: &NTT, transfer: Transfer, args: TransferArgs) -> Instr
4463
}
4564

4665
pub fn transfer_lock(ntt: &NTT, transfer: Transfer, args: TransferArgs) -> Instruction {
66+
transfer_lock_with_token_program_id(ntt, transfer, args, &Token::id())
67+
}
68+
69+
pub fn transfer_lock_with_token_program_id(
70+
ntt: &NTT,
71+
transfer: Transfer,
72+
args: TransferArgs,
73+
token_program_id: &Pubkey,
74+
) -> Instruction {
4775
let chain_id = args.recipient_chain.id;
4876
let session_authority = ntt.session_authority(&transfer.from_authority, &args);
4977
let data = example_native_token_transfers::instruction::TransferLock { args };
5078

5179
let accounts = example_native_token_transfers::accounts::TransferLock {
52-
common: common(ntt, &transfer),
80+
common: common_with_token_program_id(ntt, &transfer, token_program_id),
5381
inbox_rate_limit: ntt.inbox_rate_limit(chain_id),
5482
peer: transfer.peer,
5583
session_authority,
@@ -66,9 +94,19 @@ pub fn approve_token_authority(
6694
user_token_account: &Pubkey,
6795
user: &Pubkey,
6896
args: &TransferArgs,
97+
) -> Instruction {
98+
approve_token_authority_with_token_program_id(ntt, user_token_account, user, args, &Token::id())
99+
}
100+
101+
pub fn approve_token_authority_with_token_program_id(
102+
ntt: &NTT,
103+
user_token_account: &Pubkey,
104+
user: &Pubkey,
105+
args: &TransferArgs,
106+
token_program_id: &Pubkey,
69107
) -> Instruction {
70108
spl_token_2022::instruction::approve(
71-
&spl_token::id(), // TODO: look into how token account was originally created
109+
token_program_id,
72110
user_token_account,
73111
&ntt.session_authority(user, args),
74112
user,
@@ -78,18 +116,22 @@ pub fn approve_token_authority(
78116
.unwrap()
79117
}
80118

81-
fn common(ntt: &NTT, transfer: &Transfer) -> example_native_token_transfers::accounts::Transfer {
119+
fn common_with_token_program_id(
120+
ntt: &NTT,
121+
transfer: &Transfer,
122+
token_program_id: &Pubkey,
123+
) -> example_native_token_transfers::accounts::Transfer {
82124
example_native_token_transfers::accounts::Transfer {
83125
payer: transfer.payer,
84126
config: NotPausedConfig {
85127
config: ntt.config(),
86128
},
87129
mint: transfer.mint,
88130
from: transfer.from,
89-
token_program: Token::id(),
131+
token_program: *token_program_id,
90132
outbox_item: transfer.outbox_item,
91133
outbox_rate_limit: ntt.outbox_rate_limit(),
92134
system_program: System::id(),
93-
custody: ntt.custody(&transfer.mint),
135+
custody: ntt.custody_with_token_program_id(&transfer.mint, token_program_id),
94136
}
95137
}

0 commit comments

Comments
 (0)