foundry_evm/executors/invariant/
replay.rs
1use super::{
2 call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData,
3 shrink_sequence,
4};
5use crate::executors::Executor;
6use alloy_dyn_abi::JsonAbiExt;
7use alloy_primitives::{map::HashMap, Log};
8use eyre::Result;
9use foundry_common::{ContractsByAddress, ContractsByArtifact};
10use foundry_evm_coverage::HitMaps;
11use foundry_evm_fuzz::{
12 invariant::{BasicTxDetails, InvariantContract},
13 BaseCounterExample,
14};
15use foundry_evm_traces::{load_contracts, TraceKind, TraceMode, Traces};
16use indicatif::ProgressBar;
17use parking_lot::RwLock;
18use proptest::test_runner::TestError;
19use revm::primitives::U256;
20use std::sync::Arc;
21
22#[expect(clippy::too_many_arguments)]
25pub fn replay_run(
26 invariant_contract: &InvariantContract<'_>,
27 mut executor: Executor,
28 known_contracts: &ContractsByArtifact,
29 mut ided_contracts: ContractsByAddress,
30 logs: &mut Vec<Log>,
31 traces: &mut Traces,
32 coverage: &mut Option<HitMaps>,
33 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
34 inputs: &[BasicTxDetails],
35 show_solidity: bool,
36) -> Result<Vec<BaseCounterExample>> {
37 if executor.inspector().tracer.is_none() {
39 executor.set_tracing(TraceMode::Call);
40 }
41
42 let mut counterexample_sequence = vec![];
43
44 for tx in inputs {
46 let call_result = executor.transact_raw(
47 tx.sender,
48 tx.call_details.target,
49 tx.call_details.calldata.clone(),
50 U256::ZERO,
51 )?;
52
53 logs.extend(call_result.logs);
54 traces.push((TraceKind::Execution, call_result.traces.clone().unwrap()));
55 HitMaps::merge_opt(coverage, call_result.coverage);
56
57 ided_contracts
59 .extend(load_contracts(call_result.traces.iter().map(|a| &a.arena), known_contracts));
60
61 counterexample_sequence.push(BaseCounterExample::from_invariant_call(
63 tx.sender,
64 tx.call_details.target,
65 &tx.call_details.calldata,
66 &ided_contracts,
67 call_result.traces,
68 show_solidity,
69 ));
70 }
71
72 let (invariant_result, invariant_success) = call_invariant_function(
78 &executor,
79 invariant_contract.address,
80 invariant_contract.invariant_function.abi_encode_input(&[])?.into(),
81 )?;
82 traces.push((TraceKind::Execution, invariant_result.traces.clone().unwrap()));
83 logs.extend(invariant_result.logs);
84 deprecated_cheatcodes.extend(
85 invariant_result
86 .cheatcodes
87 .as_ref()
88 .map_or_else(Default::default, |cheats| cheats.deprecated.clone()),
89 );
90
91 if invariant_contract.call_after_invariant && invariant_success {
93 let (after_invariant_result, _) =
94 call_after_invariant_function(&executor, invariant_contract.address)?;
95 traces.push((TraceKind::Execution, after_invariant_result.traces.clone().unwrap()));
96 logs.extend(after_invariant_result.logs);
97 }
98
99 Ok(counterexample_sequence)
100}
101
102#[expect(clippy::too_many_arguments)]
104pub fn replay_error(
105 failed_case: &FailedInvariantCaseData,
106 invariant_contract: &InvariantContract<'_>,
107 mut executor: Executor,
108 known_contracts: &ContractsByArtifact,
109 ided_contracts: ContractsByAddress,
110 logs: &mut Vec<Log>,
111 traces: &mut Traces,
112 coverage: &mut Option<HitMaps>,
113 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
114 progress: Option<&ProgressBar>,
115 show_solidity: bool,
116) -> Result<Vec<BaseCounterExample>> {
117 match failed_case.test_error {
118 TestError::Abort(_) => Ok(vec![]),
120 TestError::Fail(_, ref calls) => {
121 let calls = shrink_sequence(
123 failed_case,
124 calls,
125 &executor,
126 invariant_contract.call_after_invariant,
127 progress,
128 )?;
129
130 set_up_inner_replay(&mut executor, &failed_case.inner_sequence);
131
132 replay_run(
134 invariant_contract,
135 executor,
136 known_contracts,
137 ided_contracts,
138 logs,
139 traces,
140 coverage,
141 deprecated_cheatcodes,
142 &calls,
143 show_solidity,
144 )
145 }
146 }
147}
148
149fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option<BasicTxDetails>]) {
151 if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer {
152 if let Some(call_generator) = &mut fuzzer.call_generator {
153 call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned()));
154 call_generator.set_replay(true);
155 }
156 }
157}