Skip to main content

evm_fork_cache/cache/
overlay.rs

1//! Per-simulation state overlays layered over a snapshot or the live cache.
2//!
3//! An [`EvmOverlay`] wraps a read-only base (an
4//! [`EvmSnapshot`] or the cache itself) with
5//! a scratch write layer, so a simulation can mutate balances, storage, and code
6//! and run calls without disturbing the base or other overlays. Overlays are the
7//! `Send` unit of parallel fan-out: snapshot once, clone cheaply, overlay per
8//! candidate. Reads fall through the write layer to the base; writes and reverts
9//! stay local to the overlay.
10
11use std::cell::RefCell;
12use std::collections::{HashMap, HashSet};
13use std::rc::Rc;
14use std::sync::Arc;
15
16use alloy_eips::eip2930::{AccessList, AccessListItem};
17use alloy_primitives::{Address, B256, Bytes, TxKind, U256};
18use foundry_fork_db::{DatabaseError, SharedBackend};
19use revm::{
20    Context, ExecuteCommitEvm, ExecuteEvm, InspectEvm, MainBuilder, MainContext,
21    context::{BlockEnv, CfgEnv, Journal, LocalContext, TxEnv, result::ExecutionResult},
22    database_interface::{Database, DatabaseRef},
23    state::{AccountInfo, Bytecode},
24};
25
26use super::snapshot::EvmSnapshot;
27use super::{CallSimulationResult, IERC20, SimStatus, TxConfig, unix_timestamp_secs_saturating};
28use crate::access_set::StorageAccessList;
29use crate::bundle::{BundleOptions, BundleResult, BundleTx, RevertPolicy, TxOutcome};
30use crate::errors::{
31    OverlayError, OverlayResult as Result, SimError, SimHostError, SimulationError,
32    SimulationResult,
33};
34use crate::inspector::TransferInspector;
35use crate::mapping_probe::HashStorageProbe;
36use alloy_sol_types::SolCall;
37
38type OverlayEvm<'a> = revm::MainnetEvm<
39    Context<BlockEnv, TxEnv, CfgEnv, &'a mut EvmOverlay, Journal<&'a mut EvmOverlay>, ()>,
40>;
41
42type InspectorOverlayEvm<'a, INSP> = revm::MainnetEvm<
43    Context<BlockEnv, TxEnv, CfgEnv, &'a mut EvmOverlay, Journal<&'a mut EvmOverlay>, ()>,
44    INSP,
45>;
46
47/// State an RPC-disconnected overlay could not resolve from its immutable
48/// snapshot.
49///
50/// The EVM database interface requires a value even when an offline snapshot is
51/// incomplete. [`EvmOverlay`] continues to return the protocol-neutral fallback
52/// (`None`, empty bytecode, ZERO storage, or ZERO block hash), but records every
53/// such fallback here. Callers must treat a non-empty report as an incomplete
54/// simulation rather than authoritative execution.
55#[derive(Clone, Debug, Default, PartialEq, Eq)]
56pub struct MissingState {
57    /// Account headers absent from the snapshot.
58    pub accounts: HashSet<Address>,
59    /// Runtime-code hashes absent from the snapshot.
60    pub code_hashes: HashSet<B256>,
61    /// Storage slots absent from the snapshot.
62    pub storage: HashSet<(Address, U256)>,
63    /// In-range block numbers whose hashes were absent from the snapshot.
64    pub block_hashes: HashSet<u64>,
65}
66
67impl MissingState {
68    /// Whether the offline overlay resolved every database read locally.
69    pub fn is_empty(&self) -> bool {
70        self.accounts.is_empty()
71            && self.code_hashes.is_empty()
72            && self.storage.is_empty()
73            && self.block_hashes.is_empty()
74    }
75
76    /// Convert unresolved reads into the generic execution read-set shape.
77    pub fn as_read_set(&self) -> StorageAccessList {
78        StorageAccessList {
79            accounts: self.accounts.clone(),
80            code_hashes: self.code_hashes.clone(),
81            slots: self.storage.clone(),
82            block_numbers: self.block_hashes.clone(),
83        }
84    }
85}
86
87/// Per-simulation mutable overlay on an immutable snapshot.
88///
89/// Lookup order: dirty layer → snapshot → ext_db (optional RPC fallback).
90///
91/// This type is `Send` (unlike `EvmCache`) because it uses no `Rc`/`RefCell`.
92/// Each simulation task gets its own `EvmOverlay` with a cheap `Arc::clone`
93/// of the shared `EvmSnapshot`.
94///
95/// # Reuse across simulations (Pillar A.2)
96///
97/// A worker doing many sims against the same snapshot can call [`Self::new`]
98/// once and [`Self::reset`] between sims instead of allocating a fresh overlay
99/// each time. The reusable shared-memory buffer is also recycled across calls —
100/// see [`Self::call_raw`] — without making the overlay `!Send`.
101pub struct EvmOverlay {
102    snapshot: Arc<EvmSnapshot>,
103    /// Per-simulation mutations (accounts fetched from ext_db, committed changes).
104    dirty_accounts: HashMap<Address, AccountInfo>,
105    /// Per-simulation storage mutations.
106    dirty_storage: HashMap<Address, HashMap<U256, U256>>,
107    /// Optional RPC fallback for data not in snapshot.
108    ext_db: Option<SharedBackend>,
109    /// Reusable shared-memory buffer, recycled across the build→transact→revert
110    /// call methods to avoid reallocating a 64 KB `Vec` per call.
111    ///
112    /// Stored as a plain `Vec<u8>` (not an `Rc`) so the overlay stays `Send`. A
113    /// call method `mem::take`s it, wraps it in a method-local `Rc<RefCell<_>>`
114    /// for revm's [`LocalContext`], runs, then reclaims and clears it after the
115    /// EVM is dropped (see [`Self::build_evm_with_local`]).
116    reusable_buffer: Vec<u8>,
117    /// Target pre-allocation (bytes) for [`Self::reusable_buffer`] and each
118    /// per-call buffer, taken from the snapshot's configured
119    /// [`SharedMemoryCapacity`](super::SharedMemoryCapacity) so overlays honor the
120    /// capacity set on the originating [`EvmCache`].
121    buffer_capacity: usize,
122    /// Set when a `BLOCKHASH` read fell through to the ZERO fallback (no
123    /// snapshot-provided hash and no `ext_db`). The freshness validator reads
124    /// this via [`Self::blockhash_zero_fallback`] to fail closed instead of
125    /// confirming a sim whose control flow may rest on a hash its overlays
126    /// cannot resolve. Cleared by [`Self::reset`].
127    blockhash_zero_fallback: bool,
128    /// Exact unresolved reads observed while no external database was attached.
129    missing_state: MissingState,
130}
131
132impl EvmOverlay {
133    /// Create a new overlay on the given snapshot.
134    ///
135    /// The reusable shared-memory buffer is pre-allocated to the snapshot's
136    /// configured shared-memory capacity (see
137    /// [`SharedMemoryCapacity`](super::SharedMemoryCapacity)).
138    pub fn new(snapshot: Arc<EvmSnapshot>, ext_db: Option<SharedBackend>) -> Self {
139        let buffer_capacity = snapshot.shared_memory_capacity;
140        Self {
141            snapshot,
142            dirty_accounts: HashMap::new(),
143            dirty_storage: HashMap::new(),
144            ext_db,
145            reusable_buffer: Vec::with_capacity(buffer_capacity),
146            buffer_capacity,
147            blockhash_zero_fallback: false,
148            missing_state: MissingState::default(),
149        }
150    }
151
152    /// Clear the per-simulation dirty layer so this overlay can be reused for the
153    /// next simulation against the same snapshot, without reallocating (Pillar
154    /// A.2).
155    ///
156    /// A worker doing K sims calls [`Self::new`] once and `reset()` between sims
157    /// instead of allocating a fresh overlay (plus dirty maps plus an `Arc`
158    /// clone) each time. After `reset()` the overlay reads the pristine snapshot
159    /// again — it is exactly equivalent to a freshly-built overlay on the same
160    /// snapshot. The snapshot `Arc`, the optional `ext_db`, and the reusable
161    /// shared-memory buffer (kept at capacity) are retained.
162    pub fn reset(&mut self) {
163        self.dirty_accounts.clear();
164        self.dirty_storage.clear();
165        self.blockhash_zero_fallback = false;
166        self.missing_state = MissingState::default();
167        // Keep: snapshot Arc, ext_db, and the reusable buffer. The buffer is
168        // already cleared after each call, so nothing to do for it here.
169    }
170
171    /// `true` if any `BLOCKHASH` read on this overlay fell through to the ZERO
172    /// fallback (no snapshot-provided hash for that number and no `ext_db`)
173    /// since construction or the last [`reset`](Self::reset).
174    ///
175    /// The freshness validator uses this to **fail closed**: a sim that read a
176    /// hash its ext-db-less overlays cannot resolve is reported
177    /// [`Unverified`](crate::freshness::Validation::Unverified) rather than
178    /// silently confirmed against a ZERO stand-in.
179    ///
180    /// Only reads revm actually routes to the database can set this: requests
181    /// outside the EVM's valid lookback window (`[current − 256, current)`)
182    /// return spec-mandated ZERO without a database call — that value is
183    /// correct on-chain too, so such reads are deliberately not flagged.
184    pub fn blockhash_zero_fallback(&self) -> bool {
185        self.blockhash_zero_fallback
186    }
187
188    /// Exact database reads that fell back because this overlay has no external
189    /// provider and its immutable snapshot did not contain the requested state.
190    ///
191    /// A non-empty report makes the simulation non-authoritative even when EVM
192    /// execution itself returned success: a missing storage slot, for example,
193    /// is represented as ZERO to satisfy the database trait.
194    pub fn missing_state(&self) -> &MissingState {
195        &self.missing_state
196    }
197
198    /// Chain ID of the block context captured by the underlying snapshot.
199    ///
200    /// This is the value installed into `cfg.chain_id` by [`Self::build_evm`].
201    pub fn chain_id(&self) -> u64 {
202        self.snapshot.chain_id
203    }
204
205    /// Block number of the snapshot's block context, or `None` if it was not
206    /// captured.
207    ///
208    /// When present this is the `block.number` simulations run against; when
209    /// `None`, [`Self::build_evm`] leaves revm's default block number in place.
210    pub fn block_number(&self) -> Option<u64> {
211        self.snapshot.block_number
212    }
213
214    /// Base fee of the snapshot's block context, or `None` if it was not
215    /// captured.
216    ///
217    /// Note that base-fee checks are disabled in the simulation EVM, so this is
218    /// informational rather than enforced against the transaction.
219    pub fn basefee(&self) -> Option<u64> {
220        self.snapshot.basefee
221    }
222
223    /// Timestamp of the snapshot's block context, or `None` if it was not
224    /// captured.
225    ///
226    /// When `None`, [`Self::build_evm`] substitutes the current wall-clock time
227    /// for `block.timestamp`.
228    pub fn timestamp(&self) -> Option<u64> {
229        self.snapshot.timestamp
230    }
231
232    /// A fresh [`LocalContext`] with a newly-allocated 64 KB shared-memory buffer.
233    ///
234    /// Used by the public [`Self::build_evm`], which hands out the EVM and cannot
235    /// reclaim its buffer afterwards. The internal call methods instead recycle
236    /// [`Self::reusable_buffer`] via [`Self::build_evm_with_local`].
237    fn fresh_local(&self) -> LocalContext {
238        LocalContext {
239            shared_memory_buffer: Rc::new(RefCell::new(Vec::with_capacity(self.buffer_capacity))),
240            precompile_error_message: None,
241        }
242    }
243
244    /// Build a revm EVM instance backed by this overlay, using a caller-supplied
245    /// [`LocalContext`].
246    ///
247    /// This is the shared body behind [`Self::build_evm`] and the internal call
248    /// methods. The call methods pass a `local` wrapping the recycled
249    /// [`Self::reusable_buffer`] (Pillar A.2) and reclaim it after the EVM is
250    /// dropped; [`Self::build_evm`] passes a fresh one.
251    ///
252    /// Note: the returned EVM is `!Send` (due to `LocalContext`'s `Rc<RefCell>`),
253    /// but this is fine because it's created and used within a single task.
254    fn build_evm_with_local(&mut self, local: LocalContext) -> OverlayEvm<'_> {
255        // Read snapshot values before the mutable borrow of self
256        let chain_id = self.snapshot.chain_id;
257        let spec_id = self.snapshot.spec_id;
258        let timestamp = self
259            .snapshot
260            .timestamp
261            .unwrap_or_else(|| unix_timestamp_secs_saturating(std::time::SystemTime::now()));
262        let block_number = self.snapshot.block_number;
263        let basefee = self.snapshot.basefee;
264        let coinbase = self.snapshot.coinbase;
265        let prevrandao = self.snapshot.prevrandao;
266        let gas_limit = self.snapshot.gas_limit;
267
268        let mut evm = Context::mainnet()
269            .with_db(&mut *self)
270            .with_local(local)
271            .modify_cfg_chained(|cfg| {
272                cfg.disable_nonce_check = true;
273                cfg.disable_eip3607 = true;
274                cfg.disable_base_fee = true;
275                cfg.disable_balance_check = true;
276                cfg.chain_id = chain_id;
277                cfg.limit_contract_code_size = None;
278                cfg.tx_chain_id_check = false;
279                cfg.spec = spec_id;
280            })
281            .build_mainnet();
282
283        evm.block.timestamp = U256::from(timestamp);
284        if let Some(number) = block_number {
285            evm.block.number = U256::from(number);
286        }
287        if let Some(basefee) = basefee {
288            evm.block.basefee = basefee;
289        }
290        if let Some(coinbase) = coinbase {
291            evm.block.beneficiary = coinbase;
292        }
293        if let Some(prevrandao) = prevrandao {
294            evm.block.prevrandao = Some(prevrandao);
295        }
296        if let Some(gas_limit) = gas_limit {
297            evm.block.gas_limit = gas_limit;
298        }
299        evm
300    }
301
302    /// Build a revm EVM instance backed by this overlay.
303    ///
304    /// This allocates a fresh 64 KB shared-memory buffer each call: it hands the
305    /// EVM out to the caller and cannot reclaim the buffer afterwards, so it
306    /// cannot recycle the overlay's reusable buffer. The internal call methods
307    /// ([`Self::call_raw`], etc.) recycle the buffer instead (Pillar A.2).
308    ///
309    /// Note: The returned EVM is `!Send` (due to `LocalContext`'s `Rc<RefCell>`),
310    /// but this is fine because it's created and used within a single task.
311    pub fn build_evm(&mut self) -> OverlayEvm<'_> {
312        let local = self.fresh_local();
313        self.build_evm_with_local(local)
314    }
315
316    /// Execute a non-committing call and return the raw [`ExecutionResult`].
317    ///
318    /// The EVM state is reverted to a checkpoint after execution on *both*
319    /// success and failure, so the call never mutates this overlay's dirty
320    /// layer. Each overlay simulation is therefore isolated: repeated calls all
321    /// observe the same base state.
322    ///
323    /// A revert or halt is *not* an error here — it is reported through the
324    /// returned [`ExecutionResult`] variant. Only failure to build or transact
325    /// the call yields `Err`.
326    ///
327    /// # Errors
328    ///
329    /// Returns an error if the [`TxEnv`] cannot be built from the given inputs,
330    /// or if revm fails to transact the call (for example a database error
331    /// while loading state from the RPC fallback).
332    ///
333    /// # Examples
334    ///
335    /// ```no_run
336    /// # use std::sync::Arc;
337    /// # use alloy_primitives::{Address, Bytes};
338    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
339    /// # fn run(snapshot: Arc<EvmSnapshot>) -> Result<(), Box<dyn std::error::Error>> {
340    /// let mut overlay = EvmOverlay::new(snapshot, None);
341    /// let result = overlay.call_raw(Address::ZERO, Address::ZERO, Bytes::new())?;
342    /// // State is reverted; a second call sees the same base state.
343    /// let _again = overlay.call_raw(Address::ZERO, Address::ZERO, Bytes::new())?;
344    /// # let _ = result;
345    /// # Ok(())
346    /// # }
347    /// ```
348    pub fn call_raw(
349        &mut self,
350        from: Address,
351        to: Address,
352        calldata: Bytes,
353    ) -> Result<ExecutionResult> {
354        let tx = TxEnv::builder()
355            .caller(from)
356            .kind(TxKind::Call(to))
357            .data(calldata)
358            .value(U256::ZERO)
359            .build()
360            .map_err(OverlayError::tx_env)?;
361
362        // Recycle the reusable buffer (Pillar A.2): take it out as a plain Vec
363        // (keeping the overlay Send), lend it to a method-local Rc<RefCell> for
364        // revm's LocalContext, then reclaim and clear it after the EVM is dropped.
365        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
366        let local = LocalContext {
367            shared_memory_buffer: Rc::clone(&buffer),
368            precompile_error_message: None,
369        };
370
371        let result = {
372            let mut evm = self.build_evm_with_local(local);
373            use revm::context_interface::JournalTr;
374            let checkpoint = evm.journaled_state.checkpoint();
375            let result = evm.transact_one(tx).map_err(OverlayError::transact);
376            evm.journaled_state.checkpoint_revert(checkpoint);
377            result
378        };
379
380        self.reclaim_buffer(buffer);
381        result
382    }
383
384    /// Execute one non-committing call with temporary account-code overrides.
385    ///
386    /// Overrides live only for this call: the previous dirty-layer account (or
387    /// its absence) is restored before this method returns, including when the
388    /// EVM returns an execution error. The shared snapshot is never mutated.
389    /// This is useful for execution helpers whose control flow depends on an
390    /// external call but whose result does not depend on that callee's state;
391    /// for example, a revert-based V3 quoter transfers the output token before
392    /// deliberately reverting with the quote payload.
393    ///
394    /// Each supplied runtime bytecode replaces only the account's code and code
395    /// hash. Its balance, nonce, and account id continue to come from the
396    /// overlay/snapshot account when present.
397    ///
398    /// # Errors
399    ///
400    /// Returns an error if an overridden account cannot be loaded, the
401    /// transaction environment cannot be built, or revm cannot transact the
402    /// call. Empty bytecode is accepted and behaves like an EOA.
403    pub fn call_raw_with_code_overrides(
404        &mut self,
405        from: Address,
406        to: Address,
407        calldata: Bytes,
408        overrides: &[(Address, Bytes)],
409    ) -> Result<ExecutionResult> {
410        let mut prior = Vec::with_capacity(overrides.len());
411        for (address, runtime_bytecode) in overrides {
412            let old_dirty = self.dirty_accounts.get(address).cloned();
413            let mut info = match self.basic(*address) {
414                Ok(info) => info.unwrap_or_default(),
415                Err(error) => {
416                    for (prior_address, prior_info) in prior.into_iter().rev() {
417                        if let Some(info) = prior_info {
418                            self.dirty_accounts.insert(prior_address, info);
419                        } else {
420                            self.dirty_accounts.remove(&prior_address);
421                        }
422                    }
423                    return Err(OverlayError::transact(error));
424                }
425            };
426            let bytecode = Bytecode::new_raw(runtime_bytecode.clone());
427            info.code_hash = bytecode.hash_slow();
428            info.code = Some(bytecode);
429            self.dirty_accounts.insert(*address, info);
430            prior.push((*address, old_dirty));
431        }
432
433        let result = self.call_raw(from, to, calldata);
434
435        for (address, old_dirty) in prior.into_iter().rev() {
436            if let Some(info) = old_dirty {
437                self.dirty_accounts.insert(address, info);
438            } else {
439                self.dirty_accounts.remove(&address);
440            }
441        }
442        result
443    }
444
445    /// Reclaim the recycled shared-memory buffer after the EVM (and its
446    /// `LocalContext` clone of the `Rc`) has been dropped, clearing it for the
447    /// next call.
448    ///
449    /// The `Rc` was only ever held by the dropped EVM and this method's local, so
450    /// `try_unwrap` succeeds in the normal path. If a panic somewhere left an
451    /// extra strong reference the buffer is simply re-allocated next call — no
452    /// correctness impact.
453    fn reclaim_buffer(&mut self, buffer: Rc<RefCell<Vec<u8>>>) {
454        if let Ok(cell) = Rc::try_unwrap(buffer) {
455            let mut buf = cell.into_inner();
456            buf.clear();
457            self.reusable_buffer = buf;
458        } else {
459            self.reusable_buffer = Vec::with_capacity(self.buffer_capacity);
460        }
461    }
462
463    /// Build a revm EVM instance with an inspector, backed by this overlay, using
464    /// a caller-supplied [`LocalContext`].
465    ///
466    /// Like [`Self::build_evm_with_local`] but attaches `inspector`. The call
467    /// methods pass a `local` wrapping the recycled [`Self::reusable_buffer`]
468    /// (Pillar A.2) and reclaim it after the EVM is dropped.
469    fn build_evm_with_inspector_local<INSP>(
470        &mut self,
471        inspector: INSP,
472        local: LocalContext,
473    ) -> InspectorOverlayEvm<'_, INSP> {
474        let chain_id = self.snapshot.chain_id;
475        let spec_id = self.snapshot.spec_id;
476        let timestamp = self
477            .snapshot
478            .timestamp
479            .unwrap_or_else(|| unix_timestamp_secs_saturating(std::time::SystemTime::now()));
480        let block_number = self.snapshot.block_number;
481        let basefee = self.snapshot.basefee;
482        let coinbase = self.snapshot.coinbase;
483        let prevrandao = self.snapshot.prevrandao;
484        let gas_limit = self.snapshot.gas_limit;
485
486        let mut evm = Context::mainnet()
487            .with_db(&mut *self)
488            .with_local(local)
489            .modify_cfg_chained(|cfg| {
490                cfg.disable_nonce_check = true;
491                cfg.disable_eip3607 = true;
492                cfg.disable_base_fee = true;
493                cfg.disable_balance_check = true;
494                cfg.chain_id = chain_id;
495                cfg.limit_contract_code_size = None;
496                cfg.tx_chain_id_check = false;
497                cfg.spec = spec_id;
498            })
499            .build_mainnet_with_inspector(inspector);
500
501        evm.block.timestamp = U256::from(timestamp);
502        if let Some(number) = block_number {
503            evm.block.number = U256::from(number);
504        }
505        if let Some(basefee) = basefee {
506            evm.block.basefee = basefee;
507        }
508        if let Some(coinbase) = coinbase {
509            evm.block.beneficiary = coinbase;
510        }
511        if let Some(prevrandao) = prevrandao {
512            evm.block.prevrandao = Some(prevrandao);
513        }
514        if let Some(gas_limit) = gas_limit {
515            evm.block.gas_limit = gas_limit;
516        }
517        evm
518    }
519
520    /// Simulate a call with transfer tracking via the `TransferInspector`.
521    ///
522    /// This is the overlay-compatible equivalent of
523    /// [`super::EvmCache::simulate_with_transfer_tracking`]. It captures ERC20
524    /// Transfer events during execution to compute balance deltas for `owner`
525    /// (restricted to `tokens` when provided) without relying on pre/post
526    /// balance queries.
527    ///
528    /// On a reverting or halting call the EVM state is reverted to a checkpoint
529    /// before returning, so a failed simulation never mutates this overlay. On
530    /// success the call either commits the journaled changes into the overlay's
531    /// dirty layer (`commit == true`) or reverts them (`commit == false`); a
532    /// non-committing run leaves each overlay simulation isolated from the next.
533    ///
534    /// # Errors
535    ///
536    /// Returns an error if the [`TxEnv`] cannot be built, if revm fails to
537    /// transact the call, if the call reverts (mapped from the revert payload),
538    /// or if the call halts. In every error case the EVM state is reverted
539    /// first, regardless of `commit`.
540    ///
541    /// # Examples
542    ///
543    /// ```no_run
544    /// # use std::sync::Arc;
545    /// # use alloy_primitives::{Address, Bytes};
546    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
547    /// # fn run(snapshot: Arc<EvmSnapshot>, token: Address, owner: Address) -> Result<(), Box<dyn std::error::Error>> {
548    /// let mut overlay = EvmOverlay::new(snapshot, None);
549    /// let sim = overlay.simulate_with_transfer_tracking(
550    ///     owner,
551    ///     token,
552    ///     Bytes::new(),
553    ///     owner,
554    ///     Some([token]),
555    ///     false, // non-committing: state is reverted afterwards
556    /// )?;
557    /// let _delta = sim.token_deltas.get(&token);
558    /// # Ok(())
559    /// # }
560    /// ```
561    pub fn simulate_with_transfer_tracking(
562        &mut self,
563        from: Address,
564        to: Address,
565        calldata: Bytes,
566        owner: Address,
567        tokens: Option<impl IntoIterator<Item = Address>>,
568        commit: bool,
569    ) -> SimulationResult<CallSimulationResult> {
570        let tx = TxEnv::builder()
571            .caller(from)
572            .kind(TxKind::Call(to))
573            .data(calldata)
574            .value(U256::ZERO)
575            .build()
576            .map_err(|e| SimError::Other(SimHostError::tx_env(e)))?;
577
578        let inspector = TransferInspector::new();
579
580        // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
581        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
582        let local = LocalContext {
583            shared_memory_buffer: Rc::clone(&buffer),
584            precompile_error_message: None,
585        };
586
587        let outcome = {
588            let mut evm = self.build_evm_with_inspector_local(inspector, local);
589
590            use revm::context_interface::JournalTr;
591            let checkpoint = evm.journaled_state.checkpoint();
592
593            let result = evm
594                .inspect_one_tx(tx)
595                .map_err(|e| SimError::Other(SimHostError::transact(e)));
596
597            match result {
598                Ok(ExecutionResult::Success {
599                    logs,
600                    gas_used,
601                    output,
602                    ..
603                }) => {
604                    let token_deltas = if let Some(token_list) = tokens {
605                        evm.inspector.balance_deltas_for_tokens(owner, token_list)
606                    } else {
607                        evm.inspector.balance_deltas(owner)
608                    };
609
610                    // Extract EIP-2930 access list from journaled state
611                    let access_list = extract_access_list(&evm.journaled_state.state);
612
613                    if commit {
614                        evm.commit_inner();
615                    } else {
616                        evm.journaled_state.checkpoint_revert(checkpoint);
617                    }
618
619                    Ok(CallSimulationResult {
620                        status: SimStatus::Success,
621                        gas_used,
622                        token_deltas,
623                        logs,
624                        access_list,
625                        output: output.into_data(),
626                    })
627                }
628                Ok(ExecutionResult::Revert { gas_used, output }) => {
629                    evm.journaled_state.checkpoint_revert(checkpoint);
630                    Err(SimulationError::from_revert(gas_used, output).into())
631                }
632                Ok(ExecutionResult::Halt { reason, gas_used }) => {
633                    evm.journaled_state.checkpoint_revert(checkpoint);
634                    Err(SimError::Halt {
635                        reason: format!("{reason:?}"),
636                        gas_used,
637                    })
638                }
639                Err(err) => {
640                    evm.journaled_state.checkpoint_revert(checkpoint);
641                    Err(err)
642                }
643            }
644        };
645
646        self.reclaim_buffer(buffer);
647        outcome
648    }
649
650    /// Run a single call with a caller-supplied [`Inspector`](revm::Inspector),
651    /// returning the raw [`ExecutionResult`] and handing the inspector back for the
652    /// caller to read.
653    ///
654    /// This is the inspector-generic public seam: where
655    /// [`Self::simulate_with_transfer_tracking`] hard-wires the
656    /// [`TransferInspector`], this accepts any
657    /// [`revm::Inspector`] — a [`CallTracer`](crate::tracing::CallTracer), an
658    /// [`InspectorStack`](crate::tracing::InspectorStack) composing several, or a
659    /// caller-defined one. It honors a full [`TxConfig`] (value/gas/nonce/access
660    /// list) exactly like [`Self::call_raw_with_access_list_with`] and recycles the
661    /// reusable shared-memory buffer like the other call methods.
662    ///
663    /// Unlike `simulate_with_transfer_tracking`, a revert or halt is **not** an
664    /// error: the raw [`ExecutionResult`] variant
665    /// ([`Success`](ExecutionResult::Success) /
666    /// [`Revert`](ExecutionResult::Revert) / [`Halt`](ExecutionResult::Halt)) is
667    /// returned as `Ok` so the inspector's captured frames (e.g. a reverted call
668    /// tree) remain observable. Only a tx-env build failure or a transact/database
669    /// error yields `Err`.
670    ///
671    /// On a successful transact the journaled changes are either committed into the
672    /// overlay's dirty layer (`commit == true`) or reverted (`commit == false`),
673    /// matching [`Self::simulate_with_transfer_tracking`]. On a revert/halt the
674    /// checkpoint is always reverted regardless of `commit`, so a failed call never
675    /// mutates this overlay. On a transact error the checkpoint is reverted too.
676    ///
677    /// # Errors
678    ///
679    /// Returns an error if the [`TxEnv`] cannot be built from `from`/`to`/`tx`, or
680    /// if revm fails to transact the call (e.g. a database error while loading
681    /// state).
682    ///
683    /// # Examples
684    ///
685    /// ```no_run
686    /// # use std::sync::Arc;
687    /// # use alloy_primitives::{Address, Bytes};
688    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot, TxConfig};
689    /// # use evm_fork_cache::CallTracer;
690    /// # fn run(snapshot: Arc<EvmSnapshot>, to: Address) -> Result<(), Box<dyn std::error::Error>> {
691    /// let mut overlay = EvmOverlay::new(snapshot, None);
692    /// let (result, tracer) = overlay.call_raw_with_inspector(
693    ///     Address::ZERO,
694    ///     to,
695    ///     Bytes::new(),
696    ///     &TxConfig::default(),
697    ///     CallTracer::new(),
698    ///     false,
699    /// )?;
700    /// let _ = result;
701    /// let _trace = tracer.into_trace();
702    /// # Ok(())
703    /// # }
704    /// ```
705    pub fn call_raw_with_inspector<I>(
706        &mut self,
707        from: Address,
708        to: Address,
709        calldata: Bytes,
710        tx: &TxConfig,
711        inspector: I,
712        commit: bool,
713    ) -> SimulationResult<(ExecutionResult, I)>
714    where
715        I: for<'a> revm::Inspector<
716                Context<
717                    BlockEnv,
718                    TxEnv,
719                    CfgEnv,
720                    &'a mut EvmOverlay,
721                    Journal<&'a mut EvmOverlay>,
722                    (),
723                >,
724            >,
725    {
726        let mut builder = TxEnv::builder()
727            .caller(from)
728            .kind(TxKind::Call(to))
729            .data(calldata)
730            .value(tx.value);
731        if let Some(gas_limit) = tx.gas_limit {
732            builder = builder.gas_limit(gas_limit);
733        }
734        if let Some(gas_price) = tx.gas_price {
735            builder = builder.gas_price(gas_price);
736        }
737        if let Some(nonce) = tx.nonce {
738            builder = builder.nonce(nonce);
739        }
740        if let Some(access_list) = &tx.access_list {
741            builder = builder.access_list(access_list.clone());
742        }
743        let tx_env = builder
744            .build()
745            .map_err(|e| SimError::Other(SimHostError::tx_env(e)))?;
746
747        // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
748        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
749        let local = LocalContext {
750            shared_memory_buffer: Rc::clone(&buffer),
751            precompile_error_message: None,
752        };
753
754        let outcome = {
755            let mut evm = self.build_evm_with_inspector_local(inspector, local);
756
757            use revm::context_interface::JournalTr;
758            let checkpoint = evm.journaled_state.checkpoint();
759
760            match evm.inspect_one_tx(tx_env) {
761                Ok(result) => {
762                    if commit && matches!(result, ExecutionResult::Success { .. }) {
763                        evm.commit_inner();
764                    } else {
765                        evm.journaled_state.checkpoint_revert(checkpoint);
766                    }
767                    // Hand the inspector back to the caller.
768                    Ok((result, evm.inspector))
769                }
770                Err(e) => {
771                    evm.journaled_state.checkpoint_revert(checkpoint);
772                    Err(SimError::Other(SimHostError::transact(e)))
773                }
774            }
775        };
776
777        self.reclaim_buffer(buffer);
778        outcome
779    }
780
781    /// Apply `txs` in order against this overlay over **cumulative** block state,
782    /// with a revert policy and coinbase/miner-payment accounting (Phase 6
783    /// Track A+B).
784    ///
785    /// Each transaction observes the committed writes of the ones before it:
786    /// the bundle runs on a single overlay/EVM with one outer checkpoint plus a
787    /// per-transaction inner checkpoint, so it does **not** rebuild a fresh
788    /// overlay per transaction. See the [`bundle`](crate::bundle) module for the
789    /// public vocabulary ([`BundleTx`], [`BundleOptions`], [`RevertPolicy`],
790    /// [`TxOutcome`], [`BundleResult`]).
791    ///
792    /// # Revert policy
793    ///
794    /// - [`RevertPolicy::Atomic`]: the first transaction that reverts/halts
795    ///   rolls the whole bundle back to the outer checkpoint, sets
796    ///   `succeeded = false`, and stops (`per_tx` ends at the failing
797    ///   transaction). `coinbase_payment` is `0` and the overlay is unchanged.
798    /// - [`RevertPolicy::AllowReverts`]: a revert at a whitelisted index rolls
799    ///   back only that transaction (inner checkpoint) and execution continues;
800    ///   a revert at a non-whitelisted index behaves like `Atomic`.
801    ///
802    /// # Coinbase accounting
803    ///
804    /// `coinbase_payment` is the block beneficiary's balance delta across the kept
805    /// transactions. Under EIP-1559 revm credits the beneficiary only the priority
806    /// fee (`(effective_gas_price − basefee) × gas_used`) and burns the base fee
807    /// in-EVM, so the delta is the honest miner payment (plus any direct coinbase
808    /// tips). Saturating.
809    ///
810    /// # Commit semantics
811    ///
812    /// `opts.commit == true` folds the bundle's cumulative state into this
813    /// overlay's dirty layer (observable by subsequent overlay calls);
814    /// `false` reverts the outer checkpoint so the overlay is unchanged. A
815    /// failed atomic bundle never leaves partial state regardless of `commit`.
816    ///
817    /// # Errors
818    ///
819    /// Returns [`SimError`] if a transaction environment cannot be built or revm
820    /// fails to transact (e.g. a database error). A transaction *reverting* is
821    /// not an error — it is reported through the per-transaction
822    /// [`TxOutcome`] and the revert policy.
823    pub fn simulate_bundle(
824        &mut self,
825        txs: &[BundleTx],
826        opts: &BundleOptions,
827    ) -> SimulationResult<BundleResult> {
828        // Build every TxEnv up front so a build failure surfaces as an error
829        // before we touch the EVM/journal (and the borrow of `self` is clean).
830        let tx_envs: Vec<TxEnv> = txs
831            .iter()
832            .map(|bt| {
833                let mut builder = TxEnv::builder()
834                    .caller(bt.from)
835                    .kind(TxKind::Call(bt.to))
836                    .data(bt.calldata.clone())
837                    .value(bt.tx.value);
838                if let Some(gas_limit) = bt.tx.gas_limit {
839                    builder = builder.gas_limit(gas_limit);
840                }
841                if let Some(gas_price) = bt.tx.gas_price {
842                    builder = builder.gas_price(gas_price);
843                }
844                if let Some(nonce) = bt.tx.nonce {
845                    builder = builder.nonce(nonce);
846                }
847                if let Some(access_list) = &bt.tx.access_list {
848                    builder = builder.access_list(access_list.clone());
849                }
850                builder
851                    .build()
852                    .map_err(|e| SimError::Other(SimHostError::tx_env(e)))
853            })
854            .collect::<std::result::Result<_, _>>()?;
855
856        // Resolve the beneficiary and read its pre-bundle balance before the
857        // mutable borrow of `self` by the EVM (the post-bundle delta is the miner
858        // payment; revm already burns the base fee per EIP-1559).
859        let beneficiary = self
860            .snapshot
861            .coinbase
862            .unwrap_or_else(|| revm::context::BlockEnv::default().beneficiary);
863        let pre_beneficiary_balance = self
864            .basic(beneficiary)
865            .map_err(|e| SimError::Other(SimHostError::database(e)))?
866            .map(|info| info.balance)
867            .unwrap_or(U256::ZERO);
868
869        // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
870        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
871        let local = LocalContext {
872            shared_memory_buffer: Rc::clone(&buffer),
873            precompile_error_message: None,
874        };
875
876        let outcome = {
877            use revm::context_interface::JournalTr;
878            let mut evm = self.build_evm_with_local(local);
879
880            // Outer checkpoint: the whole-bundle savepoint.
881            let outer = evm.journaled_state.checkpoint();
882
883            let mut per_tx: Vec<TxOutcome> = Vec::with_capacity(tx_envs.len());
884            let mut total_gas: u64 = 0;
885            let mut aborted = false;
886
887            'bundle: for (idx, tx_env) in tx_envs.into_iter().enumerate() {
888                // Inner checkpoint: this transaction's savepoint.
889                let inner = evm.journaled_state.checkpoint();
890                let result = match evm.transact_one(tx_env) {
891                    Ok(result) => result,
892                    Err(e) => {
893                        // Host/transact error: undo this tx and the whole bundle,
894                        // reclaim the buffer, and surface as SimError.
895                        evm.journaled_state.checkpoint_revert(inner);
896                        evm.journaled_state.checkpoint_revert(outer);
897                        drop(evm);
898                        self.reclaim_buffer(buffer);
899                        return Err(SimError::Other(SimHostError::transact(e)));
900                    }
901                };
902
903                let gas_used = result.gas_used();
904                let reverted = !result.is_success();
905                let logs = result.logs().to_vec();
906                total_gas = total_gas.saturating_add(gas_used);
907
908                per_tx.push(TxOutcome {
909                    result,
910                    gas_used,
911                    reverted,
912                    logs,
913                });
914
915                if reverted {
916                    let allowed = match &opts.revert_policy {
917                        RevertPolicy::Atomic => false,
918                        RevertPolicy::AllowReverts(idxs) => idxs.contains(&idx),
919                    };
920                    if allowed {
921                        // Roll back only this transaction; later txs still run.
922                        evm.journaled_state.checkpoint_revert(inner);
923                        continue 'bundle;
924                    } else {
925                        // Atomic abort: roll the whole bundle back and stop.
926                        evm.journaled_state.checkpoint_revert(outer);
927                        aborted = true;
928                        break 'bundle;
929                    }
930                }
931                // Successful tx: its effects stay journaled for the next tx.
932            }
933
934            // Partition total gas into successful/reverted buckets in a single
935            // pass. Saturating (consistent with `total_gas`); the invariant
936            // `successful_tx_gas + reverted_tx_gas == total_gas` holds by
937            // construction since every executed tx lands in exactly one bucket.
938            let (successful_tx_gas, reverted_tx_gas) =
939                per_tx.iter().fold((0u64, 0u64), |(succ, rev), tx| {
940                    if tx.reverted {
941                        (succ, rev.saturating_add(tx.gas_used))
942                    } else {
943                        (succ.saturating_add(tx.gas_used), rev)
944                    }
945                });
946
947            if aborted {
948                // State is reverted to the pre-bundle outer checkpoint regardless
949                // of `commit`; no payment.
950                BundleResult {
951                    per_tx,
952                    coinbase_payment: U256::ZERO,
953                    gas_used: total_gas,
954                    successful_tx_gas,
955                    reverted_tx_gas,
956                    succeeded: false,
957                }
958            } else {
959                // Read the beneficiary's post-bundle balance from the journaled
960                // state (present iff it was touched) BEFORE commit/revert, since
961                // `commit_inner` finalizes (drains) the journal and an outer
962                // revert would undo the credit.
963                let post_beneficiary_balance = evm
964                    .journaled_state
965                    .state
966                    .get(&beneficiary)
967                    .map(|acct| acct.info.balance)
968                    .unwrap_or(pre_beneficiary_balance);
969                // revm already excludes the base fee from the beneficiary credit
970                // (EIP-1559), so the delta is the honest miner payment.
971                let coinbase_payment =
972                    post_beneficiary_balance.saturating_sub(pre_beneficiary_balance);
973
974                if opts.commit {
975                    evm.commit_inner();
976                } else {
977                    evm.journaled_state.checkpoint_revert(outer);
978                }
979
980                BundleResult {
981                    per_tx,
982                    coinbase_payment,
983                    gas_used: total_gas,
984                    successful_tx_gas,
985                    reverted_tx_gas,
986                    succeeded: true,
987                }
988            }
989        };
990
991        self.reclaim_buffer(buffer);
992        Ok(outcome)
993    }
994
995    /// Execute a non-committing call and return the result plus the touched
996    /// [`StorageAccessList`].
997    ///
998    /// The access list is collected from every account marked touched in the
999    /// journaled state after execution, recording both the touched accounts and
1000    /// the storage slots accessed under each.
1001    ///
1002    /// The EVM state is reverted to a checkpoint after a successful transact on
1003    /// both success and revert/halt outcomes, so the call never mutates this
1004    /// overlay's dirty layer and each overlay simulation stays isolated. As with
1005    /// [`Self::call_raw`], a revert or halt is reported through the returned
1006    /// [`ExecutionResult`] rather than as an error.
1007    ///
1008    /// # Errors
1009    ///
1010    /// Returns an error if the [`TxEnv`] cannot be built, or if revm fails to
1011    /// transact the call (for example a database error while loading state).
1012    ///
1013    /// # Examples
1014    ///
1015    /// ```no_run
1016    /// # use std::sync::Arc;
1017    /// # use alloy_primitives::{Address, Bytes};
1018    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
1019    /// # fn run(snapshot: Arc<EvmSnapshot>) -> Result<(), Box<dyn std::error::Error>> {
1020    /// let mut overlay = EvmOverlay::new(snapshot, None);
1021    /// let (result, access_list) =
1022    ///     overlay.call_raw_with_access_list(Address::ZERO, Address::ZERO, Bytes::new())?;
1023    /// # let _ = (result, access_list);
1024    /// # Ok(())
1025    /// # }
1026    /// ```
1027    pub fn call_raw_with_access_list(
1028        &mut self,
1029        from: Address,
1030        to: Address,
1031        calldata: Bytes,
1032    ) -> Result<(ExecutionResult, StorageAccessList)> {
1033        self.call_raw_with_access_list_with(from, to, calldata, &TxConfig::default())
1034    }
1035
1036    /// Like [`call_raw_with_access_list`](Self::call_raw_with_access_list) but
1037    /// honors a full [`TxConfig`]: native `value`, `gas_limit`, `gas_price`,
1038    /// `nonce`, and a pre-warming EIP-2930 `access_list`.
1039    ///
1040    /// This is what the freshness optimistic loop uses so a [`SimRequest`]'s tx
1041    /// environment — e.g. a payable call carrying `value`, or a gas-bounded call
1042    /// — is reproduced faithfully instead of silently running as a zero-value,
1043    /// default-gas call. Like the shorthand it is non-committing (the checkpoint
1044    /// is reverted) and returns the captured storage access list.
1045    ///
1046    /// [`SimRequest`]: crate::freshness::SimRequest
1047    pub fn call_raw_with_access_list_with(
1048        &mut self,
1049        from: Address,
1050        to: Address,
1051        calldata: Bytes,
1052        tx: &TxConfig,
1053    ) -> Result<(ExecutionResult, StorageAccessList)> {
1054        let mut builder = TxEnv::builder()
1055            .caller(from)
1056            .kind(TxKind::Call(to))
1057            .data(calldata)
1058            .value(tx.value);
1059        if let Some(gas_limit) = tx.gas_limit {
1060            builder = builder.gas_limit(gas_limit);
1061        }
1062        if let Some(gas_price) = tx.gas_price {
1063            builder = builder.gas_price(gas_price);
1064        }
1065        if let Some(nonce) = tx.nonce {
1066            builder = builder.nonce(nonce);
1067        }
1068        if let Some(access_list) = &tx.access_list {
1069            builder = builder.access_list(access_list.clone());
1070        }
1071        let tx_env = builder.build().map_err(OverlayError::tx_env)?;
1072
1073        // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
1074        let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
1075        let local = LocalContext {
1076            shared_memory_buffer: Rc::clone(&buffer),
1077            precompile_error_message: None,
1078        };
1079
1080        let outcome = {
1081            let mut evm = self.build_evm_with_local(local);
1082            use revm::context_interface::JournalTr;
1083            let checkpoint = evm.journaled_state.checkpoint();
1084            match evm.transact_one(tx_env) {
1085                Ok(result) => {
1086                    let mut access_list = StorageAccessList::default();
1087                    for (address, account) in evm.journaled_state.state.iter() {
1088                        if account.is_touched() {
1089                            access_list.accounts.insert(*address);
1090                            let code_hash = account.info.code_hash;
1091                            if code_hash != B256::ZERO
1092                                && code_hash != revm::primitives::KECCAK_EMPTY
1093                            {
1094                                access_list.code_hashes.insert(code_hash);
1095                            }
1096                            for slot_key in account.storage.keys() {
1097                                access_list.slots.insert((*address, *slot_key));
1098                            }
1099                        }
1100                    }
1101                    evm.journaled_state.checkpoint_revert(checkpoint);
1102                    Ok((result, access_list))
1103                }
1104                Err(e) => {
1105                    // Revert the checkpoint even on a host/transact error so the EVM
1106                    // journal is not left dirty (mirrors `call_raw`).
1107                    evm.journaled_state.checkpoint_revert(checkpoint);
1108                    Err(OverlayError::transact(e))
1109                }
1110            }
1111        };
1112
1113        self.reclaim_buffer(buffer);
1114        outcome
1115    }
1116
1117    /// Write a storage value into this overlay's dirty layer.
1118    ///
1119    /// The dirty layer takes precedence over the snapshot on subsequent reads
1120    /// (see the lookup order on [`EvmOverlay`]), so this injects a value into a
1121    /// snapshot-backed overlay without mutating the shared snapshot.
1122    ///
1123    /// # Freshness validation
1124    ///
1125    /// This is the freshness validator's correction step. When a slot the
1126    /// snapshot captured is found to be stale, the validator writes the
1127    /// freshly-fetched value here and then re-runs the simulation (e.g. via
1128    /// [`Self::call_raw`]): the re-run reads the corrected slot out of the dirty
1129    /// layer instead of the stale snapshot value, so the corrected result
1130    /// becomes observable. Because the override lives only in this overlay,
1131    /// other overlays sharing the same `Arc<EvmSnapshot>` are unaffected.
1132    ///
1133    /// # Examples
1134    ///
1135    /// ```no_run
1136    /// # use std::sync::Arc;
1137    /// # use alloy_primitives::{Address, Bytes, U256};
1138    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
1139    /// # fn run(snapshot: Arc<EvmSnapshot>, token: Address, slot: U256) -> Result<(), Box<dyn std::error::Error>> {
1140    /// let mut overlay = EvmOverlay::new(snapshot, None);
1141    /// // Inject the fresh value, then re-run to observe the corrected result.
1142    /// overlay.override_slot(token, slot, U256::from(42u64));
1143    /// let corrected = overlay.call_raw(Address::ZERO, token, Bytes::new())?;
1144    /// # let _ = corrected;
1145    /// # Ok(())
1146    /// # }
1147    /// ```
1148    pub fn override_slot(&mut self, address: Address, slot: U256, value: U256) {
1149        self.dirty_storage
1150            .entry(address)
1151            .or_default()
1152            .insert(slot, value);
1153    }
1154
1155    /// Execute a non-committing typed Solidity call from [`Address::ZERO`],
1156    /// decoding the return — the overlay counterpart to
1157    /// [`EvmCache::call_sol`](super::EvmCache::call_sol).
1158    ///
1159    /// ```no_run
1160    /// # use std::sync::Arc;
1161    /// # use alloy_primitives::Address;
1162    /// # use alloy_sol_types::sol;
1163    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
1164    /// # sol! { interface IErc20 { function balanceOf(address account) returns (uint256); } }
1165    /// # fn run(mut overlay: EvmOverlay, token: Address, alice: Address) -> Result<(), Box<dyn std::error::Error>> {
1166    /// let bal = overlay.call_sol(token, IErc20::balanceOfCall { account: alice })?;
1167    /// # let _ = bal; Ok(()) }
1168    /// ```
1169    pub fn call_sol<C: SolCall>(&mut self, to: Address, call: C) -> Result<C::Return> {
1170        self.call_sol_from(Address::ZERO, to, call)
1171    }
1172
1173    /// Execute a non-committing typed Solidity call from an explicit sender,
1174    /// decoding the return.
1175    pub fn call_sol_from<C: SolCall>(
1176        &mut self,
1177        from: Address,
1178        to: Address,
1179        call: C,
1180    ) -> Result<C::Return> {
1181        let result = self.call_raw(from, to, Bytes::from(call.abi_encode()))?;
1182        match result {
1183            ExecutionResult::Success { output, .. } => {
1184                let output = output.into_data();
1185                C::abi_decode_returns(&output).map_err(|error| OverlayError::SolCallDecode {
1186                    signature: C::SIGNATURE,
1187                    from,
1188                    to,
1189                    output_len: output.len(),
1190                    details: format!("{error:?}"),
1191                })
1192            }
1193            other => Err(OverlayError::SolCallFailed {
1194                signature: C::SIGNATURE,
1195                from,
1196                to,
1197                result: format!("{other:?}"),
1198            }),
1199        }
1200    }
1201
1202    /// Mock `holder`'s ERC-20 balance of `token` to `amount` — **overlay-local**.
1203    ///
1204    /// Discovers the balance mapping slot and layout (Solidity / Vyper / Solady)
1205    /// from a single `balanceOf(holder)` simulation, writes `amount` to that slot
1206    /// in this overlay's dirty layer via [`override_slot`](Self::override_slot),
1207    /// and verifies. The cache and snapshot are never mutated; the mock is
1208    /// dropped with the overlay.
1209    ///
1210    /// Returns `Ok(true)` if set and verified, `Ok(false)` if no balance slot was
1211    /// discoverable or the write did not drive the return (e.g. a rebasing token,
1212    /// or `holder == Address::ZERO`, which is refused). A failed attempt leaves no
1213    /// stray write.
1214    pub fn mock_balance(
1215        &mut self,
1216        token: Address,
1217        holder: Address,
1218        amount: U256,
1219    ) -> SimulationResult<bool> {
1220        if holder == Address::ZERO {
1221            return Ok(false);
1222        }
1223        let calldata = Bytes::from(IERC20::balanceOfCall { target: holder }.abi_encode());
1224        let holder_word = holder.into_word();
1225        self.mock_slot_driving(token, calldata, amount, move |probe, ret| {
1226            probe
1227                .accesses(&[holder_word])
1228                .into_iter()
1229                .filter(|a| a.keyed_by(holder_word))
1230                .max_by_key(|a| (a.value == ret, a.confidence))
1231                .map(|a| (a.slot, a.value))
1232        })
1233    }
1234
1235    /// Mock `owner`'s ERC-20 allowance to `spender` on `token` — overlay-local.
1236    ///
1237    /// Discovers the (nested) `allowance` mapping entry keyed by both addresses,
1238    /// writes `amount` (pass `U256::MAX` for "unlimited"), and verifies. Refuses
1239    /// `owner == Address::ZERO`. Same isolation and failure semantics as
1240    /// [`mock_balance`](Self::mock_balance).
1241    pub fn mock_allowance(
1242        &mut self,
1243        token: Address,
1244        owner: Address,
1245        spender: Address,
1246        amount: U256,
1247    ) -> SimulationResult<bool> {
1248        if owner == Address::ZERO {
1249            return Ok(false);
1250        }
1251        let calldata = Bytes::from(IERC20::allowanceCall { owner, spender }.abi_encode());
1252        let (owner_word, spender_word) = (owner.into_word(), spender.into_word());
1253        self.mock_slot_driving(token, calldata, amount, move |probe, _ret| {
1254            probe
1255                .accesses(&[owner_word, spender_word])
1256                .into_iter()
1257                .filter(|a| a.keyed_by(owner_word) && a.keyed_by(spender_word))
1258                .max_by_key(|a| (a.depth, a.confidence))
1259                .map(|a| (a.slot, a.value))
1260        })
1261    }
1262
1263    /// Mock the return value of a single-word view call by finding the storage
1264    /// slot that drives it and overriding that slot — overlay-local.
1265    ///
1266    /// Runs `to.calldata`, identifies the `SLOAD` whose loaded value equals the
1267    /// call's returned word (see
1268    /// [`HashStorageProbe::slots_returning`](crate::mapping_probe::HashStorageProbe::slots_returning)),
1269    /// writes `desired` there, and verifies the call now returns `desired`. Works
1270    /// for balances, allowances, `totalSupply`, and any getter that returns a
1271    /// single stored word. Returns `Ok(false)` (leaving no stray write) when the
1272    /// return is computed from more than one slot, so it can't be set by a single
1273    /// override.
1274    pub fn mock_view(
1275        &mut self,
1276        to: Address,
1277        calldata: Bytes,
1278        desired: U256,
1279    ) -> SimulationResult<bool> {
1280        self.mock_slot_driving(to, calldata, desired, |probe, ret| {
1281            probe
1282                .slots_returning(ret)
1283                .into_iter()
1284                .next()
1285                .map(|slot| (slot, ret))
1286        })
1287    }
1288
1289    /// Typed [`mock_view`](Self::mock_view): mock the `desired` return of a
1290    /// [`SolCall`] getter that returns a single word.
1291    ///
1292    /// ```no_run
1293    /// # use std::sync::Arc;
1294    /// # use alloy_primitives::{Address, U256};
1295    /// # use alloy_sol_types::sol;
1296    /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
1297    /// # sol! { interface IErc20 { function totalSupply() returns (uint256); } }
1298    /// # fn run(mut overlay: EvmOverlay, token: Address) -> Result<(), Box<dyn std::error::Error>> {
1299    /// overlay.mock_call(token, IErc20::totalSupplyCall {}, U256::from(1_000u64))?;
1300    /// # Ok(()) }
1301    /// ```
1302    pub fn mock_call<C: SolCall>(
1303        &mut self,
1304        to: Address,
1305        call: C,
1306        desired: U256,
1307    ) -> SimulationResult<bool> {
1308        self.mock_view(to, Bytes::from(call.abi_encode()), desired)
1309    }
1310
1311    /// Extract the leading 32-byte word of a successful call's return data.
1312    fn success_word(result: &ExecutionResult) -> Option<U256> {
1313        match result {
1314            ExecutionResult::Success { output, .. } => {
1315                let data = output.data();
1316                (data.len() >= 32).then(|| U256::from_be_slice(&data[..32]))
1317            }
1318            _ => None,
1319        }
1320    }
1321
1322    /// Shared core for the `mock_*` methods: discover the slot driving
1323    /// `to.calldata`'s return via `choose`, override it to `desired`, verify, and
1324    /// restore the slot on a failed verify so a mis-pick leaves no stray write.
1325    fn mock_slot_driving<F>(
1326        &mut self,
1327        to: Address,
1328        calldata: Bytes,
1329        desired: U256,
1330        choose: F,
1331    ) -> SimulationResult<bool>
1332    where
1333        F: FnOnce(&HashStorageProbe, U256) -> Option<(B256, U256)>,
1334    {
1335        let (result, probe) = self.call_raw_with_inspector(
1336            Address::ZERO,
1337            to,
1338            calldata.clone(),
1339            &TxConfig::default(),
1340            HashStorageProbe::new(),
1341            false,
1342        )?;
1343        let Some(ret) = Self::success_word(&result) else {
1344            return Ok(false);
1345        };
1346        let Some((slot, prev)) = choose(&probe, ret) else {
1347            return Ok(false);
1348        };
1349        let slot_u = U256::from_be_bytes(slot.0);
1350        self.override_slot(to, slot_u, desired);
1351
1352        let (verify, _) = self.call_raw_with_inspector(
1353            Address::ZERO,
1354            to,
1355            calldata,
1356            &TxConfig::default(),
1357            HashStorageProbe::new(),
1358            false,
1359        )?;
1360        if Self::success_word(&verify) == Some(desired) {
1361            Ok(true)
1362        } else {
1363            self.override_slot(to, slot_u, prev); // undo the mis-pick
1364            Ok(false)
1365        }
1366    }
1367}
1368
1369impl revm::database_interface::DatabaseCommit for EvmOverlay {
1370    fn commit(&mut self, changes: alloy_primitives::map::HashMap<Address, revm::state::Account>) {
1371        for (address, account) in changes {
1372            self.dirty_accounts.insert(address, account.info);
1373            let storage = self.dirty_storage.entry(address).or_default();
1374            for (slot, value) in account.storage {
1375                storage.insert(slot, value.present_value);
1376            }
1377        }
1378    }
1379}
1380
1381impl Database for EvmOverlay {
1382    type Error = DatabaseError;
1383
1384    fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
1385        // 1. Check dirty layer
1386        if let Some(info) = self.dirty_accounts.get(&address) {
1387            return Ok(Some(info.clone()));
1388        }
1389        // 2. Check snapshot (O(1) HashMap lookup, no locks). `account_info` folds
1390        //    the two snapshot tiers (overlay ▸ base) and already short-circuits a
1391        //    NotExisting account to None — it must NOT fall through to the ext_db,
1392        //    mirroring revm `DbAccount::info()` and the live `EvmCache` read.
1393        if self.snapshot.accounts_not_existing.contains(&address) {
1394            return Ok(None);
1395        }
1396        if let Some(info) = self.snapshot.account_info(address) {
1397            return Ok(Some(info.clone()));
1398        }
1399        // 3. RPC fallback
1400        if let Some(ref ext_db) = self.ext_db {
1401            let info = ext_db.basic_ref(address)?;
1402            if let Some(ref info) = info {
1403                self.dirty_accounts.insert(address, info.clone());
1404            }
1405            return Ok(info);
1406        }
1407        self.missing_state.accounts.insert(address);
1408        Ok(None)
1409    }
1410
1411    fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
1412        // Check dirty accounts first
1413        for info in self.dirty_accounts.values() {
1414            if info.code_hash == code_hash
1415                && let Some(code) = &info.code
1416            {
1417                return Ok(code.clone());
1418            }
1419        }
1420        // Check the snapshot's code index (overlay ▸ base).
1421        if let Some(code) = self.snapshot.code(code_hash) {
1422            return Ok(code.clone());
1423        }
1424        // RPC fallback
1425        if let Some(ref ext_db) = self.ext_db {
1426            return ext_db.code_by_hash_ref(code_hash);
1427        }
1428        self.missing_state.code_hashes.insert(code_hash);
1429        Ok(Bytecode::default())
1430    }
1431
1432    fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
1433        // 1. Check dirty layer
1434        if let Some(account_storage) = self.dirty_storage.get(&address)
1435            && let Some(value) = account_storage.get(&index)
1436        {
1437            return Ok(*value);
1438        }
1439        // 2. Check snapshot (O(1)). `storage_value` folds the two tiers (overlay ▸
1440        //    cleared-as-ZERO ▸ base); a cleared account's absent slot reads ZERO
1441        //    and must NOT fall through to the ext_db, mirroring the live EVM SLOAD
1442        //    for a StorageCleared/NotExisting account.
1443        if let Some(value) = self.snapshot.storage_value(address, index) {
1444            return Ok(value);
1445        }
1446        // 3. RPC fallback
1447        if let Some(ref ext_db) = self.ext_db {
1448            let value = ext_db.storage_ref(address, index)?;
1449            self.dirty_storage
1450                .entry(address)
1451                .or_default()
1452                .insert(index, value);
1453            return Ok(value);
1454        }
1455        self.missing_state.storage.insert((address, index));
1456        Ok(U256::ZERO)
1457    }
1458
1459    fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
1460        if let Some(hash) = self.snapshot.block_hashes.get(&number) {
1461            return Ok(*hash);
1462        }
1463        if let Some(ref ext_db) = self.ext_db {
1464            return ext_db.block_hash_ref(number);
1465        }
1466        // A hash that was not resident when the snapshot was taken cannot be
1467        // fetched by an RPC-disconnected overlay, so `BLOCKHASH` resolves to
1468        // ZERO. The fallback is recorded so readiness validation fails closed
1469        // instead of confirming control flow that may depend on the real hash.
1470        self.blockhash_zero_fallback = true;
1471        self.missing_state.block_hashes.insert(number);
1472        Ok(B256::ZERO)
1473    }
1474}
1475
1476fn extract_access_list(state: &revm::state::EvmState) -> AccessList {
1477    let items: Vec<AccessListItem> = state
1478        .iter()
1479        .filter(|(_, account)| account.is_touched())
1480        .map(|(address, account)| AccessListItem {
1481            address: *address,
1482            storage_keys: account
1483                .storage
1484                .keys()
1485                .map(|slot| B256::from(*slot))
1486                .collect(),
1487        })
1488        .collect();
1489    AccessList(items)
1490}
1491
1492#[cfg(test)]
1493mod tests {
1494    use super::*;
1495    use crate::cache::snapshot::BaseState;
1496    use revm::primitives::hardfork::SpecId;
1497    use std::collections::HashSet;
1498
1499    /// Build a two-tier `EvmSnapshot` whose cold base holds the given accounts,
1500    /// storage, and code, with an empty hot overlay — the shape
1501    /// `snapshot_deep_clone` produces. The `Arc`-per-account storage of the
1502    /// base is built from the plain per-account maps.
1503    fn snap(
1504        accounts: HashMap<Address, AccountInfo>,
1505        storage: HashMap<Address, HashMap<U256, U256>>,
1506        code_by_hash: HashMap<B256, Bytecode>,
1507        block_hashes: HashMap<u64, B256>,
1508    ) -> Arc<EvmSnapshot> {
1509        let base = BaseState {
1510            accounts,
1511            storage: storage
1512                .into_iter()
1513                .map(|(addr, slots)| (addr, Arc::new(slots)))
1514                .collect(),
1515            code_by_hash,
1516        };
1517        Arc::new(EvmSnapshot {
1518            base: Arc::new(base),
1519            overlay_accounts: HashMap::new(),
1520            overlay_storage: HashMap::new(),
1521            overlay_code_by_hash: HashMap::new(),
1522            storage_cleared: HashSet::new(),
1523            accounts_not_existing: HashSet::new(),
1524            block_hashes,
1525            block_number: None,
1526            basefee: None,
1527            coinbase: None,
1528            prevrandao: None,
1529            gas_limit: None,
1530            chain_id: 42161,
1531            timestamp: None,
1532            spec_id: SpecId::CANCUN,
1533            shared_memory_capacity: 64_000,
1534        })
1535    }
1536
1537    #[test]
1538    fn test_overlay_is_send() {
1539        fn assert_send<T: Send>() {}
1540        assert_send::<EvmOverlay>();
1541    }
1542
1543    #[test]
1544    fn blockhash_zero_fallback_flags_only_unresolved_reads() {
1545        let known = B256::repeat_byte(0xAB);
1546        let snapshot = snap(
1547            HashMap::new(),
1548            HashMap::new(),
1549            HashMap::new(),
1550            HashMap::from([(5u64, known)]),
1551        );
1552        let mut overlay = EvmOverlay::new(snapshot, None);
1553
1554        // A snapshot-provided hash resolves for real: no flag.
1555        assert_eq!(overlay.block_hash(5).unwrap(), known);
1556        assert!(!overlay.blockhash_zero_fallback());
1557
1558        // An untracked number falls back to ZERO and is flagged so the
1559        // freshness validator can fail closed.
1560        assert_eq!(overlay.block_hash(6).unwrap(), B256::ZERO);
1561        assert!(overlay.blockhash_zero_fallback());
1562
1563        // The flag is per-simulation state: reset clears it.
1564        overlay.reset();
1565        assert!(!overlay.blockhash_zero_fallback());
1566    }
1567
1568    #[test]
1569    fn test_overlay_basic_from_snapshot() {
1570        let mut accounts = HashMap::new();
1571        let info = AccountInfo {
1572            balance: U256::from(1000),
1573            nonce: 1,
1574            code_hash: B256::ZERO,
1575            code: None,
1576            account_id: None,
1577        };
1578        let addr = Address::repeat_byte(0x01);
1579        accounts.insert(addr, info);
1580
1581        let snapshot = snap(accounts, HashMap::new(), HashMap::new(), HashMap::new());
1582
1583        let mut overlay = EvmOverlay::new(snapshot, None);
1584        let result = overlay.basic(addr).unwrap();
1585        assert!(result.is_some());
1586        assert_eq!(result.unwrap().balance, U256::from(1000));
1587    }
1588
1589    #[test]
1590    fn test_overlay_storage_from_snapshot() {
1591        let addr = Address::repeat_byte(0x01);
1592        let slot = U256::from(42);
1593        let value = U256::from(999);
1594
1595        let mut storage = HashMap::new();
1596        let mut account_storage = HashMap::new();
1597        account_storage.insert(slot, value);
1598        storage.insert(addr, account_storage);
1599
1600        let snapshot = snap(HashMap::new(), storage, HashMap::new(), HashMap::new());
1601
1602        let mut overlay = EvmOverlay::new(snapshot, None);
1603        let result = overlay.storage(addr, slot).unwrap();
1604        assert_eq!(result, value);
1605    }
1606
1607    #[test]
1608    fn test_overlay_dirty_overrides_snapshot() {
1609        let addr = Address::repeat_byte(0x01);
1610        let slot = U256::from(42);
1611
1612        let mut storage = HashMap::new();
1613        let mut account_storage = HashMap::new();
1614        account_storage.insert(slot, U256::from(100));
1615        storage.insert(addr, account_storage);
1616
1617        let snapshot = snap(HashMap::new(), storage, HashMap::new(), HashMap::new());
1618
1619        let mut overlay = EvmOverlay::new(snapshot, None);
1620
1621        // Write to dirty layer
1622        overlay
1623            .dirty_storage
1624            .entry(addr)
1625            .or_default()
1626            .insert(slot, U256::from(200));
1627
1628        // Should read dirty value, not snapshot
1629        let result = overlay.storage(addr, slot).unwrap();
1630        assert_eq!(result, U256::from(200));
1631    }
1632
1633    #[test]
1634    fn test_overlay_missing_returns_zero() {
1635        let snapshot = snap(
1636            HashMap::new(),
1637            HashMap::new(),
1638            HashMap::new(),
1639            HashMap::new(),
1640        );
1641
1642        let mut overlay = EvmOverlay::new(snapshot, None);
1643        let addr = Address::repeat_byte(0x99);
1644        let result = overlay.storage(addr, U256::from(1)).unwrap();
1645        assert_eq!(result, U256::ZERO);
1646
1647        let account = overlay.basic(addr).unwrap();
1648        assert!(account.is_none());
1649    }
1650
1651    #[test]
1652    fn test_overlay_code_by_hash_from_snapshot() {
1653        let code = Bytecode::new_raw(Bytes::from(vec![0x60, 0x00, 0x60, 0x00]));
1654        let hash = code.hash_slow();
1655
1656        let mut code_by_hash = HashMap::new();
1657        code_by_hash.insert(hash, code.clone());
1658
1659        let snapshot = snap(HashMap::new(), HashMap::new(), code_by_hash, HashMap::new());
1660
1661        let mut overlay = EvmOverlay::new(snapshot, None);
1662        let result = overlay.code_by_hash(hash).unwrap();
1663        assert_eq!(result.len(), 4);
1664    }
1665
1666    #[test]
1667    fn test_overlay_block_hash() {
1668        let mut block_hashes = HashMap::new();
1669        let hash = B256::repeat_byte(0xAB);
1670        block_hashes.insert(42u64, hash);
1671
1672        let snapshot = snap(HashMap::new(), HashMap::new(), HashMap::new(), block_hashes);
1673
1674        let mut overlay = EvmOverlay::new(snapshot, None);
1675        assert_eq!(overlay.block_hash(42).unwrap(), hash);
1676        assert_eq!(overlay.block_hash(99).unwrap(), B256::ZERO);
1677    }
1678}