Skip to main content

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