Skip to content

test: add an in-process Tevm test lane, replacing Anvil where rc.151 supports it - #1

Open
roninjin10 wants to merge 1 commit into
mainfrom
tevm-test/final
Open

test: add an in-process Tevm test lane, replacing Anvil where rc.151 supports it#1
roninjin10 wants to merge 1 commit into
mainfrom
tevm-test/final

Conversation

@roninjin10

Copy link
Copy Markdown

Motivation

The suite starts an anvil child process for essentially every stateful test, behind a Prool proxy on a fixed port. That means a Foundry binary on PATH, a process per worker, port bookkeeping, and startup polling before a single assertion runs.

Tevm is an EVM that runs inside the Vitest worker. This PR wires it up as a first-class test lane and migrates as much of the suite to it as rc.151 correctly supports — while showing off what the Tevm test library can actually do.

Before / after

Before (Anvil) After (Tevm lane)
Node anvil child process in-process MemoryClient
Discovery Prool proxy, fixed port none — direct EIP-1193
Startup spawn + poll until ready createMemoryClient() returns immediately; tevmReady() awaits the fork anchor
Isolation pool instance per worker evm_snapshot baseline, reverted before every test
Global setup setup.global.ts none
Mining noMining: true miningConfig: { type: 'manual' }

Viem clients are still constructed with this repository's createClient, over a custom EIP-1193 transport that forwards to memoryClient.request. Migrated suites therefore still exercise this fork's actions, client, and transport code — Tevm only supplies the node. Tevm depends on a newer published Viem than this fork builds; that boundary is crossed in exactly one place, the adapter in test/src/tevm.ts.

What's here

A third Vitest project. tevm matches src/**/*.tevm.test.ts, uses test/setup.tevm.ts, and has no globalSetup. Everything it shares with core moved to test/setup.shared.ts.

test/src/tevm.tstevmMainnet, the Tevm counterpart of anvilMainnet. Pinned mainnet fork, manual mining, the familiar prefunded accounts, getClient() with the same shape as the Anvil fixture, and snapshot-based per-test isolation (a successful evm_revert consumes its id, so a replacement snapshot is taken immediately).

17 migrated action suitesgetCode, getStorageAt, dropTransaction, dumpState, impersonateAccount, increaseTime, mine, revert, setBalance, setCode, setNextBlockTimestamp, setNonce, setStorageAt, snapshot, stopImpersonatingAccount, setBlockTimestampInterval, removeBlockTimestampInterval.

The complete HTTP transport suite. test/src/tevm-server.ts serves a MemoryClient over an ephemeral 127.0.0.1 listener (port 0) using createServer from tevm/server for the real-node paths; test/src/http-server.ts is a plain node:http server for the JSON-RPC error and header cases, which tevm/server answers with HTTP 400 rather than 200-with-error. No coverage was dropped.

Tevm test features shown off

src/tevm-showcase.tevm.test.ts, split into focused cases:

  • in-process node — a MemoryClient and this repo's Viem client in one process; manual mining (transactions stay pending until tevmMine); snapshot & revert through the Anvil-compatible anvil_snapshot / anvil_revert RPC surface
  • forking — a pinned mainnet fork, and a direct measurement of Tevm's lazy in-memory fork-state cache using a counting wrapper around the upstream provider
  • high-level actionstevmSetAccount, tevmDeal, tevmDeploy, tevmContract, tevmMine, impersonateAccount
  • low-level TevmNode handlerscallHandler, contractHandler, dealHandler, deployHandler, setAccountHandler against a bare createTevmNode()
  • published fixturesSimpleContract, AdvancedContract, ErrorContract, TestERC20 from @tevm/test-utils
  • every @tevm/test-matchers family — primitives (toBeAddress, toBeHex, toEqualAddress, toEqualHex), account & state (toBeInitializedAccount, toHaveState, toHaveStorageAt), events (toEmit with withEventArgs / withEventNamedArgs), traces (toCallContractFunction with withFunctionNamedArgs), reverts (toBeRevertedWithString, toBeRevertedWithError with withErrorNamedArgs), balances (toChangeBalance, toChangeTokenBalance)

Honesty about rc.151

Several matcher families run against non-fork createTevmNode() instances: on a forked node rc.151 resolves balances and storage through eth_getProof against the upstream historical block, so locally written values are invisible. test/README.md lists every gap found.

Six suites keep their original Anvil coverage and gain a .tevm sibling that asserts Tevm's actual behaviour rather than restating Anvil's — nothing is weakened:

Suite Divergence the .tevm sibling documents
setStorageAt eth_getStorageAt returns the storage word right-padded
getStorageAt forked slots other than the lazily cached one read back as zero
impersonateAccount / stopImpersonatingAccount Tevm auto-impersonates, so the "No Signer available" negatives cannot hold
setBlockTimestampInterval / removeBlockTimestampInterval anvil_setBlockTimestampInterval is recorded but not applied to mined blocks

The fork is pinned to 22263621, the nearest blob-free block below Anvil's 22263623: rc.151's Common.copy() drops customCrypto, so a forked anchor containing EIP-4844 transactions cannot be deserialized. Choosing a blob-free anchor is preferred over filtering type-3 transactions out of an otherwise inconsistent block response — the fork sees exactly what the provider returns.

Anvil is not going anywhere: WebSocket and IPC transports, the Alto bundler / account-abstraction suites, the Optimism / zkSync / Sepolia fixtures, wallet-provider emulation, sendUnsignedTransaction, setAutomine and setIntervalMining all still need it, and pnpm install still runs contracts:build with forge.

Dependency hygiene

Tevm packages are pinned to exact 1.0.0-rc.151 (the moving latest / next tags resolve to older builds with a much smaller Anvil surface), and pnpm.packageExtensions pins the entire Tevm family onto one published Viem — @tevm/errors was resolving a second copy and is now pinned too. @tevm/server is deliberately not installed (its latest tag is an obsolete 0.0.1); createServer comes from tevm/server. @tevm/ts-plugin is not a dependency until direct Solidity imports are actually enabled.

src/tevm-resolution.tevm.test.ts guards all of that at runtime: it imports every Tevm entrypoint the suite uses, so a peer-resolution regression — Tevm silently binding the unbuilt workspace Viem — fails here, first and legibly, instead of as an opaque module error deep inside an unrelated suite.

No changeset: this change is entirely test infrastructure and devDependencies, with no effect on the published package.

Test evidence

The Tevm lane, against an archive mainnet RPC:

$ VITE_ANVIL_FORK_URL=<archive-rpc> pnpm exec vitest run -c test/vitest.config.ts --project tevm

 RUN  v4.1.10 /Users/williamcory/evmts-viem-wt-final

 Test Files  20 passed (20)
      Tests  59 passed (59)
   Duration  8.66s

The six suites whose original Anvil coverage was restored, on the core (Anvil) project:

$ pnpm exec vitest run -c test/vitest.config.ts --project core \
    src/actions/public/getStorageAt.test.ts \
    src/actions/test/{setStorageAt,impersonateAccount,stopImpersonatingAccount}.test.ts \
    src/actions/test/{setBlockTimestampInterval,removeBlockTimestampInterval}.test.ts

 Test Files  6 passed (6)
      Tests  7 passed (7)

biome check src test reports no new findings (the 9 warnings are pre-existing, all in src/tempo/). tsc -b reports no errors in any file this PR adds or modifies; the remaining errors are the pre-existing Voltaire-migration ones on main.

Credits

Produced by a multi-agent run: three independent implementation lanes (codex-sol, opus, kimi) were built and judged, and this branch is the winning lane with the best ideas from the others grafted in — the focused showcase split, the explicit rc.151 limitations matrix and retained-Anvil coverage, the blob-free fork block, dropping @tevm/ts-plugin, and the runtime resolution smoke tests.

🤖 Generated with Smithers multi-agent orchestration

Replaces the Anvil child process for a large slice of the suite with an
in-process Tevm `MemoryClient`, running a complete EVM inside the Vitest
worker: no binary, no port, no Prool proxy, no startup polling.

- `test/src/tevm.ts` exports `tevmMainnet`, the Tevm counterpart of
  `anvilMainnet`: a mainnet fork pinned to the blob-free block 22263621,
  manual mining, the familiar prefunded accounts, and per-test snapshot
  isolation.
- A new `tevm` Vitest project (`src/**/*.tevm.test.ts`) runs with no
  `globalSetup`; `setup.shared.ts` holds what it shares with `core`.
- 17 action/public suites and the complete HTTP transport suite are
  migrated. `test/src/tevm-server.ts` serves a `MemoryClient` over an
  ephemeral listener via `tevm/server`; `test/src/http-server.ts` covers
  the JSON-RPC error cases `tevm/server` answers with HTTP 400.
- `src/tevm-showcase.tevm.test.ts` demonstrates the Tevm test library:
  in-process execution, manual mining, snapshot/revert, pinned forking,
  the lazy fork-state cache, the `tevm*` actions, the low-level
  `TevmNode` handlers, and every `@tevm/test-matchers` family.
- `src/tevm-resolution.tevm.test.ts` smoke-tests that every Tevm
  entrypoint resolves its own published Viem, not the unbuilt workspace
  Viem, so a peer-resolution regression fails loudly and immediately.
- Six suites keep their original Anvil coverage and gain a `.tevm`
  sibling asserting Tevm's actual rc.151 behaviour; `test/README.md`
  documents the retained-Anvil matrix and the known rc.151 gaps.

Viem clients are still built with this repository's `createClient`, over a
`custom` EIP-1193 transport; Tevm only supplies the node.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant