Expand description
Forked EVM simulation engine for EVM search, MEV, and backtesting.
evm-fork-cache simulates EVM transactions against recent on-chain state
without re-deriving that state on every call. It builds on revm,
alloy, and foundry-fork-db to provide a lazy-loading state cache,
immutable snapshots shareable across threads, per-simulation overlays, a
freshness control plane, and the state-manipulation helpers a search loop
needs (balance overrides, batched multicalls, verified code seeding +
Foundry-style bytecode etching, CREATE3 address derivation, and an
extensible revert decoder).
§The state stack
Reads flow up; the fork DB lazily fetches misses from RPC. Writes and purges are applied directly to the cache (no RPC on the hot path).
EvmOverlay × N isolated, Send simulations (cheap Arc clones)
▲ clone × N
EvmSnapshot immutable, point-in-time, Send + Sync
▲ snapshot()
EvmCache lazy RPC fetch + local state cache + targeted writes/purge
▲ lazy fetch
RPC providerThe entry point is cache::EvmCache: construct one over an RPC backend
(see cache::EvmCacheBuilder), then snapshot it with
cache::EvmCache::snapshot to fan out parallel simulations, each
driving its own cache::EvmOverlay. EvmCache is !Send (it owns the
mutable fork and blocks on RPC internally); EvmSnapshot is Send + Sync
and EvmOverlay is Send, so the fan-out parallelizes safely.
§Modules
cache— the fork cache, snapshots, overlays, and on-disk persistence.access_list/access_set— EIP-2930 access-list construction and EIP-2929 warm-slot tracking for gas estimation.errors— structured simulation errors (errors::SimError) and an extensible revert-reason decoder you can teach your own custom Solidity error selectors.freshness— the four-layer freshness model (classification, observation, policy, mechanism) and the optimistic verify-and-rerun execution loop with deferred validation.state_update— the generic state-mutation vocabulary (StateUpdate/AccountPatch/PurgeScope, plus relativeSlotDeltaread-modify-write and maskedSlotMaskedwrites) applied byEvmCache::apply_update/apply_updates/modify_slot, with a structuredStateDiffoutput (Pillar B.1).events— the event → state pipeline (Pillar B.2):EventDecoder/StateView/DecoderRegistrydecode an on-chainLogintoStateUpdates, andEventPipelineingests, reorg-purges, and reconciles a block’s logs. Ships an ERC-20Transferdecoder plus traits for external decoders.reactive— default-enabled provider-neutral handler runtime for logs, blocks, and pending transaction signals. Pure handlers emitStateUpdates, invalidations, resync requests, speculative signals, and hooks; the runtime validates and applies canonical cache mutations, journals canonical block effects for depth-bounded reorg recovery, and includes a liveAlloySubscriber(WebSocket) transport.cold_start— default-enabled (reactive-gated) declarative warming of a working set of accounts/slots into the cache in one batched pass (EvmCache::run_cold_start+ColdStartPlanner), returning a structuredColdStartRunReport. Each round verifies any pending code seeds first (theverify_codephase), so sims never run over unverified claims.bundle— multi-transaction bundle execution over cumulative block state (EvmOverlay::simulate_bundle): ordered txs, anAtomic/AllowRevertsrevert policy, and coinbase/miner-payment accounting.inspector— anInspectorthat captures ERC20Transferevents to reconstruct balance deltas from a simulation.tracing— a call-frameInspector(CallTracerbuilding aCallTracetree) plusInspectorStackfor composing several inspectors over one pass, driven throughEvmOverlay::call_raw_with_inspector.bulk_storage— bulk storage extraction overeth_callstate overrides (the default batch storage fetcher): thousands of slots — across many contracts — per call, plus custom storage programs and account-fields/block-context extractors. Seedocs/bulk-storage-extraction.mdfor measured economics.multicall— batched read-only calls through Multicall3.deploy/create3— contract deployment and CREATE3 address math.prefetch_registry— two-stage storage-slot pre-warming (complemented by the declarativecache::EvmCache::prewarm_slots).mapping_probe— trace-based discovery of hash-derived storage slots: derive a mapping’s base slot and byte-order layout (Solidity / Vyper / Solady / nested) from one simulation, viaEvmCache::trace_hashed_slots/discover_erc20_balance_slot/track_erc20_balances.
§Requirements
Any constructor or method that may touch RPC fetches missing state through a
synchronous façade over an async provider
(tokio::task::block_in_place), so it must run on a multi-thread tokio
runtime:
#[tokio::main(flavor = "multi_thread")]
async fn main() { /* ... */ }
#[tokio::test(flavor = "multi_thread")]
async fn my_test() { /* ... */ }On a current-thread runtime, fetch paths do not panic: the synchronous
bridge checks the runtime flavor up front and returns a typed
RuntimeError (CurrentThreadRuntime) instead, so a
misconfigured runtime surfaces as a handled error rather than a panic deep in
a callback. The offline examples and integration tests build the cache over a
mocked provider and never reach the network, so they are exempt.
§Error handling
Simulation entry points that distinguish failure modes return
errors::SimulationResult (Result<T, SimError>), where
SimError separates a decoded Revert,
an EVM Halt, and an unexpected host-side
Other SimHostError
(RPC, database, ABI encoding). Other fallible modules expose domain errors
such as CacheError, FreshnessError, and StorageFetchError rather
than erasing failures
into a dynamic catch-all error. The
freshness loop never silently trusts stale data: a transient RPC failure
surfaces as freshness::Validation::Unverified so callers can retry rather
than act on unverified results.
§Maturity & stability
This crate is pre-1.0 and developed against a phased roadmap (see
docs/ROADMAP.md). Until 1.0, breaking changes may land in minor releases;
each is recorded in the crate CHANGELOG.md. MSRV is Rust 1.88 (edition 2024).
The examples/ directory has runnable, documented walkthroughs of each
module — offline ones that need no network, plus a few that fork real chain
state over RPC. See the crate README for the full list.
Re-exports§
pub use access_set::StorageAccessList;pub use bulk_storage::AccountFieldsSample;pub use bulk_storage::BlockContextSample;pub use bulk_storage::BulkCallConfig;pub use bulk_storage::BulkFetcherStatus;pub use bulk_storage::CallDispatch;pub use bulk_storage::StorageProgram;pub use bulk_storage::bulk_call_storage_fetcher;pub use bulk_storage::bulk_call_storage_fetcher_with_fallback;pub use bulk_storage::bulk_call_storage_fetcher_with_status;pub use bulk_storage::fetch_account_fields_bulk;pub use bulk_storage::fetch_block_context;pub use bulk_storage::fetch_slots_bulk;pub use bulk_storage::planned_call_count;pub use bulk_storage::run_storage_program;pub use bulk_storage::run_storage_programs;pub use bundle::BundleOptions;pub use bundle::BundleResult;pub use bundle::BundleTx;pub use bundle::RevertPolicy;pub use bundle::TxOutcome;pub use cache::AccountFieldsFetchFn;pub use cache::AccountProof;pub use cache::AccountProofFetchFn;pub use cache::BlockContextRequirements;pub use cache::BlockStateAccountDiff;pub use cache::BlockStateDiff;pub use cache::BlockStateDiffFetchFn;pub use cache::BlockStateStorageDiff;pub use cache::CacheSpeedMode;pub use cache::CallSimulationResult;pub use cache::CodeMismatch;pub use cache::CodeSeedState;pub use cache::CodeVerifyReport;pub use cache::EvmCache;pub use cache::EvmCacheBuilder;pub use cache::EvmOverlay;pub use cache::EvmSnapshot;pub use cache::PrewarmReport;pub use cache::StorageBatchConfig;pub use cache::StorageFetchStrategy;pub use cache::TxConfig;pub use cache::point_read_storage_fetcher;pub use cold_start::ColdStartCall;pub use cold_start::ColdStartCallResult;pub use cold_start::ColdStartConfig;pub use cold_start::ColdStartError;pub use cold_start::ColdStartPin;pub use cold_start::ColdStartPlan;pub use cold_start::ColdStartPlanner;pub use cold_start::ColdStartResults;pub use cold_start::ColdStartRoundSummary;pub use cold_start::ColdStartRunReport;pub use cold_start::ColdStartStep;pub use cold_start::RootBaseline;pub use cold_start::RootBaselinePlanner;pub use cold_start::RootProbeOutcome;pub use cold_start::RoundOutcome;pub use errors::AccessListError;pub use errors::AccessListResult;pub use errors::BlockContextError;pub use errors::CacheError;pub use errors::CacheResult;pub use errors::DeployError;pub use errors::DeployResult;pub use errors::FreshnessError;pub use errors::FreshnessResult;pub use errors::MulticallError;pub use errors::MulticallResult;pub use errors::OverlayError;pub use errors::OverlayResult;pub use errors::PersistenceError;pub use errors::RpcError;pub use errors::RuntimeError;pub use errors::SimError;pub use errors::SimHostError;pub use errors::SimulationError;pub use errors::SimulationResult;pub use errors::StorageFetchError;pub use errors::StorageFetchResult;pub use events::erc20::Erc20TransferDecoder;pub use events::BlockDigest;pub use events::DecoderRegistry;pub use events::EventDecoder;pub use events::EventPipeline;pub use events::ReconcileReport;pub use events::ReorgConfig;pub use events::StateView;pub use freshness::AlwaysVerify;pub use freshness::BlockClock;pub use freshness::FreshnessClock;pub use freshness::FreshnessController;pub use freshness::FreshnessParams;pub use freshness::FreshnessPolicy;pub use freshness::FreshnessRegistry;pub use freshness::NeverVerify;pub use freshness::ObservationDriven;pub use freshness::SimRequest;pub use freshness::SlotChange;pub use freshness::SlotFetch;pub use freshness::SlotOutcome;pub use freshness::SpeculativeSim;pub use freshness::Validation;pub use freshness::Validity;pub use freshness::WallClock;pub use mapping_probe::Confidence;pub use mapping_probe::HashSlotAccess;pub use mapping_probe::HashStorageProbe;pub use mapping_probe::SlotLayout;pub use mapping_probe::TrackedBalances;pub use mapping_probe::TrackedMapping;pub use reactive::InterestOwnerSubscriber;pub use reactive::ReactiveConfig;pub use reactive::ReactiveEngine;pub use reactive::ReactiveEngineError;pub use reactive::ReactiveEngineRegisterError;pub use reactive::ReactiveHandler;pub use reactive::ReactiveRuntime;pub use state_update::AccountChange;pub use state_update::AccountPatch;pub use state_update::PurgeRecord;pub use state_update::PurgeScope;pub use state_update::SkippedAccountPatch;pub use state_update::SkippedBalanceDelta;pub use state_update::SkippedDelta;pub use state_update::SkippedMask;pub use state_update::SlotDelta;pub use state_update::StateDiff;pub use state_update::StateUpdate;pub use tracing::CallKind;pub use tracing::CallStatus;pub use tracing::CallTrace;pub use tracing::CallTracer;pub use tracing::InspectorStack;
Modules§
- access_
list - EIP-2930 access list builder with L2 profitability accounting.
- access_
set - Compact account/storage touch sets captured from EVM execution.
- bulk_
storage - Bulk storage extraction over
eth_callstate overrides. - bundle
- Multi-transaction bundle simulation over cumulative block state, plus coinbase / miner-payment accounting (Phase 6 Track A+B).
- cache
- The forked-EVM state cache: lazy RPC loading, a layered write funnel, and cheap copy-on-write snapshots.
- cold_
start - Protocol-neutral cold-start sync for
EvmCache. - create3
- CREATE3 deployment-address derivation.
- deploy
- Helpers for deploying Foundry artifacts into an
EvmCache. - errors
- Simulation error types and revert-reason decoding.
- events
- Event → state pipeline (Pillar B.2 — the reader half of the event pipeline).
- freshness
- Freshness control plane and the optimistic verify-and-rerun execution loop.
- inspector
- ERC20
Transfer-event capture for reconstructing balance deltas. - mapping_
probe - Trace-based discovery of hash-derived storage slots.
- multicall
- Multicall3 batching support for EvmCache.
- prefetch_
registry - Generalized storage prefetch registry for EVM cache pre-warming.
- reactive
- Protocol-neutral reactive runtime for cache state effects.
- state_
update - Targeted state-mutation vocabulary and the structured diff it produces (Pillar B.1 — the writer half of the event → state pipeline).
- tracing
- Call-frame tracing and a composing inspector seam.