foundry_evm/executors/invariant/
replay.rs1use super::{call_after_invariant_function, call_invariant_function, execute_tx};
2use crate::executors::{
3 EarlyExit, Executor,
4 invariant::shrink::{
5 CheckSequenceOutcome, ShrinkProgress, shrink_sequence, shrink_sequence_value,
6 },
7};
8use alloy_dyn_abi::JsonAbiExt;
9use alloy_json_abi::Function;
10use alloy_primitives::{
11 Bytes, I256, Log,
12 map::{AddressHashMap, HashMap},
13};
14use eyre::Result;
15use foundry_common::{ContractsByAddress, ContractsByArtifact};
16use foundry_config::InvariantConfig;
17use foundry_evm_core::{decode::RevertDecoder, evm::FoundryEvmNetwork};
18use foundry_evm_coverage::HitMaps;
19use foundry_evm_fuzz::{BaseCounterExample, BasicTxDetails, invariant::InvariantContract};
20use foundry_evm_traces::{TraceKind, TraceRequirements, Traces, load_contracts};
21use indicatif::ProgressBar;
22use parking_lot::RwLock;
23use std::sync::Arc;
24
25pub struct ReplayErrorResult {
26 pub counterexample_sequence: Vec<BaseCounterExample>,
27 pub check_result: Option<CheckSequenceOutcome>,
28}
29
30#[expect(clippy::too_many_arguments)]
33pub fn replay_run<FEN: FoundryEvmNetwork>(
34 invariant_contract: &InvariantContract<'_>,
35 target_invariant: &Function,
36 mut executor: Executor<FEN>,
37 known_contracts: &ContractsByArtifact,
38 mut ided_contracts: ContractsByAddress,
39 logs: &mut Vec<Log>,
40 traces: &mut Traces,
41 debug_bytecodes: &mut AddressHashMap<Bytes>,
42 line_coverage: &mut Option<HitMaps>,
43 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
44 inputs: &[BasicTxDetails],
45 show_solidity: bool,
46) -> Result<Vec<BaseCounterExample>> {
47 if executor.inspector().tracer.is_none() {
49 executor.set_trace_requirements(TraceRequirements::none().with_calls(true));
50 }
51
52 let mut counterexample_sequence = vec![];
53
54 for tx in inputs {
56 let mut call_result = execute_tx(&mut executor, tx)?;
57 logs.append(&mut call_result.logs);
58 debug_bytecodes.extend(std::mem::take(&mut call_result.debug_bytecodes));
59 traces.push((TraceKind::Execution, call_result.traces.clone().unwrap()));
60 HitMaps::merge_opt(line_coverage, call_result.line_coverage.take());
61
62 executor.commit(&mut call_result);
64
65 ided_contracts
67 .extend(load_contracts(call_result.traces.iter().map(|a| &a.arena), known_contracts));
68
69 counterexample_sequence.push(BaseCounterExample::from_invariant_call(
71 tx,
72 &ided_contracts,
73 call_result.traces,
74 show_solidity,
75 ));
76 }
77
78 let (invariant_result, invariant_success) = call_invariant_function(
84 &executor,
85 invariant_contract.address,
86 target_invariant.abi_encode_input(&[])?.into(),
87 )?;
88 debug_bytecodes.extend(invariant_result.debug_bytecodes);
89 traces.push((TraceKind::Execution, invariant_result.traces.clone().unwrap()));
90 logs.extend(invariant_result.logs);
91 deprecated_cheatcodes.extend(
92 invariant_result
93 .cheatcodes
94 .as_ref()
95 .map_or_else(Default::default, |cheats| cheats.deprecated.clone()),
96 );
97
98 if invariant_contract.call_after_invariant && invariant_success {
100 let (after_invariant_result, _) =
101 call_after_invariant_function(&executor, invariant_contract.address)?;
102 debug_bytecodes.extend(after_invariant_result.debug_bytecodes);
103 traces.push((TraceKind::Execution, after_invariant_result.traces.clone().unwrap()));
104 logs.extend(after_invariant_result.logs);
105 }
106
107 Ok(counterexample_sequence)
108}
109
110#[expect(clippy::too_many_arguments)]
115pub fn replay_error<FEN: FoundryEvmNetwork>(
116 config: InvariantConfig,
117 mut executor: Executor<FEN>,
118 calls: &[BasicTxDetails],
119 inner_sequence: Option<Vec<Option<BasicTxDetails>>>,
120 expect_assertion_failure: bool,
121 rd: Option<&RevertDecoder>,
122 target_value: Option<I256>,
123 invariant_contract: &InvariantContract<'_>,
124 target_invariant: &Function,
125 known_contracts: &ContractsByArtifact,
126 ided_contracts: ContractsByAddress,
127 logs: &mut Vec<Log>,
128 traces: &mut Traces,
129 debug_bytecodes: &mut AddressHashMap<Bytes>,
130 line_coverage: &mut Option<HitMaps>,
131 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
132 progress: Option<&ProgressBar>,
133 early_exit: &EarlyExit,
134 position: Option<(usize, usize)>,
135) -> Result<ReplayErrorResult> {
136 let shrink_progress = ShrinkProgress::new(
139 &config,
140 progress,
141 &target_invariant.name,
142 position,
143 Some(&ided_contracts),
144 config.show_solidity,
145 );
146
147 let (calls, check_result) = if let Some(target) = target_value {
148 (
149 shrink_sequence_value(
150 &config,
151 invariant_contract,
152 target_invariant,
153 calls,
154 &executor,
155 target,
156 &shrink_progress,
157 early_exit,
158 )?,
159 None,
160 )
161 } else {
162 let shrunk = shrink_sequence(
163 &config,
164 invariant_contract,
165 target_invariant,
166 calls,
167 expect_assertion_failure,
168 &executor,
169 rd,
170 &shrink_progress,
171 early_exit,
172 )?;
173 (shrunk.calls, shrunk.result)
174 };
175
176 if let Some(sequence) = inner_sequence {
177 set_up_inner_replay(&mut executor, &sequence);
178 }
179
180 let counterexample_sequence = replay_run(
181 invariant_contract,
182 target_invariant,
183 executor,
184 known_contracts,
185 ided_contracts,
186 logs,
187 traces,
188 debug_bytecodes,
189 line_coverage,
190 deprecated_cheatcodes,
191 &calls,
192 config.show_solidity,
193 )?;
194
195 Ok(ReplayErrorResult { counterexample_sequence, check_result })
196}
197
198fn set_up_inner_replay<FEN: FoundryEvmNetwork>(
200 executor: &mut Executor<FEN>,
201 inner_sequence: &[Option<BasicTxDetails>],
202) {
203 if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer
204 && let Some(call_generator) = &mut fuzzer.call_generator
205 {
206 call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned()));
207 call_generator.set_replay(true);
208 }
209}