Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save mablr/6e79e3cce90ba1aeba15a835b29b2a13 to your computer and use it in GitHub Desktop.

Select an option

Save mablr/6e79e3cce90ba1aeba15a835b29b2a13 to your computer and use it in GitHub Desktop.
Network dispatching consistency after Monad support introduction

Network dispatching consistency after Monad support introduction

Context

This audit follows the introduction of first-class Monad support in Foundry and a sequence of focused follow-up PRs intended to tighten network dispatch, isolate Monad-specific state and behavior, and reduce its footprint in generic EVM infrastructure:

These changes substantially narrowed the generic interfaces. The remaining concern is dispatch consistency: after a concrete FoundryEvmNetwork type has been selected, several generic execution paths still consult NetworkConfigs::is_monad() as a second source of truth.

Audit result

There are 17 production is_monad() checks after typed FEN dispatch that deserve cleanup.

The deeper issue is that FEN and NetworkConfigs are not currently guaranteed to agree. Executor<MonadEvmNetwork> can be built with default Ethereum NetworkConfigs; an existing test demonstrates this in crates/evm/evm/src/executors/mod.rs.

Therefore, these checks are conceptually redundant under the desired architecture, but removing them individually today would change behavior. The first cleanup must establish:

selected execution family = FEN
NetworkConfigs = configuration for that selected family, not a second dispatcher

Legitimate dispatch boundaries

These checks select the concrete FEN and should remain:

  • cast run: crates/cast/src/cmd/run.rs
  • cast call: crates/cast/src/cmd/call.rs
  • Forge tests: crates/forge/src/cmd/test/mod.rs
  • Forge mutation testing: crates/forge/src/mutation/runner.rs
  • Scripts: crates/script/src/lib.rs
  • Chisel: crates/chisel/src/args.rs
  • Bytecode verification uses an explicit NetworkVariant match in crates/verify/src/bytecode.rs

Everything below these boundaries should ideally trust the chosen FEN.

Redundant downstream dispatch

Area Checks Assessment
Cast transaction replay 1 Definitely redundant after run_with_evm::<FEN> selection
Bytecode verification 3 Definitely redundant after run_with_network_and_config::<FEN> selection
Generic block-context helper 1 Runtime family dispatch hidden in a generic helper
Cow replay backend 1 Runtime family dispatch selecting Monad protocol execution
Generic backend 11 Main concentration of split typed/runtime dispatch

Cast replay

crates/cast/src/cmd/run.rs conditionally reconstructs BlockContext<FEN> using networks.is_monad() after run() has already selected MonadEvmNetwork.

This should eventually become a Monad-specific preparation path, not a generic function consulting NetworkConfigs.

Bytecode verification

The three checks in crates/verify/src/bytecode.rs control:

  • Deployment-context reconstruction
  • Creation-transaction replay context
  • Missing-context rejection

All are inside run_with_network_and_config<FEN>, which is entered through an explicit NetworkVariant match. They duplicate the earlier family dispatch.

Generic block-context helper

context_for_child_transaction<FEN> in crates/evm/core/src/evm/block_context.rs accepts both FEN and NetworkConfigs.

It then asks networks.is_monad() to decide whether FEN requires ancestry. This is exactly the split-source-of-truth pattern. Its Cast caller is already within a typed run_with_network_and_opts<FEN> path.

Cow replay

crates/evm/core/src/backend/cow.rs checks:

!self.backend.networks().is_monad()

before invoking protocol-system replay through FEN::EvmFactory.

This is especially suspicious: execution semantics are selected partly by FEN::EvmFactory and partly by runtime configuration. It allows a MonadEvmNetwork backend with non-Monad configuration to skip Monad replay.

Generic backend

The 11 checks in crates/evm/core/src/backend/mod.rs cover:

  • Pending transaction rejection
  • Synthetic chain-context reconstruction
  • Executor cursor reconstruction
  • Block-roll context reconstruction
  • Standard/Monad transaction-roll dispatch
  • Duplicate target validation in the inner path
  • Duplicate block-context construction
  • Replay context assertion
  • Monad fork-position validation
  • Transaction-position reconstruction
  • Transaction block-context reconstruction

These are not independent problems. They all result from generic Backend<FEN> and DatabaseExt<FEN::EvmFactory> methods containing both ordinary and Monad algorithms.

Adjacent infrastructure without explicit checks

The audit also found Monad behavior embedded generically without calling is_monad():

  • ContextUpdateFor<F> becomes Monad-shaped for every FEN whenever the feature is compiled.
  • Executor<FEN> always stores Option<BlockContext<FEN>>.
  • Cursor progression methods exist on every executor.
  • DatabaseExt exposes chain-context/update plumbing to every factory.

These surfaces explain why merely deleting is_monad() checks cannot complete the cleanup.

Checks that should remain runtime-driven

The following should not be included in this cleanup:

  • Anvil's in-memory backend. It is intentionally one runtime-dispatched node backend rather than Backend<FEN>.
  • Trace decoder network selection. Decoders are independently usable and receive runtime network metadata.
  • NetworkConfigs validation, fork-source compatibility, serialization, and inference.
  • Extra cheatcode addresses and other configuration-derived inspector policy.
  • Parent beacon-root policy in Cast. That helper is not generic over FEN and expresses protocol behavior rather than selecting an EVM implementation.
  • Hardfork translation helpers that convert runtime configuration into FoundryHardfork.

Recommended cleanup sequence

1. Establish and test the FEN/config invariant

Stop treating mismatched Executor<FEN> and NetworkConfigs as supported behavior. Replace tests that permit mismatch with construction-time rejection or normalization.

This is the prerequisite. Without it, downstream checks remain behaviorally significant.

2. Move top-level context preparation behind typed entrypoints

Clean Cast and Verify first. They already have explicit typed dispatch and account for four checks plus context_for_child_transaction. This is narrow and relatively low risk.

3. Specialize Cow system replay

Move system-replay entrypoints to a statically Monad-owned implementation. This should remove the Cow is_monad() check without restoring a factory replay-policy hook.

4. Split backend ordinary and Monad operations

Separate the transaction-roll and replay algorithms at the DatabaseExt implementation boundary. Generic helpers may remain for common mechanics, but Monad implementations should own block-position reconstruction and replay.

5. Remove generic context surfaces

Once no generic caller needs them, gate or remove:

  • Executor<FEN>::block_context
  • Cursor storage and progression methods
  • Generic block-context reconstruction helpers
  • ContextUpdateFor plumbing

The key is to establish the FEN/config invariant before splitting the backend paths. Otherwise runtime configuration still retains authority to disagree with the selected EVM family.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment