Skip to content

Commit aad3e9b

Browse files
committed
solana: Add testcase to check burning and locking mode for transfer fee token
1 parent 7d7c6c1 commit aad3e9b

File tree

2 files changed

+469
-0
lines changed

2 files changed

+469
-0
lines changed
+232
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
import * as anchor from "@coral-xyz/anchor";
2+
import * as spl from "@solana/spl-token";
3+
import {
4+
SystemProgram,
5+
Transaction,
6+
sendAndConfirmTransaction,
7+
} from "@solana/web3.js";
8+
import {
9+
AccountAddress,
10+
ChainAddress,
11+
ChainContext,
12+
Signer,
13+
UniversalAddress,
14+
Wormhole,
15+
contracts,
16+
encoding,
17+
} from "@wormhole-foundation/sdk";
18+
import * as testing from "@wormhole-foundation/sdk-definitions/testing";
19+
import {
20+
SolanaPlatform,
21+
getSolanaSignAndSendSigner,
22+
} from "@wormhole-foundation/sdk-solana";
23+
import * as fs from "fs";
24+
import { SolanaNtt } from "../ts/sdk/index.js";
25+
import { handleTestSkip, signSendWait } from "./utils/index.js";
26+
27+
handleTestSkip(__filename);
28+
29+
const solanaRootDir = `${__dirname}/../`;
30+
31+
const CORE_BRIDGE_ADDRESS = contracts.coreBridge("Mainnet", "Solana");
32+
const NTT_ADDRESS = anchor.workspace.ExampleNativeTokenTransfers.programId;
33+
34+
const w = new Wormhole("Devnet", [SolanaPlatform], {
35+
chains: { Solana: { contracts: { coreBridge: CORE_BRIDGE_ADDRESS } } },
36+
});
37+
38+
const remoteXcvr: ChainAddress = {
39+
chain: "Ethereum",
40+
address: new UniversalAddress(
41+
encoding.bytes.encode("transceiver".padStart(32, "\0"))
42+
),
43+
};
44+
const remoteMgr: ChainAddress = {
45+
chain: "Ethereum",
46+
address: new UniversalAddress(
47+
encoding.bytes.encode("nttManager".padStart(32, "\0"))
48+
),
49+
};
50+
51+
const receiver = testing.utils.makeUniversalChainAddress("Ethereum");
52+
53+
const payerSecretKey = Uint8Array.from(
54+
JSON.parse(
55+
fs.readFileSync(`${solanaRootDir}/keys/test.json`, {
56+
encoding: "utf-8",
57+
})
58+
)
59+
);
60+
const payer = anchor.web3.Keypair.fromSecretKey(payerSecretKey);
61+
62+
const connection = new anchor.web3.Connection(
63+
"http://localhost:8899",
64+
"confirmed"
65+
);
66+
67+
// make sure we're using the exact same Connection obj for rpc
68+
const ctx: ChainContext<"Devnet", "Solana"> = w
69+
.getPlatform("Solana")
70+
.getChain("Solana", connection);
71+
72+
const mintAuthority = anchor.web3.Keypair.generate();
73+
const mintKeypair = anchor.web3.Keypair.generate();
74+
const mint = mintKeypair.publicKey;
75+
const transferFeeConfigAuthority = anchor.web3.Keypair.generate();
76+
const withdrawWithheldAuthority = anchor.web3.Keypair.generate();
77+
const decimals = 9;
78+
const feeBasisPoints = 50;
79+
const maxFee = BigInt(5_000);
80+
const mintAmount = BigInt(1_000_000_000);
81+
82+
const transferAmount = 100_000n;
83+
84+
let signer: Signer;
85+
let sender: AccountAddress<"Solana">;
86+
let ntt: SolanaNtt<"Devnet", "Solana">;
87+
let tokenAccount: anchor.web3.PublicKey;
88+
let tokenAddress: string;
89+
90+
const TOKEN_PROGRAM = spl.TOKEN_2022_PROGRAM_ID;
91+
92+
describe("example-native-token-transfers", () => {
93+
describe("Transfer Fee Burning", () => {
94+
beforeAll(async () => {
95+
try {
96+
signer = await getSolanaSignAndSendSigner(connection, payer, {
97+
//debug: true,
98+
});
99+
sender = Wormhole.parseAddress("Solana", signer.address());
100+
101+
// initialize mint
102+
const extensions = [spl.ExtensionType.TransferFeeConfig];
103+
const mintLen = spl.getMintLen(extensions);
104+
const lamports = await connection.getMinimumBalanceForRentExemption(
105+
mintLen
106+
);
107+
const transaction = new Transaction().add(
108+
SystemProgram.createAccount({
109+
fromPubkey: payer.publicKey,
110+
newAccountPubkey: mint,
111+
space: mintLen,
112+
lamports,
113+
programId: TOKEN_PROGRAM,
114+
}),
115+
spl.createInitializeTransferFeeConfigInstruction(
116+
mint,
117+
transferFeeConfigAuthority.publicKey,
118+
withdrawWithheldAuthority.publicKey,
119+
feeBasisPoints,
120+
maxFee,
121+
TOKEN_PROGRAM
122+
),
123+
spl.createInitializeMintInstruction(
124+
mint,
125+
decimals,
126+
mintAuthority.publicKey,
127+
null,
128+
TOKEN_PROGRAM
129+
)
130+
);
131+
await sendAndConfirmTransaction(
132+
connection,
133+
transaction,
134+
[payer, mintKeypair],
135+
undefined
136+
);
137+
138+
// create and fund token account
139+
tokenAccount = await spl.createAccount(
140+
connection,
141+
payer,
142+
mint,
143+
payer.publicKey,
144+
undefined,
145+
undefined,
146+
TOKEN_PROGRAM
147+
);
148+
await spl.mintTo(
149+
connection,
150+
payer,
151+
mint,
152+
tokenAccount,
153+
mintAuthority,
154+
mintAmount,
155+
[],
156+
undefined,
157+
TOKEN_PROGRAM
158+
);
159+
160+
// create our contract client
161+
tokenAddress = mint.toBase58();
162+
ntt = new SolanaNtt("Devnet", "Solana", connection, {
163+
...ctx.config.contracts,
164+
ntt: {
165+
token: tokenAddress,
166+
manager: NTT_ADDRESS,
167+
transceiver: { wormhole: NTT_ADDRESS },
168+
},
169+
});
170+
171+
// transfer mint authority to ntt
172+
await spl.setAuthority(
173+
connection,
174+
payer,
175+
mint,
176+
mintAuthority,
177+
spl.AuthorityType.MintTokens,
178+
ntt.pdas.tokenAuthority(),
179+
[],
180+
undefined,
181+
TOKEN_PROGRAM
182+
);
183+
184+
// init
185+
const initTxs = ntt.initialize(sender, {
186+
mint,
187+
outboundLimit: 100_000_000n,
188+
mode: "burning",
189+
});
190+
await signSendWait(ctx, initTxs, signer);
191+
192+
// register
193+
const registerTxs = ntt.registerTransceiver({
194+
payer,
195+
owner: payer,
196+
transceiver: ntt.program.programId,
197+
});
198+
await signSendWait(ctx, registerTxs, signer);
199+
200+
// set Wormhole xcvr peer
201+
const setXcvrPeerTxs = ntt.setWormholeTransceiverPeer(
202+
remoteXcvr,
203+
sender
204+
);
205+
await signSendWait(ctx, setXcvrPeerTxs, signer);
206+
207+
// set manager peer
208+
const setPeerTxs = ntt.setPeer(remoteMgr, 18, 10_000_000n, sender);
209+
await signSendWait(ctx, setPeerTxs, signer);
210+
} catch (e) {
211+
console.error("Failed to setup peer: ", e);
212+
throw e;
213+
}
214+
});
215+
216+
it("Returns with error", async () => {
217+
// TODO: keep or remove the `outboxItem` param?
218+
// added as a way to keep tests the same but it technically breaks the Ntt interface
219+
const outboxItem = anchor.web3.Keypair.generate();
220+
const xferTxs = ntt.transfer(
221+
sender,
222+
transferAmount,
223+
receiver,
224+
{ queue: false, automatic: false, gasDropoff: 0n },
225+
outboxItem
226+
);
227+
await expect(
228+
signSendWait(ctx, xferTxs, signer, false, true)
229+
).rejects.toThrow();
230+
});
231+
});
232+
});

0 commit comments

Comments
 (0)