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, account_proof_fetcher, point_read_storage_fetcher,
182 provider_storage_fetcher,
183};
184#[cfg(feature = "reactive")]
185pub use cold_start::{
186 AccountCodeClaim, AccountProofOutcome, AccountProofRoundFetch, AccountProofRoundFetchError,
187 AccountProofRoundFetcher, AccountProofRoundRequest, ColdStartCall, ColdStartCallResult,
188 ColdStartConfig, ColdStartError, ColdStartPin, ColdStartPlan, ColdStartPlanner,
189 ColdStartResults, ColdStartRoundSummary, ColdStartRunReport, ColdStartStep,
190 PreparedAccountPatch, PreparedAccountPatchError, PreparedAccountValue, PreparedStoragePatch,
191 PreparedStoragePatchError, PreparedStorageValue, RootBaseline, RootBaselinePlanner,
192 RootProbeOutcome, RoundOutcome, StorageRoundFetch, StorageRoundFetchError, StorageRoundFetcher,
193 StorageRoundRequest, StorageSlotRequest,
194};
195pub use errors::{
196 AccessListError, AccessListResult, BlockContextError, CacheError, CacheResult, DeployError,
197 DeployResult, FreshnessError, FreshnessResult, MulticallError, MulticallResult, OverlayError,
198 OverlayResult, PersistenceError, RpcError, RuntimeError, SimError, SimHostError,
199 SimulationError, SimulationResult, StorageFetchError, StorageFetchResult,
200};
201pub use events::erc20::Erc20TransferDecoder;
202pub use events::{
203 BlockDigest, DecoderRegistry, EventDecoder, EventPipeline, ReconcileReport, ReorgConfig,
204 StateView,
205};
206pub use freshness::{
207 AlwaysVerify, BlockClock, FreshnessClock, FreshnessController, FreshnessParams,
208 FreshnessPolicy, FreshnessRegistry, NeverVerify, ObservationDriven, SimRequest, SlotChange,
209 SlotFetch, SlotOutcome, SpeculativeSim, Validation, Validity, WallClock,
210};
211// Trace-based hash-derived storage-slot discovery (v0.2.1).
212pub use mapping_probe::{
213 Confidence, HashSlotAccess, HashStorageProbe, SlotLayout, TrackedBalances, TrackedMapping,
214};
215#[cfg(feature = "reactive")]
216pub use reactive::{
217 InterestOwnerSubscriber, ReactiveConfig, ReactiveEngine, ReactiveEngineError,
218 ReactiveEngineRegisterError, ReactiveHandler, ReactiveRuntime,
219};
220pub use state_update::{
221 AccountChange, AccountPatch, PurgeRecord, PurgeScope, SkippedAccountPatch, SkippedBalanceDelta,
222 SkippedDelta, SkippedMask, SlotDelta, StateDiff, StateUpdate,
223};
224pub use tracing::{CallKind, CallStatus, CallTrace, CallTracer, InspectorStack};