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