Skip to content

Commit dae0830

Browse files
authored
js: Add tests for BurnChecked instruction (#119)
* test(js): add comprehensive test suite for BurnChecked instruction * test: refactor burnChecked suite and apply fmt
1 parent be42014 commit dae0830

1 file changed

Lines changed: 242 additions & 0 deletions

File tree

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
import { appendTransactionMessageInstruction, generateKeyPairSigner, pipe } from '@solana/kit';
2+
import test from 'ava';
3+
import { fetchMint, fetchToken, getApproveInstruction, getBurnCheckedInstruction } from '../src';
4+
import {
5+
createDefaultSolanaClient,
6+
createDefaultTransaction,
7+
createMint,
8+
createTokenWithAmount,
9+
generateKeyPairSignerWithSol,
10+
signAndSendTransaction,
11+
} from './_setup';
12+
13+
test('it burns tokens with correct decimals', async t => {
14+
// Given a mint with 9 decimals and a token account with 200 tokens.
15+
const client = createDefaultSolanaClient();
16+
const [payer, mintAuthority, owner] = await Promise.all([
17+
generateKeyPairSignerWithSol(client),
18+
generateKeyPairSigner(),
19+
generateKeyPairSigner(),
20+
]);
21+
const mint = await createMint(client, payer, mintAuthority.address, 9);
22+
const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 200n);
23+
24+
// When we burn 25 tokens with the correct decimals (9).
25+
const burnChecked = getBurnCheckedInstruction({
26+
account: token,
27+
mint,
28+
authority: owner,
29+
amount: 25n,
30+
decimals: 9,
31+
});
32+
await pipe(
33+
await createDefaultTransaction(client, payer),
34+
tx => appendTransactionMessageInstruction(burnChecked, tx),
35+
tx => signAndSendTransaction(client, tx),
36+
);
37+
38+
const { data: mintData } = await fetchMint(client.rpc, mint);
39+
t.is(mintData.supply, 175n);
40+
41+
// Then we expect the token account to have 175 tokens remaining.
42+
const { data: tokenData } = await fetchToken(client.rpc, token);
43+
t.is(tokenData.amount, 175n);
44+
});
45+
46+
test('it burns tokens using a delegate', async t => {
47+
// Given a token account with 100 tokens and a delegate approved for 60 tokens.
48+
const client = createDefaultSolanaClient();
49+
const [payer, mintAuthority, owner, delegate] = await Promise.all([
50+
generateKeyPairSignerWithSol(client),
51+
generateKeyPairSigner(),
52+
generateKeyPairSigner(),
53+
generateKeyPairSigner(),
54+
]);
55+
const mint = await createMint(client, payer, mintAuthority.address, 9);
56+
const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 100n);
57+
58+
// Approve delegate for 60 tokens.
59+
const approve = getApproveInstruction({
60+
source: token,
61+
delegate: delegate.address,
62+
owner,
63+
amount: 60n,
64+
});
65+
await pipe(
66+
await createDefaultTransaction(client, payer),
67+
tx => appendTransactionMessageInstruction(approve, tx),
68+
tx => signAndSendTransaction(client, tx),
69+
);
70+
71+
// When the delegate burns 30 tokens.
72+
const burnChecked = getBurnCheckedInstruction({
73+
account: token,
74+
mint,
75+
authority: delegate,
76+
amount: 30n,
77+
decimals: 9,
78+
});
79+
await pipe(
80+
await createDefaultTransaction(client, payer),
81+
tx => appendTransactionMessageInstruction(burnChecked, tx),
82+
tx => signAndSendTransaction(client, tx),
83+
);
84+
85+
// Then the token account should have 70 tokens remaining.
86+
const { data: tokenData } = await fetchToken(client.rpc, token);
87+
t.is(tokenData.amount, 70n);
88+
t.is(tokenData.delegatedAmount, 30n); // Remaining delegated amount
89+
});
90+
91+
test('it fails when decimals mismatch', async t => {
92+
// Given a mint with 9 decimals and a token account with tokens.
93+
const client = createDefaultSolanaClient();
94+
const [payer, mintAuthority, owner] = await Promise.all([
95+
generateKeyPairSignerWithSol(client),
96+
generateKeyPairSigner(),
97+
generateKeyPairSigner(),
98+
]);
99+
const mint = await createMint(client, payer, mintAuthority.address, 9);
100+
const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 100n);
101+
102+
// When we try to burn with incorrect decimals (6 instead of 9).
103+
const burnChecked = getBurnCheckedInstruction({
104+
account: token,
105+
mint,
106+
authority: owner,
107+
amount: 40n,
108+
decimals: 6, // Wrong! Should be 9
109+
});
110+
const transactionMessage = await pipe(await createDefaultTransaction(client, payer), tx =>
111+
appendTransactionMessageInstruction(burnChecked, tx),
112+
);
113+
114+
// Then it should fail with MintDecimalsMismatch error.
115+
await t.throwsAsync(signAndSendTransaction(client, transactionMessage));
116+
});
117+
118+
test('it fails when burning more than account balance', async t => {
119+
// Given a token account with only 50 tokens.
120+
const client = createDefaultSolanaClient();
121+
const [payer, mintAuthority, owner] = await Promise.all([
122+
generateKeyPairSignerWithSol(client),
123+
generateKeyPairSigner(),
124+
generateKeyPairSigner(),
125+
]);
126+
const mint = await createMint(client, payer, mintAuthority.address, 9);
127+
const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 50n);
128+
129+
// When we try to burn 150 tokens (more than available).
130+
const burnChecked = getBurnCheckedInstruction({
131+
account: token,
132+
mint,
133+
authority: owner,
134+
amount: 150n,
135+
decimals: 9,
136+
});
137+
const transactionMessage = await pipe(await createDefaultTransaction(client, payer), tx =>
138+
appendTransactionMessageInstruction(burnChecked, tx),
139+
);
140+
141+
// Then it should fail with InsufficientFunds error.
142+
await t.throwsAsync(signAndSendTransaction(client, transactionMessage));
143+
});
144+
145+
test('it fails when authority is not a signer', async t => {
146+
// Given a token account with tokens.
147+
const client = createDefaultSolanaClient();
148+
const [payer, mintAuthority, owner, wrongAuthority] = await Promise.all([
149+
generateKeyPairSignerWithSol(client),
150+
generateKeyPairSigner(),
151+
generateKeyPairSigner(),
152+
generateKeyPairSigner(),
153+
]);
154+
const mint = await createMint(client, payer, mintAuthority.address, 9);
155+
const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 100n);
156+
157+
// When we try to burn with wrong authority (not the owner).
158+
const burnChecked = getBurnCheckedInstruction({
159+
account: token,
160+
mint,
161+
authority: wrongAuthority, // Wrong authority!
162+
amount: 40n,
163+
decimals: 9,
164+
});
165+
const transactionMessage = await pipe(await createDefaultTransaction(client, payer), tx =>
166+
appendTransactionMessageInstruction(burnChecked, tx),
167+
);
168+
169+
// Then it should fail (owner mismatch or missing signature).
170+
await t.throwsAsync(signAndSendTransaction(client, transactionMessage));
171+
});
172+
173+
test('it fails when delegate has insufficient delegated amount', async t => {
174+
// Given a token account with 100 tokens and a delegate approved for only 20 tokens.
175+
const client = createDefaultSolanaClient();
176+
const [payer, mintAuthority, owner, delegate] = await Promise.all([
177+
generateKeyPairSignerWithSol(client),
178+
generateKeyPairSigner(),
179+
generateKeyPairSigner(),
180+
generateKeyPairSigner(),
181+
]);
182+
const mint = await createMint(client, payer, mintAuthority.address, 9);
183+
const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 100n);
184+
185+
// Approve delegate for only 20 tokens.
186+
const approve = getApproveInstruction({
187+
source: token,
188+
delegate: delegate.address,
189+
owner,
190+
amount: 20n,
191+
});
192+
await pipe(
193+
await createDefaultTransaction(client, payer),
194+
tx => appendTransactionMessageInstruction(approve, tx),
195+
tx => signAndSendTransaction(client, tx),
196+
);
197+
198+
// When the delegate tries to burn 50 tokens (more than delegated).
199+
const burnChecked = getBurnCheckedInstruction({
200+
account: token,
201+
mint,
202+
authority: delegate,
203+
amount: 50n,
204+
decimals: 9,
205+
});
206+
const transactionMessage = await pipe(await createDefaultTransaction(client, payer), tx =>
207+
appendTransactionMessageInstruction(burnChecked, tx),
208+
);
209+
210+
// Then it should fail with InsufficientFunds error.
211+
await t.throwsAsync(signAndSendTransaction(client, transactionMessage));
212+
});
213+
214+
test('it burns zero tokens successfully', async t => {
215+
// Given a token account with tokens.
216+
const client = createDefaultSolanaClient();
217+
const [payer, mintAuthority, owner] = await Promise.all([
218+
generateKeyPairSignerWithSol(client),
219+
generateKeyPairSigner(),
220+
generateKeyPairSigner(),
221+
]);
222+
const mint = await createMint(client, payer, mintAuthority.address, 9);
223+
const token = await createTokenWithAmount(client, payer, mintAuthority, mint, owner.address, 100n);
224+
225+
// When we burn 0 tokens (edge case).
226+
const burnChecked = getBurnCheckedInstruction({
227+
account: token,
228+
mint,
229+
authority: owner,
230+
amount: 0n,
231+
decimals: 9,
232+
});
233+
await pipe(
234+
await createDefaultTransaction(client, payer),
235+
tx => appendTransactionMessageInstruction(burnChecked, tx),
236+
tx => signAndSendTransaction(client, tx),
237+
);
238+
239+
// Then the balance should remain unchanged.
240+
const { data: tokenData } = await fetchToken(client.rpc, token);
241+
t.is(tokenData.amount, 100n);
242+
});

0 commit comments

Comments
 (0)