Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit eafe27f

Browse files
authored
token-js: added an e2e test for transferring using a mint with a transfer hook extension (#5138)
* Added extra account resolution for transfer hook extension * Added an e2e test for transferring using a mint with a transfer hook extension * Fixed failing tests * fmt and added gh actions steps needed for spl-transfer-hook-example tests * Fixed failing GH actions build * Fixed failing tests * Incorporated PR review comments and cleanup of code * Cleanup some unused imports
1 parent 7b61198 commit eafe27f

File tree

5 files changed

+142
-13
lines changed

5 files changed

+142
-13
lines changed

.github/workflows/pull-request-token.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,13 @@ jobs:
145145
- name: Build and test transfer hook example
146146
run: ./ci/cargo-test-sbf.sh token/transfer-hook-example
147147

148+
- name: Upload program
149+
uses: actions/upload-artifact@v2
150+
with:
151+
name: spl-transfer-hook-example
152+
path: "target/deploy/*.so"
153+
if-no-files-found: error
154+
148155
cargo-test-sbf-associated-token-account:
149156
runs-on: ubuntu-latest
150157
steps:
@@ -266,6 +273,11 @@ jobs:
266273
with:
267274
name: associated-token-account-program
268275
path: target/deploy
276+
- name: Download spl-transfer-hook-example program
277+
uses: actions/download-artifact@v2
278+
with:
279+
name: spl-transfer-hook-example
280+
path: target/deploy
269281
- run: ./ci/js-test-token.sh
270282

271283
cargo-build-test-cli:

token/js/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
"test": "npm run test:unit && npm run test:e2e-built && npm run test:e2e-native && npm run test:e2e-2022",
4343
"test:unit": "mocha test/unit",
4444
"test:e2e-built": "start-server-and-test 'solana-test-validator --bpf-program TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA ../../target/deploy/spl_token.so --reset --quiet' http://127.0.0.1:8899/health 'mocha test/e2e'",
45-
"test:e2e-2022": "TEST_PROGRAM_ID=TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb start-server-and-test 'solana-test-validator --bpf-program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL ../../target/deploy/spl_associated_token_account.so --bpf-program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb ../../target/deploy/spl_token_2022.so --reset --quiet' http://127.0.0.1:8899/health 'mocha test/e2e*'",
45+
"test:e2e-2022": "TEST_PROGRAM_ID=TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb start-server-and-test 'solana-test-validator --bpf-program ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL ../../target/deploy/spl_associated_token_account.so --bpf-program TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb ../../target/deploy/spl_token_2022.so --bpf-program TokenHookExampLe8smaVNrxTBezWTRbEwxwb1Zykrb ../../target/deploy/spl_transfer_hook_example.so --reset --quiet' http://127.0.0.1:8899/health 'mocha test/e2e*'",
4646
"test:e2e-native": "start-server-and-test 'solana-test-validator --reset --quiet' http://127.0.0.1:8899/health 'mocha test/e2e'",
4747
"test:build-programs": "cargo build-sbf --manifest-path ../program/Cargo.toml && cargo build-sbf --manifest-path ../program-2022/Cargo.toml && cargo build-sbf --manifest-path ../../associated-token-account/program/Cargo.toml",
4848
"deploy": "npm run deploy:docs",

token/js/test/common.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,5 @@ export async function getConnection(): Promise<Connection> {
1919
export const TEST_PROGRAM_ID = process.env.TEST_PROGRAM_ID
2020
? new PublicKey(process.env.TEST_PROGRAM_ID)
2121
: TOKEN_PROGRAM_ID;
22+
23+
export const TRANSFER_HOOK_TEST_PROGRAM_ID = new PublicKey('TokenHookExampLe8smaVNrxTBezWTRbEwxwb1Zykrb');

token/js/test/e2e-2022/transferHook.test.ts

Lines changed: 125 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import chai, { expect } from 'chai';
22
import chaiAsPromised from 'chai-as-promised';
33
chai.use(chaiAsPromised);
44

5-
import type { Connection, Signer } from '@solana/web3.js';
6-
import { PublicKey } from '@solana/web3.js';
5+
import type { AccountMeta, Connection, Signer } from '@solana/web3.js';
6+
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
77
import { sendAndConfirmTransaction, Keypair, SystemProgram, Transaction } from '@solana/web3.js';
88
import {
99
createInitializeMintInstruction,
@@ -15,31 +15,56 @@ import {
1515
updateTransferHook,
1616
AuthorityType,
1717
setAuthority,
18+
createAssociatedTokenAccountInstruction,
19+
getAssociatedTokenAddressSync,
20+
ASSOCIATED_TOKEN_PROGRAM_ID,
21+
createMintToCheckedInstruction,
22+
getExtraAccountMetaAccount,
23+
ExtraAccountMetaListLayout,
24+
ExtraAccountMetaLayout,
25+
transferCheckedWithTransferHook,
26+
createAssociatedTokenAccountIdempotent,
1827
} from '../../src';
19-
import { TEST_PROGRAM_ID, newAccountWithLamports, getConnection } from '../common';
28+
import { TEST_PROGRAM_ID, newAccountWithLamports, getConnection, TRANSFER_HOOK_TEST_PROGRAM_ID } from '../common';
29+
import { createHash } from 'crypto';
2030

2131
const TEST_TOKEN_DECIMALS = 2;
2232
const EXTENSIONS = [ExtensionType.TransferHook];
2333
describe('transferHook', () => {
2434
let connection: Connection;
2535
let payer: Signer;
36+
let payerAta: PublicKey;
37+
let destinationAuthority: PublicKey;
38+
let destinationAta: PublicKey;
2639
let transferHookAuthority: Keypair;
40+
let pdaExtraAccountMeta: PublicKey;
2741
let mint: PublicKey;
28-
let transferHookProgramId: PublicKey;
29-
let newTransferHookProgramId: PublicKey;
3042
before(async () => {
3143
connection = await getConnection();
3244
payer = await newAccountWithLamports(connection, 1000000000);
45+
destinationAuthority = Keypair.generate().publicKey;
3346
transferHookAuthority = Keypair.generate();
34-
transferHookProgramId = Keypair.generate().publicKey;
35-
newTransferHookProgramId = Keypair.generate().publicKey;
3647
});
3748
beforeEach(async () => {
3849
const mintKeypair = Keypair.generate();
3950
mint = mintKeypair.publicKey;
51+
pdaExtraAccountMeta = getExtraAccountMetaAccount(TRANSFER_HOOK_TEST_PROGRAM_ID, mint);
52+
payerAta = getAssociatedTokenAddressSync(
53+
mint,
54+
payer.publicKey,
55+
false,
56+
TEST_PROGRAM_ID,
57+
ASSOCIATED_TOKEN_PROGRAM_ID
58+
);
59+
destinationAta = getAssociatedTokenAddressSync(
60+
mint,
61+
destinationAuthority,
62+
false,
63+
TEST_PROGRAM_ID,
64+
ASSOCIATED_TOKEN_PROGRAM_ID
65+
);
4066
const mintLen = getMintLen(EXTENSIONS);
4167
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen);
42-
4368
const transaction = new Transaction().add(
4469
SystemProgram.createAccount({
4570
fromPubkey: payer.publicKey,
@@ -51,7 +76,7 @@ describe('transferHook', () => {
5176
createInitializeTransferHookInstruction(
5277
mint,
5378
transferHookAuthority.publicKey,
54-
transferHookProgramId,
79+
TRANSFER_HOOK_TEST_PROGRAM_ID,
5580
TEST_PROGRAM_ID
5681
),
5782
createInitializeMintInstruction(mint, TEST_TOKEN_DECIMALS, payer.publicKey, null, TEST_PROGRAM_ID)
@@ -65,10 +90,11 @@ describe('transferHook', () => {
6590
expect(transferHook).to.not.be.null;
6691
if (transferHook !== null) {
6792
expect(transferHook.authority).to.eql(transferHookAuthority.publicKey);
68-
expect(transferHook.programId).to.eql(transferHookProgramId);
93+
expect(transferHook.programId).to.eql(TRANSFER_HOOK_TEST_PROGRAM_ID);
6994
}
7095
});
7196
it('can be updated', async () => {
97+
const newTransferHookProgramId = Keypair.generate().publicKey;
7298
await updateTransferHook(
7399
connection,
74100
payer,
@@ -106,4 +132,93 @@ describe('transferHook', () => {
106132
expect(transferHook.authority).to.eql(PublicKey.default);
107133
}
108134
});
135+
it('transferChecked', async () => {
136+
const extraAccount = Keypair.generate().publicKey;
137+
const keys: AccountMeta[] = [
138+
{ pubkey: pdaExtraAccountMeta, isSigner: false, isWritable: true },
139+
{ pubkey: mint, isSigner: false, isWritable: false },
140+
{ pubkey: payer.publicKey, isSigner: true, isWritable: false },
141+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
142+
];
143+
144+
const data = Buffer.alloc(8 + 4 + ExtraAccountMetaLayout.span);
145+
const discriminator = createHash('sha256')
146+
.update('spl-transfer-hook-interface:initialize-extra-account-metas')
147+
.digest()
148+
.subarray(0, 8);
149+
discriminator.copy(data);
150+
ExtraAccountMetaListLayout.encode(
151+
{
152+
count: 1,
153+
extraAccounts: [
154+
{
155+
discriminator: 0,
156+
addressConfig: extraAccount.toBuffer(),
157+
isSigner: false,
158+
isWritable: false,
159+
},
160+
],
161+
},
162+
data,
163+
8
164+
);
165+
166+
const initExtraAccountMetaInstruction = new TransactionInstruction({
167+
keys,
168+
data,
169+
programId: TRANSFER_HOOK_TEST_PROGRAM_ID,
170+
});
171+
172+
const setupTransaction = new Transaction().add(
173+
initExtraAccountMetaInstruction,
174+
SystemProgram.transfer({
175+
fromPubkey: payer.publicKey,
176+
toPubkey: pdaExtraAccountMeta,
177+
lamports: 10000000,
178+
}),
179+
createAssociatedTokenAccountInstruction(
180+
payer.publicKey,
181+
payerAta,
182+
payer.publicKey,
183+
mint,
184+
TEST_PROGRAM_ID,
185+
ASSOCIATED_TOKEN_PROGRAM_ID
186+
),
187+
createMintToCheckedInstruction(
188+
mint,
189+
payerAta,
190+
payer.publicKey,
191+
5 * 10 ** TEST_TOKEN_DECIMALS,
192+
TEST_TOKEN_DECIMALS,
193+
[],
194+
TEST_PROGRAM_ID
195+
)
196+
);
197+
198+
await sendAndConfirmTransaction(connection, setupTransaction, [payer]);
199+
200+
await createAssociatedTokenAccountIdempotent(
201+
connection,
202+
payer,
203+
mint,
204+
destinationAuthority,
205+
undefined,
206+
TEST_PROGRAM_ID,
207+
ASSOCIATED_TOKEN_PROGRAM_ID
208+
);
209+
210+
await transferCheckedWithTransferHook(
211+
connection,
212+
payer,
213+
payerAta,
214+
mint,
215+
destinationAta,
216+
payer,
217+
BigInt(10 ** TEST_TOKEN_DECIMALS),
218+
TEST_TOKEN_DECIMALS,
219+
[],
220+
undefined,
221+
TEST_PROGRAM_ID
222+
);
223+
});
109224
});

token/js/test/unit/transferHook.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getExtraAccountMetas, resolveExtraAccountMeta } from '../../src';
22
import { expect } from 'chai';
3-
import { AccountMeta, Keypair, PublicKey } from '@solana/web3.js';
3+
import { PublicKey } from '@solana/web3.js';
44

55
describe('transferHookExtraAccounts', () => {
66
const testProgramId = new PublicKey('7N4HggYEJAtCLJdnHGCtFqfxcB5rhQCsQTze3ftYstVj');
@@ -70,7 +70,7 @@ describe('transferHookExtraAccounts', () => {
7070

7171
const extraAccountList = Buffer.concat([
7272
Buffer.from([0, 0, 0, 0, 0, 0, 0, 0]), // u64 accountDiscriminator
73-
Buffer.from([0, 0, 0, 0]), // u32 arrayDiscriminator
73+
Buffer.from([0, 0, 0, 0]), // u32 length
7474
Buffer.from([3, 0, 0, 0]), // u32 count
7575
plainExtraAccount,
7676
pdaExtraAccount,

0 commit comments

Comments
 (0)