Skip to content

Commit 5175445

Browse files
authored
[multisig-cli] Add support for json (#430)
* Draft * Add verify for instruction payload * Typos * Refactor json parsing
1 parent e484f5c commit 5175445

File tree

1 file changed

+169
-52
lines changed
  • third_party/pyth/multisig-wh-message-builder/src

1 file changed

+169
-52
lines changed

third_party/pyth/multisig-wh-message-builder/src/index.ts

Lines changed: 169 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ program
165165
"multisig wallet secret key filepath",
166166
"keys/key.json"
167167
)
168-
.option("-p, --payload <hex-string>", "payload to sign", "0xdeadbeef")
168+
.option("-f, --file <filepath>", "Path to a json file with instructions")
169+
.option("-p, --payload <hex-string>", "Wormhole VAA payload")
169170
.option("-s, --skip-duplicate-check", "Skip checking duplicates")
170171
.action(async (options) => {
171172
const cluster: Cluster = options.cluster;
@@ -176,55 +177,103 @@ program
176177
options.ledgerDerivationChange,
177178
options.wallet
178179
);
179-
const wormholeTools = await loadWormholeTools(cluster, squad.connection);
180180

181-
if (!options.skipDuplicateCheck) {
182-
const activeProposals = await getActiveProposals(
183-
squad,
184-
CONFIG[cluster].vault
185-
);
186-
const activeInstructions = await getManyProposalsInstructions(
181+
if (options.payload && options.file) {
182+
console.log("Only one of --payload or --file must be provided");
183+
return;
184+
}
185+
186+
if (options.payload) {
187+
const wormholeTools = await loadWormholeTools(cluster, squad.connection);
188+
189+
if (!options.skipDuplicateCheck) {
190+
const activeProposals = await getActiveProposals(
191+
squad,
192+
CONFIG[cluster].vault
193+
);
194+
const activeInstructions = await getManyProposalsInstructions(
195+
squad,
196+
activeProposals
197+
);
198+
199+
const msAccount = await squad.getMultisig(CONFIG[cluster].vault);
200+
const emitter = squad.getAuthorityPDA(
201+
msAccount.publicKey,
202+
msAccount.authorityIndex
203+
);
204+
205+
for (let i = 0; i < activeProposals.length; i++) {
206+
if (
207+
hasWormholePayload(
208+
squad,
209+
emitter,
210+
activeProposals[i].publicKey,
211+
options.payload,
212+
activeInstructions[i],
213+
wormholeTools
214+
)
215+
) {
216+
console.log(
217+
`❌ Skipping, payload ${options.payload} matches instructions at ${activeProposals[i].publicKey}`
218+
);
219+
return;
220+
}
221+
}
222+
}
223+
224+
await createWormholeMsgMultisigTx(
225+
options.cluster,
187226
squad,
188-
activeProposals
227+
CONFIG[cluster].vault,
228+
options.payload,
229+
wormholeTools
189230
);
231+
}
190232

191-
const msAccount = await squad.getMultisig(CONFIG[cluster].vault);
192-
const emitter = squad.getAuthorityPDA(
193-
msAccount.publicKey,
194-
msAccount.authorityIndex
233+
if (options.file) {
234+
const instructions: SquadInstruction[] = loadInstructionsFromJson(
235+
options.file
195236
);
196237

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;
238+
if (!options.skipDuplicateCheck) {
239+
const activeProposals = await getActiveProposals(
240+
squad,
241+
CONFIG[cluster].vault
242+
);
243+
const activeInstructions = await getManyProposalsInstructions(
244+
squad,
245+
activeProposals
246+
);
247+
248+
for (let i = 0; i < activeProposals.length; i++) {
249+
if (
250+
areEqualOnChainInstructions(
251+
instructions.map((ix) => ix.instruction),
252+
activeInstructions[i]
253+
)
254+
) {
255+
console.log(
256+
`❌ Skipping, instructions from ${options.file} match instructions at ${activeProposals[i].publicKey}`
257+
);
258+
return;
259+
}
212260
}
213261
}
214-
}
215262

216-
await createWormholeMsgMultisigTx(
217-
options.cluster,
218-
squad,
219-
CONFIG[cluster].vault,
220-
options.payload,
221-
wormholeTools
222-
);
263+
const txKey = await createTx(squad, CONFIG[cluster].vault);
264+
await addInstructionsToTx(
265+
cluster,
266+
squad,
267+
CONFIG[cluster].vault,
268+
txKey,
269+
instructions
270+
);
271+
}
223272
});
224273

225274
program
226275
.command("verify")
227-
.description("Verify given wormhole transaction has the given payload")
276+
.description("Verify given proposal matches a payload")
228277
.option("-c, --cluster <network>", "solana cluster to use", "devnet")
229278
.option("-l, --ledger", "use ledger")
230279
.option(
@@ -240,7 +289,8 @@ program
240289
"multisig wallet secret key filepath",
241290
"keys/key.json"
242291
)
243-
.requiredOption("-p, --payload <hex-string>", "expected payload")
292+
.option("-p, --payload <hex-string>", "expected wormhole payload")
293+
.option("-f, --file <filepath>", "Path to a json file with instructions")
244294
.requiredOption("-t, --tx-pda <address>", "transaction PDA")
245295
.action(async (options) => {
246296
const cluster: Cluster = options.cluster;
@@ -251,6 +301,12 @@ program
251301
options.ledgerDerivationChange,
252302
options.wallet
253303
);
304+
305+
if (options.payload && options.file) {
306+
console.log("Only one of --payload or --file must be provided");
307+
return;
308+
}
309+
254310
const wormholeTools = await loadWormholeTools(cluster, squad.connection);
255311

256312
let onChainInstructions = await getProposalInstructions(
@@ -264,21 +320,42 @@ program
264320
msAccount.authorityIndex
265321
);
266322

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."
323+
if (options.payload) {
324+
if (
325+
hasWormholePayload(
326+
squad,
327+
emitter,
328+
new PublicKey(options.txPda),
329+
options.payload,
330+
onChainInstructions,
331+
wormholeTools
332+
)
333+
) {
334+
console.log(
335+
"✅ This proposal is verified to be created with the given payload."
336+
);
337+
} else {
338+
console.log("❌ This proposal does not match the given payload.");
339+
}
340+
}
341+
342+
if (options.file) {
343+
const instructions: SquadInstruction[] = loadInstructionsFromJson(
344+
options.file
279345
);
280-
} else {
281-
console.log("❌ This proposal does not match the given payload.");
346+
347+
if (
348+
areEqualOnChainInstructions(
349+
instructions.map((ix) => ix.instruction),
350+
onChainInstructions
351+
)
352+
) {
353+
console.log(
354+
"✅ This proposal is verified to be created with the given instructions."
355+
);
356+
} else {
357+
console.log("❌ This proposal does not match the given instructions.");
358+
}
282359
}
283360
});
284361

@@ -730,6 +807,24 @@ async function createWormholeMsgMultisigTx(
730807
);
731808
}
732809

810+
function areEqualOnChainInstructions(
811+
instructions: TransactionInstruction[],
812+
onChainInstructions: InstructionAccount[]
813+
): boolean {
814+
if (instructions.length != onChainInstructions.length) {
815+
console.debug(
816+
`Proposals have a different number of instructions ${instructions.length} vs ${onChainInstructions.length}`
817+
);
818+
return false;
819+
} else {
820+
return lodash
821+
.range(0, instructions.length)
822+
.every((i) =>
823+
isEqualOnChainInstruction(instructions[i], onChainInstructions[i])
824+
);
825+
}
826+
}
827+
733828
function hasWormholePayload(
734829
squad: Squads,
735830
emitter: PublicKey,
@@ -980,3 +1075,25 @@ async function removeMember(
9801075
squadIxs
9811076
);
9821077
}
1078+
1079+
function loadInstructionsFromJson(path: string): SquadInstruction[] {
1080+
const inputInstructions = JSON.parse(fs.readFileSync(path).toString());
1081+
const instructions: SquadInstruction[] = inputInstructions.map(
1082+
(ix: any): SquadInstruction => {
1083+
return {
1084+
instruction: new TransactionInstruction({
1085+
programId: new PublicKey(ix.program_id),
1086+
keys: ix.accounts.map((acc: any) => {
1087+
return {
1088+
pubkey: new PublicKey(acc.pubkey),
1089+
isSigner: acc.is_signer,
1090+
isWritable: acc.is_writable,
1091+
};
1092+
}),
1093+
data: Buffer.from(ix.data, "hex"),
1094+
}),
1095+
};
1096+
}
1097+
);
1098+
return instructions;
1099+
}

0 commit comments

Comments
 (0)