Skip to content

Complete the Anvil-to-Tevm test migration - #2

Open
roninjin10 wants to merge 39 commits into
mainfrom
tevm-test/deanvil
Open

Complete the Anvil-to-Tevm test migration#2
roninjin10 wants to merge 39 commits into
mainfrom
tevm-test/deanvil

Conversation

@roninjin10

@roninjin10 roninjin10 commented Jul 29, 2026

Copy link
Copy Markdown

Summary

This completes the verifier pass over the Anvil-to-Tevm migration.

  • Removed Anvil from the default build and CI path: no Foundry setup, no forge postinstall build, no default Anvil process, and no default prool Anvil pool.
  • Replaced the deleted test/src/anvil.ts imports throughout the remaining mainnet, OP Stack, and ZKsync suites with the in-process Tevm harness.
  • Reconciled the chain-specific fixtures (tevmMainnet, tevmOptimism, tevmSepolia, and tevmZksync) and made non-harness test files avoid eager fork initialization.
  • Connected Alto to an HTTP tevm/server backed by the same MemoryClient. prool remains required for Alto, Tempo, and the optional transport blocker fixture.
  • Removed the duplicate HTTP-server helper and repaired shared bench, utility, and bundler imports.
  • Kept the four WebSocket/IPC suites intact in an explicit opt-in Anvil lane because @tevm/server is HTTP-only.
  • Documented archive RPC requirements, the Tevm-first workflow, and the narrowly scoped Foundry exception.

Harness size

Measured against main:test/src/anvil.ts:

  • before: 298 lines
  • after: 244 lines in test/src/tevm.ts
  • reduction: 54 lines (18.1%)

The replacement also covers lazy chain-specific fixtures, in-process transport adaptation, baseline snapshots, funded accounts, and fork transport setup.

Tevm matchers

The migrated test tree uses the actual @tevm/test-matchers APIs:

  • toBeAddress (8), toEqualAddress (39)
  • toBeHex (48), toEqualHex (5)
  • toBeInitializedAccount (2), toHaveState (2), toHaveStorageAt (2)
  • toEmit (12)
  • toCallContractFunction (2)
  • toBeReverted/revert variants (4)
  • toChangeBalance (2), toChangeTokenBalance (3)

Verification evidence

  • biome check over all 81 changed TypeScript/Markdown/JSON files: passed.
  • Focused Tevm runtime verification: 5 files passed, 22 tests passed.
  • Full package-script run, CI=true pnpm test: failed after 1216.45s.
    • Test files: 181 failed, 306 passed, 2 skipped (489)
    • Tests: 544 failed, 2604 passed, 490 skipped, 4 todo (3642)
    • Failed suites: 96
    • Runtime errors: 232
    • Snapshots: 90 failed
  • pnpm check:types: failed with 720 existing dependency/type-drift diagnostics, predominantly Hex versus Uint8Array and number versus bigint.

The full run is intentionally reported red. The captured failures include:

  • archive-provider rate limits (429 Too Many Requests) during fork setup;
  • the available Optimism endpoint rejecting historical eth_getProof outside its 10,000-block window;
  • Tevm filtering unsupported Optimism deposit transaction types, which changes block hashes;
  • Tevm method gaps and InvalidBlockError fork divergences;
  • Alto/account-abstraction failures when required EntryPoint/fork state cannot be loaded;
  • expected snapshot drift caused by the different node behavior;
  • independent Tempo failures that are part of the same package-script run.

Remaining Anvil references

Anvil is not fully absent from the repository. The default runtime/build/CI dependency is gone, but 70 grep-positive files remain for two explicit reasons.

Viem public compatibility and Tevm's Anvil-compatible test RPC dialect

These are supported public chain/test-client APIs, documentation/examples, compatibility tests, error mappings, or Tevm tests using Viem's existing mode: 'anvil' type and anvil_* method spellings. They do not start an Anvil node:

src/README.md
src/actions/public/watchContractEvent.test.ts
src/actions/public/watchEvent.test.ts
src/actions/test/dropTransaction.ts
src/actions/test/dumpState.ts
src/actions/test/getAutomine.ts
src/actions/test/getTxpoolContent.ts
src/actions/test/getTxpoolStatus.ts
src/actions/test/impersonateAccount.ts
src/actions/test/increaseTime.ts
src/actions/test/inspectTxpool.ts
src/actions/test/loadState.test.ts
src/actions/test/loadState.ts
src/actions/test/mine.ts
src/actions/test/removeBlockTimestampInterval.ts
src/actions/test/reset.ts
src/actions/test/revert.ts
src/actions/test/sendUnsignedTransaction.ts
src/actions/test/setAutomine.ts
src/actions/test/setBalance.ts
src/actions/test/setBlockGasLimit.ts
src/actions/test/setBlockTimestampInterval.ts
src/actions/test/setCode.ts
src/actions/test/setCoinbase.ts
src/actions/test/setIntervalMining.ts
src/actions/test/setLoggingEnabled.ts
src/actions/test/setMinGasPrice.test.ts
src/actions/test/setMinGasPrice.ts
src/actions/test/setNextBlockBaseFeePerGas.ts
src/actions/test/setNextBlockTimestamp.ts
src/actions/test/setNonce.ts
src/actions/test/setRpcUrl.ts
src/actions/test/setStorageAt.ts
src/actions/test/snapshot.ts
src/actions/test/stopImpersonatingAccount.ts
src/actions/wallet/deployContract.test.ts
src/actions/wallet/getCallsStatus.test.ts
src/actions/wallet/prepareTransactionRequest.test.ts
src/actions/wallet/sendCallsSync.test.ts
src/actions/wallet/sendRawTransactionSync.test.ts
src/actions/wallet/sendTransaction.test.ts
src/actions/wallet/sendTransactionSync.test.ts
src/actions/wallet/signTransaction.test.ts
src/actions/wallet/waitForCallsStatus.test.ts
src/actions/wallet/writeContract.test.ts
src/actions/wallet/writeContractSync.test.ts
src/chains/definitions/anvil.ts
src/chains/index.ts
src/clients/createPublicClient.test.ts
src/clients/createTestClient.bench-d.ts
src/clients/createTestClient.test-d.ts
src/clients/createTestClient.test.ts
src/clients/createTestClient.ts
src/clients/createWalletClient.test.ts
src/clients/decorators/public.test.ts
src/clients/decorators/test.test.ts
src/clients/decorators/test.ts
src/errors/node.ts
src/tevm-showcase.tevm.test.ts
src/types/eip1193.test-d.ts
test/src/tevm.ts

WebSocket/IPC blocker lane

@tevm/server cannot serve WebSocket or IPC. These four suites and their opt-in fixture/configuration remain unchanged in substance and are excluded from default CI:

src/clients/transports/ipc.test.ts
src/clients/transports/webSocket.test.ts
src/utils/rpc/ipc.test.ts
src/utils/rpc/webSocket.test.ts
test/README.md
test/setup.anvil-transports.global.ts
test/setup.ts
test/src/anvil-transports.ts
test/vitest.config.ts

🤖 Generated with Smithers multi-agent orchestration.

roninjin10 and others added 30 commits July 28, 2026 16:49
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>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…hers

Use toBeAddress/toBeHex instead of bare toBeDefined() for paymaster
addresses, paymaster data, and user operation hashes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…harness

Replaces the deleted `~test/anvil.js` fixture with `~test/tevm.js` across the
core-utils domain, serves `getHttpRpcClient` from an in-process Tevm HTTP
server instead of an Anvil port, rebuilds the nonce-manager suite around the
harness's per-test snapshot reset, and adopts `toEqualAddress` for address
assertions.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sponses

`@tevm/server` answers JSON-RPC errors with HTTP 400 instead of the spec's
200, which makes `getHttpRpcClient` throw rather than resolve. Point the two
resolve-with-error-body tests at a spec-compliant stub server so their coverage
is preserved without asserting the divergence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signs locally in the signature-verification suites (Tevm implements neither
`personal_sign` nor `eth_signTypedData_v4`), gives the nonce-manager's second
node a `mode` so `setBalance` resolves, and replaces ccip's hand-rolled
promise-executor revert extraction with `BaseError.walk`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Snapshot-only WebSocket cases use a literal URL; the three
createPublicClient cases that need a live WebSocket server move to the
anvil-transports project until tevm/server supports WebSocket.
…ction

The harness mines manually and Tevm's `pending` transaction count ignores the
txpool, so the nonce-manager suite mines each transaction. ccip extracts the
`OffchainLookup` payload via `BaseError.walk` instead of a fixed `cause.data`
path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tevm omits `error.data` on reverts and only renders the payload into the
message, so parse that as a fallback behind the spec-compliant `walk` path.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Answers the browser-wallet RPC surface (wallet_*, personal_sign,
eth_signTypedData_v4, eth_accounts) locally and emulates EIP-7966
eth_sendRawTransactionSync (unsupported in tevm rc.151) via
broadcast+mine+receipt; everything else runs against the in-process
tevm node. Hash/address/signature assertions now use the tevm
matchers (toBeHex, toEqualAddress).
- ENS reads use a dedicated harness pinned to blob-free block 23085561
  (the historical 23085558 anchor contains EIP-4844 transactions tevm
  rc.151 cannot deserialize), replacing the anvil_reset dance.
- getEip712Domain deploys SoladyAccount07 directly: tevm rc.151 reverts
  inside SoladyAccountFactory07.createAccount (bare EVM revert).
- getLogs/getContractEvents query the blob-free anchor block only; tevm
  crashes instantiating 4844 transactions when serving wider ranges.
- estimateContractGas expectation updated to tevm's gas accounting.
- sendRawTransactionSync emulates EIP-7966 (unsupported in rc.151) via
  broadcast+mine+receipt in a local transport.
- Address assertions use the toBeAddress/toEqualAddress matchers.
Nearly all anvil_*/evm_* test RPCs work on tevm rc.151. Three caveats:
- sendUnsignedTransaction skipped: eth_sendUnsignedTransaction is
  intentionally unsupported by tevm.
- setMinGasPrice skipped: anvil rejects it post-EIP-1559; tevm accepts.
- reset and setRpcUrl run on isolated harnesses because they
  reinitialize fork state, invalidating the shared harness's baseline
  snapshot.
The shared ~test/utils.js currently imports the deleted anvil fixture at
HEAD; inlining the 20-line node http helper keeps this suite independent
of the contested shared file.
- public decorator mines through a properly-typed test client
  (anvil_mine works on tevm).
- wallet decorator drops the redundant manual mine (the EIP-7966
  emulation mines) and casts typed-data messages: the vendored abitype
  maps bytes->Uint8Array and uint8->bigint, while the JSON-RPC boundary
  uses hex strings and numbers.
- webSocket transport suite casts the anvil-transports fixture client
  whose inferred type drops mode (pre-existing).
@roninjin10

Copy link
Copy Markdown
Author

Review: correctness of the migration itself

Read at f8618fe (291 files, +16173/-3015). I focused on the question "does a passing test still prove what it proved before?" rather than on style. Five things below are, I think, genuine defects rather than known-issue triage. Everything I claim to have run is pasted verbatim.


1. Every file that touches the harness now hard-requires an archive RPC — including tests that need no chain state

test/setup.ts gates fork init on tevmMainnet.isUsed(), and isUsed() is driven by a Proxy in test/src/tevm.ts whose trap flips used = true on any property read except isUsed itself:

get(target, property, receiver) {
  if (property !== 'isUsed') used = true
  return Reflect.get(target, property, receiver)
}

A module-level const client = tevmMainnet.getClient() — the single most common line in this diff — therefore forces a real network fork in beforeAll, even when no test in the file ever sends a request to the node. test/src/utils.ts makes this worse: it reads tevmMainnet.forkUrl at module scope, so every file importing ~test/utils.js is marked used too.

Concretely, on this branch with a default (non-archive) fallback endpoint:

$ pnpm exec vitest run -c test/vitest.config.ts --project core \
    src/actions/wallet/requestAddresses.test.ts src/utils/signature/verifyTypedData.test.ts

URL: https://cloudflare-eth.com
Request body: {"method":"eth_getBlockByNumber","params":["0x153b745",false]}
Details: Internal error
...
 Test Files  2 failed (2)
      Tests  3 skipped (3)
     Errors  4 errors
   Duration  13.06s

Neither of those files needs fork state. requestAddresses is answered by the harness's own stub; verifyTypedData's default case is local ECDSA. Proof — same two files, same commit, fork disabled:

$ VITE_TEVM_NO_FORK=true pnpm exec vitest run -c test/vitest.config.ts --project core \
    src/actions/wallet/requestAddresses.test.ts src/utils/signature/verifyTypedData.test.ts

 Test Files  2 passed (2)
      Tests  2 passed | 1 skipped (3)
   Duration  17.10s

This is not "archive-provider rate limits". It is the laziness mechanism not being lazy: isUsed tracks reference, not use. Under Anvil the equivalent files were cheap because the pooled node was spawned once per worker and reset with setIntervalMining, not re-forked per file. A meaningful slice of the 232 reported runtime errors is likely this, and it is a migration bug, not a Tevm gap.

Suggested fix: mark used on the transport request (inside custom({ async request(...) })) rather than on property access, or expose an explicit harness.use() that getClient() calls while plain metadata reads (forkUrl, chain, forkBlockNumber) do not.


2. setup.ts only resets tevmMainnet. The OP-Stack, Sepolia and ZKsync harnesses are never reset — ever

if (process.env.SKIP_GLOBAL_SETUP || !usesTevm || !tevmMainnet.isUsed()) return
await tevmMainnet.reset()

tevmOptimism, tevmSepolia and tevmZksync are module-level singletons in test/src/tevm.ts. Nothing snapshots or reverts them between tests or between files in the same worker. On main, setup.global.ts started one Anvil instance per exported fixture and the pooled proxy gave each worker a fresh node, and test/setup.ts reset mining state per test.

So today: a ZKsync or OP-Stack test that mutates balances, nonces or storage leaks that state into every later test in the same worker. Those suites become order-dependent, and with retry: 3 in test/vitest.config.ts a leak-induced failure can be masked on retry or, worse, a retry can pass against dirty state. This changes what those tests prove.


3. Tests call anvil_reset on the shared harness, which invalidates the baseline snapshot the whole file depends on

test/src/tevm.ts holds one baselineSnapshot and reset() does evm_revert(baselineSnapshot), throwing Failed to revert Tevm snapshot ... if the node says no. But 19 migrated test files call viem's reset() action against the shared client, e.g. src/actions/public/estimateGas.test.ts:

await reset(client, {
  blockNumber: tevmMainnet.forkBlockNumber,
  jsonRpcUrl: tevmMainnet.forkUrl,
})

and the whole OP-Stack suite (getL2Output, getGame, getWithdrawalStatus, proveWithdrawal, waitToFinalize, …).

The PR clearly knows this hazard — src/clients/createTestClient.test.ts was specially rewritten to spin up an isolated harness for exactly reset and setRpcUrl:

// `anvil_reset` invalidates the shared harness's baseline snapshot, so
// this test runs against an isolated Tevm node.
const harness = createTevmTestHarness()

The same reasoning applies to all 19 files above and wasn't applied there. This is a plausible mechanical source of the reported InvalidBlockError "fork divergences": after an in-test anvil_reset, the next beforeEach reverts to a snapshot taken against a different fork anchor. I'd want that hypothesis excluded before any of those failures is written off as a Tevm gap.

Cheap structural fix: have harness.reset() re-take the baseline if evm_revert returns false, and/or add an afterEach that re-anchors. Better: make createTevmTestHarness() per-file the default and drop the shared singleton.


4. Anvil is intentionally opt-in now, but the opt-in lane it was replaced with does not actually work for IPC

test/src/anvil-transports.ts advertises rpcUrl.ipc = '/tmp/viem-anvil-transports.ipc', but start() never passes it to Anvil:

const instance = Instance.anvil({
  chainId: mainnet.id,
  forkBlockNumber: this.forkBlockNumber,
  forkUrl: this.forkUrl,
})

No ipcPath/--ipc, so src/clients/transports/ipc.test.ts and src/utils/rpc/ipc.test.ts have no socket to connect to. Also dropped versus main's anvilMainnet: noMining: true. Those suites were written against a non-auto-mining node; on this fixture Anvil auto-mines. So the claim that these four suites "remain unchanged in substance" doesn't hold — the fixture underneath them changed, and since the lane is excluded from CI nothing catches it. Worth running TEST_ANVIL_TRANSPORTS=true once and pasting the result, otherwise this lane is dead code from day one.

@roninjin10

Copy link
Copy Markdown
Author

Review, part 2: coverage silently removed, and triage of the "known failures"

5. The WebSocket half of the local test matrix was deleted from CI, and this isn't in the PR description

.github/workflows/verify.yml:

       matrix:
         multicall: ['true', 'false']
-        transport-mode: ['http', 'webSocket']
         shard: [1, 2, 3]
...
-          VITE_NETWORK_TRANSPORT_MODE: ${{ matrix.transport-mode }}

That leg did not run "four WebSocket suites" — it ran the entire local test suite a second time with every client's transport switched to WebSocket. Losing it is a far bigger coverage loss than the four opt-in files, and the PR description frames the WebSocket gap as only those four files. It should be called out explicitly as part of the blocker, because it's the thing that has to come back when @tevm/server WS lands.

Related, three more tests were moved out of default CI without being listed: webSocket - getRpcClient()/subscribe()/request() - keepAlive & reconnect disabled moved from src/clients/createPublicClient.test.ts into src/clients/transports/webSocket.test.ts, which is in the excluded lane. Net: 4 files + 3 tests + the whole WS matrix leg.

6. CI now passes one fork secret where it used to pass five

-          VITE_ANVIL_FORK_URL: ...
-          VITE_ANVIL_FORK_URL_OPTIMISM: ...
-          VITE_ANVIL_FORK_URL_OPTIMISM_SEPOLIA: ...
-          VITE_ANVIL_FORK_URL_SEPOLIA: ...
-          VITE_ANVIL_FORK_URL_ZKSYNC: ...
+          VITE_TEVM_FORK_URL: ${{ secrets.VITE_TEVM_FORK_URL }}

test/README.md documents VITE_TEVM_FORK_URL_OPTIMISM, _SEPOLIA and _ZKSYNC, and test/src/tevm.ts reads them — but CI never sets them, so in CI the Optimism fixture always falls back to https://mainnet.optimism.io. That public endpoint has a ~10,000-block state window, and the fixture is pinned at block 113624777. The PR lists "the available Optimism endpoint rejecting historical eth_getProof outside its 10,000-block window" as an external constraint. It's a wiring omission: wire the three secrets and that class of failure should go away. Same for the Sepolia/ZKsync suites.

(For the record, the cloudflare-eth.com / rpc.sepolia.org fallbacks are not a regression — main:test/src/anvil.ts had identical fallbacks. Only the CI secret wiring regressed.)

7. Foundry: it's now fully gone, which is more than the description says — and the generated artifacts are unguarded

contracts:build is deleted from package.json, removed from postinstall, removed from two CI jobs, forge removed from the knip ignoreBinaries, generated.ts un-gitignored, and 8,348 lines of contracts/generated.ts checked in. That's a good outcome (no Foundry needed to install or to run CI), but two consequences aren't addressed:

  • The .sol sources and the lib/ submodule remain, and there is now no check that contracts/generated.ts matches them. Edit a contract, forget to regenerate, and nothing anywhere fails. Previously postinstall regenerated on every install. A contracts:check CI job (build with forge, git diff --exit-code contracts/generated.ts) would restore the guarantee without putting Foundry back in the default install path.
  • There's no documented command left for regenerating them at all, since the script itself was removed. scripts/generateTypedArtifacts.ts is now unreachable from any package script.

8. Triage of the described remaining failures

Splitting them, because the PR's list mixes categories:

Reported failure My read
archive rate limits (429) during fork setup Migration. Amplified enormously by finding #1 — every harness-referencing file re-forks. Fix the isUsed trap and the request volume drops by a large factor.
Optimism eth_getProof outside 10k-block window Migration. VITE_TEVM_FORK_URL_OPTIMISM is documented and read but never set in CI (wevm#6).
InvalidBlockError fork divergences Suspect migration. #3 (in-test anvil_reset vs. a stale shared baseline snapshot) is a concrete mechanism. Needs to be excluded before blaming Tevm.
Alto / account-abstraction failures Suspect migration. test/src/bundler.ts points Alto at a long-lived tevm/server over the shared tevmMainnet memory client — while setup.ts reverts that same client to its baseline snapshot before every test. Alto's cached view of chain/EntryPoint state cannot survive an out-of-band evm_revert. The bundler almost certainly needs its own non-reset harness.
Tevm filtering unsupported OP deposit tx types → different block hashes Genuine Tevm gap. OP deposit (type 0x7e) support. Real upstream issue; block-hash-sensitive assertions can't be salvaged locally.
eth_sendUnsignedTransaction missing (test.skip in createTestClient.test.ts) Genuine gap, and correctly skipped. Worth an upstream issue link in the comment.
eth_signTypedData_v4 missing (test.skip in verifyTypedData.test.ts) Genuine gap. The skip comment is exactly right about why it can't be rewritten around a local account. Link the tracking issue.
setMinGasPrice skip Neither — that test asserted an Anvil rejection behaviour. Tevm accepting it is a divergence, not a gap; the skip is fine but the comment should say the assertion is obsolete rather than blocked.
~90 snapshot failures Partly mechanical, not behavioural. The mainnet fork anchor moved 22263623n22263621n (per test/README.md, to dodge a Tevm rc.151 KZG-deserialization limitation on 4844 txs in the anchor block). Any snapshot carrying a block number, block hash, base fee or gas figure changes for that reason alone. Those should be re-recorded and separated out; whatever remains after re-anchoring is the real behavioural drift and is the interesting number.

9. Smaller notes

  • createCommon({ ..., customCrypto: { kzg: createMockKzg() } }) in test/src/tevm.ts means the node performs no real KZG verification. The active 4844 tests are test.skipped today so nothing is currently mis-asserting, but when args: blobs / the 4844 cases are unskipped they'd pass against a mock verifier. Worth a comment at the mock so nobody unskips them and believes the result.
  • src/clients/createTestClient.test.ts creates two extra createTevmTestHarness() instances (reset, setRpcUrl) with no teardown. The harness exposes no dispose/close at all, so each leaks a MemoryClient and its fork transport for the life of the worker. Adding harness.close() (and calling it in afterAll) is cheap now and gets much harder later.
  • src/actions/wallet/requestAddresses.test.ts still snapshots one address, but the harness now answers eth_requestAccounts from a hardcoded [accounts[0].address] rather than from the node. The snapshot is unchanged from main so nothing regressed, but the test no longer exercises anything.
  • const client = anvilMainnet.getClient() as never in webSocket.test.ts casts away the client type for the whole file. Prefer widening the fixture's return type.

Nothing here needs to block the direction — the migration is clearly the right move and the honest red report is appreciated. But #1, #2, #3 and wevm#5 change what passing tests prove, and I'd want those separated from the Tevm-gap list before the red run is treated as a baseline. Follow-up scope for finishing the job now that tevm WS (wevm#2084) and IPC (wevm#2080) are merged is in a separate comment.

@roninjin10

Copy link
Copy Markdown
Author

Follow-up scope: retiring the Anvil lane once tevm WS/IPC publish

evmts/tevm wevm#2084 (createWebSocketServer, JSON-RPC over WS on the same port as HTTP, eth_subscribe/eth_unsubscribe with real notification push) and wevm#2080 (createIpcServer, Unix domain socket, framing across fragmented/concatenated/newline-delimited chunks, newHeads/newPendingTransactions/logs subscriptions, cleanup on eth_unsubscribe and socket close) are both merged into evmts/tevm main. The stated blocker for this PR's Anvil lane is therefore gone in source — but not on npm: publishing is blocked on npm trusted-publisher configuration, and this fork pins tevm@1.0.0-rc.151 from the registry. So this is sequenced, not immediately actionable.

Phase 0 — do now, independent of publishing

None of this waits on tevm:

  1. Fix isUsed so it tracks transport requests, not property reads (review comment test: add an in-process Tevm test lane, replacing Anvil where rc.151 supports it #1). Biggest single win; likely removes most of the 429s and a large share of the 232 runtime errors.
  2. Reset (or isolate) tevmOptimism / tevmSepolia / tevmZksync in test/setup.ts (Complete the Anvil-to-Tevm test migration #2).
  3. Give the 19 files that call reset() their own harness, or make harness.reset() re-anchor when evm_revert returns false (📝 docs(test): follow-up scope for finishing the Anvil-to-Tevm migration #3).
  4. Wire VITE_TEVM_FORK_URL_OPTIMISM / _SEPOLIA / _ZKSYNC secrets into verify.yml (docs: sidebar scaffold wevm/viem#6).
  5. Give Alto its own non-snapshot-reverted harness in test/src/bundler.ts (feat: polish websocket rpc implementation wevm/viem#8).
  6. Re-record snapshots against the new 22263621n anchor as a separate mechanical commit, then measure what behavioural drift actually remains.
  7. Add a contracts:check CI job so contracts/generated.ts can't silently go stale (feat: fetchTransactionReceipt wevm/viem#7).
  8. Fix test/src/anvil-transports.ts: pass an ipcPath to Instance.anvil and restore noMining: true, then actually run TEST_ANVIL_TRANSPORTS=true in a scheduled (non-blocking) CI job so the lane doesn't rot while it exists (refactor: convert blockNumber to bigint wevm/viem#4).

Phase 1 — when a tevm release containing wevm#2084/wevm#2080 is on npm

Bump tevm/@tevm/* off 1.0.0-rc.151 (also drop the pnpm.packageExtensions viem-injection hack in package.json if the new release ships a compatible viem peer).

Then extend test/src/tevm.ts with a withServers() variant that starts, on one harness:

  • createServer (HTTP, already used by test/src/bundler.ts),
  • createWebSocketServer on the same port,
  • createIpcServer on a per-worker socket path (/tmp/viem-tevm-${VITEST_POOL_ID}.ipc),

exposing rpcUrl.{http,ws,ipc} with the same shape anvil-transports.ts fakes today, plus a real close(). Ports must be VITEST_POOL_ID-offset like test/src/bundler.ts already does; the IPC path must be unlinked in teardown.

The four suites, and exactly what each needs

Suite What it needs from the new servers
src/utils/rpc/webSocket.test.ts Lowest-level. Raw socket client behaviour: request/response correlation, batching, eth_subscribe → notification delivery, error frames, socket close/reconnect semantics, keepAlive. Needs createWebSocketServer plus the ability to kill the server mid-test to exercise reconnect. Verify tevm sends eth_subscription notifications with the exact envelope viem's socket client parses.
src/clients/transports/webSocket.test.ts webSocket() transport over the above, plus the 3 createPublicClient keepAlive & reconnect disabled tests that were relocated here — those should move back to src/clients/createPublicClient.test.ts once a WS URL is available by default. Several cases call mine() and await newHeads, so tevm's manual mining config must still push newHeads on tevm_mine.
src/utils/rpc/ipc.test.ts createIpcServer with a socket path. Exercises exactly the framing wevm#2080 says it handles (fragmented / concatenated / newline-delimited), so it's the real acceptance test for that PR. Needs deterministic teardown to avoid stale sockets across workers.
src/clients/transports/ipc.test.ts ipc() transport over the above; also imports Instance from prool today — that import goes away entirely.

Once those four are on the tevm servers: delete test/src/anvil-transports.ts, test/setup.anvil-transports.global.ts, the anvilTransportTests array and the conditional anvil-transports project in test/vitest.config.ts, and the TEST_ANVIL_TRANSPORTS branch in test/setup.ts.

Then restore the WebSocket CI matrix leg

The transport-mode: ['http', 'webSocket'] axis and VITE_NETWORK_TRANSPORT_MODE deleted from verify.yml must come back pointed at the tevm WS server. This is the largest piece of coverage the migration dropped and it is only recoverable at this phase. Note test/src/tevm.ts currently has no VITE_NETWORK_TRANSPORT_MODE handling at all — the harness always returns the in-process custom() transport — so getClient() needs a mode switch that returns webSocket(rpcUrl.ws) when the env var says so.

What's left before Anvil is fully gone

After the above, the residue is:

  • prool — still needed for Alto (and the Tempo fixture), not for Anvil. It stays as a devDependency but no longer starts an Ethereum node. Removing the Alto dependency is a separate, larger project (an in-process bundler), not part of this.
  • ~70 files with anvil in them that are public API, not a node: src/chains/definitions/anvil.ts, mode: 'anvil' in createTestClient, the anvil_* method spellings in src/actions/test/*, error mappings in src/errors/node.ts, docs. These are viem's supported surface and Tevm's test-RPC dialect. They should stay, and the PR description is right to separate them — but the migration would read better if the harness's mode: 'anvil' as const had a one-line comment at each site (it has one, in getClient(); the createTestClient call sites don't).
  • Foundry: separate from Anvil, and this PR already removes it from postinstall and CI by checking in contracts/generated.ts. The remaining tie is the lib/ submodule + .sol sources with no regeneration path (feat: fetchTransactionReceipt wevm/viem#7). Restoring a contracts:build script gated behind a contracts:check CI job is the right end state: Foundry needed only by contract authors, never by installers or CI test runs.

Definition of done

  • pnpm test green with no TEST_ANVIL_TRANSPORTS project defined and no anvil binary on PATH.
  • CI matrix has both http and webSocket legs again.
  • grep -rl 'prool' test/ returns only Alto/Tempo fixtures.
  • test.skips reduced to the three genuine Tevm gaps (eth_sendUnsignedTransaction, eth_signTypedData_v4, EIP-4844 fork anchor), each linked to an upstream evmts/tevm issue.

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