evm_fork_cache/cache/overlay.rs
1use std::cell::RefCell;
2use std::collections::HashMap;
3use std::rc::Rc;
4use std::sync::Arc;
5
6use alloy_eips::eip2930::{AccessList, AccessListItem};
7use alloy_primitives::{Address, B256, Bytes, TxKind, U256};
8use anyhow::{Result, anyhow};
9use foundry_fork_db::{DatabaseError, SharedBackend};
10use revm::{
11 Context, ExecuteCommitEvm, ExecuteEvm, InspectEvm, MainBuilder, MainContext,
12 context::{BlockEnv, CfgEnv, Journal, LocalContext, TxEnv, result::ExecutionResult},
13 database_interface::{Database, DatabaseRef},
14 state::{AccountInfo, Bytecode},
15};
16
17use super::snapshot::EvmSnapshot;
18use super::{CallSimulationResult, SimStatus, TxConfig, unix_timestamp_secs_saturating};
19use crate::access_set::StorageAccessList;
20use crate::bundle::{BundleOptions, BundleResult, BundleTx, RevertPolicy, TxOutcome};
21use crate::errors::{SimError, SimulationError, SimulationResult};
22use crate::inspector::TransferInspector;
23
24type OverlayEvm<'a> = revm::MainnetEvm<
25 Context<BlockEnv, TxEnv, CfgEnv, &'a mut EvmOverlay, Journal<&'a mut EvmOverlay>, ()>,
26>;
27
28type InspectorOverlayEvm<'a, INSP> = revm::MainnetEvm<
29 Context<BlockEnv, TxEnv, CfgEnv, &'a mut EvmOverlay, Journal<&'a mut EvmOverlay>, ()>,
30 INSP,
31>;
32
33/// Per-simulation mutable overlay on an immutable snapshot.
34///
35/// Lookup order: dirty layer → snapshot → ext_db (optional RPC fallback).
36///
37/// This type is `Send` (unlike `EvmCache`) because it uses no `Rc`/`RefCell`.
38/// Each simulation task gets its own `EvmOverlay` with a cheap `Arc::clone`
39/// of the shared `EvmSnapshot`.
40///
41/// # Reuse across simulations (Pillar A.2)
42///
43/// A worker doing many sims against the same snapshot can call [`Self::new`]
44/// once and [`Self::reset`] between sims instead of allocating a fresh overlay
45/// each time. The reusable shared-memory buffer is also recycled across calls —
46/// see [`Self::call_raw`] — without making the overlay `!Send`.
47pub struct EvmOverlay {
48 snapshot: Arc<EvmSnapshot>,
49 /// Per-simulation mutations (accounts fetched from ext_db, committed changes).
50 dirty_accounts: HashMap<Address, AccountInfo>,
51 /// Per-simulation storage mutations.
52 dirty_storage: HashMap<Address, HashMap<U256, U256>>,
53 /// Optional RPC fallback for data not in snapshot.
54 ext_db: Option<SharedBackend>,
55 /// Reusable shared-memory buffer, recycled across the build→transact→revert
56 /// call methods to avoid reallocating a 64 KB `Vec` per call.
57 ///
58 /// Stored as a plain `Vec<u8>` (not an `Rc`) so the overlay stays `Send`. A
59 /// call method `mem::take`s it, wraps it in a method-local `Rc<RefCell<_>>`
60 /// for revm's [`LocalContext`], runs, then reclaims and clears it after the
61 /// EVM is dropped (see [`Self::build_evm_with_local`]).
62 reusable_buffer: Vec<u8>,
63 /// Target pre-allocation (bytes) for [`Self::reusable_buffer`] and each
64 /// per-call buffer, taken from the snapshot's configured
65 /// [`SharedMemoryCapacity`](super::SharedMemoryCapacity) so overlays honor the
66 /// capacity set on the originating [`EvmCache`].
67 buffer_capacity: usize,
68}
69
70impl EvmOverlay {
71 /// Create a new overlay on the given snapshot.
72 ///
73 /// The reusable shared-memory buffer is pre-allocated to the snapshot's
74 /// configured shared-memory capacity (see
75 /// [`SharedMemoryCapacity`](super::SharedMemoryCapacity)).
76 pub fn new(snapshot: Arc<EvmSnapshot>, ext_db: Option<SharedBackend>) -> Self {
77 let buffer_capacity = snapshot.shared_memory_capacity;
78 Self {
79 snapshot,
80 dirty_accounts: HashMap::new(),
81 dirty_storage: HashMap::new(),
82 ext_db,
83 reusable_buffer: Vec::with_capacity(buffer_capacity),
84 buffer_capacity,
85 }
86 }
87
88 /// Clear the per-simulation dirty layer so this overlay can be reused for the
89 /// next simulation against the same snapshot, without reallocating (Pillar
90 /// A.2).
91 ///
92 /// A worker doing K sims calls [`Self::new`] once and `reset()` between sims
93 /// instead of allocating a fresh overlay (plus dirty maps plus an `Arc`
94 /// clone) each time. After `reset()` the overlay reads the pristine snapshot
95 /// again — it is exactly equivalent to a freshly-built overlay on the same
96 /// snapshot. The snapshot `Arc`, the optional `ext_db`, and the reusable
97 /// shared-memory buffer (kept at capacity) are retained.
98 pub fn reset(&mut self) {
99 self.dirty_accounts.clear();
100 self.dirty_storage.clear();
101 // Keep: snapshot Arc, ext_db, and the reusable buffer. The buffer is
102 // already cleared after each call, so nothing to do for it here.
103 }
104
105 /// Chain ID of the block context captured by the underlying snapshot.
106 ///
107 /// This is the value installed into `cfg.chain_id` by [`Self::build_evm`].
108 pub fn chain_id(&self) -> u64 {
109 self.snapshot.chain_id
110 }
111
112 /// Block number of the snapshot's block context, or `None` if it was not
113 /// captured.
114 ///
115 /// When present this is the `block.number` simulations run against; when
116 /// `None`, [`Self::build_evm`] leaves revm's default block number in place.
117 pub fn block_number(&self) -> Option<u64> {
118 self.snapshot.block_number
119 }
120
121 /// Base fee of the snapshot's block context, or `None` if it was not
122 /// captured.
123 ///
124 /// Note that base-fee checks are disabled in the simulation EVM, so this is
125 /// informational rather than enforced against the transaction.
126 pub fn basefee(&self) -> Option<u64> {
127 self.snapshot.basefee
128 }
129
130 /// Timestamp of the snapshot's block context, or `None` if it was not
131 /// captured.
132 ///
133 /// When `None`, [`Self::build_evm`] substitutes the current wall-clock time
134 /// for `block.timestamp`.
135 pub fn timestamp(&self) -> Option<u64> {
136 self.snapshot.timestamp
137 }
138
139 /// A fresh [`LocalContext`] with a newly-allocated 64 KB shared-memory buffer.
140 ///
141 /// Used by the public [`Self::build_evm`], which hands out the EVM and cannot
142 /// reclaim its buffer afterwards. The internal call methods instead recycle
143 /// [`Self::reusable_buffer`] via [`Self::build_evm_with_local`].
144 fn fresh_local(&self) -> LocalContext {
145 LocalContext {
146 shared_memory_buffer: Rc::new(RefCell::new(Vec::with_capacity(self.buffer_capacity))),
147 precompile_error_message: None,
148 }
149 }
150
151 /// Build a revm EVM instance backed by this overlay, using a caller-supplied
152 /// [`LocalContext`].
153 ///
154 /// This is the shared body behind [`Self::build_evm`] and the internal call
155 /// methods. The call methods pass a `local` wrapping the recycled
156 /// [`Self::reusable_buffer`] (Pillar A.2) and reclaim it after the EVM is
157 /// dropped; [`Self::build_evm`] passes a fresh one.
158 ///
159 /// Note: the returned EVM is `!Send` (due to `LocalContext`'s `Rc<RefCell>`),
160 /// but this is fine because it's created and used within a single task.
161 fn build_evm_with_local(&mut self, local: LocalContext) -> OverlayEvm<'_> {
162 // Read snapshot values before the mutable borrow of self
163 let chain_id = self.snapshot.chain_id;
164 let spec_id = self.snapshot.spec_id;
165 let timestamp = self
166 .snapshot
167 .timestamp
168 .unwrap_or_else(|| unix_timestamp_secs_saturating(std::time::SystemTime::now()));
169 let block_number = self.snapshot.block_number;
170 let basefee = self.snapshot.basefee;
171 let coinbase = self.snapshot.coinbase;
172 let prevrandao = self.snapshot.prevrandao;
173 let gas_limit = self.snapshot.gas_limit;
174
175 let mut evm = Context::mainnet()
176 .with_db(&mut *self)
177 .with_local(local)
178 .modify_cfg_chained(|cfg| {
179 cfg.disable_nonce_check = true;
180 cfg.disable_eip3607 = true;
181 cfg.disable_base_fee = true;
182 cfg.disable_balance_check = true;
183 cfg.chain_id = chain_id;
184 cfg.limit_contract_code_size = None;
185 cfg.tx_chain_id_check = false;
186 cfg.spec = spec_id;
187 })
188 .build_mainnet();
189
190 evm.block.timestamp = U256::from(timestamp);
191 if let Some(number) = block_number {
192 evm.block.number = U256::from(number);
193 }
194 if let Some(basefee) = basefee {
195 evm.block.basefee = basefee;
196 }
197 if let Some(coinbase) = coinbase {
198 evm.block.beneficiary = coinbase;
199 }
200 if let Some(prevrandao) = prevrandao {
201 evm.block.prevrandao = Some(prevrandao);
202 }
203 if let Some(gas_limit) = gas_limit {
204 evm.block.gas_limit = gas_limit;
205 }
206 evm
207 }
208
209 /// Build a revm EVM instance backed by this overlay.
210 ///
211 /// This allocates a fresh 64 KB shared-memory buffer each call: it hands the
212 /// EVM out to the caller and cannot reclaim the buffer afterwards, so it
213 /// cannot recycle the overlay's reusable buffer. The internal call methods
214 /// ([`Self::call_raw`], etc.) recycle the buffer instead (Pillar A.2).
215 ///
216 /// Note: The returned EVM is `!Send` (due to `LocalContext`'s `Rc<RefCell>`),
217 /// but this is fine because it's created and used within a single task.
218 pub fn build_evm(&mut self) -> OverlayEvm<'_> {
219 let local = self.fresh_local();
220 self.build_evm_with_local(local)
221 }
222
223 /// Execute a non-committing call and return the raw [`ExecutionResult`].
224 ///
225 /// The EVM state is reverted to a checkpoint after execution on *both*
226 /// success and failure, so the call never mutates this overlay's dirty
227 /// layer. Each overlay simulation is therefore isolated: repeated calls all
228 /// observe the same base state.
229 ///
230 /// A revert or halt is *not* an error here — it is reported through the
231 /// returned [`ExecutionResult`] variant. Only failure to build or transact
232 /// the call yields `Err`.
233 ///
234 /// # Errors
235 ///
236 /// Returns an error if the [`TxEnv`] cannot be built from the given inputs,
237 /// or if revm fails to transact the call (for example a database error
238 /// while loading state from the RPC fallback).
239 ///
240 /// # Examples
241 ///
242 /// ```no_run
243 /// # use std::sync::Arc;
244 /// # use alloy_primitives::{Address, Bytes};
245 /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
246 /// # fn run(snapshot: Arc<EvmSnapshot>) -> anyhow::Result<()> {
247 /// let mut overlay = EvmOverlay::new(snapshot, None);
248 /// let result = overlay.call_raw(Address::ZERO, Address::ZERO, Bytes::new())?;
249 /// // State is reverted; a second call sees the same base state.
250 /// let _again = overlay.call_raw(Address::ZERO, Address::ZERO, Bytes::new())?;
251 /// # let _ = result;
252 /// # Ok(())
253 /// # }
254 /// ```
255 pub fn call_raw(
256 &mut self,
257 from: Address,
258 to: Address,
259 calldata: Bytes,
260 ) -> Result<ExecutionResult> {
261 let tx = TxEnv::builder()
262 .caller(from)
263 .kind(TxKind::Call(to))
264 .data(calldata)
265 .value(U256::ZERO)
266 .build()
267 .map_err(|e| anyhow!("Failed to build tx env: {:?}", e))?;
268
269 // Recycle the reusable buffer (Pillar A.2): take it out as a plain Vec
270 // (keeping the overlay Send), lend it to a method-local Rc<RefCell> for
271 // revm's LocalContext, then reclaim and clear it after the EVM is dropped.
272 let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
273 let local = LocalContext {
274 shared_memory_buffer: Rc::clone(&buffer),
275 precompile_error_message: None,
276 };
277
278 let result = {
279 let mut evm = self.build_evm_with_local(local);
280 use revm::context_interface::JournalTr;
281 let checkpoint = evm.journaled_state.checkpoint();
282 let result = evm
283 .transact_one(tx)
284 .map_err(|e| anyhow!("Failed to transact: {:?}", e));
285 evm.journaled_state.checkpoint_revert(checkpoint);
286 result
287 };
288
289 self.reclaim_buffer(buffer);
290 result
291 }
292
293 /// Reclaim the recycled shared-memory buffer after the EVM (and its
294 /// `LocalContext` clone of the `Rc`) has been dropped, clearing it for the
295 /// next call.
296 ///
297 /// The `Rc` was only ever held by the dropped EVM and this method's local, so
298 /// `try_unwrap` succeeds in the normal path. If a panic somewhere left an
299 /// extra strong reference the buffer is simply re-allocated next call — no
300 /// correctness impact.
301 fn reclaim_buffer(&mut self, buffer: Rc<RefCell<Vec<u8>>>) {
302 if let Ok(cell) = Rc::try_unwrap(buffer) {
303 let mut buf = cell.into_inner();
304 buf.clear();
305 self.reusable_buffer = buf;
306 } else {
307 self.reusable_buffer = Vec::with_capacity(self.buffer_capacity);
308 }
309 }
310
311 /// Build a revm EVM instance with an inspector, backed by this overlay, using
312 /// a caller-supplied [`LocalContext`].
313 ///
314 /// Like [`Self::build_evm_with_local`] but attaches `inspector`. The call
315 /// methods pass a `local` wrapping the recycled [`Self::reusable_buffer`]
316 /// (Pillar A.2) and reclaim it after the EVM is dropped.
317 fn build_evm_with_inspector_local<INSP>(
318 &mut self,
319 inspector: INSP,
320 local: LocalContext,
321 ) -> InspectorOverlayEvm<'_, INSP> {
322 let chain_id = self.snapshot.chain_id;
323 let spec_id = self.snapshot.spec_id;
324 let timestamp = self
325 .snapshot
326 .timestamp
327 .unwrap_or_else(|| unix_timestamp_secs_saturating(std::time::SystemTime::now()));
328 let block_number = self.snapshot.block_number;
329 let basefee = self.snapshot.basefee;
330 let coinbase = self.snapshot.coinbase;
331 let prevrandao = self.snapshot.prevrandao;
332 let gas_limit = self.snapshot.gas_limit;
333
334 let mut evm = Context::mainnet()
335 .with_db(&mut *self)
336 .with_local(local)
337 .modify_cfg_chained(|cfg| {
338 cfg.disable_nonce_check = true;
339 cfg.disable_eip3607 = true;
340 cfg.disable_base_fee = true;
341 cfg.disable_balance_check = true;
342 cfg.chain_id = chain_id;
343 cfg.limit_contract_code_size = None;
344 cfg.tx_chain_id_check = false;
345 cfg.spec = spec_id;
346 })
347 .build_mainnet_with_inspector(inspector);
348
349 evm.block.timestamp = U256::from(timestamp);
350 if let Some(number) = block_number {
351 evm.block.number = U256::from(number);
352 }
353 if let Some(basefee) = basefee {
354 evm.block.basefee = basefee;
355 }
356 if let Some(coinbase) = coinbase {
357 evm.block.beneficiary = coinbase;
358 }
359 if let Some(prevrandao) = prevrandao {
360 evm.block.prevrandao = Some(prevrandao);
361 }
362 if let Some(gas_limit) = gas_limit {
363 evm.block.gas_limit = gas_limit;
364 }
365 evm
366 }
367
368 /// Simulate a call with transfer tracking via the `TransferInspector`.
369 ///
370 /// This is the overlay-compatible equivalent of
371 /// [`super::EvmCache::simulate_with_transfer_tracking`]. It captures ERC20
372 /// Transfer events during execution to compute balance deltas for `owner`
373 /// (restricted to `tokens` when provided) without relying on pre/post
374 /// balance queries.
375 ///
376 /// On a reverting or halting call the EVM state is reverted to a checkpoint
377 /// before returning, so a failed simulation never mutates this overlay. On
378 /// success the call either commits the journaled changes into the overlay's
379 /// dirty layer (`commit == true`) or reverts them (`commit == false`); a
380 /// non-committing run leaves each overlay simulation isolated from the next.
381 ///
382 /// # Errors
383 ///
384 /// Returns an error if the [`TxEnv`] cannot be built, if revm fails to
385 /// transact the call, if the call reverts (mapped from the revert payload),
386 /// or if the call halts. In every error case the EVM state is reverted
387 /// first, regardless of `commit`.
388 ///
389 /// # Examples
390 ///
391 /// ```no_run
392 /// # use std::sync::Arc;
393 /// # use alloy_primitives::{Address, Bytes};
394 /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
395 /// # fn run(snapshot: Arc<EvmSnapshot>, token: Address, owner: Address) -> anyhow::Result<()> {
396 /// let mut overlay = EvmOverlay::new(snapshot, None);
397 /// let sim = overlay.simulate_with_transfer_tracking(
398 /// owner,
399 /// token,
400 /// Bytes::new(),
401 /// owner,
402 /// Some([token]),
403 /// false, // non-committing: state is reverted afterwards
404 /// )?;
405 /// let _delta = sim.token_deltas.get(&token);
406 /// # Ok(())
407 /// # }
408 /// ```
409 pub fn simulate_with_transfer_tracking(
410 &mut self,
411 from: Address,
412 to: Address,
413 calldata: Bytes,
414 owner: Address,
415 tokens: Option<impl IntoIterator<Item = Address>>,
416 commit: bool,
417 ) -> SimulationResult<CallSimulationResult> {
418 let tx = TxEnv::builder()
419 .caller(from)
420 .kind(TxKind::Call(to))
421 .data(calldata)
422 .value(U256::ZERO)
423 .build()
424 .map_err(|e| SimError::Other(anyhow!("Failed to build tx env: {:?}", e)))?;
425
426 let inspector = TransferInspector::new();
427
428 // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
429 let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
430 let local = LocalContext {
431 shared_memory_buffer: Rc::clone(&buffer),
432 precompile_error_message: None,
433 };
434
435 let outcome = {
436 let mut evm = self.build_evm_with_inspector_local(inspector, local);
437
438 use revm::context_interface::JournalTr;
439 let checkpoint = evm.journaled_state.checkpoint();
440
441 let result = evm
442 .inspect_one_tx(tx)
443 .map_err(|e| SimError::Other(anyhow!("Failed to transact: {:?}", e)));
444
445 match result {
446 Ok(ExecutionResult::Success {
447 logs,
448 gas_used,
449 output,
450 ..
451 }) => {
452 let token_deltas = if let Some(token_list) = tokens {
453 evm.inspector.balance_deltas_for_tokens(owner, token_list)
454 } else {
455 evm.inspector.balance_deltas(owner)
456 };
457
458 // Extract EIP-2930 access list from journaled state
459 let access_list = extract_access_list(&evm.journaled_state.state);
460
461 if commit {
462 evm.commit_inner();
463 } else {
464 evm.journaled_state.checkpoint_revert(checkpoint);
465 }
466
467 Ok(CallSimulationResult {
468 status: SimStatus::Success,
469 gas_used,
470 token_deltas,
471 logs,
472 access_list,
473 output: output.into_data(),
474 })
475 }
476 Ok(ExecutionResult::Revert { gas_used, output }) => {
477 evm.journaled_state.checkpoint_revert(checkpoint);
478 Err(SimulationError::from_revert(gas_used, output).into())
479 }
480 Ok(ExecutionResult::Halt { reason, gas_used }) => {
481 evm.journaled_state.checkpoint_revert(checkpoint);
482 Err(SimError::Halt {
483 reason: format!("{reason:?}"),
484 gas_used,
485 })
486 }
487 Err(err) => {
488 evm.journaled_state.checkpoint_revert(checkpoint);
489 Err(err)
490 }
491 }
492 };
493
494 self.reclaim_buffer(buffer);
495 outcome
496 }
497
498 /// Run a single call with a caller-supplied [`Inspector`](revm::Inspector),
499 /// returning the raw [`ExecutionResult`] and handing the inspector back for the
500 /// caller to read.
501 ///
502 /// This is the inspector-generic public seam: where
503 /// [`Self::simulate_with_transfer_tracking`] hard-wires the
504 /// [`TransferInspector`], this accepts any
505 /// [`revm::Inspector`] — a [`CallTracer`](crate::tracing::CallTracer), an
506 /// [`InspectorStack`](crate::tracing::InspectorStack) composing several, or a
507 /// caller-defined one. It honors a full [`TxConfig`] (value/gas/nonce/access
508 /// list) exactly like [`Self::call_raw_with_access_list_with`] and recycles the
509 /// reusable shared-memory buffer like the other call methods.
510 ///
511 /// Unlike `simulate_with_transfer_tracking`, a revert or halt is **not** an
512 /// error: the raw [`ExecutionResult`] variant
513 /// ([`Success`](ExecutionResult::Success) /
514 /// [`Revert`](ExecutionResult::Revert) / [`Halt`](ExecutionResult::Halt)) is
515 /// returned as `Ok` so the inspector's captured frames (e.g. a reverted call
516 /// tree) remain observable. Only a tx-env build failure or a transact/database
517 /// error yields `Err`.
518 ///
519 /// On a successful transact the journaled changes are either committed into the
520 /// overlay's dirty layer (`commit == true`) or reverted (`commit == false`),
521 /// matching [`Self::simulate_with_transfer_tracking`]. On a revert/halt the
522 /// checkpoint is always reverted regardless of `commit`, so a failed call never
523 /// mutates this overlay. On a transact error the checkpoint is reverted too.
524 ///
525 /// # Errors
526 ///
527 /// Returns an error if the [`TxEnv`] cannot be built from `from`/`to`/`tx`, or
528 /// if revm fails to transact the call (e.g. a database error while loading
529 /// state).
530 ///
531 /// # Examples
532 ///
533 /// ```no_run
534 /// # use std::sync::Arc;
535 /// # use alloy_primitives::{Address, Bytes};
536 /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot, TxConfig};
537 /// # use evm_fork_cache::CallTracer;
538 /// # fn run(snapshot: Arc<EvmSnapshot>, to: Address) -> anyhow::Result<()> {
539 /// let mut overlay = EvmOverlay::new(snapshot, None);
540 /// let (result, tracer) = overlay.call_raw_with_inspector(
541 /// Address::ZERO,
542 /// to,
543 /// Bytes::new(),
544 /// &TxConfig::default(),
545 /// CallTracer::new(),
546 /// false,
547 /// )?;
548 /// let _ = result;
549 /// let _trace = tracer.into_trace();
550 /// # Ok(())
551 /// # }
552 /// ```
553 pub fn call_raw_with_inspector<I>(
554 &mut self,
555 from: Address,
556 to: Address,
557 calldata: Bytes,
558 tx: &TxConfig,
559 inspector: I,
560 commit: bool,
561 ) -> SimulationResult<(ExecutionResult, I)>
562 where
563 I: for<'a> revm::Inspector<
564 Context<
565 BlockEnv,
566 TxEnv,
567 CfgEnv,
568 &'a mut EvmOverlay,
569 Journal<&'a mut EvmOverlay>,
570 (),
571 >,
572 >,
573 {
574 let mut builder = TxEnv::builder()
575 .caller(from)
576 .kind(TxKind::Call(to))
577 .data(calldata)
578 .value(tx.value);
579 if let Some(gas_limit) = tx.gas_limit {
580 builder = builder.gas_limit(gas_limit);
581 }
582 if let Some(gas_price) = tx.gas_price {
583 builder = builder.gas_price(gas_price);
584 }
585 if let Some(nonce) = tx.nonce {
586 builder = builder.nonce(nonce);
587 }
588 if let Some(access_list) = &tx.access_list {
589 builder = builder.access_list(access_list.clone());
590 }
591 let tx_env = builder
592 .build()
593 .map_err(|e| SimError::Other(anyhow!("Failed to build tx env: {:?}", e)))?;
594
595 // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
596 let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
597 let local = LocalContext {
598 shared_memory_buffer: Rc::clone(&buffer),
599 precompile_error_message: None,
600 };
601
602 let outcome = {
603 let mut evm = self.build_evm_with_inspector_local(inspector, local);
604
605 use revm::context_interface::JournalTr;
606 let checkpoint = evm.journaled_state.checkpoint();
607
608 match evm.inspect_one_tx(tx_env) {
609 Ok(result) => {
610 if commit && matches!(result, ExecutionResult::Success { .. }) {
611 evm.commit_inner();
612 } else {
613 evm.journaled_state.checkpoint_revert(checkpoint);
614 }
615 // Hand the inspector back to the caller.
616 Ok((result, evm.inspector))
617 }
618 Err(e) => {
619 evm.journaled_state.checkpoint_revert(checkpoint);
620 Err(SimError::Other(anyhow!("Failed to transact: {:?}", e)))
621 }
622 }
623 };
624
625 self.reclaim_buffer(buffer);
626 outcome
627 }
628
629 /// Apply `txs` in order against this overlay over **cumulative** block state,
630 /// with a revert policy and coinbase/miner-payment accounting (Phase 6
631 /// Track A+B).
632 ///
633 /// Each transaction observes the committed writes of the ones before it:
634 /// the bundle runs on a single overlay/EVM with one outer checkpoint plus a
635 /// per-transaction inner checkpoint, so it does **not** rebuild a fresh
636 /// overlay per transaction. See the [`bundle`](crate::bundle) module for the
637 /// public vocabulary ([`BundleTx`], [`BundleOptions`], [`RevertPolicy`],
638 /// [`TxOutcome`], [`BundleResult`]).
639 ///
640 /// # Revert policy
641 ///
642 /// - [`RevertPolicy::Atomic`]: the first transaction that reverts/halts
643 /// rolls the whole bundle back to the outer checkpoint, sets
644 /// `succeeded = false`, and stops (`per_tx` ends at the failing
645 /// transaction). `coinbase_payment` is `0` and the overlay is unchanged.
646 /// - [`RevertPolicy::AllowReverts`]: a revert at a whitelisted index rolls
647 /// back only that transaction (inner checkpoint) and execution continues;
648 /// a revert at a non-whitelisted index behaves like `Atomic`.
649 ///
650 /// # Coinbase accounting
651 ///
652 /// `coinbase_payment` is the block beneficiary's balance delta across the kept
653 /// transactions. Under EIP-1559 revm credits the beneficiary only the priority
654 /// fee (`(effective_gas_price − basefee) × gas_used`) and burns the base fee
655 /// in-EVM, so the delta is the honest miner payment (plus any direct coinbase
656 /// tips). Saturating.
657 ///
658 /// # Commit semantics
659 ///
660 /// `opts.commit == true` folds the bundle's cumulative state into this
661 /// overlay's dirty layer (observable by subsequent overlay calls);
662 /// `false` reverts the outer checkpoint so the overlay is unchanged. A
663 /// failed atomic bundle never leaves partial state regardless of `commit`.
664 ///
665 /// # Errors
666 ///
667 /// Returns [`SimError`] if a transaction environment cannot be built or revm
668 /// fails to transact (e.g. a database error). A transaction *reverting* is
669 /// not an error — it is reported through the per-transaction
670 /// [`TxOutcome`] and the revert policy.
671 pub fn simulate_bundle(
672 &mut self,
673 txs: &[BundleTx],
674 opts: &BundleOptions,
675 ) -> SimulationResult<BundleResult> {
676 // Build every TxEnv up front so a build failure surfaces as an error
677 // before we touch the EVM/journal (and the borrow of `self` is clean).
678 let tx_envs: Vec<TxEnv> = txs
679 .iter()
680 .map(|bt| {
681 let mut builder = TxEnv::builder()
682 .caller(bt.from)
683 .kind(TxKind::Call(bt.to))
684 .data(bt.calldata.clone())
685 .value(bt.tx.value);
686 if let Some(gas_limit) = bt.tx.gas_limit {
687 builder = builder.gas_limit(gas_limit);
688 }
689 if let Some(gas_price) = bt.tx.gas_price {
690 builder = builder.gas_price(gas_price);
691 }
692 if let Some(nonce) = bt.tx.nonce {
693 builder = builder.nonce(nonce);
694 }
695 if let Some(access_list) = &bt.tx.access_list {
696 builder = builder.access_list(access_list.clone());
697 }
698 builder
699 .build()
700 .map_err(|e| SimError::Other(anyhow!("Failed to build tx env: {:?}", e)))
701 })
702 .collect::<Result<_, _>>()?;
703
704 // Resolve the beneficiary and read its pre-bundle balance before the
705 // mutable borrow of `self` by the EVM (the post-bundle delta is the miner
706 // payment; revm already burns the base fee per EIP-1559).
707 let beneficiary = self
708 .snapshot
709 .coinbase
710 .unwrap_or_else(|| revm::context::BlockEnv::default().beneficiary);
711 let pre_beneficiary_balance = self
712 .basic(beneficiary)
713 .map_err(|e| SimError::Other(anyhow!("Failed to load beneficiary: {:?}", e)))?
714 .map(|info| info.balance)
715 .unwrap_or(U256::ZERO);
716
717 // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
718 let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
719 let local = LocalContext {
720 shared_memory_buffer: Rc::clone(&buffer),
721 precompile_error_message: None,
722 };
723
724 let outcome = {
725 use revm::context_interface::JournalTr;
726 let mut evm = self.build_evm_with_local(local);
727
728 // Outer checkpoint: the whole-bundle savepoint.
729 let outer = evm.journaled_state.checkpoint();
730
731 let mut per_tx: Vec<TxOutcome> = Vec::with_capacity(tx_envs.len());
732 let mut total_gas: u64 = 0;
733 let mut aborted = false;
734
735 'bundle: for (idx, tx_env) in tx_envs.into_iter().enumerate() {
736 // Inner checkpoint: this transaction's savepoint.
737 let inner = evm.journaled_state.checkpoint();
738 let result = match evm.transact_one(tx_env) {
739 Ok(result) => result,
740 Err(e) => {
741 // Host/transact error: undo this tx and the whole bundle,
742 // reclaim the buffer, and surface as SimError.
743 evm.journaled_state.checkpoint_revert(inner);
744 evm.journaled_state.checkpoint_revert(outer);
745 drop(evm);
746 self.reclaim_buffer(buffer);
747 return Err(SimError::Other(anyhow!("Failed to transact: {:?}", e)));
748 }
749 };
750
751 let gas_used = result.gas_used();
752 let reverted = !result.is_success();
753 let logs = result.logs().to_vec();
754 total_gas = total_gas.saturating_add(gas_used);
755
756 per_tx.push(TxOutcome {
757 result,
758 gas_used,
759 reverted,
760 logs,
761 });
762
763 if reverted {
764 let allowed = match &opts.revert_policy {
765 RevertPolicy::Atomic => false,
766 RevertPolicy::AllowReverts(idxs) => idxs.contains(&idx),
767 };
768 if allowed {
769 // Roll back only this transaction; later txs still run.
770 evm.journaled_state.checkpoint_revert(inner);
771 continue 'bundle;
772 } else {
773 // Atomic abort: roll the whole bundle back and stop.
774 evm.journaled_state.checkpoint_revert(outer);
775 aborted = true;
776 break 'bundle;
777 }
778 }
779 // Successful tx: its effects stay journaled for the next tx.
780 }
781
782 if aborted {
783 // State is reverted to the pre-bundle outer checkpoint regardless
784 // of `commit`; no payment.
785 BundleResult {
786 per_tx,
787 coinbase_payment: U256::ZERO,
788 gas_used: total_gas,
789 succeeded: false,
790 }
791 } else {
792 // Read the beneficiary's post-bundle balance from the journaled
793 // state (present iff it was touched) BEFORE commit/revert, since
794 // `commit_inner` finalizes (drains) the journal and an outer
795 // revert would undo the credit.
796 let post_beneficiary_balance = evm
797 .journaled_state
798 .state
799 .get(&beneficiary)
800 .map(|acct| acct.info.balance)
801 .unwrap_or(pre_beneficiary_balance);
802 // revm already excludes the base fee from the beneficiary credit
803 // (EIP-1559), so the delta is the honest miner payment.
804 let coinbase_payment =
805 post_beneficiary_balance.saturating_sub(pre_beneficiary_balance);
806
807 if opts.commit {
808 evm.commit_inner();
809 } else {
810 evm.journaled_state.checkpoint_revert(outer);
811 }
812
813 BundleResult {
814 per_tx,
815 coinbase_payment,
816 gas_used: total_gas,
817 succeeded: true,
818 }
819 }
820 };
821
822 self.reclaim_buffer(buffer);
823 Ok(outcome)
824 }
825
826 /// Execute a non-committing call and return the result plus the touched
827 /// [`StorageAccessList`].
828 ///
829 /// The access list is collected from every account marked touched in the
830 /// journaled state after execution, recording both the touched accounts and
831 /// the storage slots accessed under each.
832 ///
833 /// The EVM state is reverted to a checkpoint after a successful transact on
834 /// both success and revert/halt outcomes, so the call never mutates this
835 /// overlay's dirty layer and each overlay simulation stays isolated. As with
836 /// [`Self::call_raw`], a revert or halt is reported through the returned
837 /// [`ExecutionResult`] rather than as an error.
838 ///
839 /// # Errors
840 ///
841 /// Returns an error if the [`TxEnv`] cannot be built, or if revm fails to
842 /// transact the call (for example a database error while loading state).
843 ///
844 /// # Examples
845 ///
846 /// ```no_run
847 /// # use std::sync::Arc;
848 /// # use alloy_primitives::{Address, Bytes};
849 /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
850 /// # fn run(snapshot: Arc<EvmSnapshot>) -> anyhow::Result<()> {
851 /// let mut overlay = EvmOverlay::new(snapshot, None);
852 /// let (result, access_list) =
853 /// overlay.call_raw_with_access_list(Address::ZERO, Address::ZERO, Bytes::new())?;
854 /// # let _ = (result, access_list);
855 /// # Ok(())
856 /// # }
857 /// ```
858 pub fn call_raw_with_access_list(
859 &mut self,
860 from: Address,
861 to: Address,
862 calldata: Bytes,
863 ) -> Result<(ExecutionResult, StorageAccessList)> {
864 self.call_raw_with_access_list_with(from, to, calldata, &TxConfig::default())
865 }
866
867 /// Like [`call_raw_with_access_list`](Self::call_raw_with_access_list) but
868 /// honors a full [`TxConfig`]: native `value`, `gas_limit`, `gas_price`,
869 /// `nonce`, and a pre-warming EIP-2930 `access_list`.
870 ///
871 /// This is what the freshness optimistic loop uses so a [`SimRequest`]'s tx
872 /// environment — e.g. a payable call carrying `value`, or a gas-bounded call
873 /// — is reproduced faithfully instead of silently running as a zero-value,
874 /// default-gas call. Like the shorthand it is non-committing (the checkpoint
875 /// is reverted) and returns the captured storage access list.
876 ///
877 /// [`SimRequest`]: crate::freshness::SimRequest
878 pub fn call_raw_with_access_list_with(
879 &mut self,
880 from: Address,
881 to: Address,
882 calldata: Bytes,
883 tx: &TxConfig,
884 ) -> Result<(ExecutionResult, StorageAccessList)> {
885 let mut builder = TxEnv::builder()
886 .caller(from)
887 .kind(TxKind::Call(to))
888 .data(calldata)
889 .value(tx.value);
890 if let Some(gas_limit) = tx.gas_limit {
891 builder = builder.gas_limit(gas_limit);
892 }
893 if let Some(gas_price) = tx.gas_price {
894 builder = builder.gas_price(gas_price);
895 }
896 if let Some(nonce) = tx.nonce {
897 builder = builder.nonce(nonce);
898 }
899 if let Some(access_list) = &tx.access_list {
900 builder = builder.access_list(access_list.clone());
901 }
902 let tx_env = builder
903 .build()
904 .map_err(|e| anyhow!("Failed to build tx env: {:?}", e))?;
905
906 // Recycle the reusable buffer (Pillar A.2); reclaimed after the EVM drops.
907 let buffer = Rc::new(RefCell::new(std::mem::take(&mut self.reusable_buffer)));
908 let local = LocalContext {
909 shared_memory_buffer: Rc::clone(&buffer),
910 precompile_error_message: None,
911 };
912
913 let outcome = {
914 let mut evm = self.build_evm_with_local(local);
915 use revm::context_interface::JournalTr;
916 let checkpoint = evm.journaled_state.checkpoint();
917 match evm.transact_one(tx_env) {
918 Ok(result) => {
919 let mut access_list = StorageAccessList::default();
920 for (address, account) in evm.journaled_state.state.iter() {
921 if account.is_touched() {
922 access_list.accounts.insert(*address);
923 for (slot_key, _) in account.storage.iter() {
924 access_list.slots.insert((*address, *slot_key));
925 }
926 }
927 }
928 evm.journaled_state.checkpoint_revert(checkpoint);
929 Ok((result, access_list))
930 }
931 Err(e) => {
932 // Revert the checkpoint even on a host/transact error so the EVM
933 // journal is not left dirty (mirrors `call_raw`).
934 evm.journaled_state.checkpoint_revert(checkpoint);
935 Err(anyhow!("Failed to transact: {:?}", e))
936 }
937 }
938 };
939
940 self.reclaim_buffer(buffer);
941 outcome
942 }
943
944 /// Write a storage value into this overlay's dirty layer.
945 ///
946 /// The dirty layer takes precedence over the snapshot on subsequent reads
947 /// (see the lookup order on [`EvmOverlay`]), so this injects a value into a
948 /// snapshot-backed overlay without mutating the shared snapshot.
949 ///
950 /// # Freshness validation
951 ///
952 /// This is the freshness validator's correction step. When a slot the
953 /// snapshot captured is found to be stale, the validator writes the
954 /// freshly-fetched value here and then re-runs the simulation (e.g. via
955 /// [`Self::call_raw`]): the re-run reads the corrected slot out of the dirty
956 /// layer instead of the stale snapshot value, so the corrected result
957 /// becomes observable. Because the override lives only in this overlay,
958 /// other overlays sharing the same `Arc<EvmSnapshot>` are unaffected.
959 ///
960 /// # Examples
961 ///
962 /// ```no_run
963 /// # use std::sync::Arc;
964 /// # use alloy_primitives::{Address, Bytes, U256};
965 /// # use evm_fork_cache::cache::{EvmOverlay, EvmSnapshot};
966 /// # fn run(snapshot: Arc<EvmSnapshot>, token: Address, slot: U256) -> anyhow::Result<()> {
967 /// let mut overlay = EvmOverlay::new(snapshot, None);
968 /// // Inject the fresh value, then re-run to observe the corrected result.
969 /// overlay.override_slot(token, slot, U256::from(42u64));
970 /// let corrected = overlay.call_raw(Address::ZERO, token, Bytes::new())?;
971 /// # let _ = corrected;
972 /// # Ok(())
973 /// # }
974 /// ```
975 pub fn override_slot(&mut self, address: Address, slot: U256, value: U256) {
976 self.dirty_storage
977 .entry(address)
978 .or_default()
979 .insert(slot, value);
980 }
981}
982
983impl revm::database_interface::DatabaseCommit for EvmOverlay {
984 fn commit(&mut self, changes: alloy_primitives::map::HashMap<Address, revm::state::Account>) {
985 for (address, account) in changes {
986 self.dirty_accounts.insert(address, account.info);
987 let storage = self.dirty_storage.entry(address).or_default();
988 for (slot, value) in account.storage {
989 storage.insert(slot, value.present_value);
990 }
991 }
992 }
993}
994
995impl Database for EvmOverlay {
996 type Error = DatabaseError;
997
998 fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
999 // 1. Check dirty layer
1000 if let Some(info) = self.dirty_accounts.get(&address) {
1001 return Ok(Some(info.clone()));
1002 }
1003 // 2. Check snapshot (O(1) HashMap lookup, no locks). `account_info` folds
1004 // the two snapshot tiers (overlay ▸ base) and already short-circuits a
1005 // NotExisting account to None — it must NOT fall through to the ext_db,
1006 // mirroring revm `DbAccount::info()` and the live `EvmCache` read.
1007 if self.snapshot.accounts_not_existing.contains(&address) {
1008 return Ok(None);
1009 }
1010 if let Some(info) = self.snapshot.account_info(address) {
1011 return Ok(Some(info.clone()));
1012 }
1013 // 3. RPC fallback
1014 if let Some(ref ext_db) = self.ext_db {
1015 let info = ext_db.basic_ref(address)?;
1016 if let Some(ref info) = info {
1017 self.dirty_accounts.insert(address, info.clone());
1018 }
1019 return Ok(info);
1020 }
1021 Ok(None)
1022 }
1023
1024 fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
1025 // Check dirty accounts first
1026 for info in self.dirty_accounts.values() {
1027 if info.code_hash == code_hash
1028 && let Some(code) = &info.code
1029 {
1030 return Ok(code.clone());
1031 }
1032 }
1033 // Check the snapshot's code index (overlay ▸ base).
1034 if let Some(code) = self.snapshot.code(code_hash) {
1035 return Ok(code.clone());
1036 }
1037 // RPC fallback
1038 if let Some(ref ext_db) = self.ext_db {
1039 return ext_db.code_by_hash_ref(code_hash);
1040 }
1041 Ok(Bytecode::default())
1042 }
1043
1044 fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
1045 // 1. Check dirty layer
1046 if let Some(account_storage) = self.dirty_storage.get(&address)
1047 && let Some(value) = account_storage.get(&index)
1048 {
1049 return Ok(*value);
1050 }
1051 // 2. Check snapshot (O(1)). `storage_value` folds the two tiers (overlay ▸
1052 // cleared-as-ZERO ▸ base); a cleared account's absent slot reads ZERO
1053 // and must NOT fall through to the ext_db, mirroring the live EVM SLOAD
1054 // for a StorageCleared/NotExisting account.
1055 if let Some(value) = self.snapshot.storage_value(address, index) {
1056 return Ok(value);
1057 }
1058 // 3. RPC fallback
1059 if let Some(ref ext_db) = self.ext_db {
1060 let value = ext_db.storage_ref(address, index)?;
1061 self.dirty_storage
1062 .entry(address)
1063 .or_default()
1064 .insert(index, value);
1065 return Ok(value);
1066 }
1067 Ok(U256::ZERO)
1068 }
1069
1070 fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
1071 if let Some(hash) = self.snapshot.block_hashes.get(&number) {
1072 return Ok(*hash);
1073 }
1074 if let Some(ref ext_db) = self.ext_db {
1075 return ext_db.block_hash_ref(number);
1076 }
1077 // Snapshots never populate `block_hashes` (the live cache does not track
1078 // block hashes), so without an `ext_db` the `BLOCKHASH` opcode resolves to
1079 // ZERO. Overlays built internally (e.g. the freshness validator) pass
1080 // `ext_db = None`; a contract that reads `BLOCKHASH` through such an
1081 // overlay sees ZERO. Documented in docs/KNOWN_ISSUES.md.
1082 Ok(B256::ZERO)
1083 }
1084}
1085
1086fn extract_access_list(state: &revm::state::EvmState) -> AccessList {
1087 let items: Vec<AccessListItem> = state
1088 .iter()
1089 .filter(|(_, account)| account.is_touched())
1090 .map(|(address, account)| AccessListItem {
1091 address: *address,
1092 storage_keys: account
1093 .storage
1094 .keys()
1095 .map(|slot| B256::from(*slot))
1096 .collect(),
1097 })
1098 .collect();
1099 AccessList(items)
1100}
1101
1102#[cfg(test)]
1103mod tests {
1104 use super::*;
1105 use crate::cache::snapshot::BaseState;
1106 use revm::primitives::hardfork::SpecId;
1107 use std::collections::HashSet;
1108
1109 /// Build a two-tier `EvmSnapshot` whose cold base holds the given accounts,
1110 /// storage, and code, with an empty hot overlay — the shape
1111 /// `create_snapshot_deep_clone` produces. The `Arc`-per-account storage of the
1112 /// base is built from the plain per-account maps.
1113 fn snap(
1114 accounts: HashMap<Address, AccountInfo>,
1115 storage: HashMap<Address, HashMap<U256, U256>>,
1116 code_by_hash: HashMap<B256, Bytecode>,
1117 block_hashes: HashMap<u64, B256>,
1118 ) -> Arc<EvmSnapshot> {
1119 let base = BaseState {
1120 accounts,
1121 storage: storage
1122 .into_iter()
1123 .map(|(addr, slots)| (addr, Arc::new(slots)))
1124 .collect(),
1125 code_by_hash,
1126 };
1127 Arc::new(EvmSnapshot {
1128 base: Arc::new(base),
1129 overlay_accounts: HashMap::new(),
1130 overlay_storage: HashMap::new(),
1131 overlay_code_by_hash: HashMap::new(),
1132 storage_cleared: HashSet::new(),
1133 accounts_not_existing: HashSet::new(),
1134 block_hashes,
1135 block_number: None,
1136 basefee: None,
1137 coinbase: None,
1138 prevrandao: None,
1139 gas_limit: None,
1140 chain_id: 42161,
1141 timestamp: None,
1142 spec_id: SpecId::CANCUN,
1143 shared_memory_capacity: 64_000,
1144 })
1145 }
1146
1147 #[test]
1148 fn test_overlay_is_send() {
1149 fn assert_send<T: Send>() {}
1150 assert_send::<EvmOverlay>();
1151 }
1152
1153 #[test]
1154 fn test_overlay_basic_from_snapshot() {
1155 let mut accounts = HashMap::new();
1156 let info = AccountInfo {
1157 balance: U256::from(1000),
1158 nonce: 1,
1159 code_hash: B256::ZERO,
1160 code: None,
1161 account_id: None,
1162 };
1163 let addr = Address::repeat_byte(0x01);
1164 accounts.insert(addr, info);
1165
1166 let snapshot = snap(accounts, HashMap::new(), HashMap::new(), HashMap::new());
1167
1168 let mut overlay = EvmOverlay::new(snapshot, None);
1169 let result = overlay.basic(addr).unwrap();
1170 assert!(result.is_some());
1171 assert_eq!(result.unwrap().balance, U256::from(1000));
1172 }
1173
1174 #[test]
1175 fn test_overlay_storage_from_snapshot() {
1176 let addr = Address::repeat_byte(0x01);
1177 let slot = U256::from(42);
1178 let value = U256::from(999);
1179
1180 let mut storage = HashMap::new();
1181 let mut account_storage = HashMap::new();
1182 account_storage.insert(slot, value);
1183 storage.insert(addr, account_storage);
1184
1185 let snapshot = snap(HashMap::new(), storage, HashMap::new(), HashMap::new());
1186
1187 let mut overlay = EvmOverlay::new(snapshot, None);
1188 let result = overlay.storage(addr, slot).unwrap();
1189 assert_eq!(result, value);
1190 }
1191
1192 #[test]
1193 fn test_overlay_dirty_overrides_snapshot() {
1194 let addr = Address::repeat_byte(0x01);
1195 let slot = U256::from(42);
1196
1197 let mut storage = HashMap::new();
1198 let mut account_storage = HashMap::new();
1199 account_storage.insert(slot, U256::from(100));
1200 storage.insert(addr, account_storage);
1201
1202 let snapshot = snap(HashMap::new(), storage, HashMap::new(), HashMap::new());
1203
1204 let mut overlay = EvmOverlay::new(snapshot, None);
1205
1206 // Write to dirty layer
1207 overlay
1208 .dirty_storage
1209 .entry(addr)
1210 .or_default()
1211 .insert(slot, U256::from(200));
1212
1213 // Should read dirty value, not snapshot
1214 let result = overlay.storage(addr, slot).unwrap();
1215 assert_eq!(result, U256::from(200));
1216 }
1217
1218 #[test]
1219 fn test_overlay_missing_returns_zero() {
1220 let snapshot = snap(
1221 HashMap::new(),
1222 HashMap::new(),
1223 HashMap::new(),
1224 HashMap::new(),
1225 );
1226
1227 let mut overlay = EvmOverlay::new(snapshot, None);
1228 let addr = Address::repeat_byte(0x99);
1229 let result = overlay.storage(addr, U256::from(1)).unwrap();
1230 assert_eq!(result, U256::ZERO);
1231
1232 let account = overlay.basic(addr).unwrap();
1233 assert!(account.is_none());
1234 }
1235
1236 #[test]
1237 fn test_overlay_code_by_hash_from_snapshot() {
1238 let code = Bytecode::new_raw(Bytes::from(vec![0x60, 0x00, 0x60, 0x00]));
1239 let hash = code.hash_slow();
1240
1241 let mut code_by_hash = HashMap::new();
1242 code_by_hash.insert(hash, code.clone());
1243
1244 let snapshot = snap(HashMap::new(), HashMap::new(), code_by_hash, HashMap::new());
1245
1246 let mut overlay = EvmOverlay::new(snapshot, None);
1247 let result = overlay.code_by_hash(hash).unwrap();
1248 assert_eq!(result.len(), 4);
1249 }
1250
1251 #[test]
1252 fn test_overlay_block_hash() {
1253 let mut block_hashes = HashMap::new();
1254 let hash = B256::repeat_byte(0xAB);
1255 block_hashes.insert(42u64, hash);
1256
1257 let snapshot = snap(HashMap::new(), HashMap::new(), HashMap::new(), block_hashes);
1258
1259 let mut overlay = EvmOverlay::new(snapshot, None);
1260 assert_eq!(overlay.block_hash(42).unwrap(), hash);
1261 assert_eq!(overlay.block_hash(99).unwrap(), B256::ZERO);
1262 }
1263}