Skip to content

Commit bab7517

Browse files
authored
[xc-admin] Decoders can't throw (#486)
* Push code * Format
1 parent 411ed32 commit bab7517

File tree

8 files changed

+109
-67
lines changed

8 files changed

+109
-67
lines changed

xc-admin/packages/xc-admin-common/src/__tests__/GovernancePayload.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ test("GovernancePayload ser/de", (done) => {
1414
buffer.equals(Buffer.from([80, 84, 71, 77, 0, 0, 0, 26]))
1515
).toBeTruthy();
1616
let governanceHeader = PythGovernanceHeader.decode(buffer);
17-
expect(governanceHeader.targetChainId).toBe("pythnet");
18-
expect(governanceHeader.action).toBe("ExecutePostedVaa");
17+
expect(governanceHeader?.targetChainId).toBe("pythnet");
18+
expect(governanceHeader?.action).toBe("ExecutePostedVaa");
1919

2020
// Valid header 2
2121
expectedGovernanceHeader = new PythGovernanceHeader(
@@ -37,25 +37,25 @@ test("GovernancePayload ser/de", (done) => {
3737
expect(governanceHeader?.action).toBe("SetFee");
3838

3939
// Wrong magic number
40-
expect(() =>
40+
expect(
4141
PythGovernanceHeader.decode(
4242
Buffer.from([0, 0, 0, 0, 0, 0, 0, 26, 0, 0, 0, 0])
4343
)
44-
).toThrow("Wrong magic number");
44+
).toBeUndefined();
4545

4646
// Wrong chain
47-
expect(() =>
47+
expect(
4848
PythGovernanceHeader.decode(
4949
Buffer.from([80, 84, 71, 77, 0, 0, 255, 255, 0, 0, 0, 0])
5050
)
51-
).toThrow("Chain Id not found");
51+
).toBeUndefined();
5252

5353
// Wrong module/action combination
54-
expect(() =>
54+
expect(
5555
PythGovernanceHeader.decode(
5656
Buffer.from([80, 84, 71, 77, 0, 1, 0, 26, 0, 0, 0, 0])
5757
)
58-
).toThrow("Invalid header, action doesn't match module");
58+
).toBeUndefined();
5959

6060
// Decode executePostVaa with empty instructions
6161
let expectedExecutePostedVaa = new ExecutePostedVaa("pythnet", []);

xc-admin/packages/xc-admin-common/src/__tests__/WormholeMultisigInstruction.test.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,6 @@ test("Wormhole multisig instruction parse: send message with governance payload"
199199
.then((instruction) => {
200200
const parsedInstruction = parser.parseInstruction(instruction);
201201
if (parsedInstruction instanceof WormholeMultisigInstruction) {
202-
expect(
203-
parsedInstruction instanceof WormholeMultisigInstruction
204-
).toBeTruthy();
205202
expect(parsedInstruction.program).toBe(
206203
MultisigInstructionProgram.WormholeBridge
207204
);
@@ -313,15 +310,13 @@ test("Wormhole multisig instruction parse: send message with governance payload"
313310
);
314311
expect(parsedInstruction.args.consistencyLevel).toBe(0);
315312

316-
if (
317-
parsedInstruction.args.governanceAction instanceof ExecutePostedVaa
318-
) {
319-
expect(parsedInstruction.args.governanceAction.targetChainId).toBe(
313+
if (parsedInstruction.governanceAction instanceof ExecutePostedVaa) {
314+
expect(parsedInstruction.governanceAction.targetChainId).toBe(
320315
"pythnet"
321316
);
322317

323318
(
324-
parsedInstruction.args.governanceAction
319+
parsedInstruction.governanceAction
325320
.instructions as TransactionInstruction[]
326321
).forEach((instruction, i) => {
327322
expect(

xc-admin/packages/xc-admin-common/src/governance_payload/ExecutePostedVaa.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { ChainId, ChainName } from "@certusone/wormhole-sdk";
22
import * as BufferLayout from "@solana/buffer-layout";
3-
import { PythGovernanceAction, PythGovernanceHeader } from ".";
3+
import {
4+
PythGovernanceAction,
5+
PythGovernanceHeader,
6+
safeLayoutDecode,
7+
} from ".";
48
import { Layout } from "@solana/buffer-layout";
59
import {
610
AccountMeta,
@@ -75,11 +79,16 @@ export class ExecutePostedVaa implements PythGovernanceAction {
7579
}
7680

7781
/** Decode ExecutePostedVaa */
78-
static decode(data: Buffer): ExecutePostedVaa {
79-
let header = PythGovernanceHeader.decode(data);
80-
let deserialized = this.layout.decode(
82+
static decode(data: Buffer): ExecutePostedVaa | undefined {
83+
const header = PythGovernanceHeader.decode(data);
84+
if (!header) return undefined;
85+
86+
const deserialized = safeLayoutDecode(
87+
this.layout,
8188
data.subarray(PythGovernanceHeader.span)
8289
);
90+
if (!deserialized) return undefined;
91+
8392
let instructions: TransactionInstruction[] = deserialized.map((ix) => {
8493
let programId: PublicKey = new PublicKey(ix.programId);
8594
let keys: AccountMeta[] = ix.accounts.map((acc) => {

xc-admin/packages/xc-admin-common/src/governance_payload/index.ts

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const TargetAction = {
3030
/** Helper to get the ActionName from a (moduleId, actionId) tuple*/
3131
export function toActionName(
3232
deserialized: Readonly<{ moduleId: number; actionId: number }>
33-
): ActionName {
33+
): ActionName | undefined {
3434
if (deserialized.moduleId == MODULE_EXECUTOR && deserialized.actionId == 0) {
3535
return "ExecutePostedVaa";
3636
} else if (deserialized.moduleId == MODULE_TARGET) {
@@ -49,7 +49,7 @@ export function toActionName(
4949
return "RequestGovernanceDataSourceTransfer";
5050
}
5151
}
52-
throw new Error("Invalid header, action doesn't match module");
52+
return undefined;
5353
}
5454

5555
export declare type ActionName =
@@ -84,23 +84,28 @@ export class PythGovernanceHeader {
8484
this.action = action;
8585
}
8686
/** Decode Pyth Governance Header */
87-
static decode(data: Buffer): PythGovernanceHeader {
88-
let deserialized = this.layout.decode(data);
89-
if (deserialized.magicNumber !== MAGIC_NUMBER) {
90-
throw new Error("Wrong magic number");
91-
}
87+
static decode(data: Buffer): PythGovernanceHeader | undefined {
88+
const deserialized = safeLayoutDecode(this.layout, data);
9289

93-
if (!toChainName(deserialized.chain)) {
94-
throw new Error("Chain Id not found");
95-
}
90+
if (!deserialized) return undefined;
9691

97-
return new PythGovernanceHeader(
98-
toChainName(deserialized.chain),
99-
toActionName({
100-
actionId: deserialized.action,
101-
moduleId: deserialized.module,
102-
})
103-
);
92+
if (deserialized.magicNumber !== MAGIC_NUMBER) return undefined;
93+
94+
if (!toChainName(deserialized.chain)) return undefined;
95+
96+
const actionName = toActionName({
97+
actionId: deserialized.action,
98+
moduleId: deserialized.module,
99+
});
100+
101+
if (actionName) {
102+
return new PythGovernanceHeader(
103+
toChainName(deserialized.chain),
104+
actionName
105+
);
106+
} else {
107+
return undefined;
108+
}
104109
}
105110

106111
/** Encode Pyth Governance Header */
@@ -134,13 +139,28 @@ export const MODULE_EXECUTOR = 0;
134139
export const MODULE_TARGET = 1;
135140

136141
/** Decode a governance payload */
137-
export function decodeGovernancePayload(data: Buffer): PythGovernanceAction {
142+
export function decodeGovernancePayload(
143+
data: Buffer
144+
): PythGovernanceAction | undefined {
138145
const header = PythGovernanceHeader.decode(data);
146+
if (!header) return undefined;
147+
139148
switch (header.action) {
140149
case "ExecutePostedVaa":
141150
return ExecutePostedVaa.decode(data);
142151
default:
143-
throw "Not supported";
152+
return undefined;
153+
}
154+
}
155+
156+
export function safeLayoutDecode<T>(
157+
layout: BufferLayout.Layout<T>,
158+
data: Buffer
159+
): T | undefined {
160+
try {
161+
return layout.decode(data);
162+
} catch {
163+
return undefined;
144164
}
145165
}
146166

xc-admin/packages/xc-admin-common/src/multisig_transaction/PythMultisigInstruction.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { MultisigInstruction, MultisigInstructionProgram } from ".";
1+
import {
2+
MultisigInstruction,
3+
MultisigInstructionProgram,
4+
UNRECOGNIZED_INSTRUCTION,
5+
} from ".";
26
import { AnchorAccounts, resolveAccountNames } from "./anchor";
37
import { pythIdl, pythOracleCoder } from "@pythnetwork/client";
48
import { TransactionInstruction } from "@solana/web3.js";
@@ -35,8 +39,8 @@ export class PythMultisigInstruction implements MultisigInstruction {
3539
);
3640
} else {
3741
return new PythMultisigInstruction(
38-
"Unrecognized instruction",
39-
{},
42+
UNRECOGNIZED_INSTRUCTION,
43+
{ data: instruction.data },
4044
{ named: {}, remaining: instruction.keys }
4145
);
4246
}

xc-admin/packages/xc-admin-common/src/multisig_transaction/WormholeMultisigInstruction.ts

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ import { createReadOnlyWormholeProgramInterface } from "@certusone/wormhole-sdk/
22
import { WormholeInstructionCoder } from "@certusone/wormhole-sdk/lib/cjs/solana/wormhole/coder/instruction";
33
import { getPythClusterApiUrl } from "@pythnetwork/client/lib/cluster";
44
import { Connection, TransactionInstruction } from "@solana/web3.js";
5-
import { MultisigInstruction, MultisigInstructionProgram } from ".";
5+
import {
6+
MultisigInstruction,
7+
MultisigInstructionProgram,
8+
UNRECOGNIZED_INSTRUCTION,
9+
} from ".";
610
import {
711
decodeGovernancePayload,
812
PythGovernanceAction,
@@ -14,15 +18,18 @@ export class WormholeMultisigInstruction implements MultisigInstruction {
1418
readonly name: string;
1519
readonly args: { [key: string]: any };
1620
readonly accounts: AnchorAccounts;
21+
readonly governanceAction: PythGovernanceAction | undefined;
1722

1823
constructor(
1924
name: string,
2025
args: { [key: string]: any },
21-
accounts: AnchorAccounts
26+
accounts: AnchorAccounts,
27+
governanceAction: PythGovernanceAction | undefined
2228
) {
2329
this.name = name;
2430
this.args = args;
2531
this.accounts = accounts;
32+
this.governanceAction = governanceAction;
2633
}
2734

2835
static fromTransactionInstruction(
@@ -38,32 +45,38 @@ export class WormholeMultisigInstruction implements MultisigInstruction {
3845
).decode(instruction.data);
3946

4047
if (deserializedData) {
41-
let result = new WormholeMultisigInstruction(
42-
deserializedData.name,
43-
deserializedData.data,
44-
resolveAccountNames(
45-
wormholeProgram.idl,
46-
deserializedData.name,
47-
instruction
48-
)
49-
);
48+
if (deserializedData.name === "postMessage") {
49+
const decodedGovernanceAction: PythGovernanceAction | undefined =
50+
decodeGovernancePayload((deserializedData.data as any).payload);
5051

51-
if (result.name === "postMessage") {
52-
try {
53-
const decoded: PythGovernanceAction = decodeGovernancePayload(
54-
result.args.payload
55-
);
56-
result.args.governanceAction = decoded;
57-
} catch {
58-
result.args.governanceAction = {};
59-
}
52+
return new WormholeMultisigInstruction(
53+
deserializedData.name,
54+
deserializedData.data,
55+
resolveAccountNames(
56+
wormholeProgram.idl,
57+
deserializedData.name,
58+
instruction
59+
),
60+
decodedGovernanceAction
61+
);
62+
} else {
63+
return new WormholeMultisigInstruction(
64+
deserializedData.name,
65+
deserializedData.data,
66+
resolveAccountNames(
67+
wormholeProgram.idl,
68+
deserializedData.name,
69+
instruction
70+
),
71+
undefined
72+
);
6073
}
61-
return result;
6274
} else {
6375
return new WormholeMultisigInstruction(
64-
"Unrecognized instruction",
65-
{},
66-
{ named: {}, remaining: instruction.keys }
76+
UNRECOGNIZED_INSTRUCTION,
77+
{ data: instruction.data },
78+
{ named: {}, remaining: instruction.keys },
79+
undefined
6780
);
6881
}
6982
}

xc-admin/packages/xc-admin-common/src/multisig_transaction/anchor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function resolveAccountNames(
1515
): { named: NamedAccounts; remaining: RemainingAccounts } {
1616
const ix = idl.instructions.find((ix) => ix.name == name);
1717
if (!ix) {
18-
throw Error("Instruction name not found");
18+
return { named: {}, remaining: instruction.keys };
1919
}
2020
const named: NamedAccounts = {};
2121
const remaining: RemainingAccounts = [];

xc-admin/packages/xc-admin-common/src/multisig_transaction/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { WORMHOLE_ADDRESS } from "../wormhole";
77
import { PythMultisigInstruction } from "./PythMultisigInstruction";
88
import { WormholeMultisigInstruction } from "./WormholeMultisigInstruction";
99

10+
export const UNRECOGNIZED_INSTRUCTION = "unrecognizedInstruction";
1011
export enum MultisigInstructionProgram {
1112
PythOracle,
1213
WormholeBridge,

0 commit comments

Comments
 (0)