From 67273048845593ab72b465230f724f6c2e9cb453 Mon Sep 17 00:00:00 2001 From: Evan Gray Date: Fri, 11 Jul 2025 10:27:06 -0400 Subject: [PATCH] tilt e2e test --- .github/workflows/tilt.yml | 2 + Dockerfile | 2 +- Dockerfile.e2e | 9 +++ Tiltfile | 20 +++++ evm/Dockerfile | 8 ++ k8s/e2e.yaml | 15 ++++ src/e2e.test.ts | 156 ++++++++++++++++++++++++++++++------- tsconfig.json | 2 +- 8 files changed, 185 insertions(+), 29 deletions(-) create mode 100644 Dockerfile.e2e create mode 100644 evm/Dockerfile create mode 100644 k8s/e2e.yaml diff --git a/.github/workflows/tilt.yml b/.github/workflows/tilt.yml index fb382f0..babaa49 100644 --- a/.github/workflows/tilt.yml +++ b/.github/workflows/tilt.yml @@ -7,6 +7,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + submodules: recursive - name: start minikube id: minikube uses: medyagh/setup-minikube@cea33675329b799adccc9526aa5daccc26cd5052 # pinned to v0.0.19 commit diff --git a/Dockerfile b/Dockerfile index 4c62713..5fce710 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,5 +17,5 @@ USER bun EXPOSE 3000/tcp ENTRYPOINT [ "bun", "start" ] -FROM release as test +FROM release AS test diff --git a/Dockerfile.e2e b/Dockerfile.e2e new file mode 100644 index 0000000..470f3d0 --- /dev/null +++ b/Dockerfile.e2e @@ -0,0 +1,9 @@ +FROM forge + +FROM executor + +WORKDIR /usr/src/app + +COPY --from=forge /app/out evm/out + +ENTRYPOINT [ "bun", "test" ] diff --git a/Tiltfile b/Tiltfile index 8aeef51..fed2166 100644 --- a/Tiltfile +++ b/Tiltfile @@ -25,6 +25,19 @@ docker_build( ] ) +docker_build( + ref = "forge", + context = "evm", + dockerfile = "./evm/Dockerfile", + only=["foundry.toml", "lib", "src"] +) +docker_build( + ref = "e2e", + context = ".", + dockerfile = "./Dockerfile.e2e", + only=[] +) + k8s_yaml("k8s/executor.yaml") k8s_resource( "executor", @@ -32,3 +45,10 @@ k8s_resource( resource_deps = ["anvil-eth-sepolia", "anvil-base-sepolia"], labels = ["app"], ) + +k8s_yaml("k8s/e2e.yaml") +k8s_resource( + "e2e", + resource_deps=["anvil-eth-sepolia", "anvil-base-sepolia", "executor"], + labels=["tests"] +) diff --git a/evm/Dockerfile b/evm/Dockerfile new file mode 100644 index 0000000..a833bf6 --- /dev/null +++ b/evm/Dockerfile @@ -0,0 +1,8 @@ +FROM ghcr.io/foundry-rs/foundry:v1.2.3@sha256:d9133dae61c19383b72695dc7eeca29d1e7a89f1f1b5fdfd8900c660b46b4303 AS forge + +WORKDIR /app +COPY foundry.toml foundry.toml +COPY lib lib +COPY src src + +RUN forge build diff --git a/k8s/e2e.yaml b/k8s/e2e.yaml new file mode 100644 index 0000000..64132ee --- /dev/null +++ b/k8s/e2e.yaml @@ -0,0 +1,15 @@ +kind: Job +apiVersion: batch/v1 +metadata: + name: e2e +spec: + backoffLimit: 0 + template: + metadata: + labels: + app: e2e + spec: + restartPolicy: Never + containers: + - name: e2e + image: e2e diff --git a/src/e2e.test.ts b/src/e2e.test.ts index ddf18bd..11d8ec2 100644 --- a/src/e2e.test.ts +++ b/src/e2e.test.ts @@ -1,23 +1,26 @@ -import { mnemonicToAccount } from "viem/accounts"; -import { ANVIL_MNEMONIC } from "./consts"; -import { test } from "bun:test"; +import { serializeLayout } from "@wormhole-foundation/sdk-base"; +import { relayInstructionsLayout } from "@wormhole-foundation/sdk-definitions"; +import axios from "axios"; +import { sleep } from "bun"; +import { expect, test } from "bun:test"; import { createPublicClient, createWalletClient, getContract, http, + isHex, padHex, toHex, + type Account, + type Chain, + type PublicClient, + type WalletClient, } from "viem"; -import { anvil, sepolia } from "viem/chains"; -import { serializeLayout } from "@wormhole-foundation/sdk-base"; -import { - quoteLayout, - relayInstructionsLayout, - signedQuoteLayout, -} from "@wormhole-foundation/sdk-definitions"; -import axios from "axios"; -import { deserialize } from "binary-layout"; +import { mnemonicToAccount } from "viem/accounts"; +import forgeOutput from "../evm/out/ExecutorVAAv1Integration.sol/ExecutorVAAv1Integration.json"; +import { enabledChains } from "./chains"; +import { ANVIL_MNEMONIC } from "./consts"; +import { RelayStatus } from "./types"; const ABI = [ { @@ -113,7 +116,90 @@ const ABI = [ }, ] as const; +async function deployIntegrationContract( + publicClient: PublicClient, + client: WalletClient, + account: Account, + viemChain: Chain, + coreContractAddress: string, + executorAddress: string, +) { + if (!isHex(forgeOutput.bytecode.object)) { + throw new Error("invalid bytecode"); + } + if (!isHex(coreContractAddress)) { + throw new Error("invalid coreContractAddress"); + } + if (!isHex(executorAddress)) { + throw new Error("invalid executorAddress"); + } + const hash = await client.deployContract({ + account, + chain: viemChain, + abi: ABI, + bytecode: forgeOutput.bytecode.object, + args: [coreContractAddress, executorAddress, 200], + }); + const receipt = await publicClient.waitForTransactionReceipt({ + hash, + }); + if (!isHex(receipt.contractAddress)) { + throw new Error("invalid contractAddress"); + } + return receipt.contractAddress; +} + test("it performs a VAA v1 relay", async () => { + const srcChain = enabledChains[10002]!; + const dstChain = enabledChains[10004]!; + const account = mnemonicToAccount(ANVIL_MNEMONIC, { addressIndex: 0 }); + if (!srcChain.viemChain || !dstChain.viemChain) { + throw new Error("invalid viem chain"); + } + const srcTransport = http(srcChain.rpc); + const srcPublicClient = createPublicClient({ + chain: srcChain.viemChain, + transport: srcTransport, + }); + const srcClient = createWalletClient({ + account, + chain: srcChain.viemChain, + transport: srcTransport, + }); + const dstTransport = http(dstChain.rpc); + const dstPublicClient = createPublicClient({ + chain: dstChain.viemChain, + transport: dstTransport, + }); + const dstClient = createWalletClient({ + account, + chain: dstChain.viemChain, + transport: dstTransport, + }); + const srcContract = await deployIntegrationContract( + srcPublicClient, + srcClient, + account, + srcChain.viemChain, + srcChain.coreContractAddress, + srcChain.executorAddress, + ); + console.log(`Deployed source contract: ${srcContract}`); + const dstContract = await deployIntegrationContract( + dstPublicClient, + dstClient, + account, + dstChain.viemChain, + dstChain.coreContractAddress, + dstChain.executorAddress, + ); + console.log(`Deployed destination contract: ${dstContract}`); + const dstTestContract = getContract({ + address: srcContract, + abi: ABI, + client: srcClient, + }); + expect(await dstTestContract.read.number()).toBe(0n); const relayInstructions = toHex( serializeLayout(relayInstructionsLayout, { requests: [ @@ -127,27 +213,20 @@ test("it performs a VAA v1 relay", async () => { ], }), ); - const response = await axios.post("http://localhost:3000/v0/quote", { + const response = await axios.post("http://executor:3000/v0/quote", { srcChain: 10002, dstChain: 10004, relayInstructions, }); - const transport = http("http://localhost:8545"); - const account = mnemonicToAccount(ANVIL_MNEMONIC, { addressIndex: 0 }); - const client = createWalletClient({ - account, - chain: sepolia, - transport, - }); - const testContract = getContract({ - address: "0x8e98Bd10a6f4c1Ee0C4b5d9F50a18D1a7E20EaF8", + const srcTestContract = getContract({ + address: srcContract, abi: ABI, - client, + client: srcClient, }); - const tx = await testContract.write.incrementAndSend( + const hash = await srcTestContract.write.incrementAndSend( [ 10004, - padHex("0x7d77360666066967579a2235332d271587cd62dC", { + padHex(dstContract, { dir: "left", size: 32, }), @@ -160,6 +239,29 @@ test("it performs a VAA v1 relay", async () => { { value: BigInt(response.data.estimatedCost) }, ); console.log( - `https://wormholelabs-xyz.github.io/executor-explorer/#/chain/10002/tx/${tx}?endpoint=http%3A%2F%2Flocalhost%3A3000&env=Testnet`, + `Request execution: https://wormholelabs-xyz.github.io/executor-explorer/#/chain/10002/tx/${hash}?endpoint=http%3A%2F%2Flocalhost%3A3000&env=Testnet`, ); -}); + await srcPublicClient.waitForTransactionReceipt({ + hash, + }); + let statusResult; + while ( + !statusResult || + statusResult.data?.[0].status === RelayStatus.Pending + ) { + console.log(`Statusing tx: ${hash}`); + if (statusResult) { + await sleep(1000); + } + statusResult = await axios.post("http://executor:3000/v0/status/tx", { + chainId: srcChain.wormholeChainId, + txHash: hash, + }); + if (statusResult.data.length !== 1) { + throw new Error(`unexpected status result length`); + } + } + expect(statusResult.data?.[0].status).toBe(RelayStatus.Submitted); + expect(await srcTestContract.read.number()).toBe(1n); + expect(await dstTestContract.read.number()).toBe(1n); +}, 60000); diff --git a/tsconfig.json b/tsconfig.json index fa048c2..4df190b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,5 +26,5 @@ "noUnusedParameters": false, "noPropertyAccessFromIndexSignature": false }, - "exclude": ["evm"] + "exclude": ["evm", "src/e2e.test.ts"] }