-
Notifications
You must be signed in to change notification settings - Fork 52
/
Copy pathutils.ts
730 lines (631 loc) · 22.7 KB
/
utils.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
import { web3 } from "@coral-xyz/anchor";
import * as spl from "@solana/spl-token";
import { Connection, PublicKey } from "@solana/web3.js";
import {
ChainAddress,
ChainContext,
NativeSigner,
Platform,
Signer,
VAA,
Wormhole,
WormholeMessageId,
amount,
chainToPlatform,
encoding,
keccak256,
serialize,
signSendWait as ssw,
toChainId,
} from "@wormhole-foundation/sdk";
import evm from "@wormhole-foundation/sdk/platforms/evm";
import solana from "@wormhole-foundation/sdk/platforms/solana";
import { ethers } from "ethers";
import { DummyTokenMintAndBurn__factory } from "../evm/ethers-ci-contracts/factories/DummyToken.sol/DummyTokenMintAndBurn__factory.js";
import { DummyToken__factory } from "../evm/ethers-ci-contracts/factories/DummyToken.sol/DummyToken__factory.js";
import { ERC1967Proxy__factory } from "../evm/ethers-ci-contracts/factories/ERC1967Proxy__factory.js";
import { IWormholeRelayer__factory } from "../evm/ethers-ci-contracts/factories/IWormholeRelayer.sol/IWormholeRelayer__factory.js";
import { NttManager__factory } from "../evm/ethers-ci-contracts/factories/NttManager__factory.js";
import { TransceiverStructs__factory } from "../evm/ethers-ci-contracts/factories/TransceiverStructs__factory.js";
import { TrimmedAmountLib__factory } from "../evm/ethers-ci-contracts/factories/TrimmedAmount.sol/TrimmedAmountLib__factory.js";
import { WormholeTransceiver__factory } from "../evm/ethers-ci-contracts/factories/WormholeTransceiver__factory.js";
import solanaTiltKey from "./solana-tilt.json"; // from https://github.com/wormhole-foundation/wormhole/blob/main/solana/keys/solana-devnet.json
import { Ntt } from "../definitions/src/index.js";
import "../evm/src/index.js";
import "../solana/src/index.js";
import { SolanaNtt } from "../solana/src/index.js";
import { submitAccountantVAA } from "./accountant.js";
// Note: Currently, in order for this to run, the evm bindings with extra contracts must be build
// To do that, at the root, run `npm run generate:test`
export const NETWORK: "Devnet" = "Devnet";
const ETH_PRIVATE_KEY =
"0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"; // Ganache default private key
const SOL_PRIVATE_KEY = web3.Keypair.fromSecretKey(
new Uint8Array(solanaTiltKey)
);
type NativeSdkSigner<P extends Platform> = P extends "Evm"
? ethers.Wallet
: P extends "Solana"
? web3.Keypair
: never;
interface Signers<P extends Platform = Platform> {
address: ChainAddress;
signer: Signer;
nativeSigner: NativeSdkSigner<P>;
}
interface StartingCtx {
context: ChainContext<typeof NETWORK>;
mode: Ntt.Mode;
}
export interface Ctx extends StartingCtx {
signers: Signers;
contracts?: Ntt.Contracts;
}
export const wh = new Wormhole(NETWORK, [evm.Platform, solana.Platform], {
...(process.env["CI"]
? {
chains: {
Ethereum: {
contracts: {
relayer: "0xcC680D088586c09c3E0E099a676FA4b6e42467b4",
},
},
Bsc: {
contracts: {
relayer: "0xcC680D088586c09c3E0E099a676FA4b6e42467b4",
},
},
},
}
: {
api: "http://localhost:7071",
chains: {
Ethereum: { rpc: "http://localhost:8545" },
Bsc: { rpc: "http://localhost:8546" },
Solana: { rpc: "http://localhost:8899" },
},
}),
});
export async function deploy(_ctx: StartingCtx): Promise<Ctx> {
const platform = chainToPlatform(_ctx.context!.chain);
const ctx = { ..._ctx, signers: await getSigners(_ctx) };
switch (platform) {
case "Evm":
return deployEvm(ctx);
case "Solana":
return deploySolana(ctx);
default:
throw new Error(
"Unsupported platform " + platform + " (add it to deploy)"
);
}
}
export async function link(chainInfos: Ctx[]) {
console.log("\nStarting linking process");
console.log("========================");
// first submit hub init to accountant
const hub = chainInfos[0]!;
const hubChain = hub.context.chain;
const msgId: WormholeMessageId = {
chain: hubChain,
emitter: Wormhole.chainAddress(
hubChain,
hub.contracts!.transceiver.wormhole
).address.toUniversalAddress(),
sequence: 0n,
};
const vaa = await wh.getVaa(msgId, "Ntt:TransceiverInfo");
await submitAccountantVAA(serialize(vaa!));
// [target, peer, vaa]
const registrations: [string, string, VAA<"Ntt:TransceiverRegistration">][] =
[];
for (const targetInfo of chainInfos) {
const toRegister = chainInfos.filter(
(peerInfo) => peerInfo.context.chain !== targetInfo.context.chain
);
console.log(
"Registering peers for ",
targetInfo.context.chain,
": ",
toRegister.map((x) => x.context.chain)
);
for (const peerInfo of toRegister) {
const vaa = await setupPeer(targetInfo, peerInfo);
if (!vaa) throw new Error("No VAA found");
// Add to registrations by PEER chain so we can register hub first
registrations.push([
targetInfo.context.chain,
peerInfo.context.chain,
vaa,
]);
}
}
// Submit Hub to Spoke registrations
const hubToSpokeRegistrations = registrations.filter(
([_, peer]) => peer === hubChain
);
for (const [, , vaa] of hubToSpokeRegistrations) {
console.log(
"Submitting hub to spoke registrations: ",
vaa.emitterChain,
vaa.payload.chain,
vaa.payload.transceiver.toString()
);
await submitAccountantVAA(serialize(vaa));
}
// Submit Spoke to Hub registrations
const spokeToHubRegistrations = registrations.filter(
([target, _]) => target === hubChain
);
for (const [, , vaa] of spokeToHubRegistrations) {
console.log(
"Submitting spoke to hub registrations: ",
vaa.emitterChain,
vaa.payload.chain,
vaa.payload.transceiver.toString()
);
await submitAccountantVAA(serialize(vaa));
}
// Submit all other registrations
const spokeToSpokeRegistrations = registrations.filter(
([target, peer]) => target !== hubChain && peer !== hubChain
);
for (const [, , vaa] of spokeToSpokeRegistrations) {
console.log(
"Submitting spoke to spoke registrations: ",
vaa.emitterChain,
vaa.payload.chain,
vaa.payload.transceiver.toString()
);
await submitAccountantVAA(serialize(vaa));
}
}
export async function transferWithChecks(sourceCtx: Ctx, destinationCtx: Ctx) {
const sendAmt = "0.01";
const srcAmt = amount.units(
amount.parse(sendAmt, sourceCtx.context.config.nativeTokenDecimals)
);
const dstAmt = amount.units(
amount.parse(sendAmt, destinationCtx.context.config.nativeTokenDecimals)
);
const [managerBalanceBeforeSend, userBalanceBeforeSend] =
await getManagerAndUserBalance(sourceCtx);
const [managerBalanceBeforeRecv, userBalanceBeforeRecv] =
await getManagerAndUserBalance(destinationCtx);
const { signer: srcSigner } = sourceCtx.signers;
const { signer: dstSigner } = destinationCtx.signers;
const sender = Wormhole.chainAddress(srcSigner.chain(), srcSigner.address());
const receiver = Wormhole.chainAddress(
dstSigner.chain(),
dstSigner.address()
);
const useRelayer =
chainToPlatform(sourceCtx.context.chain) === "Evm" &&
chainToPlatform(destinationCtx.context.chain) === "Evm";
console.log("Calling transfer on: ", sourceCtx.context.chain);
const srcNtt = await getNtt(sourceCtx);
const transferTxs = srcNtt.transfer(sender.address, srcAmt, receiver, {
queue: false,
automatic: useRelayer,
gasDropoff: 0n,
});
const txids = await signSendWait(sourceCtx.context, transferTxs, srcSigner);
const srcCore = await sourceCtx.context.getWormholeCore();
const msgId = (
await srcCore.parseTransaction(txids[txids.length - 1]!.txid)
)[0]!;
if (!useRelayer) await receive(msgId, destinationCtx);
else await waitForRelay(msgId, destinationCtx);
const [managerBalanceAfterSend, userBalanceAfterSend] =
await getManagerAndUserBalance(sourceCtx);
const [managerBalanceAfterRecv, userBalanceAfterRecv] =
await getManagerAndUserBalance(destinationCtx);
checkBalances(
sourceCtx.mode,
[managerBalanceBeforeSend, managerBalanceAfterSend],
[userBalanceBeforeSend, userBalanceAfterSend],
srcAmt
);
checkBalances(
destinationCtx.mode,
[managerBalanceBeforeRecv, managerBalanceAfterRecv],
[userBalanceBeforeRecv, userBalanceAfterRecv],
-dstAmt
);
}
async function waitForRelay(
msgId: WormholeMessageId,
dst: Ctx,
retryTime: number = 2000
) {
console.log("Sleeping for 1 min to allow signing of VAA");
await new Promise((resolve) => setTimeout(resolve, 60 * 1000));
// long timeout because the relayer has consistency level set to 15
const vaa = await wh.getVaa(msgId, "Uint8Array", 2 * 60 * 1000);
const deliveryHash = keccak256(vaa!.hash);
const wormholeRelayer = IWormholeRelayer__factory.connect(
dst.context.config.contracts.relayer!,
await dst.context.getRpc()
);
let success = false;
while (!success) {
try {
const successBlock = await wormholeRelayer.deliverySuccessBlock(
deliveryHash
);
if (successBlock > 0) success = true;
console.log("Relayer delivery: ", success);
} catch (e) {
console.error(e);
}
await new Promise((resolve) => setTimeout(resolve, retryTime));
}
}
// Wrap signSendWait from sdk to provide full error message
async function signSendWait(
ctx: ChainContext<typeof NETWORK>,
txs: any,
signer: Signer
) {
try {
return await ssw(ctx, txs, signer);
} catch (e) {
console.error(e);
throw e;
}
}
async function getNtt(
ctx: Ctx
): Promise<Ntt<typeof NETWORK, typeof ctx.context.chain>> {
return ctx.context.getProtocol("Ntt", { ntt: ctx.contracts });
}
function getNativeSigner(ctx: Partial<Ctx>): any {
const platform = chainToPlatform(ctx.context!.chain);
switch (platform) {
case "Evm":
return ETH_PRIVATE_KEY;
case "Solana":
return SOL_PRIVATE_KEY;
default:
throw "Unsupported platform " + platform + " (add it to getNativeSigner)";
}
}
async function getSigners(ctx: Partial<Ctx>): Promise<Signers> {
const platform = chainToPlatform(ctx.context!.chain);
let nativeSigner = getNativeSigner(ctx);
const rpc = await ctx.context?.getRpc();
let signer: Signer;
switch (platform) {
case "Evm":
signer = await evm.getSigner(rpc, nativeSigner);
nativeSigner = (signer as NativeSigner).unwrap();
break;
case "Solana":
signer = await solana.getSigner(rpc, nativeSigner);
break;
default:
throw new Error(
"Unsupported platform " + platform + " (add it to getSigner)"
);
}
return {
nativeSigner: nativeSigner,
signer: signer,
address: Wormhole.chainAddress(signer.chain(), signer.address()),
};
}
async function deployEvm(ctx: Ctx): Promise<Ctx> {
const { signer, nativeSigner: wallet } = ctx.signers as Signers<"Evm">;
// Deploy libraries used by various things
console.log("Deploying transceiverStructs");
const transceiverStructsFactory = new TransceiverStructs__factory(wallet);
const transceiverStructsContract = await transceiverStructsFactory.deploy();
await transceiverStructsContract.waitForDeployment();
console.log("Deploying trimmed amount");
const trimmedAmountFactory = new TrimmedAmountLib__factory(wallet);
const trimmedAmountContract = await trimmedAmountFactory.deploy();
await trimmedAmountContract.waitForDeployment();
console.log("Deploying dummy token");
// Deploy the NTT token
const NTTAddress = await new (ctx.mode === "locking"
? DummyToken__factory
: DummyTokenMintAndBurn__factory)(wallet).deploy();
await NTTAddress.waitForDeployment();
if (ctx.mode === "locking") {
await tryAndWaitThrice(() =>
NTTAddress.mintDummy(
signer.address(),
amount.units(amount.parse("100", 18))
)
);
}
const transceiverStructsAddress =
await transceiverStructsContract.getAddress();
const trimmedAmountAddress = await trimmedAmountContract.getAddress();
const ERC20NTTAddress = await NTTAddress.getAddress();
const myObj = {
"src/libraries/TransceiverStructs.sol:TransceiverStructs":
transceiverStructsAddress,
"src/libraries/TrimmedAmount.sol:TrimmedAmountLib": trimmedAmountAddress,
};
const chainId = toChainId(ctx.context.chain);
// https://github.com/search?q=repo%3Awormhole-foundation%2Fwormhole-connect%20__factory&type=code
// https://github.com/wormhole-foundation/wormhole/blob/00f504ef452ae2d94fa0024c026be2d8cf903ad5/clients/js/src/evm.ts#L335
console.log("Deploying manager implementation");
const wormholeManager = new NttManager__factory(myObj, wallet);
const managerAddress = await wormholeManager.deploy(
ERC20NTTAddress, // Token address
ctx.mode === "locking" ? 0 : 1, // Lock
chainId, // chain id
0, // Locking time
true
);
await managerAddress.waitForDeployment();
console.log("Deploying manager proxy");
const ERC1967ProxyFactory = new ERC1967Proxy__factory(wallet);
const managerProxyAddress = await ERC1967ProxyFactory.deploy(
await managerAddress.getAddress(),
"0x"
);
await managerProxyAddress.waitForDeployment();
// After we've deployed the proxy AND the manager then connect to the proxy with the interface of the manager.
const manager = NttManager__factory.connect(
await managerProxyAddress.getAddress(),
wallet
);
console.log("Deploy transceiver implementation");
const WormholeTransceiverFactory = new WormholeTransceiver__factory(
myObj,
wallet
);
const WormholeTransceiverAddress = await WormholeTransceiverFactory.deploy(
// List of useful wormhole contracts - https://github.com/wormhole-foundation/wormhole/blob/00f504ef452ae2d94fa0024c026be2d8cf903ad5/ethereum/ts-scripts/relayer/config/ci/contracts.json
await manager.getAddress(),
ctx.context.config.contracts.coreBridge!, // Core wormhole contract - https://docs.wormhole.com/wormhole/blockchain-environments/evm#local-network-contract -- may need to be changed to support other chains
ctx.context.config.contracts.relayer!, // Relayer contract -- double check these...https://github.com/wormhole-foundation/wormhole/blob/main/sdk/js/src/relayer/__tests__/wormhole_relayer.ts
"0x0000000000000000000000000000000000000000", // TODO - Specialized relayer??????
200, // Consistency level
500000n // Gas limit
);
await WormholeTransceiverAddress.waitForDeployment();
// // Setup with the proxy
console.log("Deploy transceiver proxy");
const transceiverProxyFactory = new ERC1967Proxy__factory(wallet);
const transceiverProxyDeployment = await transceiverProxyFactory.deploy(
await WormholeTransceiverAddress.getAddress(),
"0x"
);
await transceiverProxyDeployment.waitForDeployment();
const transceiverProxyAddress = await transceiverProxyDeployment.getAddress();
const transceiver = WormholeTransceiver__factory.connect(
transceiverProxyAddress,
wallet
);
// initialize() on both the manager and transceiver
console.log("Initialize the manager");
await tryAndWaitThrice(() => manager.initialize());
console.log("Initialize the transceiver");
await tryAndWaitThrice(() => transceiver.initialize());
// Setup the initial calls, like transceivers for the manager
console.log("Set transceiver for manager");
await tryAndWaitThrice(() => manager.setTransceiver(transceiverProxyAddress));
console.log("Set outbound limit for manager");
await tryAndWaitThrice(() =>
manager.setOutboundLimit(amount.units(amount.parse("10000", 18)))
);
return {
...ctx,
contracts: {
transceiver: {
wormhole: transceiverProxyAddress,
},
manager: await managerProxyAddress.getAddress(),
token: ERC20NTTAddress,
},
};
}
async function deploySolana(ctx: Ctx): Promise<Ctx> {
const { signer, nativeSigner: keypair } = ctx.signers as Signers<"Solana">;
const connection = (await ctx.context.getRpc()) as Connection;
const address = new PublicKey(signer.address());
console.log(`Using public key: ${address}`);
const mint = await spl.createMint(connection, keypair, address, null, 9);
console.log("Created mint", mint.toString());
const tokenAccount = await spl.createAssociatedTokenAccount(
connection,
keypair,
mint,
address
);
console.log("Created token account", tokenAccount.toString());
if (ctx.mode === "locking") {
const amt = amount.units(amount.parse("100", 9));
await spl.mintTo(connection, keypair, mint, tokenAccount, keypair, amt);
console.log(`Minted ${amt} tokens`);
}
const managerProgramId =
ctx.mode === "locking"
? "NTTManager222222222222222222222222222222222"
: "NTTManager111111111111111111111111111111111";
ctx.contracts = {
token: mint.toBase58(),
manager: managerProgramId,
transceiver: {
wormhole: managerProgramId,
},
};
const manager = (await getNtt(ctx)) as SolanaNtt<typeof NETWORK, "Solana">;
// Check to see if already deployed, dirty env
const mgrProgram = await connection.getAccountInfo(
new PublicKey(manager.pdas.configAccount())
);
if (!mgrProgram || mgrProgram.data.length === 0) {
await spl.setAuthority(
connection,
keypair,
mint,
keypair,
0,
manager.pdas.tokenAuthority()
);
console.log(
"Set token authority to",
manager.pdas.tokenAuthority().toString()
);
const initTxs = manager.initialize({
payer: keypair,
owner: keypair,
chain: "Solana",
mint,
outboundLimit: 1000000000n,
mode: ctx.mode,
});
await signSendWait(ctx.context, initTxs, signer);
console.log("Initialized ntt at", manager.program.programId.toString());
// NOTE: this is a hack. The next instruction will fail if we don't wait
// here, because the address lookup table is not yet available, despite
// the transaction having been confirmed.
// Looks like a bug, but I haven't investigated further. In practice, this
// won't be an issue, becase the address lookup table will have been
// created well before anyone is trying to use it, but we might want to be
// mindful in the deploy script too.
await new Promise((resolve) => setTimeout(resolve, 400));
const registrTxs = manager.registerTransceiver({
payer: keypair,
owner: keypair,
transceiver: manager.program.programId,
});
await signSendWait(ctx.context, registrTxs, signer);
console.log("Registered transceiver with self");
}
return {
...ctx,
contracts: {
transceiver: {
wormhole: manager.pdas.emitterAccount().toString(),
},
manager: manager.program.programId.toString(),
token: mint.toString(),
},
};
}
async function setupPeer(targetCtx: Ctx, peerCtx: Ctx) {
const target = targetCtx.context;
const peer = peerCtx.context;
const {
manager,
transceiver: { wormhole: transceiver },
} = peerCtx.contracts!;
const peerManager = Wormhole.chainAddress(peer.chain, manager);
const peerTransceiver = Wormhole.chainAddress(peer.chain, transceiver);
const tokenDecimals = target.config.nativeTokenDecimals;
const inboundLimit = amount.units(amount.parse("1000", tokenDecimals));
const { signer, address: sender } = targetCtx.signers;
const nttManager = await getNtt(targetCtx);
const setPeerTxs = nttManager.setPeer(
peerManager,
tokenDecimals,
inboundLimit,
sender.address
);
await signSendWait(target, setPeerTxs, signer);
const setXcvrPeerTxs = nttManager.setWormholeTransceiverPeer(
peerTransceiver,
sender.address
);
const xcvrPeerTxids = await signSendWait(target, setXcvrPeerTxs, signer);
const [whm] = await target.parseTransaction(xcvrPeerTxids[0]!.txid);
console.log("Set peers for: ", target.chain, peer.chain);
if (
chainToPlatform(target.chain) === "Evm" &&
chainToPlatform(peer.chain) === "Evm"
) {
const nativeSigner = (signer as NativeSigner).unwrap();
const xcvr = WormholeTransceiver__factory.connect(
targetCtx.contracts!.transceiver.wormhole,
nativeSigner.signer
);
const peerChainId = toChainId(peer.chain);
console.log("Setting isEvmChain for: ", peer.chain);
await tryAndWaitThrice(() =>
xcvr.setIsWormholeEvmChain.send(peerChainId, true)
);
console.log("Setting wormhole relaying for: ", peer.chain);
await tryAndWaitThrice(() =>
xcvr.setIsWormholeRelayingEnabled.send(peerChainId, true)
);
}
return await wh.getVaa(whm!, "Ntt:TransceiverRegistration");
}
async function receive(msgId: WormholeMessageId, destination: Ctx) {
const { signer, address: sender } = destination.signers;
console.log(
`Fetching VAA ${toChainId(msgId.chain)}/${encoding.hex.encode(
msgId.emitter.toUint8Array(),
false
)}/${msgId.sequence}`
);
const _vaa = await wh.getVaa(msgId, "Ntt:WormholeTransfer");
console.log("Calling redeem on: ", destination.context.chain);
const ntt = await getNtt(destination);
const redeemTxs = ntt.redeem([_vaa!], sender.address);
await signSendWait(destination.context, redeemTxs, signer);
}
async function getManagerAndUserBalance(ctx: Ctx): Promise<[bigint, bigint]> {
const chain = ctx.context;
const contracts = ctx.contracts!;
const tokenAddress = Wormhole.parseAddress(chain.chain, contracts.token);
const ntt = await getNtt(ctx);
const managerAddress = await ntt.getCustodyAddress();
const { address } = ctx.signers;
const accountAddress = address.address.toString();
const [mbal, abal] = await Promise.all([
chain.getBalance(managerAddress, tokenAddress),
chain.getBalance(accountAddress, tokenAddress),
]);
return [mbal ?? 0n, abal ?? 0n];
}
function checkBalances(
mode: Ntt.Mode,
managerBalances: [bigint, bigint],
userBalances: [bigint, bigint],
check: bigint
) {
console.log(mode, managerBalances, userBalances, check);
const [managerBefore, managerAfter] = managerBalances;
if (
mode === "burning"
? !(managerAfter === 0n)
: !(managerAfter === managerBefore + check)
) {
throw new Error(
`Source manager amount incorrect: before ${managerBefore.toString()}, after ${managerAfter.toString()}`
);
}
const [userBefore, userAfter] = userBalances;
if (!(userAfter == userBefore - check)) {
throw new Error(
`Source user amount incorrect: before ${userBefore.toString()}, after ${userAfter.toString()}`
);
}
}
async function tryAndWaitThrice(
txGen: () => Promise<ethers.ContractTransactionResponse>
): Promise<ethers.ContractTransactionReceipt | null> {
// these tests have some issue with getting a nonce mismatch despite everything being awaited
let attempts = 0;
while (attempts < 3) {
try {
return await (await txGen()).wait();
} catch (e) {
console.error(e);
attempts++;
if (attempts < 3) {
console.log(`retry ${attempts}...`);
} else {
throw e;
}
}
}
return null;
}