wormhole/clients/js/src/vaa.ts

1076 lines
29 KiB
TypeScript

import { Parser } from "binary-parser";
import * as elliptic from "elliptic";
import { BigNumber, ethers } from "ethers";
import { solidityKeccak256 } from "ethers/lib/utils";
export interface Signature {
guardianSetIndex: number;
signature: string;
}
export interface VAA<T> {
version: number;
guardianSetIndex: number;
signatures: Signature[];
timestamp: number;
nonce: number;
emitterChain: number;
emitterAddress: string;
sequence: bigint;
consistencyLevel: number;
payload: T;
}
class P<T> {
private parser: Parser;
constructor(parser: Parser) {
this.parser = parser;
}
// Try to parse a buffer with a parser, and return null if it failed due to an
// assertion error.
parse(buffer: Buffer): T | null {
try {
let result = this.parser.parse(buffer);
delete result["end"];
return result;
} catch (e: any) {
if (e.message?.includes("Assertion error")) {
return null;
} else {
throw e;
}
}
}
or<U>(other: P<U>): P<T | U> {
let p = new P<T | U>(other.parser);
p.parse = (buffer: Buffer): T | U | null => {
return this.parse(buffer) ?? other.parse(buffer);
};
return p;
}
}
export interface Other {
type: "Other";
hex: string;
ascii?: string;
}
// All the different types of payloads
export type Payload =
| GuardianSetUpgrade
| CoreContractUpgrade
| PortalContractUpgrade<"TokenBridge">
| PortalContractUpgrade<"NFTBridge">
| PortalContractUpgrade<"WormholeRelayer">
| PortalRegisterChain<"TokenBridge">
| PortalRegisterChain<"NFTBridge">
| PortalRegisterChain<"WormholeRelayer">
| TokenBridgeTransfer
| TokenBridgeTransferWithPayload
| TokenBridgeAttestMeta
| NFTBridgeTransfer
| CoreContractRecoverChainId
| PortalContractRecoverChainId<"TokenBridge">
| PortalContractRecoverChainId<"NFTBridge">
| WormholeRelayerSetDefaultDeliveryProvider;
export type ContractUpgrade =
| CoreContractUpgrade
| PortalContractUpgrade<"TokenBridge">
| PortalContractUpgrade<"NFTBridge">
| PortalContractUpgrade<"WormholeRelayer">;
export type RecoverChainId =
| CoreContractRecoverChainId
| PortalContractRecoverChainId<"TokenBridge">
| PortalContractRecoverChainId<"NFTBridge">;
export function parse(buffer: Buffer): VAA<Payload | Other> {
const vaa = parseEnvelope(buffer);
const parser = guardianSetUpgradeParser
.or(coreContractUpgradeParser)
.or(portalContractUpgradeParser("TokenBridge"))
.or(portalContractUpgradeParser("NFTBridge"))
.or(portalContractUpgradeParser("WormholeRelayer"))
.or(portalRegisterChainParser("TokenBridge"))
.or(portalRegisterChainParser("NFTBridge"))
.or(portalRegisterChainParser("WormholeRelayer"))
.or(tokenBridgeTransferParser())
.or(tokenBridgeTransferWithPayloadParser())
.or(tokenBridgeAttestMetaParser())
.or(nftBridgeTransferParser())
.or(coreContractRecoverChainId())
.or(portalContractRecoverChainId("TokenBridge"))
.or(portalContractRecoverChainId("NFTBridge"))
.or(wormholeRelayerSetDefaultDeliveryProvider());
let payload: Payload | Other | null = parser.parse(vaa.payload);
if (payload === null) {
payload = {
type: "Other",
hex: Buffer.from(vaa.payload).toString("hex"),
ascii: Buffer.from(vaa.payload).toString("utf8"),
};
} else {
delete (payload as any)["tokenURILength"];
}
var myVAA = { ...vaa, payload };
return myVAA;
}
export function assertKnownPayload(
vaa: VAA<Payload | Other>
): asserts vaa is VAA<Payload> {
if (vaa.payload.type === "Other") {
throw Error(`Couldn't parse VAA payload: ${vaa.payload.hex}`);
}
}
// Parse the VAA envelope without looking into the payload.
// If you want to parse the payload as well, use 'parse'.
export function parseEnvelope(buffer: Buffer): VAA<Buffer> {
var vaa = vaaParser.parse(buffer);
delete vaa["end"];
delete vaa["signatureCount"];
vaa.payload = Buffer.from(vaa.payload);
return vaa;
}
// Parse a signature
const signatureParser = new Parser()
.endianess("big")
.uint8("guardianSetIndex")
.array("signature", {
type: "uint8",
lengthInBytes: 65,
formatter: (arr) => Buffer.from(arr).toString("hex"),
});
function serialiseSignature(sig: Signature): string {
const body = [encode("uint8", sig.guardianSetIndex), sig.signature];
return body.join("");
}
// Parse a vaa envelope. The payload is returned as a byte array.
const vaaParser = new Parser()
.endianess("big")
.uint8("version")
.uint32("guardianSetIndex")
.uint8("signatureCount")
.array("signatures", {
type: signatureParser,
length: "signatureCount",
})
.uint32("timestamp")
.uint32("nonce")
.uint16("emitterChain")
.array("emitterAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.uint64("sequence")
.uint8("consistencyLevel")
.array("payload", {
type: "uint8",
readUntil: "eof",
})
.string("end", {
greedy: true,
assert: (str) => str === "",
});
export function serialiseVAA(vaa: VAA<Payload>) {
const body = [
encode("uint8", vaa.version),
encode("uint32", vaa.guardianSetIndex),
encode("uint8", vaa.signatures.length),
...vaa.signatures.map((sig) => serialiseSignature(sig)),
vaaBody(vaa),
];
return body.join("");
}
export function vaaDigest(vaa: VAA<Payload | Other>) {
return solidityKeccak256(
["bytes"],
[solidityKeccak256(["bytes"], ["0x" + vaaBody(vaa)])]
);
}
function vaaBody(vaa: VAA<Payload | Other>) {
let payload_str: string;
if (vaa.payload.type === "Other") {
payload_str = vaa.payload.hex;
} else {
let payload = vaa.payload;
switch (payload.module) {
case "Core":
switch (payload.type) {
case "GuardianSetUpgrade":
payload_str = serialiseGuardianSetUpgrade(payload);
break;
case "ContractUpgrade":
payload_str = serialiseCoreContractUpgrade(payload);
break;
case "RecoverChainId":
payload_str = serialiseCoreContractRecoverChainId(payload);
break;
default:
impossible(payload);
break;
}
break;
case "NFTBridge":
switch (payload.type) {
case "ContractUpgrade":
payload_str = serialisePortalContractUpgrade(payload);
break;
case "RecoverChainId":
payload_str = serialisePortalContractRecoverChainId(payload);
break;
case "RegisterChain":
payload_str = serialisePortalRegisterChain(payload);
break;
case "Transfer":
payload_str = serialiseNFTBridgeTransfer(payload);
break;
default:
impossible(payload);
break;
}
break;
case "TokenBridge":
switch (payload.type) {
case "ContractUpgrade":
payload_str = serialisePortalContractUpgrade(payload);
break;
case "RecoverChainId":
payload_str = serialisePortalContractRecoverChainId(payload);
break;
case "RegisterChain":
payload_str = serialisePortalRegisterChain(payload);
break;
case "Transfer":
payload_str = serialiseTokenBridgeTransfer(payload);
break;
case "TransferWithPayload":
payload_str = serialiseTokenBridgeTransferWithPayload(payload);
break;
case "AttestMeta":
payload_str = serialiseTokenBridgeAttestMeta(payload);
break;
default:
impossible(payload);
break;
}
break;
case "WormholeRelayer":
switch (payload.type) {
case "ContractUpgrade":
payload_str = serialisePortalContractUpgrade(payload);
break;
case "RegisterChain":
payload_str = serialisePortalRegisterChain(payload);
break;
case "SetDefaultDeliveryProvider":
payload_str =
serialiseWormholeRelayerSetDefaultDeliveryProvider(payload);
break;
default:
impossible(payload);
break;
}
break;
default:
impossible(payload);
break;
}
}
const body = [
encode("uint32", vaa.timestamp),
encode("uint32", vaa.nonce),
encode("uint16", vaa.emitterChain),
encode("bytes32", hex(vaa.emitterAddress)),
encode("uint64", vaa.sequence),
encode("uint8", vaa.consistencyLevel),
payload_str,
];
return body.join("");
}
export function sign(signers: string[], vaa: VAA<Payload>): Signature[] {
const hash = vaaDigest(vaa);
const ec = new elliptic.ec("secp256k1");
return signers.map((signer, i) => {
const key = ec.keyFromPrivate(signer);
const signature = key.sign(Buffer.from(hash.substr(2), "hex"), {
canonical: true,
});
const packed = [
signature.r.toString("hex").padStart(64, "0"),
signature.s.toString("hex").padStart(64, "0"),
encode("uint8", signature.recoveryParam),
].join("");
return {
guardianSetIndex: i,
signature: packed,
};
});
}
// Parse an address of given length, and render it as hex
const addressParser = (length: number) =>
new Parser().endianess("big").array("address", {
type: "uint8",
lengthInBytes: length,
formatter: (arr) => Buffer.from(arr).toString("hex"),
});
////////////////////////////////////////////////////////////////////////////////
// Guardian set upgrade
export interface GuardianSetUpgrade {
module: "Core";
type: "GuardianSetUpgrade";
chain: number;
newGuardianSetIndex: number;
newGuardianSetLength: number;
newGuardianSet: string[];
}
// Parse a guardian set upgrade payload
const guardianSetUpgradeParser: P<GuardianSetUpgrade> = new P(
new Parser()
.endianess("big")
.string("module", {
length: 32,
encoding: "hex",
assert: Buffer.from("Core").toString("hex").padStart(64, "0"),
formatter: (_str) => "Core",
})
.uint8("type", {
assert: 2,
formatter: (_action) => "GuardianSetUpgrade",
})
.uint16("chain")
.uint32("newGuardianSetIndex")
.uint8("newGuardianSetLength")
.array("newGuardianSet", {
type: addressParser(20),
length: "newGuardianSetLength",
formatter: (arr: [{ address: string }]) =>
arr.map((addr) => addr.address),
})
.string("end", {
greedy: true,
assert: (str) => str === "",
})
);
function serialiseGuardianSetUpgrade(payload: GuardianSetUpgrade): string {
const body = [
encode("bytes32", encodeString(payload.module)),
encode("uint8", 2),
encode("uint16", payload.chain),
encode("uint32", payload.newGuardianSetIndex),
encode("uint8", payload.newGuardianSet.length),
...payload.newGuardianSet,
];
return body.join("");
}
////////////////////////////////////////////////////////////////////////////////
// Contract upgrades
export interface CoreContractUpgrade {
module: "Core";
type: "ContractUpgrade";
chain: number;
address: string;
}
// Parse a core contract upgrade payload
const coreContractUpgradeParser: P<CoreContractUpgrade> = new P(
new Parser()
.endianess("big")
.string("module", {
length: 32,
encoding: "hex",
assert: Buffer.from("Core").toString("hex").padStart(64, "0"),
formatter: (_str) => "Core",
})
.uint8("type", {
assert: 1,
formatter: (_action) => "ContractUpgrade",
})
.uint16("chain")
.array("address", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.string("end", {
greedy: true,
assert: (str) => str === "",
})
);
function serialiseCoreContractUpgrade(payload: CoreContractUpgrade): string {
const body = [
encode("bytes32", encodeString(payload.module)),
encode("uint8", 1),
encode("uint16", payload.chain),
encode("bytes32", payload.address),
];
return body.join("");
}
export interface PortalContractUpgrade<
Module extends "NFTBridge" | "TokenBridge" | "WormholeRelayer",
> {
module: Module;
type: "ContractUpgrade";
chain: number;
address: string;
}
// Parse a portal contract upgrade payload
function portalContractUpgradeParser<
Module extends "NFTBridge" | "TokenBridge" | "WormholeRelayer",
>(module: Module): P<PortalContractUpgrade<Module>> {
return new P(
new Parser()
.endianess("big")
.string("module", {
length: 32,
encoding: "hex",
assert: Buffer.from(module).toString("hex").padStart(64, "0"),
formatter: (_str: string) => module,
})
.uint8("type", {
assert: 2,
formatter: (_action: number) => "ContractUpgrade",
})
.uint16("chain")
.array("address", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.string("end", {
greedy: true,
assert: (str) => str === "",
})
);
}
function serialisePortalContractUpgrade<
Module extends "NFTBridge" | "TokenBridge" | "WormholeRelayer",
>(payload: PortalContractUpgrade<Module>): string {
const body = [
encode("bytes32", encodeString(payload.module)),
encode("uint8", 2),
encode("uint16", payload.chain),
encode("bytes32", payload.address),
];
return body.join("");
}
////////////////////////////////////////////////////////////////////////////////
// Registrations
export interface PortalRegisterChain<
Module extends "NFTBridge" | "TokenBridge" | "WormholeRelayer",
> {
module: Module;
type: "RegisterChain";
chain: number;
emitterChain: number;
emitterAddress: string;
}
// Parse a portal chain registration payload
function portalRegisterChainParser<
Module extends "NFTBridge" | "TokenBridge" | "WormholeRelayer",
>(module: Module): P<PortalRegisterChain<Module>> {
return new P(
new Parser()
.endianess("big")
.string("module", {
length: 32,
encoding: "hex",
assert: Buffer.from(module).toString("hex").padStart(64, "0"),
formatter: (_str) => module,
})
.uint8("type", {
assert: 1,
formatter: (_action) => "RegisterChain",
})
.uint16("chain")
.uint16("emitterChain")
.array("emitterAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.string("end", {
greedy: true,
assert: (str) => str === "",
})
);
}
function serialisePortalRegisterChain<
Module extends "NFTBridge" | "TokenBridge" | "WormholeRelayer",
>(payload: PortalRegisterChain<Module>): string {
const body = [
encode("bytes32", encodeString(payload.module)),
encode("uint8", 1),
encode("uint16", payload.chain),
encode("uint16", payload.emitterChain),
encode("bytes32", payload.emitterAddress),
];
return body.join("");
}
////////////////////////////////////////////////////////////////////////////////
// RecoverChainId
export interface CoreContractRecoverChainId {
module: "Core";
type: "RecoverChainId";
evmChainId: bigint;
newChainId: number;
}
// Parse a core contract recoverChainId payload
function coreContractRecoverChainId(): P<CoreContractRecoverChainId> {
return new P(
new Parser()
.endianess("big")
.string("module", {
length: 32,
encoding: "hex",
assert: Buffer.from("Core").toString("hex").padStart(64, "0"),
formatter: (_str) => "Core",
})
.uint8("type", {
assert: 5,
formatter: (_action) => "RecoverChainId",
})
.array("evmChainId", {
type: "uint8",
lengthInBytes: 32,
formatter: (bytes) => BigNumber.from(bytes).toBigInt(),
})
.uint16("newChainId")
.string("end", {
greedy: true,
assert: (str) => str === "",
})
);
}
function serialiseCoreContractRecoverChainId(
payload: CoreContractRecoverChainId
): string {
const body = [
encode("bytes32", encodeString(payload.module)),
encode("uint8", 5),
encode("uint256", payload.evmChainId),
encode("uint16", payload.newChainId),
];
return body.join("");
}
export interface PortalContractRecoverChainId<
Module extends "NFTBridge" | "TokenBridge" | "WormholeRelayer",
> {
module: Module;
type: "RecoverChainId";
evmChainId: bigint;
newChainId: number;
}
// Parse a portal contract recoverChainId payload
function portalContractRecoverChainId<
Module extends "NFTBridge" | "TokenBridge" | "WormholeRelayer",
>(module: Module): P<PortalContractRecoverChainId<Module>> {
return new P(
new Parser()
.endianess("big")
.string("module", {
length: 32,
encoding: "hex",
assert: Buffer.from(module).toString("hex").padStart(64, "0"),
formatter: (_str: string) => module,
})
.uint8("type", {
assert: 3,
formatter: (_action: number) => "RecoverChainId",
})
.array("evmChainId", {
type: "uint8",
lengthInBytes: 32,
formatter: (bytes) => BigNumber.from(bytes).toBigInt(),
})
.uint16("newChainId")
.string("end", {
greedy: true,
assert: (str) => str === "",
})
);
}
function serialisePortalContractRecoverChainId<
Module extends "NFTBridge" | "TokenBridge" | "WormholeRelayer",
>(payload: PortalContractRecoverChainId<Module>): string {
const body = [
encode("bytes32", encodeString(payload.module)),
encode("uint8", 3),
encode("uint256", payload.evmChainId),
encode("uint16", payload.newChainId),
];
return body.join("");
}
////////////////////////////////////////////////////////////////////////////////
// Token bridge
// payload 1
export interface TokenBridgeTransfer {
module: "TokenBridge";
type: "Transfer";
amount: bigint;
tokenAddress: string;
tokenChain: number;
toAddress: string;
chain: number;
fee: bigint;
}
function tokenBridgeTransferParser(): P<TokenBridgeTransfer> {
return new P(
new Parser()
.endianess("big")
.string("module", {
length: (_) => 0,
formatter: (_) => "TokenBridge",
})
.uint8("type", {
assert: 1,
formatter: (_action) => "Transfer",
})
.array("amount", {
type: "uint8",
lengthInBytes: 32,
formatter: (bytes) => BigNumber.from(bytes).toBigInt(),
})
.array("tokenAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.uint16("tokenChain")
.array("toAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.uint16("chain")
.array("fee", {
type: "uint8",
lengthInBytes: 32,
formatter: (bytes) => BigNumber.from(bytes).toBigInt(),
})
.string("end", {
greedy: true,
assert: (str) => str === "",
})
);
}
function serialiseTokenBridgeTransfer(payload: TokenBridgeTransfer): string {
const body = [
encode("uint8", 1),
encode("uint256", payload.amount),
encode("bytes32", hex(payload.tokenAddress)),
encode("uint16", payload.tokenChain),
encode("bytes32", hex(payload.toAddress)),
encode("uint16", payload.chain),
encode("uint256", payload.fee),
];
return body.join("");
}
// payload 2
export interface TokenBridgeAttestMeta {
module: "TokenBridge";
type: "AttestMeta";
chain: 0;
tokenAddress: string;
tokenChain: number;
decimals: number;
symbol: string;
name: string;
}
function tokenBridgeAttestMetaParser(): P<TokenBridgeAttestMeta> {
return new P(
new Parser()
.endianess("big")
.string("module", {
length: (_) => 0,
formatter: (_) => "TokenBridge",
})
.string("chain", {
length: (_) => 0,
formatter: (_) => 0,
})
.uint8("type", {
assert: 2,
formatter: (_action) => "AttestMeta",
})
.array("tokenAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.uint16("tokenChain")
.uint8("decimals")
.array("symbol", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr: Uint8Array) =>
Buffer.from(arr).toString(
"utf8",
arr.findIndex((val) => val != 0)
),
})
.array("name", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr: Uint8Array) =>
Buffer.from(arr).toString(
"utf8",
arr.findIndex((val) => val != 0)
),
})
.string("end", {
greedy: true,
assert: (str) => str === "",
})
);
}
function serialiseTokenBridgeAttestMeta(
payload: TokenBridgeAttestMeta
): string {
const body = [
encode("uint8", 2),
encode("bytes32", hex(payload.tokenAddress)),
encode("uint16", payload.tokenChain),
encode("uint8", payload.decimals),
encode("bytes32", encodeStringRight(payload.symbol)),
encode("bytes32", encodeStringRight(payload.name)),
];
return body.join("");
}
// payload 3
export interface TokenBridgeTransferWithPayload {
module: "TokenBridge";
type: "TransferWithPayload";
amount: bigint;
tokenAddress: string;
tokenChain: number;
toAddress: string;
chain: number;
fromAddress: string;
payload: string;
}
function tokenBridgeTransferWithPayloadParser(): P<TokenBridgeTransferWithPayload> {
return new P(
new Parser()
.endianess("big")
.string("module", {
length: (_) => 0,
formatter: (_) => "TokenBridge",
})
.uint8("type", {
assert: 3,
formatter: (_action) => "TransferWithPayload",
})
.array("amount", {
type: "uint8",
lengthInBytes: 32,
formatter: (bytes) => BigNumber.from(bytes).toBigInt(),
})
.array("tokenAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.uint16("tokenChain")
.array("toAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.uint16("chain")
.array("fromAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.array("payload", {
type: "uint8",
greedy: true,
readUntil: "eof",
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
);
}
function serialiseTokenBridgeTransferWithPayload(
payload: TokenBridgeTransferWithPayload
): string {
const body = [
encode("uint8", 3),
encode("uint256", payload.amount),
encode("bytes32", hex(payload.tokenAddress)),
encode("uint16", payload.tokenChain),
encode("bytes32", hex(payload.toAddress)),
encode("uint16", payload.chain),
encode("bytes32", hex(payload.fromAddress)),
payload.payload.substring(2),
];
return body.join("");
}
////////////////////////////////////////////////////////////////////////////////
// NFT bridge
export interface NFTBridgeTransfer {
module: "NFTBridge";
type: "Transfer";
tokenAddress: string;
tokenChain: number;
tokenSymbol: string;
tokenName: string;
tokenId: bigint;
tokenURI: string;
toAddress: string;
chain: number;
}
function nftBridgeTransferParser(): P<NFTBridgeTransfer> {
return new P(
new Parser()
.endianess("big")
.string("module", {
length: (_) => 0,
formatter: (_) => "NFTBridge",
})
.uint8("type", {
assert: 1,
formatter: (_action) => "Transfer",
})
.array("tokenAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.uint16("tokenChain")
.array("tokenSymbol", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr: Uint8Array) =>
Buffer.from(arr).toString(
"utf8",
arr.findIndex((val) => val != 0)
),
})
.array("tokenName", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr: Uint8Array) =>
Buffer.from(arr).toString(
"utf8",
arr.findIndex((val) => val != 0)
),
})
.array("tokenId", {
type: "uint8",
lengthInBytes: 32,
formatter: (bytes) => BigNumber.from(bytes).toBigInt(),
})
.uint8("tokenURILength")
.array("tokenURI", {
type: "uint8",
lengthInBytes: function () {
return (this as any).tokenURILength;
},
formatter: (arr: Uint8Array) => Buffer.from(arr).toString("utf8"),
})
.array("toAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.uint16("chain")
.string("end", {
greedy: true,
assert: (str) => str === "",
})
);
}
function serialiseNFTBridgeTransfer(payload: NFTBridgeTransfer): string {
const body = [
encode("uint8", 1),
encode("bytes32", hex(payload.tokenAddress)),
encode("uint16", payload.tokenChain),
encode("bytes32", encodeStringRight(payload.tokenSymbol)),
encode("bytes32", encodeStringRight(payload.tokenName)),
encode("uint256", payload.tokenId),
encode("uint8", payload.tokenURI.length),
Buffer.from(payload.tokenURI, "utf8").toString("hex"),
encode("bytes32", hex(payload.toAddress)),
encode("uint16", payload.chain),
];
return body.join("");
}
////////////////////////////////////////////////////////////////////////////////
// WormholeRelayer
export interface WormholeRelayerSetDefaultDeliveryProvider {
module: "WormholeRelayer";
type: "SetDefaultDeliveryProvider";
relayProviderAddress: string;
chain: number;
}
function wormholeRelayerSetDefaultDeliveryProvider(): P<WormholeRelayerSetDefaultDeliveryProvider> {
return new P(
new Parser()
.endianess("big")
.string("module", {
length: 32,
encoding: "hex",
assert: Buffer.from("WormholeRelayer")
.toString("hex")
.padStart(64, "0"),
formatter: (_str: string) => "WormholeRelayer",
})
.uint8("type", {
assert: 3,
formatter: (_action) => "SetDefaultDeliveryProvider",
})
.uint16("chain")
.array("relayProviderAddress", {
type: "uint8",
lengthInBytes: 32,
formatter: (arr) => "0x" + Buffer.from(arr).toString("hex"),
})
.string("end", {
greedy: true,
assert: (str) => str === "",
})
);
}
function serialiseWormholeRelayerSetDefaultDeliveryProvider(
payload: WormholeRelayerSetDefaultDeliveryProvider
): string {
const body = [
encode("bytes32", encodeString(payload.module)),
encode("uint8", 3),
encode("uint16", payload.chain),
encode("bytes32", hex(payload.relayProviderAddress)),
];
return body.join("");
}
// This function should be called after pattern matching on all possible options
// of an enum (union) type, so that typescript can derive that no other options
// are possible. If (from JavaScript land) an unsupported argument is passed
// in, this function just throws. If the enum type is extended with new cases,
// the call to this function will then fail to compile, drawing attention to an
// unhandled case somewhere.
export function impossible(a: never): never {
throw new Error(`Impossible: ${a}`);
}
////////////////////////////////////////////////////////////////////////////////
// Encoder utils
export type Encoding =
| "uint8"
| "uint16"
| "uint32"
| "uint64"
| "uint128"
| "uint256"
| "bytes32"
| "address";
export function typeWidth(type: Encoding): number {
switch (type) {
case "uint8":
return 1;
case "uint16":
return 2;
case "uint32":
return 4;
case "uint64":
return 8;
case "uint128":
return 16;
case "uint256":
return 32;
case "bytes32":
return 32;
case "address":
return 20;
}
}
// Couldn't find a satisfactory binary serialisation solution, so we just use
// the ethers library's encoding logic
export function encode(type: Encoding, val: any): string {
// ethers operates on hex strings (sigh) and left pads everything to 32
// bytes (64 characters). We take last 2*n characters where n is the width
// of the type being serialised in bytes (since a byte is represented as 2
// digits in hex).
return ethers.utils.defaultAbiCoder
.encode([type], [val])
.substr(-2 * typeWidth(type));
}
// Encode a string as binary left-padded to 32 bytes, represented as a hex
// string (64 chars long)
export function encodeString(str: string): Buffer {
return Buffer.from(Buffer.from(str).toString("hex").padStart(64, "0"), "hex");
}
// Encode a string as binary right-padded to 32 bytes, represented as a hex
// string (64 chars long)
export function encodeStringRight(str: string): Buffer {
return Buffer.from(Buffer.from(str).toString("hex").padEnd(64, "0"), "hex");
}
// Turn hex string with potentially missing 0x prefix into Buffer
function hex(x: string): Buffer {
return Buffer.from(
ethers.utils.hexlify(x, { allowMissingPrefix: true }).substring(2),
"hex"
);
}