1use crate::inspectors::{
10 Cheatcodes, CmpOperands, EdgeCoverage, EdgeIndexMap, InspectorData, InspectorStack,
11 cheatcodes::BroadcastableTransactions,
12};
13use alloy_dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt};
14use alloy_eips::eip4788::{BEACON_ROOTS_ADDRESS, SYSTEM_ADDRESS};
15use alloy_evm::Evm;
16use alloy_json_abi::Function;
17use alloy_primitives::{
18 Address, Bytes, Log, TxKind, U256, keccak256,
19 map::{AddressHashMap, HashMap},
20};
21use alloy_sol_types::{SolCall, sol};
22use foundry_evm_core::{
23 EvmEnv, FoundryBlock, FoundryTransaction,
24 backend::{
25 Backend, BackendError, BackendResult, CowBackend, DatabaseError, DatabaseExt,
26 GLOBAL_FAIL_SLOT,
27 },
28 constants::{
29 CALLER, CHEATCODE_ADDRESS, CHEATCODE_CONTRACT_HASH, DEFAULT_CREATE2_DEPLOYER,
30 DEFAULT_CREATE2_DEPLOYER_CODE, DEFAULT_CREATE2_DEPLOYER_DEPLOYER,
31 },
32 decode::{RevertDecoder, SkipReason},
33 eip2935::{
34 HISTORY_STORAGE_ADDRESS, HISTORY_STORAGE_CODE, history_storage_slot, history_storage_value,
35 history_window_start,
36 },
37 evm::{
38 EthEvmNetwork, EvmEnvFor, FoundryEvmFactory, FoundryEvmNetwork, HaltReasonFor,
39 IntoInstructionResult, SpecFor, TxEnvFor,
40 },
41 utils::StateChangeset,
42};
43use foundry_evm_coverage::HitMaps;
44use foundry_evm_fuzz::ObservedCall;
45use foundry_evm_traces::{SparsedTraceArena, TraceRequirements};
46use revm::{
47 bytecode::Bytecode,
48 context::{Block, Transaction},
49 context_interface::{
50 result::{ExecutionResult, Output, ResultAndState},
51 transaction::SignedAuthorization,
52 },
53 database::{Database, DatabaseCommit, DatabaseRef},
54 interpreter::{InstructionResult, return_ok},
55 primitives::hardfork::SpecId,
56};
57use sancov::SancovGuard;
58use std::{
59 borrow::Cow,
60 sync::{
61 Arc,
62 atomic::{AtomicBool, Ordering},
63 },
64 time::{Duration, Instant},
65};
66
67mod builder;
68pub use builder::ExecutorBuilder;
69
70pub mod fuzz;
71pub use fuzz::FuzzedExecutor;
72
73pub mod invariant;
74pub use invariant::InvariantExecutor;
75
76mod corpus;
77mod corpus_io;
78mod sancov;
79mod showmap;
80mod trace;
81
82pub use corpus::{DynamicTargetCtx, StatelessReplayTarget, persist_corpus_seed};
83pub use corpus_io::{
84 CorpusDirEntry, canonical_replay_dirs, parse_corpus_filename, read_corpus_dir, read_corpus_tree,
85};
86pub use showmap::{
87 InvariantReplayOptions, MinimizationReplayInput, ReplayFailure, ReplayObservation,
88 ShowmapDomain, ShowmapOpts, ShowmapReplayTarget, ShowmapStats, replay_corpus_to_showmap,
89 replay_sequence_for_minimization,
90};
91pub use trace::TracingExecutor;
92
93const DURATION_BETWEEN_METRICS_REPORT: Duration = Duration::from_secs(5);
94
95sol! {
96 interface ITest {
97 function setUp() external;
98 function failed() external view returns (bool failed);
99
100 #[derive(Default)]
101 function beforeTestSetup(bytes4 testSelector) public view returns (bytes[] memory beforeTestCalldata);
102 }
103}
104
105#[derive(Clone, Debug)]
117pub struct Executor<FEN: FoundryEvmNetwork> {
118 backend: Arc<Backend<FEN>>,
127 evm_env: EvmEnvFor<FEN>,
129 tx_env: TxEnvFor<FEN>,
131 inspector: InspectorStack<FEN>,
133 gas_limit: u64,
135 legacy_assertions: bool,
137}
138
139impl<FEN: FoundryEvmNetwork> Executor<FEN> {
140 #[inline]
142 pub fn new(
143 mut backend: Backend<FEN>,
144 evm_env: EvmEnvFor<FEN>,
145 tx_env: TxEnvFor<FEN>,
146 inspector: InspectorStack<FEN>,
147 gas_limit: u64,
148 legacy_assertions: bool,
149 ) -> Self {
150 backend.insert_account_info(
153 CHEATCODE_ADDRESS,
154 revm::state::AccountInfo {
155 code: Some(Bytecode::new_raw(Bytes::from_static(&[0]))),
156 code_hash: CHEATCODE_CONTRACT_HASH,
159 ..Default::default()
160 },
161 );
162
163 if !backend.is_in_forking_mode() && evm_env.cfg_env.spec.into() >= SpecId::PRAGUE {
164 let mut account =
165 backend.basic_ref(HISTORY_STORAGE_ADDRESS).unwrap_or_default().unwrap_or_default();
166 account.code_hash = keccak256(&HISTORY_STORAGE_CODE);
167 account.code = Some(Bytecode::new_raw(HISTORY_STORAGE_CODE.clone()));
168 backend.insert_account_info(HISTORY_STORAGE_ADDRESS, account);
169
170 let current_block = evm_env.block_env.number();
171 let mut block_number = history_window_start(current_block);
172 while block_number < current_block {
173 let block_hash =
174 backend.block_hash(block_number.saturating_to()).unwrap_or_default();
175 let slot = history_storage_slot(block_number);
176 let value = history_storage_value(block_hash);
177 let _ = backend.insert_account_storage(HISTORY_STORAGE_ADDRESS, slot, value);
178 block_number += U256::from(1);
179 }
180 }
181
182 Self {
183 backend: Arc::new(backend),
184 evm_env,
185 tx_env,
186 inspector,
187 gas_limit,
188 legacy_assertions,
189 }
190 }
191
192 fn clone_with_backend(&self, backend: Backend<FEN>) -> Self {
193 let evm_env = self.evm_env.clone();
194 Self {
195 backend: Arc::new(backend),
196 evm_env,
197 tx_env: self.tx_env.clone(),
198 inspector: self.inspector().clone(),
199 gas_limit: self.gas_limit,
200 legacy_assertions: self.legacy_assertions,
201 }
202 }
203
204 pub fn backend(&self) -> &Backend<FEN> {
206 &self.backend
207 }
208
209 pub fn backend_mut(&mut self) -> &mut Backend<FEN> {
214 Arc::make_mut(&mut self.backend)
215 }
216
217 pub const fn evm_env(&self) -> &EvmEnvFor<FEN> {
219 &self.evm_env
220 }
221
222 pub const fn evm_env_mut(&mut self) -> &mut EvmEnvFor<FEN> {
224 &mut self.evm_env
225 }
226
227 pub const fn tx_env(&self) -> &TxEnvFor<FEN> {
229 &self.tx_env
230 }
231
232 pub const fn tx_env_mut(&mut self) -> &mut TxEnvFor<FEN> {
234 &mut self.tx_env
235 }
236
237 pub const fn inspector(&self) -> &InspectorStack<FEN> {
239 &self.inspector
240 }
241
242 pub const fn inspector_mut(&mut self) -> &mut InspectorStack<FEN> {
244 &mut self.inspector
245 }
246
247 pub const fn spec_id(&self) -> SpecFor<FEN> {
249 self.evm_env.cfg_env.spec
250 }
251
252 pub fn set_spec_id(&mut self, spec_id: SpecFor<FEN>) {
254 self.evm_env.cfg_env.set_spec_and_mainnet_gas_params(spec_id);
255 }
256
257 pub const fn gas_limit(&self) -> u64 {
262 self.gas_limit
263 }
264
265 pub const fn set_gas_limit(&mut self, gas_limit: u64) {
267 self.gas_limit = gas_limit;
268 }
269
270 pub const fn legacy_assertions(&self) -> bool {
273 self.legacy_assertions
274 }
275
276 pub const fn set_legacy_assertions(&mut self, legacy_assertions: bool) {
279 self.legacy_assertions = legacy_assertions;
280 }
281
282 pub fn deploy_create2_deployer(&mut self) -> eyre::Result<()> {
284 trace!("deploying local create2 deployer");
285 let create2_deployer_account = self
286 .backend()
287 .basic_ref(DEFAULT_CREATE2_DEPLOYER)?
288 .ok_or_else(|| BackendError::MissingAccount(DEFAULT_CREATE2_DEPLOYER))?;
289
290 if create2_deployer_account.code.is_none_or(|code| code.is_empty()) {
292 let creator = DEFAULT_CREATE2_DEPLOYER_DEPLOYER;
293
294 let initial_balance = self.get_balance(creator)?;
296 self.set_balance(creator, U256::MAX)?;
297
298 let res =
299 self.deploy(creator, DEFAULT_CREATE2_DEPLOYER_CODE.into(), U256::ZERO, None)?;
300 trace!(create2=?res.address, "deployed local create2 deployer");
301
302 self.set_balance(creator, initial_balance)?;
303 }
304 Ok(())
305 }
306
307 pub fn set_balance(&mut self, address: Address, amount: U256) -> BackendResult<()> {
309 trace!(?address, ?amount, "setting account balance");
310 let mut account = self.backend().basic_ref(address)?.unwrap_or_default();
311 account.balance = amount;
312 self.backend_mut().insert_account_info(address, account);
313 Ok(())
314 }
315
316 pub fn get_balance(&self, address: Address) -> BackendResult<U256> {
318 Ok(self.backend().basic_ref(address)?.map(|acc| acc.balance).unwrap_or_default())
319 }
320
321 pub fn set_nonce(&mut self, address: Address, nonce: u64) -> BackendResult<()> {
323 let mut account = self.backend().basic_ref(address)?.unwrap_or_default();
324 account.nonce = nonce;
325 self.backend_mut().insert_account_info(address, account);
326 self.tx_env_mut().set_nonce(nonce);
327 Ok(())
328 }
329
330 pub fn get_nonce(&self, address: Address) -> BackendResult<u64> {
332 Ok(self.backend().basic_ref(address)?.map(|acc| acc.nonce).unwrap_or_default())
333 }
334
335 pub fn set_code(&mut self, address: Address, code: Bytecode) -> BackendResult<()> {
337 let mut account = self.backend().basic_ref(address)?.unwrap_or_default();
338 account.code_hash = keccak256(code.original_byte_slice());
339 account.code = Some(code);
340 self.backend_mut().insert_account_info(address, account);
341 Ok(())
342 }
343
344 pub fn set_storage(
346 &mut self,
347 address: Address,
348 storage: HashMap<U256, U256>,
349 ) -> BackendResult<()> {
350 self.backend_mut().replace_account_storage(address, storage)?;
351 Ok(())
352 }
353
354 pub fn set_storage_slot(
356 &mut self,
357 address: Address,
358 slot: U256,
359 value: U256,
360 ) -> BackendResult<()> {
361 self.backend_mut().insert_account_storage(address, slot, value)?;
362 Ok(())
363 }
364
365 pub fn apply_prestate_trace(
371 &mut self,
372 prestate: std::collections::BTreeMap<Address, alloy_rpc_types::trace::geth::AccountState>,
373 ) -> eyre::Result<()> {
374 let backend = self.backend_mut();
375 for (address, account_state) in prestate {
376 let code = account_state.code.map(Bytecode::new_raw).unwrap_or_default();
377 let info = revm::state::AccountInfo {
378 nonce: account_state.nonce.unwrap_or_default(),
379 balance: account_state.balance.unwrap_or_default(),
380 code_hash: keccak256(code.original_byte_slice()),
381 code: Some(code),
382 account_id: Default::default(),
383 };
384 backend.insert_account_info(address, info);
385
386 for (slot, value) in account_state.storage {
387 let slot = U256::from_be_bytes(slot.0);
388 let value = U256::from_be_bytes(value.0);
389 backend.insert_account_storage(address, slot, value)?;
390 }
391 }
392 Ok(())
393 }
394
395 pub fn is_empty_code(&self, address: Address) -> BackendResult<bool> {
397 Ok(self.backend().basic_ref(address)?.map(|acc| acc.is_empty_code_hash()).unwrap_or(true))
398 }
399
400 #[inline]
401 pub fn set_trace_requirements(&mut self, requirements: TraceRequirements) -> &mut Self {
402 self.inspector_mut().tracing_requirements(requirements);
403 self
404 }
405
406 #[inline]
407 pub fn set_script_execution(&mut self, script_address: Address) {
408 self.inspector_mut().script(script_address);
409 }
410
411 #[inline]
412 pub fn set_trace_printer(&mut self, trace_printer: bool) -> &mut Self {
413 self.inspector_mut().print(trace_printer);
414 self
415 }
416
417 #[inline]
418 pub fn create2_deployer(&self) -> Address {
419 self.inspector().create2_deployer
420 }
421
422 pub fn deploy(
427 &mut self,
428 from: Address,
429 code: Bytes,
430 value: U256,
431 rd: Option<&RevertDecoder>,
432 ) -> Result<DeployResult<FEN>, EvmError<FEN>> {
433 let (evm_env, tx_env) = self.build_test_env(from, TxKind::Create, code, value);
434 self.deploy_with_env(evm_env, tx_env, rd)
435 }
436
437 #[instrument(name = "deploy", level = "debug", skip_all)]
444 pub fn deploy_with_env(
445 &mut self,
446 evm_env: EvmEnvFor<FEN>,
447 tx_env: TxEnvFor<FEN>,
448 rd: Option<&RevertDecoder>,
449 ) -> Result<DeployResult<FEN>, EvmError<FEN>> {
450 assert!(
451 matches!(tx_env.kind(), TxKind::Create),
452 "Expected create transaction, got {:?}",
453 tx_env.kind()
454 );
455 trace!(sender=%tx_env.caller(), "deploying contract");
456
457 let mut result = self.transact_with_env(evm_env, tx_env)?;
458 result = result.into_result(rd)?;
459 let Some(Output::Create(_, Some(address))) = result.out else {
460 panic!("Deployment succeeded, but no address was returned: {result:#?}");
461 };
462
463 self.backend_mut().add_persistent_account(address);
466
467 trace!(%address, "deployed contract");
468
469 Ok(DeployResult { raw: result, address })
470 }
471
472 #[instrument(name = "setup", level = "debug", skip_all)]
479 pub fn setup(
480 &mut self,
481 from: Option<Address>,
482 to: Address,
483 rd: Option<&RevertDecoder>,
484 ) -> Result<RawCallResult<FEN>, EvmError<FEN>> {
485 trace!(?from, ?to, "setting up contract");
486
487 let from = from.unwrap_or(CALLER);
488 self.backend_mut().set_test_contract(to).set_caller(from);
489 let calldata = Bytes::from_static(&ITest::setUpCall::SELECTOR);
490 let mut res = self.transact_raw(from, to, calldata, U256::ZERO)?;
491 res = res.into_result(rd)?;
492
493 self.evm_env_mut().block_env = res.evm_env.block_env.clone();
495 self.evm_env_mut().cfg_env.chain_id = res.evm_env.cfg_env.chain_id;
497
498 let success =
499 self.is_raw_call_success(to, Cow::Borrowed(&res.state_changeset), &res, false);
500 if !success {
501 return Err(res.into_execution_error("execution error".to_string()).into());
502 }
503
504 Ok(res)
505 }
506
507 pub fn call(
509 &self,
510 from: Address,
511 to: Address,
512 func: &Function,
513 args: &[DynSolValue],
514 value: U256,
515 rd: Option<&RevertDecoder>,
516 ) -> Result<CallResult<DynSolValue, FEN>, EvmError<FEN>> {
517 let calldata = Bytes::from(func.abi_encode_input(args)?);
518 let result = self.call_raw(from, to, calldata, value)?;
519 result.into_decoded_result(func, rd)
520 }
521
522 pub fn call_sol<C: SolCall>(
524 &self,
525 from: Address,
526 to: Address,
527 args: &C,
528 value: U256,
529 rd: Option<&RevertDecoder>,
530 ) -> Result<CallResult<C::Return, FEN>, EvmError<FEN>> {
531 let calldata = Bytes::from(args.abi_encode());
532 let mut raw = self.call_raw(from, to, calldata, value)?;
533 raw = raw.into_result(rd)?;
534 Ok(CallResult { decoded_result: C::abi_decode_returns(&raw.result)?, raw })
535 }
536
537 pub fn transact(
539 &mut self,
540 from: Address,
541 to: Address,
542 func: &Function,
543 args: &[DynSolValue],
544 value: U256,
545 rd: Option<&RevertDecoder>,
546 ) -> Result<CallResult<DynSolValue, FEN>, EvmError<FEN>> {
547 let calldata = Bytes::from(func.abi_encode_input(args)?);
548 let result = self.transact_raw(from, to, calldata, value)?;
549 result.into_decoded_result(func, rd)
550 }
551
552 pub fn call_raw(
554 &self,
555 from: Address,
556 to: Address,
557 calldata: Bytes,
558 value: U256,
559 ) -> eyre::Result<RawCallResult<FEN>> {
560 let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value);
561 self.call_with_env(evm_env, tx_env)
562 }
563
564 pub fn call_raw_with_authorization(
567 &mut self,
568 from: Address,
569 to: Address,
570 calldata: Bytes,
571 value: U256,
572 authorization_list: Vec<SignedAuthorization>,
573 ) -> eyre::Result<RawCallResult<FEN>> {
574 let (evm_env, mut tx_env) = self.build_test_env(from, to.into(), calldata, value);
575 tx_env.set_signed_authorization(authorization_list);
576 tx_env.set_tx_type(4);
577 self.call_with_env(evm_env, tx_env)
578 }
579
580 pub fn transact_raw(
582 &mut self,
583 from: Address,
584 to: Address,
585 calldata: Bytes,
586 value: U256,
587 ) -> eyre::Result<RawCallResult<FEN>> {
588 let (evm_env, tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value);
589 self.transact_with_env(evm_env, tx_env)
590 }
591
592 pub fn transact_raw_with_authorization(
595 &mut self,
596 from: Address,
597 to: Address,
598 calldata: Bytes,
599 value: U256,
600 authorization_list: Vec<SignedAuthorization>,
601 ) -> eyre::Result<RawCallResult<FEN>> {
602 let (evm_env, mut tx_env) = self.build_test_env(from, TxKind::Call(to), calldata, value);
603 tx_env.set_signed_authorization(authorization_list);
604 tx_env.set_tx_type(4);
605 self.transact_with_env(evm_env, tx_env)
606 }
607
608 pub fn apply_beacon_root(
611 &mut self,
612 parent_beacon_block_root: alloy_primitives::B256,
613 ) -> eyre::Result<()> {
614 let calldata = Bytes::copy_from_slice(parent_beacon_block_root.as_slice());
615 let mut evm_env = self.evm_env.clone();
616 let inspector = self.inspector().clone();
617 let mut state = {
618 let mut backend = CowBackend::new_borrowed(self.backend());
619 let mut evm = FEN::EvmFactory::default().create_foundry_evm_with_inspector(
620 &mut backend,
621 evm_env.clone(),
622 inspector,
623 );
624 let result =
625 evm.transact_system_call(SYSTEM_ADDRESS, BEACON_ROOTS_ADDRESS, calldata)?;
626 evm_env = evm.finish().1;
627 result.state
628 };
629 state.retain(|address, _| *address == BEACON_ROOTS_ADDRESS);
630
631 self.backend_mut().commit(state);
632 self.inspector_mut().set_block(evm_env.block_env);
633
634 Ok(())
635 }
636
637 #[instrument(name = "call", level = "debug", skip_all)]
641 pub fn call_with_env(
642 &self,
643 mut evm_env: EvmEnvFor<FEN>,
644 mut tx_env: TxEnvFor<FEN>,
645 ) -> eyre::Result<RawCallResult<FEN>> {
646 let mut stack = self.inspector().clone();
647 let sancov_edges = stack.inner.sancov_edges;
648 let sancov_trace_cmp = stack.inner.sancov_trace_cmp;
649 let sancov_active = sancov_edges || sancov_trace_cmp;
650 let mut backend = CowBackend::new_borrowed(self.backend());
651 let result = {
652 let _guard = sancov_active.then(|| SancovGuard::new(sancov_edges, sancov_trace_cmp));
653 backend.inspect(&mut evm_env, &mut tx_env, &mut stack)?
654 };
655 let has_state_snapshot_failure = backend.has_state_snapshot_failure();
656 let mut result = convert_executed_result(
657 evm_env,
658 tx_env,
659 stack,
660 result,
661 &backend,
662 has_state_snapshot_failure,
663 )?;
664 if sancov_edges {
665 SancovGuard::append_edges_into(&mut result);
666 }
667 if sancov_trace_cmp {
668 SancovGuard::drain_cmp_into(&mut result);
669 }
670 Ok(result)
671 }
672
673 #[instrument(name = "transact", level = "debug", skip_all)]
675 pub fn transact_with_env(
676 &mut self,
677 mut evm_env: EvmEnvFor<FEN>,
678 mut tx_env: TxEnvFor<FEN>,
679 ) -> eyre::Result<RawCallResult<FEN>> {
680 let mut stack = self.inspector().clone();
681 let sancov_edges = stack.inner.sancov_edges;
682 let sancov_trace_cmp = stack.inner.sancov_trace_cmp;
683 let sancov_active = sancov_edges || sancov_trace_cmp;
684 let backend = self.backend_mut();
685 let result = {
686 let _guard = sancov_active.then(|| SancovGuard::new(sancov_edges, sancov_trace_cmp));
687 backend.inspect(&mut evm_env, &mut tx_env, &mut stack)?
688 };
689 let has_state_snapshot_failure = backend.has_state_snapshot_failure();
690 let mut result = convert_executed_result(
691 evm_env,
692 tx_env,
693 stack,
694 result,
695 &*backend,
696 has_state_snapshot_failure,
697 )?;
698 if sancov_edges {
699 SancovGuard::append_edges_into(&mut result);
700 }
701 if sancov_trace_cmp {
702 SancovGuard::drain_cmp_into(&mut result);
703 }
704 self.commit(&mut result);
705 Ok(result)
706 }
707
708 #[instrument(name = "commit", level = "debug", skip_all)]
713 fn commit(&mut self, result: &mut RawCallResult<FEN>) {
714 self.backend_mut().commit(result.state_changeset.clone());
716
717 self.inspector_mut().cheatcodes = result.cheatcodes.take();
719 if let Some(cheats) = self.inspector_mut().cheatcodes.as_mut() {
720 cheats.broadcastable_transactions.clear();
722 cheats.ignored_traces.ignored.clear();
723
724 if let Some(last_pause_call) = cheats.ignored_traces.last_pause_call.as_mut() {
727 *last_pause_call = (0, 0);
728 }
729 }
730
731 self.inspector_mut().set_block(result.evm_env.block_env.clone());
733 self.inspector_mut().set_gas_price(result.tx_env.gas_price());
734 }
735
736 pub fn is_raw_call_mut_success(
741 &self,
742 address: Address,
743 call_result: &mut RawCallResult<FEN>,
744 should_fail: bool,
745 ) -> bool {
746 self.is_raw_call_success(
747 address,
748 Cow::Owned(std::mem::take(&mut call_result.state_changeset)),
749 call_result,
750 should_fail,
751 )
752 }
753
754 pub fn is_raw_call_success(
758 &self,
759 address: Address,
760 state_changeset: Cow<'_, StateChangeset>,
761 call_result: &RawCallResult<FEN>,
762 should_fail: bool,
763 ) -> bool {
764 if call_result.has_state_snapshot_failure {
765 return should_fail;
767 }
768 self.is_success(address, call_result.reverted, state_changeset, should_fail)
769 }
770
771 pub fn is_raw_call_mut_success_handler_gate(
775 &self,
776 address: Address,
777 call_result: &mut RawCallResult<FEN>,
778 ) -> bool {
779 if call_result.has_state_snapshot_failure {
780 return false;
781 }
782 let state_changeset = std::mem::take(&mut call_result.state_changeset);
783 self.is_success_handler_gate(address, call_result.reverted, Cow::Owned(state_changeset))
784 }
785
786 pub fn is_success(
808 &self,
809 address: Address,
810 reverted: bool,
811 state_changeset: Cow<'_, StateChangeset>,
812 should_fail: bool,
813 ) -> bool {
814 let success = self.is_success_raw(address, reverted, state_changeset, false);
815 should_fail ^ success
816 }
817
818 pub fn is_success_handler_gate(
824 &self,
825 address: Address,
826 reverted: bool,
827 state_changeset: Cow<'_, StateChangeset>,
828 ) -> bool {
829 self.is_success_raw(address, reverted, state_changeset, true)
830 }
831
832 #[instrument(name = "is_success", level = "debug", skip_all)]
833 fn is_success_raw(
834 &self,
835 address: Address,
836 reverted: bool,
837 state_changeset: Cow<'_, StateChangeset>,
838 pending_global_failure_only: bool,
839 ) -> bool {
840 if reverted {
842 return false;
843 }
844
845 if self.backend().has_state_snapshot_failure() {
847 return false;
848 }
849
850 let global_failed = if pending_global_failure_only {
855 Self::has_pending_global_failure(&state_changeset)
856 } else {
857 self.has_global_failure(&state_changeset)
858 };
859 if global_failed {
860 return false;
861 }
862
863 if !self.legacy_assertions {
864 return true;
865 }
866
867 {
869 let mut backend = self.backend().clone_empty();
871
872 for address in [address, CHEATCODE_ADDRESS] {
875 let Ok(acc) = self.backend().basic_ref(address) else { return false };
876 backend.insert_account_info(address, acc.unwrap_or_default());
877 }
878
879 backend.commit(state_changeset.into_owned());
884
885 let executor = self.clone_with_backend(backend);
887 let call = executor.call_sol(CALLER, address, &ITest::failedCall {}, U256::ZERO, None);
888 match call {
889 Ok(CallResult { raw: _, decoded_result: failed }) => {
890 trace!(failed, "DSTest::failed()");
891 !failed
892 }
893 Err(err) => {
894 trace!(%err, "failed to call DSTest::failed()");
895 true
896 }
897 }
898 }
899 }
900
901 pub fn has_pending_global_failure(state_changeset: &StateChangeset) -> bool {
904 if let Some(acc) = state_changeset.get(&CHEATCODE_ADDRESS)
905 && let Some(failed_slot) = acc.storage.get(&GLOBAL_FAIL_SLOT)
906 && !failed_slot.present_value().is_zero()
907 {
908 return true;
909 }
910
911 false
912 }
913
914 pub fn has_global_failure(&self, state_changeset: &StateChangeset) -> bool {
917 if Self::has_pending_global_failure(state_changeset) {
918 return true;
919 }
920
921 self.backend()
922 .storage_ref(CHEATCODE_ADDRESS, GLOBAL_FAIL_SLOT)
923 .is_ok_and(|failed_slot| !failed_slot.is_zero())
924 }
925
926 fn build_test_env(
931 &self,
932 caller: Address,
933 kind: TxKind,
934 data: Bytes,
935 value: U256,
936 ) -> (EvmEnvFor<FEN>, TxEnvFor<FEN>) {
937 let mut cfg_env = self.evm_env.cfg_env.clone();
938 cfg_env.spec = self.spec_id();
939
940 let mut block_env = self.evm_env.block_env.clone();
944 block_env.set_basefee(0);
945 block_env.set_gas_limit(self.gas_limit);
946
947 let mut tx_env = self.tx_env.clone();
948 tx_env.set_caller(caller);
949 tx_env.set_kind(kind);
950 tx_env.set_data(data);
951 tx_env.set_value(value);
952 tx_env.set_gas_price(0);
954 tx_env.set_gas_priority_fee(None);
955 tx_env.set_gas_limit(self.gas_limit);
956 tx_env.set_chain_id(Some(self.evm_env.cfg_env.chain_id));
957
958 (EvmEnv { cfg_env, block_env }, tx_env)
959 }
960
961 pub fn call_sol_default<C: SolCall>(&self, to: Address, args: &C) -> C::Return
962 where
963 C::Return: Default,
964 {
965 self.call_sol(CALLER, to, args, U256::ZERO, None)
966 .map(|c| c.decoded_result)
967 .inspect_err(|e| warn!(target: "forge::test", "failed calling {:?}: {e}", C::SIGNATURE))
968 .unwrap_or_default()
969 }
970}
971
972#[derive(Debug, thiserror::Error)]
974#[error("execution reverted: {reason} (gas: {})", raw.gas_used)]
975pub struct ExecutionErr<FEN: FoundryEvmNetwork = EthEvmNetwork> {
976 pub raw: RawCallResult<FEN>,
978 pub reason: String,
980}
981
982impl<FEN: FoundryEvmNetwork> std::ops::Deref for ExecutionErr<FEN> {
983 type Target = RawCallResult<FEN>;
984
985 #[inline]
986 fn deref(&self) -> &Self::Target {
987 &self.raw
988 }
989}
990
991impl<FEN: FoundryEvmNetwork> std::ops::DerefMut for ExecutionErr<FEN> {
992 #[inline]
993 fn deref_mut(&mut self) -> &mut Self::Target {
994 &mut self.raw
995 }
996}
997
998#[derive(Debug, thiserror::Error)]
999pub enum EvmError<FEN: FoundryEvmNetwork = EthEvmNetwork> {
1000 #[error(transparent)]
1002 Execution(Box<ExecutionErr<FEN>>),
1003 #[error(transparent)]
1005 Abi(#[from] alloy_dyn_abi::Error),
1006 #[error("{0}")]
1008 Skip(SkipReason),
1009 #[error("{0}")]
1011 Eyre(
1012 #[from]
1013 #[source]
1014 eyre::Report,
1015 ),
1016}
1017
1018impl<FEN: FoundryEvmNetwork> From<ExecutionErr<FEN>> for EvmError<FEN> {
1019 fn from(err: ExecutionErr<FEN>) -> Self {
1020 Self::Execution(Box::new(err))
1021 }
1022}
1023
1024impl<FEN: FoundryEvmNetwork> From<alloy_sol_types::Error> for EvmError<FEN> {
1025 fn from(err: alloy_sol_types::Error) -> Self {
1026 Self::Abi(err.into())
1027 }
1028}
1029
1030#[derive(Debug)]
1032pub struct DeployResult<FEN: FoundryEvmNetwork = EthEvmNetwork> {
1033 pub raw: RawCallResult<FEN>,
1035 pub address: Address,
1037}
1038
1039impl<FEN: FoundryEvmNetwork> std::ops::Deref for DeployResult<FEN> {
1040 type Target = RawCallResult<FEN>;
1041
1042 #[inline]
1043 fn deref(&self) -> &Self::Target {
1044 &self.raw
1045 }
1046}
1047
1048impl<FEN: FoundryEvmNetwork> std::ops::DerefMut for DeployResult<FEN> {
1049 #[inline]
1050 fn deref_mut(&mut self) -> &mut Self::Target {
1051 &mut self.raw
1052 }
1053}
1054
1055impl<FEN: FoundryEvmNetwork> From<DeployResult<FEN>> for RawCallResult<FEN> {
1056 fn from(d: DeployResult<FEN>) -> Self {
1057 d.raw
1058 }
1059}
1060
1061#[derive(Debug)]
1063pub struct RawCallResult<FEN: FoundryEvmNetwork = EthEvmNetwork> {
1064 pub exit_reason: Option<InstructionResult>,
1066 pub execution_cancelled: bool,
1068 pub reverted: bool,
1070 pub has_state_snapshot_failure: bool,
1075 pub result: Bytes,
1077 pub gas_used: u64,
1079 pub gas_refunded: u64,
1081 pub stipend: u64,
1083 pub logs: Vec<Log>,
1085 pub labels: AddressHashMap<String>,
1087 pub traces: Option<SparsedTraceArena>,
1089 pub debug_bytecodes: AddressHashMap<Bytes>,
1091 pub line_coverage: Option<HitMaps>,
1093 pub edge_coverage: Option<EdgeCoverage>,
1095 pub evm_cmp_values: Option<Vec<CmpOperands>>,
1097 pub observed_calls: Vec<ObservedCall>,
1099 pub sancov_coverage: Option<Vec<u8>>,
1102 pub sancov_cmp_values: Option<Vec<foundry_evm_sancov::CmpSample>>,
1104 pub transactions: Option<BroadcastableTransactions<FEN::Network>>,
1106 pub state_changeset: StateChangeset,
1108 pub evm_env: EvmEnvFor<FEN>,
1110 pub tx_env: TxEnvFor<FEN>,
1112 pub cheatcodes: Option<Box<Cheatcodes<FEN>>>,
1114 pub out: Option<Output>,
1116 pub chisel_state: Option<(Vec<U256>, Vec<u8>)>,
1118 pub reverter: Option<Address>,
1119}
1120
1121impl<FEN: FoundryEvmNetwork> Default for RawCallResult<FEN> {
1122 fn default() -> Self {
1123 Self {
1124 exit_reason: None,
1125 execution_cancelled: false,
1126 reverted: false,
1127 has_state_snapshot_failure: false,
1128 result: Bytes::new(),
1129 gas_used: 0,
1130 gas_refunded: 0,
1131 stipend: 0,
1132 logs: Vec::new(),
1133 labels: HashMap::default(),
1134 traces: None,
1135 debug_bytecodes: HashMap::default(),
1136 line_coverage: None,
1137 edge_coverage: None,
1138 evm_cmp_values: None,
1139 observed_calls: Vec::new(),
1140 sancov_coverage: None,
1141 sancov_cmp_values: None,
1142 transactions: None,
1143 state_changeset: HashMap::default(),
1144 evm_env: EvmEnv::default(),
1145 tx_env: TxEnvFor::<FEN>::default(),
1146 cheatcodes: Default::default(),
1147 out: None,
1148 chisel_state: None,
1149 reverter: None,
1150 }
1151 }
1152}
1153
1154impl<FEN: FoundryEvmNetwork> RawCallResult<FEN> {
1155 pub fn from_evm_result(r: Result<Self, EvmError<FEN>>) -> eyre::Result<(Self, Option<String>)> {
1157 match r {
1158 Ok(r) => Ok((r, None)),
1159 Err(EvmError::Execution(e)) => Ok((e.raw, Some(e.reason))),
1160 Err(e) => Err(e.into()),
1161 }
1162 }
1163
1164 pub fn into_evm_error(self, rd: Option<&RevertDecoder>) -> EvmError<FEN> {
1166 if self.reverter == Some(CHEATCODE_ADDRESS)
1167 && let Some(reason) = SkipReason::decode(&self.result)
1168 {
1169 return EvmError::Skip(reason);
1170 }
1171 let reason = rd.unwrap_or_default().decode(&self.result, self.exit_reason);
1172 EvmError::Execution(Box::new(self.into_execution_error(reason)))
1173 }
1174
1175 pub const fn into_execution_error(self, reason: String) -> ExecutionErr<FEN> {
1177 ExecutionErr { raw: self, reason }
1178 }
1179
1180 pub fn into_result(self, rd: Option<&RevertDecoder>) -> Result<Self, EvmError<FEN>> {
1182 if let Some(reason) = self.exit_reason
1183 && reason.is_ok()
1184 {
1185 Ok(self)
1186 } else {
1187 Err(self.into_evm_error(rd))
1188 }
1189 }
1190
1191 pub fn into_decoded_result(
1193 mut self,
1194 func: &Function,
1195 rd: Option<&RevertDecoder>,
1196 ) -> Result<CallResult<DynSolValue, FEN>, EvmError<FEN>> {
1197 self = self.into_result(rd)?;
1198 let mut result = func.abi_decode_output(&self.result)?;
1199 let decoded_result =
1200 if result.len() == 1 { result.pop().unwrap() } else { DynSolValue::Tuple(result) };
1201 Ok(CallResult { raw: self, decoded_result })
1202 }
1203
1204 pub fn transactions(&self) -> Option<&BroadcastableTransactions<FEN::Network>> {
1206 self.cheatcodes.as_ref().map(|c| &c.broadcastable_transactions)
1207 }
1208
1209 pub fn merge_edge_coverage(
1211 &mut self,
1212 history_map: &mut Vec<u8>,
1213 edge_indices: &mut EdgeIndexMap,
1214 ) -> (bool, bool) {
1215 let mut new_coverage = false;
1216 let mut is_edge = false;
1217 if let Some(x) = &mut self.edge_coverage {
1218 match x {
1219 EdgeCoverage::Hash(x) => {
1220 if history_map.len() < x.len() {
1221 history_map.resize(x.len(), 0);
1222 }
1223 for (curr, hist) in std::iter::zip(x.iter_mut(), history_map.iter_mut()) {
1226 Self::merge_edge_count(*curr, hist, &mut new_coverage, &mut is_edge);
1227
1228 *curr = 0;
1230 }
1231 }
1232 EdgeCoverage::CollisionFree(hits) => {
1233 for hit in hits.drain(..) {
1234 let edge_index = edge_indices.edge_index(hit.edge);
1235 if history_map.len() <= edge_index {
1236 history_map.resize(edge_index + 1, 0);
1237 }
1238 Self::merge_edge_count(
1239 hit.count,
1240 &mut history_map[edge_index],
1241 &mut new_coverage,
1242 &mut is_edge,
1243 );
1244 }
1245 }
1246 }
1247 }
1248 (new_coverage, is_edge)
1249 }
1250
1251 const fn merge_edge_count(
1252 curr: u8,
1253 hist: &mut u8,
1254 new_coverage: &mut bool,
1255 is_edge: &mut bool,
1256 ) {
1257 let Some(bucket) = Self::bin_count(curr) else {
1258 return;
1259 };
1260
1261 if *hist < bucket {
1263 if *hist == 0 {
1264 *is_edge = true;
1266 }
1267 *hist = bucket;
1268 *new_coverage = true;
1269 }
1270 }
1271
1272 const fn bin_count(count: u8) -> Option<u8> {
1275 match count {
1276 0 => None,
1277 1 => Some(1),
1278 2 => Some(2),
1279 3 => Some(4),
1280 4..=7 => Some(8),
1281 8..=15 => Some(16),
1282 16..=31 => Some(32),
1283 32..=127 => Some(64),
1284 128..=255 => Some(128),
1285 }
1286 }
1287
1288 pub fn merge_sancov_coverage(&mut self, history_map: &mut Vec<u8>) -> (bool, bool) {
1291 let mut new_coverage = false;
1292 let mut is_edge = false;
1293 if let Some(x) = &mut self.sancov_coverage {
1294 if history_map.len() < x.len() {
1295 history_map.resize(x.len(), 0);
1296 }
1297 for (curr, hist) in std::iter::zip(x.iter_mut(), history_map.iter_mut()) {
1298 if *curr > 0 {
1299 if let Some(bucket) = Self::bin_count(*curr)
1300 && *hist < bucket
1301 {
1302 if *hist == 0 {
1303 is_edge = true;
1304 }
1305 *hist = bucket;
1306 new_coverage = true;
1307 }
1308 *curr = 0;
1309 }
1310 }
1311 }
1312 (new_coverage, is_edge)
1313 }
1314
1315 pub fn merge_all_coverage(
1318 &mut self,
1319 evm_history: &mut Vec<u8>,
1320 evm_edge_indices: &mut EdgeIndexMap,
1321 sancov_history: &mut Vec<u8>,
1322 ) -> (bool, bool) {
1323 let (new_evm, edge_evm) = self.merge_edge_coverage(evm_history, evm_edge_indices);
1324 let (new_san, edge_san) = self.merge_sancov_coverage(sancov_history);
1325 (new_evm || new_san, edge_evm || edge_san)
1326 }
1327}
1328
1329pub struct CallResult<T = DynSolValue, FEN: FoundryEvmNetwork = EthEvmNetwork> {
1331 pub raw: RawCallResult<FEN>,
1333 pub decoded_result: T,
1335}
1336
1337impl<T, FEN: FoundryEvmNetwork> std::ops::Deref for CallResult<T, FEN> {
1338 type Target = RawCallResult<FEN>;
1339
1340 #[inline]
1341 fn deref(&self) -> &Self::Target {
1342 &self.raw
1343 }
1344}
1345
1346impl<T, FEN: FoundryEvmNetwork> std::ops::DerefMut for CallResult<T, FEN> {
1347 #[inline]
1348 fn deref_mut(&mut self) -> &mut Self::Target {
1349 &mut self.raw
1350 }
1351}
1352
1353fn convert_executed_result<FEN: FoundryEvmNetwork>(
1355 evm_env: EvmEnvFor<FEN>,
1356 tx_env: TxEnvFor<FEN>,
1357 mut inspector: InspectorStack<FEN>,
1358 ResultAndState { result, state: state_changeset }: ResultAndState<HaltReasonFor<FEN>>,
1359 db: &dyn DatabaseRef<Error = DatabaseError>,
1360 has_state_snapshot_failure: bool,
1361) -> eyre::Result<RawCallResult<FEN>> {
1362 let execution_cancelled = inspector.execution_cancelled();
1363 let (exit_reason, gas_refunded, gas_used, out, exec_logs) = match result {
1364 ExecutionResult::Success { reason, gas, output, logs } => {
1365 (reason.into(), gas.final_refunded(), gas.tx_gas_used(), Some(output), logs)
1366 }
1367 ExecutionResult::Revert { gas, output, logs } => {
1368 (InstructionResult::Revert, 0_u64, gas.tx_gas_used(), Some(Output::Call(output)), logs)
1369 }
1370 ExecutionResult::Halt { reason, gas, logs } => {
1371 (reason.into_instruction_result(), 0_u64, gas.tx_gas_used(), None, logs)
1372 }
1373 };
1374 let gas = revm::interpreter::gas::calculate_initial_tx_gas_for_tx(
1375 &tx_env,
1376 evm_env.cfg_env.spec.into(),
1377 );
1378
1379 let result = match &out {
1380 Some(Output::Call(data)) => data.clone(),
1381 _ => Bytes::new(),
1382 };
1383 let observed_calls = inspector
1384 .inner
1385 .fuzzer
1386 .as_mut()
1387 .map(|fuzzer| fuzzer.take_observed_calls())
1388 .unwrap_or_default();
1389
1390 let InspectorData {
1391 mut logs,
1392 labels,
1393 traces,
1394 line_coverage,
1395 edge_coverage,
1396 evm_cmp_values,
1397 cheatcodes,
1398 chisel_state,
1399 reverter,
1400 } = inspector.collect();
1401 let debug_bytecodes = collect_debug_bytecodes(traces.as_ref(), db);
1402
1403 if logs.is_empty() {
1404 logs = exec_logs;
1405 }
1406
1407 let transactions = cheatcodes
1408 .as_ref()
1409 .map(|c| c.broadcastable_transactions.clone())
1410 .filter(|txs| !txs.is_empty());
1411
1412 Ok(RawCallResult {
1413 exit_reason: Some(exit_reason),
1414 execution_cancelled,
1415 reverted: !matches!(exit_reason, return_ok!()),
1416 has_state_snapshot_failure,
1417 result,
1418 gas_used,
1419 gas_refunded,
1420 stipend: gas.initial_total_gas(),
1421 logs,
1422 labels,
1423 traces,
1424 debug_bytecodes,
1425 line_coverage,
1426 edge_coverage,
1427 evm_cmp_values,
1428 observed_calls,
1429 sancov_coverage: None,
1430 sancov_cmp_values: None,
1431 transactions,
1432 state_changeset,
1433 evm_env,
1434 tx_env,
1435 cheatcodes,
1436 out,
1437 chisel_state,
1438 reverter,
1439 })
1440}
1441
1442fn collect_debug_bytecodes(
1443 traces: Option<&SparsedTraceArena>,
1444 db: &dyn DatabaseRef<Error = DatabaseError>,
1445) -> AddressHashMap<Bytes> {
1446 let mut bytecodes = HashMap::default();
1447 let Some(traces) = traces else { return bytecodes };
1448
1449 for node in traces.arena.nodes() {
1450 let address = node.trace.address;
1451 if bytecodes.contains_key(&address) {
1452 continue;
1453 }
1454
1455 let Ok(Some(account)) = db.basic_ref(address) else { continue };
1456 let code: Option<Bytecode> =
1457 account.code.or_else(|| db.code_by_hash_ref(account.code_hash).ok());
1458 let code: Bytes = code.map(|code| code.original_bytes()).unwrap_or_default();
1459
1460 if !code.is_empty() {
1461 bytecodes.insert(address, code);
1462 }
1463 }
1464
1465 bytecodes
1466}
1467
1468pub struct FuzzTestTimer {
1470 inner: Option<(Instant, Duration)>,
1472}
1473
1474impl FuzzTestTimer {
1475 pub fn new(timeout: Option<u32>) -> Self {
1476 Self { inner: timeout.map(|timeout| (Instant::now(), Duration::from_secs(timeout.into()))) }
1477 }
1478
1479 pub const fn is_enabled(&self) -> bool {
1481 self.inner.is_some()
1482 }
1483
1484 pub fn is_timed_out(&self) -> bool {
1486 self.inner.is_some_and(|(start, duration)| start.elapsed() > duration)
1487 }
1488}
1489
1490#[derive(Clone, Debug)]
1493pub struct EarlyExit {
1494 inner: Arc<AtomicBool>,
1496 fail_fast: bool,
1498}
1499
1500impl EarlyExit {
1501 pub fn new(fail_fast: bool) -> Self {
1502 Self { inner: Arc::new(AtomicBool::new(false)), fail_fast }
1503 }
1504
1505 pub fn record_failure(&self) {
1507 if self.fail_fast {
1508 self.inner.store(true, Ordering::Relaxed);
1509 }
1510 }
1511
1512 pub fn record_ctrl_c(&self) {
1514 self.inner.store(true, Ordering::Relaxed);
1515 }
1516
1517 pub fn should_stop(&self) -> bool {
1519 self.inner.load(Ordering::Relaxed)
1520 }
1521}
1522
1523#[derive(Clone, Debug)]
1525pub(crate) enum EvmExecutionCancellation {
1526 EarlyExit(EarlyExit),
1528 Campaign { early_exit: EarlyExit, stop: Arc<AtomicBool>, deadline: Option<Instant> },
1530}
1531
1532impl EvmExecutionCancellation {
1533 pub(crate) const fn early_exit(early_exit: EarlyExit) -> Self {
1534 Self::EarlyExit(early_exit)
1535 }
1536
1537 pub(crate) const fn campaign(
1538 early_exit: EarlyExit,
1539 stop: Arc<AtomicBool>,
1540 deadline: Option<Instant>,
1541 ) -> Self {
1542 Self::Campaign { early_exit, stop, deadline }
1543 }
1544
1545 pub(crate) fn should_stop(&self, poll_deadline: bool) -> bool {
1547 match self {
1548 Self::EarlyExit(early_exit) => early_exit.should_stop(),
1549 Self::Campaign { early_exit, stop, deadline } => {
1550 if early_exit.should_stop() || stop.load(Ordering::Relaxed) {
1551 return true;
1552 }
1553 if poll_deadline && deadline.is_some_and(|deadline| Instant::now() > deadline) {
1554 stop.store(true, Ordering::Relaxed);
1555 return true;
1556 }
1557 false
1558 }
1559 }
1560 }
1561
1562 pub(crate) fn request_stop(&self) {
1563 if let Self::Campaign { stop, .. } = self {
1564 stop.store(true, Ordering::Relaxed);
1565 }
1566 }
1567
1568 pub(crate) const fn early_exit_ref(&self) -> &EarlyExit {
1569 match self {
1570 Self::EarlyExit(early_exit) | Self::Campaign { early_exit, .. } => early_exit,
1571 }
1572 }
1573}
1574
1575#[cfg(test)]
1576mod tests {
1577 use super::*;
1578 use crate::inspectors::{EdgeCovHit, EdgeKey};
1579 use alloy_primitives::B256;
1580 use foundry_cheatcodes::{
1581 CheatsConfig,
1582 Vm::{blobhashesCall, revertToStateCall, snapshotStateCall},
1583 };
1584 use foundry_config::Config;
1585 use foundry_evm_core::{constants::MAGIC_SKIP, opts::EvmOpts};
1586 use revm::context::{Cfg, TxEnv};
1587 use std::{sync::mpsc, thread};
1588
1589 fn dense_call(edge: EdgeKey) -> RawCallResult {
1590 RawCallResult {
1591 edge_coverage: Some(EdgeCoverage::CollisionFree(vec![EdgeCovHit { edge, count: 1 }])),
1592 ..Default::default()
1593 }
1594 }
1595
1596 #[test]
1597 fn collision_free_edge_merge_uses_stable_indices() {
1598 let first =
1599 EdgeKey { address: Address::ZERO, depth: None, pc: 0, jump_dest: U256::from(10) };
1600 let second =
1601 EdgeKey { address: Address::ZERO, depth: None, pc: 0, jump_dest: U256::from(20) };
1602 let mut history = Vec::new();
1603 let mut edge_indices = EdgeIndexMap::default();
1604
1605 assert_eq!(
1606 dense_call(first).merge_edge_coverage(&mut history, &mut edge_indices),
1607 (true, true)
1608 );
1609 assert_eq!(history, [1]);
1610
1611 assert_eq!(
1612 dense_call(second).merge_edge_coverage(&mut history, &mut edge_indices),
1613 (true, true)
1614 );
1615 assert_eq!(history, [1, 1]);
1616
1617 assert_eq!(
1618 dense_call(first).merge_edge_coverage(&mut history, &mut edge_indices),
1619 (false, false)
1620 );
1621 assert_eq!(history, [1, 1]);
1622 }
1623
1624 #[test]
1625 fn collision_free_edge_merge_handles_sparse_observation_indices() {
1626 let first =
1627 EdgeKey { address: Address::ZERO, depth: None, pc: 0, jump_dest: U256::from(10) };
1628 let second =
1629 EdgeKey { address: Address::ZERO, depth: None, pc: 0, jump_dest: U256::from(20) };
1630 let mut edge_indices = EdgeIndexMap::default();
1631 edge_indices.edge_index(first);
1632 edge_indices.edge_index(second);
1633 let mut history = Vec::new();
1634
1635 assert_eq!(
1636 dense_call(second).merge_edge_coverage(&mut history, &mut edge_indices),
1637 (true, true)
1638 );
1639 assert_eq!(history, [0, 1]);
1640 }
1641
1642 #[test]
1643 fn cheatcode_skip_payload_is_classified_as_skip() {
1644 let raw = RawCallResult::<EthEvmNetwork> {
1645 result: Bytes::from_static(b"FOUNDRY::SKIPwith reason"),
1646 reverter: Some(CHEATCODE_ADDRESS),
1647 ..Default::default()
1648 };
1649
1650 let err = raw.into_evm_error(None);
1651 assert!(matches!(err, EvmError::Skip(_)));
1652 }
1653
1654 #[test]
1655 fn forged_skip_payload_from_non_cheatcode_is_execution_error() {
1656 let raw = RawCallResult::<EthEvmNetwork> {
1657 result: Bytes::from_static(MAGIC_SKIP),
1658 reverter: Some(CALLER),
1659 ..Default::default()
1660 };
1661
1662 let err = raw.into_evm_error(None);
1663 assert!(matches!(err, EvmError::Execution(_)));
1664 }
1665
1666 #[test]
1667 fn skip_payload_without_reverter_is_execution_error() {
1668 let raw = RawCallResult::<EthEvmNetwork> {
1669 result: Bytes::from_static(MAGIC_SKIP),
1670 reverter: None,
1671 ..Default::default()
1672 };
1673
1674 let err = raw.into_evm_error(None);
1675 assert!(matches!(err, EvmError::Execution(_)));
1676 }
1677
1678 #[test]
1679 fn set_spec_id_updates_spec_dependent_cfg_state() {
1680 let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
1681 let mut executor = ExecutorBuilder::default().build(
1682 EvmEnvFor::<EthEvmNetwork>::default(),
1683 TxEnvFor::<EthEvmNetwork>::default(),
1684 backend,
1685 );
1686
1687 executor.evm_env_mut().cfg_env.set_spec_and_mainnet_gas_params(SpecId::HOMESTEAD);
1688 assert_eq!(
1689 executor.evm_env().cfg_env.gas_params(),
1690 &revm::context_interface::cfg::GasParams::new_spec(SpecId::HOMESTEAD),
1691 );
1692 assert!(!executor.evm_env().cfg_env.is_amsterdam_eip8037_enabled());
1693
1694 executor.set_spec_id(SpecId::AMSTERDAM);
1695
1696 assert_eq!(executor.spec_id(), SpecId::AMSTERDAM);
1697 assert_eq!(
1698 executor.evm_env().cfg_env.gas_params(),
1699 &revm::context_interface::cfg::GasParams::new_spec(SpecId::AMSTERDAM),
1700 );
1701 assert!(executor.evm_env().cfg_env.is_amsterdam_eip8037_enabled());
1702 }
1703
1704 #[test]
1705 fn early_exit_interrupts_active_evm_execution() {
1706 const GAS_LIMIT: u64 = 1 << 24;
1707 let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
1708 let mut executor = ExecutorBuilder::default().gas_limit(GAS_LIMIT).build(
1709 EvmEnvFor::<EthEvmNetwork>::default(),
1710 TxEnvFor::<EthEvmNetwork>::default(),
1711 backend,
1712 );
1713 let early_exit = EarlyExit::new(false);
1714 executor.inspector_mut().set_early_exit(early_exit.clone());
1715
1716 let target = Address::repeat_byte(0x11);
1717 executor
1719 .set_code(target, Bytecode::new_raw(Bytes::from_static(&[0x5b, 0x60, 0x00, 0x56])))
1720 .unwrap();
1721
1722 let (started_tx, started_rx) = mpsc::channel();
1723 let (result_tx, result_rx) = mpsc::channel();
1724 let handle = thread::spawn(move || {
1725 started_tx.send(()).unwrap();
1726 let result = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO);
1727 let _ = result_tx.send(result);
1728 });
1729
1730 started_rx.recv().unwrap();
1731 thread::sleep(Duration::from_millis(1));
1732 early_exit.record_ctrl_c();
1733
1734 let result = result_rx.recv_timeout(Duration::from_secs(1));
1735 handle.join().unwrap();
1736 let result = result.expect("active EVM execution did not observe early exit").unwrap();
1737 assert!(result.execution_cancelled);
1738 assert!(!result.reverted);
1739 assert_eq!(result.exit_reason, Some(InstructionResult::Stop));
1740 assert!(result.gas_used > 21_000, "interrupt fired before EVM execution started");
1741 assert!(result.gas_used < GAS_LIMIT, "execution ran out of gas instead of exiting");
1742 }
1743
1744 #[test]
1745 fn completed_execution_is_not_retroactively_cancelled() {
1746 let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
1747 let mut executor = ExecutorBuilder::default().gas_limit(1 << 24).build(
1748 EvmEnvFor::<EthEvmNetwork>::default(),
1749 TxEnvFor::<EthEvmNetwork>::default(),
1750 backend,
1751 );
1752 let early_exit = EarlyExit::new(false);
1753 executor.inspector_mut().set_early_exit(early_exit.clone());
1754
1755 let target = Address::repeat_byte(0x11);
1756 executor.set_code(target, Bytecode::new_raw(Bytes::from_static(&[0x00]))).unwrap();
1757 let result = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO).unwrap();
1758 early_exit.record_ctrl_c();
1759
1760 assert!(!result.execution_cancelled);
1761 assert!(!result.reverted);
1762 }
1763
1764 #[test]
1765 fn campaign_deadline_interrupts_active_evm_execution() {
1766 const GAS_LIMIT: u64 = 1 << 24;
1767 let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
1768 let mut executor = ExecutorBuilder::default().gas_limit(GAS_LIMIT).build(
1769 EvmEnvFor::<EthEvmNetwork>::default(),
1770 TxEnvFor::<EthEvmNetwork>::default(),
1771 backend,
1772 );
1773 let cancellation = EvmExecutionCancellation::campaign(
1774 EarlyExit::new(false),
1775 Arc::new(AtomicBool::new(false)),
1776 Some(Instant::now()),
1777 );
1778 executor.inspector_mut().set_execution_cancellation(cancellation);
1779
1780 let target = Address::repeat_byte(0x11);
1781 executor
1782 .set_code(target, Bytecode::new_raw(Bytes::from_static(&[0x5b, 0x60, 0x00, 0x56])))
1783 .unwrap();
1784
1785 let result = executor.transact_raw(CALLER, target, Bytes::new(), U256::ZERO).unwrap();
1786 assert!(result.execution_cancelled);
1787 assert!(!result.reverted);
1788 assert_eq!(result.exit_reason, Some(InstructionResult::Stop));
1789 assert!(result.gas_used < GAS_LIMIT, "execution ran out of gas instead of timing out");
1790 }
1791
1792 #[test]
1793 fn beacon_root_system_call_does_not_persist_system_address() {
1794 let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
1795 let mut executor = ExecutorBuilder::default().spec_id(SpecId::CANCUN).build(
1796 EvmEnvFor::<EthEvmNetwork>::default(),
1797 TxEnvFor::<EthEvmNetwork>::default(),
1798 backend,
1799 );
1800 let before = executor.backend().basic_ref(SYSTEM_ADDRESS).unwrap();
1801
1802 executor.apply_beacon_root(B256::repeat_byte(0x11)).unwrap();
1803
1804 assert_eq!(
1805 executor.backend().basic_ref(SYSTEM_ADDRESS).unwrap(),
1806 before,
1807 "EIP-4788 system calls must not persist the system caller account",
1808 );
1809 }
1810
1811 #[test]
1826 fn pre_override_blob_hashes_restored_on_revert_to_state() {
1827 let cheats_config = Arc::new(CheatsConfig::new(
1828 &Config::default(),
1829 EvmOpts::default(),
1830 None,
1831 None,
1832 None,
1833 false,
1834 ));
1835
1836 let backend = Backend::<EthEvmNetwork>::spawn(None).unwrap();
1837 let mut executor = ExecutorBuilder::default()
1838 .inspectors(|stack| stack.cheatcodes(cheats_config))
1839 .spec_id(SpecId::CANCUN)
1840 .build(EvmEnv::default(), TxEnv::default(), backend);
1841
1842 let original: Vec<B256> = vec![B256::repeat_byte(0x11), B256::repeat_byte(0x22)];
1843 executor.tx_env_mut().set_blob_hashes(original.clone());
1844
1845 let snap_result = executor
1846 .transact_raw(
1847 CALLER,
1848 CHEATCODE_ADDRESS,
1849 snapshotStateCall {}.abi_encode().into(),
1850 U256::ZERO,
1851 )
1852 .expect("snapshotState failed");
1853 assert!(!snap_result.reverted, "snapshotState reverted unexpectedly");
1854 let snapshot_id = U256::from_be_slice(&snap_result.result[..32]);
1855
1856 let new_hashes = vec![B256::repeat_byte(0x33)];
1857 let blob_result = executor
1858 .transact_raw(
1859 CALLER,
1860 CHEATCODE_ADDRESS,
1861 blobhashesCall { hashes: new_hashes }.abi_encode().into(),
1862 U256::ZERO,
1863 )
1864 .expect("blobhashes failed");
1865 assert!(!blob_result.reverted, "blobhashes reverted unexpectedly");
1866
1867 let revert_result = executor
1868 .transact_raw(
1869 CALLER,
1870 CHEATCODE_ADDRESS,
1871 revertToStateCall { snapshotId: snapshot_id }.abi_encode().into(),
1872 U256::ZERO,
1873 )
1874 .expect("revertToState failed");
1875 assert!(!revert_result.reverted, "revertToState reverted unexpectedly");
1876
1877 assert_eq!(
1878 revert_result.tx_env.blob_hashes, original,
1879 "pre_override_blob_hashes must be restored to original non-empty hashes, not []",
1880 );
1881 }
1882}