138 lines
3.2 KiB
TypeScript
138 lines
3.2 KiB
TypeScript
import {
|
|
ChainId,
|
|
ChainName,
|
|
toChainId,
|
|
toChainName,
|
|
} from "@certusone/wormhole-sdk";
|
|
import * as BufferLayout from "@solana/buffer-layout";
|
|
|
|
export const ExecutorAction = {
|
|
ExecutePostedVaa: 0,
|
|
} as const;
|
|
|
|
export const TargetAction = {
|
|
UpgradeContract: 0,
|
|
AuthorizeGovernanceDataSourceTransfer: 1,
|
|
SetDataSources: 2,
|
|
SetFee: 3,
|
|
SetValidPeriod: 4,
|
|
RequestGovernanceDataSourceTransfer: 5,
|
|
} as const;
|
|
|
|
export function toActionName(
|
|
deserialized: Readonly<{ moduleId: number; actionId: number }>
|
|
): ActionName {
|
|
if (deserialized.moduleId == MODULE_EXECUTOR && deserialized.actionId == 0) {
|
|
return "ExecutePostedVaa";
|
|
} else if (deserialized.moduleId == MODULE_TARGET) {
|
|
switch (deserialized.actionId) {
|
|
case 0:
|
|
return "UpgradeContract";
|
|
case 1:
|
|
return "AuthorizeGovernanceDataSourceTransfer";
|
|
case 2:
|
|
return "SetDataSources";
|
|
case 3:
|
|
return "SetFee";
|
|
case 4:
|
|
return "SetValidPeriod";
|
|
case 5:
|
|
return "RequestGovernanceDataSourceTransfer";
|
|
}
|
|
}
|
|
throw new Error("Invalid header, action doesn't match module");
|
|
}
|
|
export declare type ActionName =
|
|
| keyof typeof ExecutorAction
|
|
| keyof typeof TargetAction;
|
|
|
|
export type PythGovernanceHeader = {
|
|
targetChainId: ChainName;
|
|
action: ActionName;
|
|
};
|
|
|
|
export const MAGIC_NUMBER = 0x4d475450;
|
|
export const MODULE_EXECUTOR = 0;
|
|
export const MODULE_TARGET = 1;
|
|
|
|
export function governanceHeaderLayout(): BufferLayout.Structure<
|
|
Readonly<{
|
|
magicNumber: number;
|
|
module: number;
|
|
action: number;
|
|
chain: ChainId;
|
|
}>
|
|
> {
|
|
return BufferLayout.struct(
|
|
[
|
|
BufferLayout.u32("magicNumber"),
|
|
BufferLayout.u8("module"),
|
|
BufferLayout.u8("action"),
|
|
BufferLayout.u16be("chain"),
|
|
],
|
|
"header"
|
|
);
|
|
}
|
|
|
|
/** Decode Pyth Governance Header and return undefined if the header is invalid */
|
|
export function decodeHeader(data: Buffer): PythGovernanceHeader | undefined {
|
|
let deserialized = governanceHeaderLayout().decode(data);
|
|
return verifyHeader(deserialized);
|
|
}
|
|
|
|
export function encodeHeader(
|
|
src: PythGovernanceHeader,
|
|
buffer: Buffer
|
|
): number {
|
|
let module: number;
|
|
let action: number;
|
|
if (src.action in ExecutorAction) {
|
|
module = MODULE_EXECUTOR;
|
|
action = ExecutorAction[src.action as keyof typeof ExecutorAction];
|
|
} else {
|
|
module = MODULE_TARGET;
|
|
action = TargetAction[src.action as keyof typeof TargetAction];
|
|
}
|
|
return governanceHeaderLayout().encode(
|
|
{
|
|
magicNumber: MAGIC_NUMBER,
|
|
module,
|
|
action,
|
|
chain: toChainId(src.targetChainId),
|
|
},
|
|
buffer
|
|
);
|
|
}
|
|
|
|
export function verifyHeader(
|
|
deserialized: Readonly<{
|
|
magicNumber: number;
|
|
module: number;
|
|
action: number;
|
|
chain: ChainId;
|
|
}>
|
|
) {
|
|
if (deserialized.magicNumber !== MAGIC_NUMBER) {
|
|
return undefined;
|
|
}
|
|
|
|
if (!toChainName(deserialized.chain)) {
|
|
return undefined;
|
|
}
|
|
|
|
try {
|
|
let governanceHeader: PythGovernanceHeader = {
|
|
targetChainId: toChainName(deserialized.chain),
|
|
action: toActionName({
|
|
actionId: deserialized.action,
|
|
moduleId: deserialized.module,
|
|
}),
|
|
};
|
|
return governanceHeader;
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
}
|
|
|
|
export { decodeExecutePostedVaa } from "./ExecutePostedVaa";
|