evm_fork_cache/lib.rs
1//! Forked EVM **simulation engine** for EVM search, MEV, and backtesting.
2//!
3//! `evm-fork-cache` simulates EVM transactions against recent on-chain state
4//! without re-deriving that state on every call. It builds on [`revm`],
5//! [`alloy`], and [`foundry-fork-db`] to provide a lazy-loading state cache,
6//! immutable snapshots shareable across threads, per-simulation overlays, a
7//! freshness control plane, and the state-manipulation helpers a search loop
8//! needs (balance overrides, batched multicalls, verified code seeding +
9//! Foundry-style bytecode etching, CREATE3 address derivation, and an
10//! extensible revert decoder).
11//!
12//! [`revm`]: https://github.com/bluealloy/revm
13//! [`alloy`]: https://github.com/alloy-rs/alloy
14//! [`foundry-fork-db`]: https://github.com/foundry-rs/foundry-fork-db
15//!
16//! # The state stack
17//!
18//! Reads flow up; the fork DB lazily fetches misses from RPC. Writes and purges
19//! are applied directly to the cache (no RPC on the hot path).
20//!
21//! ```text
22//! EvmOverlay × N isolated, Send simulations (cheap Arc clones)
23//! ▲ clone × N
24//! EvmSnapshot immutable, point-in-time, Send + Sync
25//! ▲ snapshot()
26//! EvmCache lazy RPC fetch + local state cache + targeted writes/purge
27//! ▲ lazy fetch
28//! RPC provider
29//! ```
30//!
31//! The entry point is [`cache::EvmCache`]: construct one over an RPC backend
32//! (see [`cache::EvmCacheBuilder`]), then snapshot it with
33//! [`cache::EvmCache::snapshot`] to fan out parallel simulations, each
34//! driving its own [`cache::EvmOverlay`]. `EvmCache` is `!Send` (it owns the
35//! mutable fork and blocks on RPC internally); `EvmSnapshot` is `Send + Sync`
36//! and `EvmOverlay` is `Send`, so the fan-out parallelizes safely.
37//!
38//! # Modules
39//!
40//! - [`cache`] — the fork cache, snapshots, overlays, and on-disk persistence.
41//! - [`access_list`] / [`access_set`] — EIP-2930 access-list construction and
42//! EIP-2929 warm-slot tracking for gas estimation.
43//! - [`errors`] — structured simulation errors ([`errors::SimError`]) and an
44//! extensible revert-reason decoder you can teach your own custom Solidity
45//! error selectors.
46//! - [`freshness`] — the four-layer freshness model (classification, observation,
47//! policy, mechanism) and the optimistic verify-and-rerun execution loop with
48//! deferred validation.
49//! - [`state_update`] — the generic state-mutation vocabulary (`StateUpdate` /
50//! `AccountPatch` / `PurgeScope`, plus relative `SlotDelta` read-modify-write and
51//! masked `SlotMasked` writes) applied by `EvmCache::apply_update` /
52//! `apply_updates` / `modify_slot`, with a structured `StateDiff` output
53//! (Pillar B.1).
54//! - [`events`] — the event → state pipeline (Pillar B.2): `EventDecoder` /
55//! `StateView` / `DecoderRegistry` decode an on-chain `Log` into `StateUpdate`s,
56//! and `EventPipeline` ingests, reorg-purges, and reconciles a block's logs.
57//! Ships an ERC-20 `Transfer` decoder plus traits for external decoders.
58//! - `reactive` — default-enabled provider-neutral handler runtime for logs,
59//! blocks, and pending transaction signals. Pure handlers emit `StateUpdate`s,
60//! invalidations, resync requests, speculative signals, and hooks; the runtime
61//! validates and applies canonical cache mutations, journals canonical block
62//! effects for depth-bounded reorg recovery, and includes a live
63//! `AlloySubscriber` (WebSocket) transport.
64//! - `cold_start` — default-enabled (reactive-gated) declarative warming of a
65//! working set of accounts/slots into the cache in one batched pass
66//! (`EvmCache::run_cold_start` + `ColdStartPlanner`), returning a structured
67//! `ColdStartRunReport`. Each round verifies any pending code seeds first
68//! (the `verify_code` phase), so sims never run over unverified claims.
69//! - [`bundle`] — multi-transaction bundle execution over cumulative block state
70//! ([`EvmOverlay::simulate_bundle`](cache::EvmOverlay::simulate_bundle)): ordered
71//! txs, an `Atomic`/`AllowReverts` revert policy, and coinbase/miner-payment
72//! accounting.
73//! - [`inspector`] — an [`Inspector`](revm::Inspector) that captures ERC20
74//! `Transfer` events to reconstruct balance deltas from a simulation.
75//! - [`tracing`] — a call-frame [`Inspector`](revm::Inspector)
76//! ([`CallTracer`] building a [`CallTrace`] tree) plus [`InspectorStack`] for
77//! composing several inspectors over one pass, driven through
78//! [`EvmOverlay::call_raw_with_inspector`](cache::EvmOverlay::call_raw_with_inspector).
79//! - [`bulk_storage`] — bulk storage extraction over `eth_call` state
80//! overrides (the **default** batch storage fetcher): thousands of slots —
81//! across many contracts — per call, plus custom storage programs and
82//! account-fields/block-context extractors. See
83//! `docs/bulk-storage-extraction.md` for measured economics.
84//! - [`multicall`] — batched read-only calls through Multicall3.
85//! - [`deploy`] / [`create3`] — contract deployment and CREATE3 address math.
86//! - [`prefetch_registry`] — two-stage storage-slot pre-warming
87//! (complemented by the declarative [`cache::EvmCache::prewarm_slots`]).
88//! - [`mapping_probe`] — trace-based discovery of hash-derived storage slots:
89//! derive a mapping's base slot and byte-order layout (Solidity / Vyper /
90//! Solady / nested) from one simulation, via `EvmCache::trace_hashed_slots` /
91//! `discover_erc20_balance_slot` / `track_erc20_balances`.
92//!
93//! # Requirements
94//!
95//! Any constructor or method that may touch RPC fetches missing state through a
96//! synchronous façade over an async provider
97//! ([`tokio::task::block_in_place`]), so it must run on a **multi-thread** tokio
98//! runtime:
99//!
100//! ```ignore
101//! #[tokio::main(flavor = "multi_thread")]
102//! async fn main() { /* ... */ }
103//!
104//! #[tokio::test(flavor = "multi_thread")]
105//! async fn my_test() { /* ... */ }
106//! ```
107//!
108//! On a current-thread runtime, fetch paths do **not** panic: the synchronous
109//! bridge checks the runtime flavor up front and returns a typed
110//! [`RuntimeError`] (`CurrentThreadRuntime`) instead, so a
111//! misconfigured runtime surfaces as a handled error rather than a panic deep in
112//! a callback. The offline examples and integration tests build the cache over a
113//! mocked provider and never reach the network, so they are exempt.
114//!
115//! # Error handling
116//!
117//! Simulation entry points that distinguish failure modes return
118//! [`errors::SimulationResult`] (`Result<T, SimError>`), where
119//! [`SimError`] separates a decoded [`Revert`](errors::SimError::Revert),
120//! an EVM [`Halt`](errors::SimError::Halt), and an unexpected host-side
121//! [`Other`](errors::SimError::Other) [`SimHostError`]
122//! (RPC, database, ABI encoding). Other fallible modules expose domain errors
123//! such as [`CacheError`], [`FreshnessError`], and [`StorageFetchError`] rather
124//! than erasing failures
125//! into a dynamic catch-all error. The
126//! freshness loop never silently trusts stale data: a transient RPC failure
127//! surfaces as [`freshness::Validation::Unverified`] so callers can retry rather
128//! than act on unverified results.
129//!
130//! # Maturity & stability
131//!
132//! This crate is **pre-1.0** and developed against a phased roadmap (see
133//! `docs/ROADMAP.md`). Until 1.0, breaking changes may land in minor releases;
134//! each is recorded in the crate `CHANGELOG.md`. MSRV is Rust 1.88 (edition 2024).
135//!
136//! The `examples/` directory has runnable, documented walkthroughs of each
137//! module — offline ones that need no network, plus a few that fork real chain
138//! state over RPC. See the crate README for the full list.
139#![warn(missing_docs)]
140
141pub mod access_list;
142pub mod access_set;
143pub mod bulk_storage;
144pub mod bundle;
145pub mod cache;
146#[cfg(feature = "reactive")]
147pub mod cold_start;
148pub mod create3;
149pub mod deploy;
150pub mod errors;
151pub mod events;
152pub mod freshness;
153pub mod inspector;
154pub mod mapping_probe;
155pub mod multicall;
156pub mod prefetch_registry;
157#[cfg(feature = "reactive")]
158pub mod reactive;
159pub mod state_update;
160pub mod tracing;
161
162pub use access_set::StorageAccessList;
163// Bulk storage extraction over eth_call state overrides — the default batch
164// storage fetcher since 0.2.0 (see docs/bulk-storage-extraction.md).
165pub use bulk_storage::{
166 AccountFieldsSample, BlockContextSample, BulkCallConfig, BulkFetcherStatus, CallDispatch,
167 StorageProgram, bulk_call_storage_fetcher, bulk_call_storage_fetcher_with_fallback,
168 bulk_call_storage_fetcher_with_status, fetch_account_fields_bulk, fetch_block_context,
169 fetch_slots_bulk, planned_call_count, run_storage_program, run_storage_programs,
170};
171// Phase 6 Track A+B: bundle simulation + coinbase accounting public vocabulary.
172pub use bundle::{BundleOptions, BundleResult, BundleTx, RevertPolicy, TxOutcome};
173// Primary entry points, hoisted to the crate root for discoverability. The
174// fully-qualified module paths (`cache::EvmCache`, `reactive::ReactiveRuntime`,
175// …) remain valid, so this is purely additive.
176pub use cache::{
177 AccountFieldsFetchFn, AccountProof, AccountProofFetchFn, BlockContextRequirements,
178 BlockStateAccountDiff, BlockStateDiff, BlockStateDiffFetchFn, BlockStateStorageDiff,
179 CacheSpeedMode, CallSimulationResult, CodeMismatch, CodeSeedState, CodeVerifyReport, EvmCache,
180 EvmCacheBuilder, EvmOverlay, EvmSnapshot, PrewarmReport, StorageBatchConfig,
181 StorageFetchStrategy, TxConfig, point_read_storage_fetcher,
182};
183#[cfg(feature = "reactive")]
184pub use cold_start::{
185 ColdStartCall, ColdStartCallResult, ColdStartConfig, ColdStartError, ColdStartPin,
186 ColdStartPlan, ColdStartPlanner, ColdStartResults, ColdStartRoundSummary, ColdStartRunReport,
187 ColdStartStep, RootBaseline, RootBaselinePlanner, RootProbeOutcome, RoundOutcome,
188};
189pub use errors::{
190 AccessListError, AccessListResult, BlockContextError, CacheError, CacheResult, DeployError,
191 DeployResult, FreshnessError, FreshnessResult, MulticallError, MulticallResult, OverlayError,
192 OverlayResult, PersistenceError, RpcError, RuntimeError, SimError, SimHostError,
193 SimulationError, SimulationResult, StorageFetchError, StorageFetchResult,
194};
195pub use events::erc20::Erc20TransferDecoder;
196pub use events::{
197 BlockDigest, DecoderRegistry, EventDecoder, EventPipeline, ReconcileReport, ReorgConfig,
198 StateView,
199};
200pub use freshness::{
201 AlwaysVerify, BlockClock, FreshnessClock, FreshnessController, FreshnessParams,
202 FreshnessPolicy, FreshnessRegistry, NeverVerify, ObservationDriven, SimRequest, SlotChange,
203 SlotFetch, SlotOutcome, SpeculativeSim, Validation, Validity, WallClock,
204};
205// Trace-based hash-derived storage-slot discovery (v0.2.1).
206pub use mapping_probe::{
207 Confidence, HashSlotAccess, HashStorageProbe, SlotLayout, TrackedBalances, TrackedMapping,
208};
209#[cfg(feature = "reactive")]
210pub use reactive::{
211 InterestOwnerSubscriber, ReactiveConfig, ReactiveEngine, ReactiveEngineError,
212 ReactiveEngineRegisterError, ReactiveHandler, ReactiveRuntime,
213};
214pub use state_update::{
215 AccountChange, AccountPatch, PurgeRecord, PurgeScope, SkippedAccountPatch, SkippedBalanceDelta,
216 SkippedDelta, SkippedMask, SlotDelta, StateDiff, StateUpdate,
217};
218pub use tracing::{CallKind, CallStatus, CallTrace, CallTracer, InspectorStack};