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:
- #15343 — Monad Foundry — Introduced first-class Monad support across Forge, Cast, Anvil, Chisel, tracing, cheatcodes, and shared EVM infrastructure.
- #16150 — fix(evm): honor explicit fork network — Preserved an explicitly selected execution network instead of allowing fork inference to override it.
- #16151 — fix(anvil): preserve fork endpoint identity — Kept authoritative endpoint identity across fork URL replacement and reset validation.
- #16157 — refactor(ci): group Monad checks — Consolidated Monad CI coverage into a reusable workflow.
- #16161 — refactor(evm): clean-up FoundryEvmNetwork trait — Reduced
FoundryEvmNetworkto a lean network-to-factory association. - #16175 — refactor(evm): isolate Monad context state — Moved Monad chain-position and reserve-balance state behind the factory boundary.
- #16195 — refactor(evm): remove redundant helpers — Removed convenience layers that no longer represented meaningful abstraction boundaries.
- #16220 — refactor(evm): isolate Monad system replay — Moved Monad system-envelope recognition, execution, and rollback into feature-gated Monad code.
- #16231 — refactor(evm): remove useless monad context refresh helper — Removed a forwarding helper left redundant after the context-state fixes.
- #16250 — refactor(evm): gate Monad transaction state behind feature — Removed Monad transaction-state plumbing from non-Monad builds.
- #16251 — refactor(evm): gate Monad ContextUpdate behind feature — Feature-gated the fork, roll, and transact context-update signal used only by Monad.
- #16257 — refactor(evm): bind chain context to ContextTr::Chain — Replaced bespoke factory chain-context plumbing with revm's native
ContextTr::Chain. - #16262 — feat(evm): bind Monad's journal state to ContextTr::Journal — Moved reserve-balance tracking to revm's native journal boundary.
- #16282 — refactor(evm): resolve initcode limit by network — Removed the Monad-specific initcode limit from
FoundryEvmFactoryand resolved it from active network configuration. - #16284 — refactor(evm): move extra cheatcodes to network config — Removed extra cheatcode addresses from the factory and made them follow the resolved runtime network.
- #16285 — refactor(evm): resolve block context by network — Removed the factory-level block-context capability and gated reconstruction on the active network.
- #16290 — refactor(evm): move journal refresh to chain — Moved chain-dependent journal refresh to
FoundryChain, removing refresh hooks from generic context and nested-EVM traits. - #16292 — refactor(evm): inline nested replay dispatch — Removed
NestedEvm::transact_replayand madetransact_rawthe single nested transaction entry point. - #16312 — refactor(evm): remove foundry replay hook — Removes the factory-level Foundry system-replay hook and routes replay through the network-owned nested EVM path; revives #16297.
- #16309 — refactor(evm): move chain construction out of FoundryEvmFactory — Moves chain-context construction to
FoundryChainand removes the remaining factory forwarding methods.
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.
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
These checks select the concrete FEN and should remain:
cast run:crates/cast/src/cmd/run.rscast 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
NetworkVariantmatch incrates/verify/src/bytecode.rs
Everything below these boundaries should ideally trust the chosen FEN.
| 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 |
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.
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.
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.
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.
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.
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 storesOption<BlockContext<FEN>>.- Cursor progression methods exist on every executor.
DatabaseExtexposes chain-context/update plumbing to every factory.
These surfaces explain why merely deleting is_monad() checks cannot complete the cleanup.
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.
NetworkConfigsvalidation, 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.
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.
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.
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.
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.
Once no generic caller needs them, gate or remove:
Executor<FEN>::block_context- Cursor storage and progression methods
- Generic block-context reconstruction helpers
ContextUpdateForplumbing
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.