diff --git a/bun.lock b/bun.lock index 94d1fa0..f76b067 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,8 @@ "name": "example-executor-ci-test", "dependencies": { "@types/express": "^5.0.3", + "@wormhole-foundation/sdk-base": "^2.4.0", + "@wormhole-foundation/sdk-definitions": "^2.4.0", "express": "^5.1.0", "viem": "^2.31.7", }, @@ -57,10 +59,16 @@ "@types/serve-static": ["@types/serve-static@1.15.8", "", { "dependencies": { "@types/http-errors": "*", "@types/node": "*", "@types/send": "*" } }, "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg=="], + "@wormhole-foundation/sdk-base": ["@wormhole-foundation/sdk-base@2.4.0", "", { "dependencies": { "@scure/base": "^1.1.3", "binary-layout": "^1.0.3" } }, "sha512-8LbhBMeiDgykZSYc3hkV48bUbasYppCTt3CBQltqBGNUO56sr08HLLCde7R1y9/15qmgQv4cijjp0i/0fNu5hA=="], + + "@wormhole-foundation/sdk-definitions": ["@wormhole-foundation/sdk-definitions@2.4.0", "", { "dependencies": { "@noble/curves": "^1.4.0", "@noble/hashes": "^1.3.1", "@wormhole-foundation/sdk-base": "2.4.0" } }, "sha512-Aqx3/XLaBzbt5kt70N0lnVj3acGe/DYN66R4lG7AVv7VvDTSj2PKC0qOdbKgMh+bzFbjKK03fJkpUzl/d6eo+A=="], + "abitype": ["abitype@1.0.8", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3 >=3.22.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg=="], "accepts": ["accepts@2.0.0", "", { "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" } }, "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng=="], + "binary-layout": ["binary-layout@1.3.0", "", {}, "sha512-jDJ6rLgjjQ9q8NP5eIumdvsegbbMsNplJ7GHMuVnMWi0Qw59o8kIOw+ew4fLAryPL3LgIp5KrjfdAMoSpmpO8w=="], + "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], "bun-types": ["bun-types@1.2.18", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-04+Eha5NP7Z0A9YgDAzMk5PHR16ZuLVa83b26kH5+cp1qZW4F6FmAURngE7INf4tKOvCE69vYvDEwoNl1tGiWw=="], diff --git a/package.json b/package.json index e27eeca..85c158a 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ }, "dependencies": { "@types/express": "^5.0.3", + "@wormhole-foundation/sdk-base": "^2.4.0", + "@wormhole-foundation/sdk-definitions": "^2.4.0", "express": "^5.1.0", "viem": "^2.31.7" } diff --git a/src/abis/core.ts b/src/abis/core.ts index 3671a59..f6057a7 100644 --- a/src/abis/core.ts +++ b/src/abis/core.ts @@ -1,4 +1,4 @@ -// partial ABI from the Wormhole Core contract +// partial ABI from the Wormhole Core contract's implementation // https://etherscan.io/address/0x3c3d457f1522d3540ab3325aa5f1864e34cba9d0#code export const CORE_ABI = [ @@ -29,6 +29,13 @@ export const CORE_ABI = [ name: "LogMessagePublished", type: "event", }, + { + inputs: [], + name: "chainId", + outputs: [{ internalType: "uint16", name: "", type: "uint16" }], + stateMutability: "view", + type: "function", + }, { inputs: [], name: "getCurrentGuardianSetIndex", diff --git a/src/consts.ts b/src/consts.ts new file mode 100644 index 0000000..1027025 --- /dev/null +++ b/src/consts.ts @@ -0,0 +1,10 @@ +import { toHex } from "viem"; +import { mnemonicToAccount } from "viem/accounts"; + +const account = mnemonicToAccount( + "test test test test test test test test test test test junk", + { addressIndex: 9 } +); + +export const EVM_PUBLIC_KEY = account.address; +export const EVM_PRIVATE_KEY = toHex(account.getHdKey().privateKey || "0x"); diff --git a/src/mockGuardian.ts b/src/mockGuardian.ts new file mode 100644 index 0000000..20ded86 --- /dev/null +++ b/src/mockGuardian.ts @@ -0,0 +1,96 @@ +import { toChain } from "@wormhole-foundation/sdk-base"; +import { + createVAA, + serialize, + UniversalAddress, + type VAA, +} from "@wormhole-foundation/sdk-definitions"; +import { mocks } from "@wormhole-foundation/sdk-definitions/testing"; +import { + createPublicClient, + getContract, + http, + isAddressEqual, + padHex, + parseEventLogs, + toBytes, + type Hex, +} from "viem"; +import { anvil } from "viem/chains"; +import { CORE_ABI } from "./abis/core"; +import { EVM_PRIVATE_KEY } from "./consts"; + +async function getWormholeMessage( + rpc: string, + txHash: Hex, + coreContractAddress: Hex +): Promise | undefined> { + console.log(`Mocking guardian signatures for ${rpc} ${txHash}`); + const transport = http(rpc); + const client = createPublicClient({ + chain: anvil, + transport, + }); + const transaction = await client.getTransactionReceipt({ + hash: txHash, + }); + const coreContract = getContract({ + address: coreContractAddress, + abi: CORE_ABI, + client, + }); + const chainId = await coreContract.read.chainId(); + const guardianSetIndex = await coreContract.read.getCurrentGuardianSetIndex(); + const topics = parseEventLogs({ + eventName: "LogMessagePublished", + abi: CORE_ABI, + logs: transaction.logs, + }); + for (const topic of topics) { + if ( + topic.removed === false && + isAddressEqual(topic.address, coreContractAddress) + ) { + const emitter = topic.args.sender; + return createVAA("Uint8Array", { + guardianSet: guardianSetIndex, + timestamp: Number( + ( + await client.getBlock({ + blockHash: transaction.blockHash, + includeTransactions: false, + }) + ).timestamp + ), + // NOTE: the Wormhole SDK requires this be a known chain, though that is not strictly necessary for our use case. + emitterChain: toChain(chainId), + emitterAddress: new UniversalAddress( + toBytes(padHex(emitter, { dir: "left", size: 32 })) + ), + consistencyLevel: topic.args.consistencyLevel, + sequence: topic.args.sequence, + nonce: topic.args.nonce, + signatures: [], + payload: toBytes(topic.args.payload), + }); + } + } +} + +/** + * returns a base64 string like a guardian /v1/signed_vaa/ + */ +export async function mockWormhole( + rpc: string, + txHash: Hex, + coreContractAddress: Hex +): Promise { + const vaa = await getWormholeMessage(rpc, txHash, coreContractAddress); + if (vaa) { + const guardianSet = new mocks.MockGuardians(0, [EVM_PRIVATE_KEY]); + const signedVaa = guardianSet.addSignatures(vaa); + const base64 = Buffer.from(serialize(signedVaa)).toString("base64"); + return base64; + } + return ""; +} diff --git a/src/overrideGuardianSet.ts b/src/overrideGuardianSet.ts index beae39b..ddc7a55 100644 --- a/src/overrideGuardianSet.ts +++ b/src/overrideGuardianSet.ts @@ -12,6 +12,7 @@ import { } from "viem"; import { anvil } from "viem/chains"; import { CORE_ABI } from "./abis/core"; +import { EVM_PUBLIC_KEY } from "./consts"; export async function overrideGuardianSet( anvilRpcUrl: string, @@ -50,7 +51,7 @@ export async function overrideGuardianSet( address: coreContractAddress, index: toHex(firstIndexStorageSlot), // devnet guardian https://github.com/wormhole-foundation/wormhole/blob/b9d34bef10ec74c345fa4b406559cf44e3d70095/scripts/devnet-consts.json#L323 - value: padHex("0xbeFA429d57cD18b7F8A4d91A2da9AB4AF05d0FBe", { + value: padHex(EVM_PUBLIC_KEY, { dir: "left", size: 32, }),