From c9a19d44976ca146dd7ced155837f9b4c257af9f Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Thu, 12 Jan 2023 13:43:49 -0600 Subject: [PATCH 1/2] Push code --- .../src/__tests__/GovernancePayload.test.ts | 16 ++--- .../WormholeMultisigInstruction.test.ts | 11 +--- .../governance_payload/ExecutePostedVaa.ts | 18 ++++-- .../src/governance_payload/index.ts | 48 ++++++++++----- .../PythMultisigInstruction.ts | 10 ++- .../WormholeMultisigInstruction.ts | 61 +++++++++++-------- .../src/multisig_transaction/anchor.ts | 2 +- .../src/multisig_transaction/index.ts | 1 + 8 files changed, 104 insertions(+), 63 deletions(-) diff --git a/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts b/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts index bdb0d359b3..eb8af061e4 100644 --- a/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts +++ b/xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts @@ -14,8 +14,8 @@ test("GovernancePayload ser/de", (done) => { buffer.equals(Buffer.from([80, 84, 71, 77, 0, 0, 0, 26])) ).toBeTruthy(); let governanceHeader = PythGovernanceHeader.decode(buffer); - expect(governanceHeader.targetChainId).toBe("pythnet"); - expect(governanceHeader.action).toBe("ExecutePostedVaa"); + expect(governanceHeader?.targetChainId).toBe("pythnet"); + expect(governanceHeader?.action).toBe("ExecutePostedVaa"); // Valid header 2 expectedGovernanceHeader = new PythGovernanceHeader( @@ -37,25 +37,25 @@ test("GovernancePayload ser/de", (done) => { expect(governanceHeader?.action).toBe("SetFee"); // Wrong magic number - expect(() => + expect( PythGovernanceHeader.decode( Buffer.from([0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0]) ) - ).toThrow("Wrong magic number"); + ).toBeUndefined(); // Wrong chain - expect(() => + expect( PythGovernanceHeader.decode( Buffer.from([80, 84, 71, 77, 0, 0, 255, 255, 0, 0, 0, 0]) ) - ).toThrow("Chain Id not found"); + ).toBeUndefined(); // Wrong module/action combination - expect(() => + expect( PythGovernanceHeader.decode( Buffer.from([80, 84, 71, 77, 0, 1, 0, 26, 0, 0, 0, 0]) ) - ).toThrow("Invalid header, action doesn't match module"); + ).toBeUndefined(); // Decode executePostVaa with empty instructions let expectedExecutePostedVaa = new ExecutePostedVaa("pythnet", []); diff --git a/xc-admin/packages/xc-admin-common/src/__tests__/WormholeMultisigInstruction.test.ts b/xc-admin/packages/xc-admin-common/src/__tests__/WormholeMultisigInstruction.test.ts index eddb07c894..93a6030d9f 100644 --- a/xc-admin/packages/xc-admin-common/src/__tests__/WormholeMultisigInstruction.test.ts +++ b/xc-admin/packages/xc-admin-common/src/__tests__/WormholeMultisigInstruction.test.ts @@ -199,9 +199,6 @@ test("Wormhole multisig instruction parse: send message with governance payload" .then((instruction) => { const parsedInstruction = parser.parseInstruction(instruction); if (parsedInstruction instanceof WormholeMultisigInstruction) { - expect( - parsedInstruction instanceof WormholeMultisigInstruction - ).toBeTruthy(); expect(parsedInstruction.program).toBe( MultisigInstructionProgram.WormholeBridge ); @@ -313,15 +310,13 @@ test("Wormhole multisig instruction parse: send message with governance payload" ); expect(parsedInstruction.args.consistencyLevel).toBe(0); - if ( - parsedInstruction.args.governanceAction instanceof ExecutePostedVaa - ) { - expect(parsedInstruction.args.governanceAction.targetChainId).toBe( + if (parsedInstruction.governanceAction instanceof ExecutePostedVaa) { + expect(parsedInstruction.governanceAction.targetChainId).toBe( "pythnet" ); ( - parsedInstruction.args.governanceAction + parsedInstruction.governanceAction .instructions as TransactionInstruction[] ).forEach((instruction, i) => { expect( diff --git a/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts b/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts index 0dd661cdc1..395594b43f 100644 --- a/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts +++ b/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts @@ -75,11 +75,21 @@ export class ExecutePostedVaa implements PythGovernanceAction { } /** Decode ExecutePostedVaa */ - static decode(data: Buffer): ExecutePostedVaa { + static decode(data: Buffer): ExecutePostedVaa | undefined { let header = PythGovernanceHeader.decode(data); - let deserialized = this.layout.decode( - data.subarray(PythGovernanceHeader.span) - ); + if (!header) { + return undefined; + } + + let deserialized; + try { + deserialized = this.layout.decode( + data.subarray(PythGovernanceHeader.span) + ); + } catch { + return undefined; + } + let instructions: TransactionInstruction[] = deserialized.map((ix) => { let programId: PublicKey = new PublicKey(ix.programId); let keys: AccountMeta[] = ix.accounts.map((acc) => { diff --git a/xc-admin/packages/xc-admin-common/src/governance_payload/index.ts b/xc-admin/packages/xc-admin-common/src/governance_payload/index.ts index a357ca9a42..a417cd8460 100644 --- a/xc-admin/packages/xc-admin-common/src/governance_payload/index.ts +++ b/xc-admin/packages/xc-admin-common/src/governance_payload/index.ts @@ -30,7 +30,7 @@ export const TargetAction = { /** Helper to get the ActionName from a (moduleId, actionId) tuple*/ export function toActionName( deserialized: Readonly<{ moduleId: number; actionId: number }> -): ActionName { +): ActionName | undefined { if (deserialized.moduleId == MODULE_EXECUTOR && deserialized.actionId == 0) { return "ExecutePostedVaa"; } else if (deserialized.moduleId == MODULE_TARGET) { @@ -49,7 +49,7 @@ export function toActionName( return "RequestGovernanceDataSourceTransfer"; } } - throw new Error("Invalid header, action doesn't match module"); + return undefined; } export declare type ActionName = @@ -84,23 +84,35 @@ export class PythGovernanceHeader { this.action = action; } /** Decode Pyth Governance Header */ - static decode(data: Buffer): PythGovernanceHeader { - let deserialized = this.layout.decode(data); + static decode(data: Buffer): PythGovernanceHeader | undefined { + let deserialized; + try { + deserialized = this.layout.decode(data); + } catch { + return undefined; + } + if (deserialized.magicNumber !== MAGIC_NUMBER) { - throw new Error("Wrong magic number"); + return undefined; } if (!toChainName(deserialized.chain)) { - throw new Error("Chain Id not found"); + return undefined; } - return new PythGovernanceHeader( - toChainName(deserialized.chain), - toActionName({ - actionId: deserialized.action, - moduleId: deserialized.module, - }) - ); + const actionName = toActionName({ + actionId: deserialized.action, + moduleId: deserialized.module, + }); + + if (actionName) { + return new PythGovernanceHeader( + toChainName(deserialized.chain), + actionName + ); + } else { + return undefined; + } } /** Encode Pyth Governance Header */ @@ -134,13 +146,19 @@ export const MODULE_EXECUTOR = 0; export const MODULE_TARGET = 1; /** Decode a governance payload */ -export function decodeGovernancePayload(data: Buffer): PythGovernanceAction { +export function decodeGovernancePayload( + data: Buffer +): PythGovernanceAction | undefined { const header = PythGovernanceHeader.decode(data); + if (!header) { + return undefined; + } + switch (header.action) { case "ExecutePostedVaa": return ExecutePostedVaa.decode(data); default: - throw "Not supported"; + return undefined; } } diff --git a/xc-admin/packages/xc-admin-common/src/multisig_transaction/PythMultisigInstruction.ts b/xc-admin/packages/xc-admin-common/src/multisig_transaction/PythMultisigInstruction.ts index dc774b08e8..c69f6935ec 100644 --- a/xc-admin/packages/xc-admin-common/src/multisig_transaction/PythMultisigInstruction.ts +++ b/xc-admin/packages/xc-admin-common/src/multisig_transaction/PythMultisigInstruction.ts @@ -1,4 +1,8 @@ -import { MultisigInstruction, MultisigInstructionProgram } from "."; +import { + MultisigInstruction, + MultisigInstructionProgram, + UNRECOGNIZED_INSTRUCTION, +} from "."; import { AnchorAccounts, resolveAccountNames } from "./anchor"; import { pythIdl, pythOracleCoder } from "@pythnetwork/client"; import { TransactionInstruction } from "@solana/web3.js"; @@ -35,8 +39,8 @@ export class PythMultisigInstruction implements MultisigInstruction { ); } else { return new PythMultisigInstruction( - "Unrecognized instruction", - {}, + UNRECOGNIZED_INSTRUCTION, + { data: instruction.data }, { named: {}, remaining: instruction.keys } ); } diff --git a/xc-admin/packages/xc-admin-common/src/multisig_transaction/WormholeMultisigInstruction.ts b/xc-admin/packages/xc-admin-common/src/multisig_transaction/WormholeMultisigInstruction.ts index c173a17a1f..8a247fafec 100644 --- a/xc-admin/packages/xc-admin-common/src/multisig_transaction/WormholeMultisigInstruction.ts +++ b/xc-admin/packages/xc-admin-common/src/multisig_transaction/WormholeMultisigInstruction.ts @@ -2,7 +2,11 @@ import { createReadOnlyWormholeProgramInterface } from "@certusone/wormhole-sdk/ import { WormholeInstructionCoder } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole/coder/instruction"; import { getPythClusterApiUrl } from "@pythnetwork/client/lib/cluster"; import { Connection, TransactionInstruction } from "@solana/web3.js"; -import { MultisigInstruction, MultisigInstructionProgram } from "."; +import { + MultisigInstruction, + MultisigInstructionProgram, + UNRECOGNIZED_INSTRUCTION, +} from "."; import { decodeGovernancePayload, PythGovernanceAction, @@ -14,15 +18,18 @@ export class WormholeMultisigInstruction implements MultisigInstruction { readonly name: string; readonly args: { [key: string]: any }; readonly accounts: AnchorAccounts; + readonly governanceAction: PythGovernanceAction | undefined; constructor( name: string, args: { [key: string]: any }, - accounts: AnchorAccounts + accounts: AnchorAccounts, + governanceAction: PythGovernanceAction | undefined ) { this.name = name; this.args = args; this.accounts = accounts; + this.governanceAction = governanceAction; } static fromTransactionInstruction( @@ -38,32 +45,38 @@ export class WormholeMultisigInstruction implements MultisigInstruction { ).decode(instruction.data); if (deserializedData) { - let result = new WormholeMultisigInstruction( - deserializedData.name, - deserializedData.data, - resolveAccountNames( - wormholeProgram.idl, - deserializedData.name, - instruction - ) - ); + if (deserializedData.name === "postMessage") { + const decodedGovernanceAction: PythGovernanceAction | undefined = + decodeGovernancePayload((deserializedData.data as any).payload); - if (result.name === "postMessage") { - try { - const decoded: PythGovernanceAction = decodeGovernancePayload( - result.args.payload - ); - result.args.governanceAction = decoded; - } catch { - result.args.governanceAction = {}; - } + return new WormholeMultisigInstruction( + deserializedData.name, + deserializedData.data, + resolveAccountNames( + wormholeProgram.idl, + deserializedData.name, + instruction + ), + decodedGovernanceAction + ); + } else { + return new WormholeMultisigInstruction( + deserializedData.name, + deserializedData.data, + resolveAccountNames( + wormholeProgram.idl, + deserializedData.name, + instruction + ), + undefined + ); } - return result; } else { return new WormholeMultisigInstruction( - "Unrecognized instruction", - {}, - { named: {}, remaining: instruction.keys } + UNRECOGNIZED_INSTRUCTION, + { data: instruction.data }, + { named: {}, remaining: instruction.keys }, + undefined ); } } diff --git a/xc-admin/packages/xc-admin-common/src/multisig_transaction/anchor.ts b/xc-admin/packages/xc-admin-common/src/multisig_transaction/anchor.ts index af332a38ab..15a3629642 100644 --- a/xc-admin/packages/xc-admin-common/src/multisig_transaction/anchor.ts +++ b/xc-admin/packages/xc-admin-common/src/multisig_transaction/anchor.ts @@ -15,7 +15,7 @@ export function resolveAccountNames( ): { named: NamedAccounts; remaining: RemainingAccounts } { const ix = idl.instructions.find((ix) => ix.name == name); if (!ix) { - throw Error("Instruction name not found"); + return { named: {}, remaining: instruction.keys }; } const named: NamedAccounts = {}; const remaining: RemainingAccounts = []; diff --git a/xc-admin/packages/xc-admin-common/src/multisig_transaction/index.ts b/xc-admin/packages/xc-admin-common/src/multisig_transaction/index.ts index 11db79a3c9..2e19da60ea 100644 --- a/xc-admin/packages/xc-admin-common/src/multisig_transaction/index.ts +++ b/xc-admin/packages/xc-admin-common/src/multisig_transaction/index.ts @@ -7,6 +7,7 @@ import { WORMHOLE_ADDRESS } from "../wormhole"; import { PythMultisigInstruction } from "./PythMultisigInstruction"; import { WormholeMultisigInstruction } from "./WormholeMultisigInstruction"; +export const UNRECOGNIZED_INSTRUCTION = "unrecognizedInstruction"; export enum MultisigInstructionProgram { PythOracle, WormholeBridge, From 9090b0d7687ff9c134b345beb7ef1c98fb95c3f4 Mon Sep 17 00:00:00 2001 From: Guillermo Bescos Alapont Date: Thu, 12 Jan 2023 20:07:04 -0600 Subject: [PATCH 2/2] Format --- .../governance_payload/ExecutePostedVaa.ts | 27 ++++++++-------- .../src/governance_payload/index.ts | 32 ++++++++++--------- 2 files changed, 30 insertions(+), 29 deletions(-) diff --git a/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts b/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts index 395594b43f..265b9afe39 100644 --- a/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts +++ b/xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts @@ -1,6 +1,10 @@ import { ChainId, ChainName } from "@certusone/wormhole-sdk"; import * as BufferLayout from "@solana/buffer-layout"; -import { PythGovernanceAction, PythGovernanceHeader } from "."; +import { + PythGovernanceAction, + PythGovernanceHeader, + safeLayoutDecode, +} from "."; import { Layout } from "@solana/buffer-layout"; import { AccountMeta, @@ -76,19 +80,14 @@ export class ExecutePostedVaa implements PythGovernanceAction { /** Decode ExecutePostedVaa */ static decode(data: Buffer): ExecutePostedVaa | undefined { - let header = PythGovernanceHeader.decode(data); - if (!header) { - return undefined; - } - - let deserialized; - try { - deserialized = this.layout.decode( - data.subarray(PythGovernanceHeader.span) - ); - } catch { - return undefined; - } + const header = PythGovernanceHeader.decode(data); + if (!header) return undefined; + + const deserialized = safeLayoutDecode( + this.layout, + data.subarray(PythGovernanceHeader.span) + ); + if (!deserialized) return undefined; let instructions: TransactionInstruction[] = deserialized.map((ix) => { let programId: PublicKey = new PublicKey(ix.programId); diff --git a/xc-admin/packages/xc-admin-common/src/governance_payload/index.ts b/xc-admin/packages/xc-admin-common/src/governance_payload/index.ts index a417cd8460..e7019b15d6 100644 --- a/xc-admin/packages/xc-admin-common/src/governance_payload/index.ts +++ b/xc-admin/packages/xc-admin-common/src/governance_payload/index.ts @@ -85,20 +85,13 @@ export class PythGovernanceHeader { } /** Decode Pyth Governance Header */ static decode(data: Buffer): PythGovernanceHeader | undefined { - let deserialized; - try { - deserialized = this.layout.decode(data); - } catch { - return undefined; - } + const deserialized = safeLayoutDecode(this.layout, data); - if (deserialized.magicNumber !== MAGIC_NUMBER) { - return undefined; - } + if (!deserialized) return undefined; - if (!toChainName(deserialized.chain)) { - return undefined; - } + if (deserialized.magicNumber !== MAGIC_NUMBER) return undefined; + + if (!toChainName(deserialized.chain)) return undefined; const actionName = toActionName({ actionId: deserialized.action, @@ -150,9 +143,7 @@ export function decodeGovernancePayload( data: Buffer ): PythGovernanceAction | undefined { const header = PythGovernanceHeader.decode(data); - if (!header) { - return undefined; - } + if (!header) return undefined; switch (header.action) { case "ExecutePostedVaa": @@ -162,4 +153,15 @@ export function decodeGovernancePayload( } } +export function safeLayoutDecode( + layout: BufferLayout.Layout, + data: Buffer +): T | undefined { + try { + return layout.decode(data); + } catch { + return undefined; + } +} + export { ExecutePostedVaa } from "./ExecutePostedVaa";