1
- import {
2
- importCoreWasm ,
3
- ixFromRust ,
4
- setDefaultWasm ,
5
- utils as wormholeUtils ,
6
- } from "@certusone/wormhole-sdk" ;
1
+ import { ixFromRust , setDefaultWasm } from "@certusone/wormhole-sdk" ;
7
2
import * as anchor from "@project-serum/anchor" ;
8
3
import NodeWallet from "@project-serum/anchor/dist/cjs/nodewallet" ;
9
4
import {
@@ -15,14 +10,24 @@ import {
15
10
TransactionInstruction ,
16
11
} from "@solana/web3.js" ;
17
12
import Squads from "@sqds/mesh" ;
18
- import { getIxAuthorityPDA , getIxPDA , getMsPDA } from "@sqds/mesh" ;
13
+ import { getIxAuthorityPDA } from "@sqds/mesh" ;
19
14
import { InstructionAccount } from "@sqds/mesh/lib/types" ;
20
15
import bs58 from "bs58" ;
21
16
import { program } from "commander" ;
22
17
import * as fs from "fs" ;
23
18
import { LedgerNodeWallet } from "./wallet" ;
24
19
import lodash from "lodash" ;
25
- import { getActiveProposals , getProposalInstructions } from "./multisig" ;
20
+ import {
21
+ getActiveProposals ,
22
+ getManyProposalsInstructions ,
23
+ getProposalInstructions ,
24
+ } from "./multisig" ;
25
+ import {
26
+ WormholeNetwork ,
27
+ loadWormholeTools ,
28
+ WormholeTools ,
29
+ parse ,
30
+ } from "./wormhole" ;
26
31
27
32
setDefaultWasm ( "node" ) ;
28
33
@@ -40,16 +45,15 @@ setDefaultWasm("node");
40
45
//
41
46
// - "localdevnet" - always means the Tilt devnet
42
47
43
- type Cluster = "devnet" | "mainnet" | "localdevnet" ;
44
- type WormholeNetwork = "TESTNET" | "MAINNET" | "DEVNET" ;
48
+ export type Cluster = "devnet" | "mainnet" | "localdevnet" ;
45
49
46
50
type Config = {
47
51
wormholeClusterName : WormholeNetwork ;
48
52
wormholeRpcEndpoint : string ;
49
53
vault : PublicKey ;
50
54
} ;
51
55
52
- const CONFIG : Record < Cluster , Config > = {
56
+ export const CONFIG : Record < Cluster , Config > = {
53
57
devnet : {
54
58
wormholeClusterName : "TESTNET" ,
55
59
vault : new PublicKey ( "6baWtW1zTUVMSJHJQVxDUXWzqrQeYBr6mu31j3bTKwY3" ) ,
@@ -162,6 +166,7 @@ program
162
166
"keys/key.json"
163
167
)
164
168
. option ( "-p, --payload <hex-string>" , "payload to sign" , "0xdeadbeef" )
169
+ . option ( "-s, --skip-duplicate-check" , "Skip checking duplicates" )
165
170
. action ( async ( options ) => {
166
171
const cluster : Cluster = options . cluster ;
167
172
const squad = await getSquadsClient (
@@ -171,11 +176,49 @@ program
171
176
options . ledgerDerivationChange ,
172
177
options . wallet
173
178
) ;
179
+ const wormholeTools = await loadWormholeTools ( cluster , squad . connection ) ;
180
+
181
+ if ( ! options . skipDuplicateCheck ) {
182
+ const activeProposals = await getActiveProposals (
183
+ squad ,
184
+ CONFIG [ cluster ] . vault
185
+ ) ;
186
+ const activeInstructions = await getManyProposalsInstructions (
187
+ squad ,
188
+ activeProposals
189
+ ) ;
190
+
191
+ const msAccount = await squad . getMultisig ( CONFIG [ cluster ] . vault ) ;
192
+ const emitter = squad . getAuthorityPDA (
193
+ msAccount . publicKey ,
194
+ msAccount . authorityIndex
195
+ ) ;
196
+
197
+ for ( let i = 0 ; i < activeProposals . length ; i ++ ) {
198
+ if (
199
+ hasWormholePayload (
200
+ squad ,
201
+ emitter ,
202
+ activeProposals [ i ] . publicKey ,
203
+ options . payload ,
204
+ activeInstructions [ i ] ,
205
+ wormholeTools
206
+ )
207
+ ) {
208
+ console . log (
209
+ `❌ Skipping, payload ${ options . payload } matches instructions at ${ activeProposals [ i ] . publicKey } `
210
+ ) ;
211
+ return ;
212
+ }
213
+ }
214
+ }
215
+
174
216
await createWormholeMsgMultisigTx (
175
217
options . cluster ,
176
218
squad ,
177
219
CONFIG [ cluster ] . vault ,
178
- options . payload
220
+ options . payload ,
221
+ wormholeTools
179
222
) ;
180
223
} ) ;
181
224
@@ -208,13 +251,35 @@ program
208
251
options . ledgerDerivationChange ,
209
252
options . wallet
210
253
) ;
211
- await verifyWormholePayload (
212
- options . cluster ,
254
+ const wormholeTools = await loadWormholeTools ( cluster , squad . connection ) ;
255
+
256
+ let onChainInstructions = await getProposalInstructions (
213
257
squad ,
214
- CONFIG [ cluster ] . vault ,
215
- new PublicKey ( options . txPda ) ,
216
- options . payload
258
+ await squad . getTransaction ( new PublicKey ( options . txPda ) )
259
+ ) ;
260
+
261
+ const msAccount = await squad . getMultisig ( CONFIG [ cluster ] . vault ) ;
262
+ const emitter = squad . getAuthorityPDA (
263
+ msAccount . publicKey ,
264
+ msAccount . authorityIndex
217
265
) ;
266
+
267
+ if (
268
+ hasWormholePayload (
269
+ squad ,
270
+ emitter ,
271
+ new PublicKey ( options . txPda ) ,
272
+ options . payload ,
273
+ onChainInstructions ,
274
+ wormholeTools
275
+ )
276
+ ) {
277
+ console . log (
278
+ "✅ This proposal is verified to be created with the given payload."
279
+ ) ;
280
+ } else {
281
+ console . log ( "❌ This proposal does not match the given payload." ) ;
282
+ }
218
283
} ) ;
219
284
220
285
program
@@ -325,7 +390,8 @@ program
325
390
squad ,
326
391
CONFIG [ cluster ] . vault ,
327
392
new PublicKey ( options . txPda ) ,
328
- CONFIG [ cluster ] . wormholeRpcEndpoint
393
+ CONFIG [ cluster ] . wormholeRpcEndpoint ,
394
+ await loadWormholeTools ( cluster , squad . connection )
329
395
) ;
330
396
} ) ;
331
397
@@ -479,8 +545,7 @@ async function getSquadsClient(
479
545
if ( solRpcUrl ) {
480
546
return Squads . endpoint ( solRpcUrl , wallet ) ;
481
547
} else {
482
- console . log ( "rpc:" , solRpcUrl ) ;
483
- throw `ERROR: solRpcUrl was not specified for localdevnet!` ;
548
+ return Squads . localnet ( wallet ) ;
484
549
}
485
550
}
486
551
default : {
@@ -583,39 +648,26 @@ async function setIsActiveIx(
583
648
} ;
584
649
}
585
650
586
- async function getWormholeMessageIx (
587
- cluster : Cluster ,
651
+ function getWormholeMessageIx (
588
652
payer : PublicKey ,
589
653
emitter : PublicKey ,
590
654
message : PublicKey ,
591
- connection : anchor . web3 . Connection ,
592
- payload : string
655
+ payload : string ,
656
+ wormholeTools : WormholeTools
593
657
) {
594
- const wormholeClusterName : WormholeNetwork =
595
- CONFIG [ cluster ] . wormholeClusterName ;
596
- const wormholeAddress =
597
- wormholeUtils . CONTRACTS [ wormholeClusterName ] . solana . core ;
598
- const { post_message_ix, fee_collector_address, state_address, parse_state } =
599
- await importCoreWasm ( ) ;
600
- const feeCollector = new PublicKey ( fee_collector_address ( wormholeAddress ) ) ;
601
- const bridgeState = new PublicKey ( state_address ( wormholeAddress ) ) ;
602
- const bridgeAccountInfo = await connection . getAccountInfo ( bridgeState ) ;
603
- const bridgeStateParsed = parse_state ( bridgeAccountInfo ! . data ) ;
604
- const bridgeFee = bridgeStateParsed . config . fee ;
605
-
606
658
if ( payload . startsWith ( "0x" ) ) {
607
659
payload = payload . substring ( 2 ) ;
608
660
}
609
661
610
662
return [
611
663
SystemProgram . transfer ( {
612
664
fromPubkey : payer ,
613
- toPubkey : feeCollector ,
614
- lamports : bridgeFee ,
665
+ toPubkey : wormholeTools . feeCollector ,
666
+ lamports : wormholeTools . bridgeFee ,
615
667
} ) ,
616
668
ixFromRust (
617
- post_message_ix (
618
- wormholeAddress ,
669
+ wormholeTools . post_message_ix (
670
+ wormholeTools . wormholeAddress . toBase58 ( ) ,
619
671
payer . toBase58 ( ) ,
620
672
emitter . toBase58 ( ) ,
621
673
message . toBase58 ( ) ,
@@ -631,7 +683,8 @@ async function createWormholeMsgMultisigTx(
631
683
cluster : Cluster ,
632
684
squad : Squads ,
633
685
vault : PublicKey ,
634
- payload : string
686
+ payload : string ,
687
+ wormholeTools : WormholeTools
635
688
) {
636
689
const msAccount = await squad . getMultisig ( vault ) ;
637
690
const emitter = squad . getAuthorityPDA (
@@ -649,13 +702,12 @@ async function createWormholeMsgMultisigTx(
649
702
) ;
650
703
651
704
console . log ( "Creating wormhole instructions..." ) ;
652
- const wormholeIxs = await getWormholeMessageIx (
653
- cluster ,
705
+ const wormholeIxs = getWormholeMessageIx (
654
706
emitter ,
655
707
emitter ,
656
708
messagePDA ,
657
- squad . connection ,
658
- payload
709
+ payload ,
710
+ wormholeTools
659
711
) ;
660
712
console . log ( "Wormhole instructions created." ) ;
661
713
@@ -678,29 +730,19 @@ async function createWormholeMsgMultisigTx(
678
730
) ;
679
731
}
680
732
681
- async function verifyWormholePayload (
682
- cluster : Cluster ,
733
+ function hasWormholePayload (
683
734
squad : Squads ,
684
- vault : PublicKey ,
735
+ emitter : PublicKey ,
685
736
txPubkey : PublicKey ,
686
- payload : string
687
- ) {
688
- const msAccount = await squad . getMultisig ( vault ) ;
689
- const emitter = squad . getAuthorityPDA (
690
- msAccount . publicKey ,
691
- msAccount . authorityIndex
692
- ) ;
693
- console . log ( `Emitter Address: ${ emitter . toBase58 ( ) } ` ) ;
694
-
695
- const tx = await squad . getTransaction ( txPubkey ) ;
696
- const onChainInstructions = await getProposalInstructions ( squad , tx ) ;
697
-
737
+ payload : string ,
738
+ onChainInstructions : InstructionAccount [ ] ,
739
+ wormholeTools : WormholeTools
740
+ ) : boolean {
698
741
if ( onChainInstructions . length !== 2 ) {
699
- throw new Error (
700
- `Expected 2 instructions in the transaction, found ${
701
- tx . instructionIndex + 1
702
- } `
742
+ console . debug (
743
+ `Expected 2 instructions in the transaction, found ${ onChainInstructions . length } `
703
744
) ;
745
+ return false ;
704
746
}
705
747
706
748
const [ messagePDA ] = getIxAuthorityPDA (
@@ -709,64 +751,63 @@ async function verifyWormholePayload(
709
751
squad . multisigProgramId
710
752
) ;
711
753
712
- const wormholeIxs = await getWormholeMessageIx (
713
- cluster ,
754
+ const wormholeIxs = getWormholeMessageIx (
714
755
emitter ,
715
756
emitter ,
716
757
messagePDA ,
717
- squad . connection ,
718
- payload
719
- ) ;
720
-
721
- console . log ( "Checking equality of the 1st instruction..." ) ;
722
- verifyOnChainInstruction (
723
- wormholeIxs [ 0 ] ,
724
- onChainInstructions [ 0 ] as InstructionAccount
758
+ payload ,
759
+ wormholeTools
725
760
) ;
726
761
727
- console . log ( "Checking equality of the 2nd instruction..." ) ;
728
- verifyOnChainInstruction (
729
- wormholeIxs [ 1 ] ,
730
- onChainInstructions [ 1 ] as InstructionAccount
731
- ) ;
732
-
733
- console . log (
734
- "✅ The transaction is verified to be created with the given payload."
762
+ return (
763
+ isEqualOnChainInstruction (
764
+ wormholeIxs [ 0 ] ,
765
+ onChainInstructions [ 0 ] as InstructionAccount
766
+ ) &&
767
+ isEqualOnChainInstruction (
768
+ wormholeIxs [ 1 ] ,
769
+ onChainInstructions [ 1 ] as InstructionAccount
770
+ )
735
771
) ;
736
772
}
737
773
738
- function verifyOnChainInstruction (
774
+ function isEqualOnChainInstruction (
739
775
instruction : TransactionInstruction ,
740
776
onChainInstruction : InstructionAccount
741
- ) {
777
+ ) : boolean {
742
778
if ( ! instruction . programId . equals ( onChainInstruction . programId ) ) {
743
- throw new Error (
779
+ console . debug (
744
780
`Program id mismatch: Expected ${ instruction . programId . toBase58 ( ) } , found ${ onChainInstruction . programId . toBase58 ( ) } `
745
781
) ;
782
+ return false ;
746
783
}
747
784
748
785
if ( ! lodash . isEqual ( instruction . keys , onChainInstruction . keys ) ) {
749
- throw new Error (
786
+ console . debug (
750
787
`Instruction accounts mismatch. Expected ${ instruction . keys } , found ${ onChainInstruction . keys } `
751
788
) ;
789
+ return false ;
752
790
}
753
791
754
792
const onChainData = onChainInstruction . data as Buffer ;
755
793
if ( ! instruction . data . equals ( onChainData ) ) {
756
- throw new Error (
794
+ console . debug (
757
795
`Instruction data mismatch. Expected ${ instruction . data . toString (
758
796
"hex"
759
797
) } , Found ${ onChainData . toString ( "hex" ) } `
760
798
) ;
799
+ return false ;
761
800
}
801
+ return true ;
762
802
}
763
803
764
804
async function executeMultisigTx (
765
805
cluster : string ,
766
806
squad : Squads ,
767
807
vault : PublicKey ,
768
808
txPDA : PublicKey ,
769
- rpcUrl : string
809
+ rpcUrl : string ,
810
+ wormholeTools : WormholeTools
770
811
) {
771
812
const msAccount = await squad . getMultisig ( vault ) ;
772
813
@@ -862,7 +903,7 @@ async function executeMultisigTx(
862
903
const { vaaBytes } = await response . json ( ) ;
863
904
console . log ( `VAA (Base64): ${ vaaBytes } ` ) ;
864
905
console . log ( `VAA (Hex): ${ Buffer . from ( vaaBytes , "base64" ) . toString ( "hex" ) } ` ) ;
865
- const parsedVaa = await parse ( vaaBytes ) ;
906
+ const parsedVaa = parse ( vaaBytes , wormholeTools ) ;
866
907
console . log ( `Emitter chain: ${ parsedVaa . emitter_chain } ` ) ;
867
908
console . log ( `Nonce: ${ parsedVaa . nonce } ` ) ;
868
909
console . log ( `Payload: ${ Buffer . from ( parsedVaa . payload ) . toString ( "hex" ) } ` ) ;
@@ -939,8 +980,3 @@ async function removeMember(
939
980
squadIxs
940
981
) ;
941
982
}
942
-
943
- async function parse ( data : string ) {
944
- const { parse_vaa } = await importCoreWasm ( ) ;
945
- return parse_vaa ( Uint8Array . from ( Buffer . from ( data , "base64" ) ) ) ;
946
- }
0 commit comments