[multisig-cli] Add support for json (#430)
* Draft * Add verify for instruction payload * Typos * Refactor json parsing
This commit is contained in:
parent
e484f5cbb7
commit
51754457a6
|
@ -165,7 +165,8 @@ program
|
||||||
"multisig wallet secret key filepath",
|
"multisig wallet secret key filepath",
|
||||||
"keys/key.json"
|
"keys/key.json"
|
||||||
)
|
)
|
||||||
.option("-p, --payload <hex-string>", "payload to sign", "0xdeadbeef")
|
.option("-f, --file <filepath>", "Path to a json file with instructions")
|
||||||
|
.option("-p, --payload <hex-string>", "Wormhole VAA payload")
|
||||||
.option("-s, --skip-duplicate-check", "Skip checking duplicates")
|
.option("-s, --skip-duplicate-check", "Skip checking duplicates")
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
const cluster: Cluster = options.cluster;
|
const cluster: Cluster = options.cluster;
|
||||||
|
@ -176,6 +177,13 @@ program
|
||||||
options.ledgerDerivationChange,
|
options.ledgerDerivationChange,
|
||||||
options.wallet
|
options.wallet
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (options.payload && options.file) {
|
||||||
|
console.log("Only one of --payload or --file must be provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.payload) {
|
||||||
const wormholeTools = await loadWormholeTools(cluster, squad.connection);
|
const wormholeTools = await loadWormholeTools(cluster, squad.connection);
|
||||||
|
|
||||||
if (!options.skipDuplicateCheck) {
|
if (!options.skipDuplicateCheck) {
|
||||||
|
@ -220,11 +228,52 @@ program
|
||||||
options.payload,
|
options.payload,
|
||||||
wormholeTools
|
wormholeTools
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.file) {
|
||||||
|
const instructions: SquadInstruction[] = loadInstructionsFromJson(
|
||||||
|
options.file
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!options.skipDuplicateCheck) {
|
||||||
|
const activeProposals = await getActiveProposals(
|
||||||
|
squad,
|
||||||
|
CONFIG[cluster].vault
|
||||||
|
);
|
||||||
|
const activeInstructions = await getManyProposalsInstructions(
|
||||||
|
squad,
|
||||||
|
activeProposals
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let i = 0; i < activeProposals.length; i++) {
|
||||||
|
if (
|
||||||
|
areEqualOnChainInstructions(
|
||||||
|
instructions.map((ix) => ix.instruction),
|
||||||
|
activeInstructions[i]
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`❌ Skipping, instructions from ${options.file} match instructions at ${activeProposals[i].publicKey}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const txKey = await createTx(squad, CONFIG[cluster].vault);
|
||||||
|
await addInstructionsToTx(
|
||||||
|
cluster,
|
||||||
|
squad,
|
||||||
|
CONFIG[cluster].vault,
|
||||||
|
txKey,
|
||||||
|
instructions
|
||||||
|
);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command("verify")
|
.command("verify")
|
||||||
.description("Verify given wormhole transaction has the given payload")
|
.description("Verify given proposal matches a payload")
|
||||||
.option("-c, --cluster <network>", "solana cluster to use", "devnet")
|
.option("-c, --cluster <network>", "solana cluster to use", "devnet")
|
||||||
.option("-l, --ledger", "use ledger")
|
.option("-l, --ledger", "use ledger")
|
||||||
.option(
|
.option(
|
||||||
|
@ -240,7 +289,8 @@ program
|
||||||
"multisig wallet secret key filepath",
|
"multisig wallet secret key filepath",
|
||||||
"keys/key.json"
|
"keys/key.json"
|
||||||
)
|
)
|
||||||
.requiredOption("-p, --payload <hex-string>", "expected payload")
|
.option("-p, --payload <hex-string>", "expected wormhole payload")
|
||||||
|
.option("-f, --file <filepath>", "Path to a json file with instructions")
|
||||||
.requiredOption("-t, --tx-pda <address>", "transaction PDA")
|
.requiredOption("-t, --tx-pda <address>", "transaction PDA")
|
||||||
.action(async (options) => {
|
.action(async (options) => {
|
||||||
const cluster: Cluster = options.cluster;
|
const cluster: Cluster = options.cluster;
|
||||||
|
@ -251,6 +301,12 @@ program
|
||||||
options.ledgerDerivationChange,
|
options.ledgerDerivationChange,
|
||||||
options.wallet
|
options.wallet
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (options.payload && options.file) {
|
||||||
|
console.log("Only one of --payload or --file must be provided");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const wormholeTools = await loadWormholeTools(cluster, squad.connection);
|
const wormholeTools = await loadWormholeTools(cluster, squad.connection);
|
||||||
|
|
||||||
let onChainInstructions = await getProposalInstructions(
|
let onChainInstructions = await getProposalInstructions(
|
||||||
|
@ -264,6 +320,7 @@ program
|
||||||
msAccount.authorityIndex
|
msAccount.authorityIndex
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (options.payload) {
|
||||||
if (
|
if (
|
||||||
hasWormholePayload(
|
hasWormholePayload(
|
||||||
squad,
|
squad,
|
||||||
|
@ -280,6 +337,26 @@ program
|
||||||
} else {
|
} else {
|
||||||
console.log("❌ This proposal does not match the given payload.");
|
console.log("❌ This proposal does not match the given payload.");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.file) {
|
||||||
|
const instructions: SquadInstruction[] = loadInstructionsFromJson(
|
||||||
|
options.file
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
areEqualOnChainInstructions(
|
||||||
|
instructions.map((ix) => ix.instruction),
|
||||||
|
onChainInstructions
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
"✅ This proposal is verified to be created with the given instructions."
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log("❌ This proposal does not match the given instructions.");
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
|
@ -730,6 +807,24 @@ async function createWormholeMsgMultisigTx(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function areEqualOnChainInstructions(
|
||||||
|
instructions: TransactionInstruction[],
|
||||||
|
onChainInstructions: InstructionAccount[]
|
||||||
|
): boolean {
|
||||||
|
if (instructions.length != onChainInstructions.length) {
|
||||||
|
console.debug(
|
||||||
|
`Proposals have a different number of instructions ${instructions.length} vs ${onChainInstructions.length}`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return lodash
|
||||||
|
.range(0, instructions.length)
|
||||||
|
.every((i) =>
|
||||||
|
isEqualOnChainInstruction(instructions[i], onChainInstructions[i])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function hasWormholePayload(
|
function hasWormholePayload(
|
||||||
squad: Squads,
|
squad: Squads,
|
||||||
emitter: PublicKey,
|
emitter: PublicKey,
|
||||||
|
@ -980,3 +1075,25 @@ async function removeMember(
|
||||||
squadIxs
|
squadIxs
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function loadInstructionsFromJson(path: string): SquadInstruction[] {
|
||||||
|
const inputInstructions = JSON.parse(fs.readFileSync(path).toString());
|
||||||
|
const instructions: SquadInstruction[] = inputInstructions.map(
|
||||||
|
(ix: any): SquadInstruction => {
|
||||||
|
return {
|
||||||
|
instruction: new TransactionInstruction({
|
||||||
|
programId: new PublicKey(ix.program_id),
|
||||||
|
keys: ix.accounts.map((acc: any) => {
|
||||||
|
return {
|
||||||
|
pubkey: new PublicKey(acc.pubkey),
|
||||||
|
isSigner: acc.is_signer,
|
||||||
|
isWritable: acc.is_writable,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
data: Buffer.from(ix.data, "hex"),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return instructions;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue