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.90 (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_list::{DEFAULT_CREATE_ACCESS_LIST_GAS_CAP, create_access_list_read_set};
163pub use access_set::StorageAccessList;
164// Bulk storage extraction over eth_call state overrides — the default batch
165// storage fetcher since 0.2.0 (see docs/bulk-storage-extraction.md).
166pub use bulk_storage::{
167 AccountFieldsSample, BlockContextSample, BulkCallConfig, BulkFetcherStatus, CallDispatch,
168 StorageProgram, bulk_call_storage_fetcher, bulk_call_storage_fetcher_with_fallback,
169 bulk_call_storage_fetcher_with_status, fetch_account_fields_bulk, fetch_block_context,
170 fetch_slots_bulk, planned_call_count, run_storage_program, run_storage_programs,
171};
172// Phase 6 Track A+B: bundle simulation + coinbase accounting public vocabulary.
173pub use bundle::{BundleOptions, BundleResult, BundleTx, RevertPolicy, TxOutcome};
174// Primary entry points, hoisted to the crate root for discoverability. The
175// fully-qualified module paths (`cache::EvmCache`, `reactive::ReactiveRuntime`,
176// …) remain valid, so this is purely additive.
177pub use cache::{
178 AccessListFetchFn, AccountFieldsFetchFn, AccountProof, AccountProofFetchFn,
179 BlockContextRequirements, BlockStateAccountDiff, BlockStateDiff, BlockStateDiffFetchFn,
180 BlockStateStorageDiff, CacheSpeedMode, CallSimulationResult, CodeMismatch, CodeSeedState,
181 CodeVerifyReport, DEFAULT_MAX_DURABLE_CHECKPOINT_BYTES, DurableCheckpointBlock,
182 DurableCheckpointError, DurableCheckpointIdentity, DurableCheckpointMetadata,
183 DurableCheckpointStore, EvmCache, EvmCacheBuilder, EvmOverlay, EvmSnapshot,
184 LoadedDurableCheckpoint, PrewarmReport, ReadSetHydrationFailure, ReadSetHydrationReport,
185 ReadSetWarmupBatch, ReadSetWarmupCall, ReadSetWarmupConfig, ReadSetWarmupError,
186 ReadSetWarmupReport, ReadSetWarmupStrategy, StorageBatchConfig, StorageFetchStrategy, TxConfig,
187 account_proof_fetcher, point_read_storage_fetcher, provider_storage_fetcher,
188};
189#[cfg(feature = "reactive")]
190pub use cold_start::{
191 AccountCodeClaim, AccountProofOutcome, AccountProofRoundFetch, AccountProofRoundFetchError,
192 AccountProofRoundFetcher, AccountProofRoundRequest, ColdStartCall, ColdStartCallResult,
193 ColdStartConfig, ColdStartError, ColdStartPin, ColdStartPlan, ColdStartPlanner,
194 ColdStartResults, ColdStartRoundSummary, ColdStartRunReport, ColdStartStep,
195 PreparedAccountPatch, PreparedAccountPatchError, PreparedAccountValue, PreparedStoragePatch,
196 PreparedStoragePatchError, PreparedStorageValue, RootBaseline, RootBaselinePlanner,
197 RootProbeOutcome, RoundOutcome, StorageRoundFetch, StorageRoundFetchError, StorageRoundFetcher,
198 StorageRoundRequest, StorageSlotRequest,
199};
200pub use errors::{
201 AccessListError, AccessListResult, BlockContextError, CacheError, CacheResult, DeployError,
202 DeployResult, FreshnessError, FreshnessResult, MulticallError, MulticallResult, OverlayError,
203 OverlayResult, PersistenceError, RpcError, RuntimeError, SimError, SimHostError,
204 SimulationError, SimulationResult, StorageFetchError, StorageFetchResult,
205};
206pub use events::erc20::Erc20TransferDecoder;
207pub use events::{
208 BlockDigest, DecoderRegistry, EventDecoder, EventPipeline, ReconcileReport, ReorgConfig,
209 StateView,
210};
211pub use freshness::{
212 AlwaysVerify, BlockClock, FreshnessClock, FreshnessController, FreshnessParams,
213 FreshnessPolicy, FreshnessRegistry, NeverVerify, ObservationDriven, SimRequest, SlotChange,
214 SlotFetch, SlotOutcome, SpeculativeSim, Validation, Validity, WallClock,
215};
216// Trace-based hash-derived storage-slot discovery (v0.2.1).
217pub use mapping_probe::{
218 Confidence, HashSlotAccess, HashStorageProbe, SlotLayout, TrackedBalances, TrackedMapping,
219};
220#[cfg(feature = "reactive")]
221pub use reactive::{
222 CheckpointedIngest, InterestOwnerSubscriber, ReactiveBaselineError, ReactiveCanonicalBaseline,
223 ReactiveCheckpointRestoreError, ReactiveConfig, ReactiveEngine, ReactiveEngineError,
224 ReactiveEngineRegisterError, ReactiveHandler, ReactiveRuntime, SubscriberPayloadCommitment,
225};
226pub use state_update::{
227 AccountChange, AccountPatch, PurgeRecord, PurgeScope, SkippedAccountPatch, SkippedBalanceDelta,
228 SkippedDelta, SkippedMask, SlotDelta, StateDiff, StateUpdate,
229};
230pub use tracing::{CallKind, CallStatus, CallTrace, CallTracer, InspectorStack};