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 pub fork_block_number: Option<u64>,
29}
30
31#[expect(clippy::too_many_arguments)]
34pub fn replay_run<FEN: FoundryEvmNetwork>(
35 invariant_contract: &InvariantContract<'_>,
36 target_invariant: &Function,
37 mut executor: Executor<FEN>,
38 known_contracts: &ContractsByArtifact,
39 mut ided_contracts: ContractsByAddress,
40 logs: &mut Vec<Log>,
41 traces: &mut Traces,
42 debug_bytecodes: &mut AddressHashMap<Bytes>,
43 line_coverage: &mut Option<HitMaps>,
44 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
45 inputs: &[BasicTxDetails],
46 show_solidity: bool,
47) -> Result<ReplayErrorResult> {
48 if executor.inspector().tracer.is_none() {
50 executor.set_trace_requirements(TraceRequirements::none().with_calls(true));
51 }
52
53 let mut counterexample_sequence = vec![];
54
55 for tx in inputs {
57 let mut call_result = execute_tx(&mut executor, tx)?;
58 logs.append(&mut call_result.logs);
59 debug_bytecodes.extend(std::mem::take(&mut call_result.debug_bytecodes));
60 traces.push((TraceKind::Execution, call_result.traces.clone().unwrap()));
61 HitMaps::merge_opt(line_coverage, call_result.line_coverage.take());
62
63 executor.commit(&mut call_result);
65
66 ided_contracts
68 .extend(load_contracts(call_result.traces.iter().map(|a| &a.arena), known_contracts));
69
70 counterexample_sequence.push(BaseCounterExample::from_invariant_call(
72 tx,
73 &ided_contracts,
74 call_result.traces,
75 show_solidity,
76 ));
77 }
78
79 let (invariant_result, invariant_success) = call_invariant_function(
85 &executor,
86 invariant_contract.address,
87 target_invariant.abi_encode_input(&[])?.into(),
88 )?;
89 let fork_block_number = invariant_result.fork_block_number;
90 debug_bytecodes.extend(invariant_result.debug_bytecodes);
91 traces.push((TraceKind::Execution, invariant_result.traces.clone().unwrap()));
92 logs.extend(invariant_result.logs);
93 deprecated_cheatcodes.extend(
94 invariant_result
95 .cheatcodes
96 .as_ref()
97 .map_or_else(Default::default, |cheats| cheats.deprecated.clone()),
98 );
99
100 if invariant_contract.call_after_invariant && invariant_success {
102 let (after_invariant_result, _) =
103 call_after_invariant_function(&executor, invariant_contract.address)?;
104 debug_bytecodes.extend(after_invariant_result.debug_bytecodes);
105 traces.push((TraceKind::Execution, after_invariant_result.traces.clone().unwrap()));
106 logs.extend(after_invariant_result.logs);
107 }
108
109 Ok(ReplayErrorResult { counterexample_sequence, check_result: None, fork_block_number })
110}
111
112#[expect(clippy::too_many_arguments)]
117pub fn replay_error<FEN: FoundryEvmNetwork>(
118 config: InvariantConfig,
119 mut executor: Executor<FEN>,
120 calls: &[BasicTxDetails],
121 inner_sequence: Option<Vec<Option<BasicTxDetails>>>,
122 expect_assertion_failure: bool,
123 rd: Option<&RevertDecoder>,
124 target_value: Option<I256>,
125 invariant_contract: &InvariantContract<'_>,
126 target_invariant: &Function,
127 known_contracts: &ContractsByArtifact,
128 ided_contracts: ContractsByAddress,
129 logs: &mut Vec<Log>,
130 traces: &mut Traces,
131 debug_bytecodes: &mut AddressHashMap<Bytes>,
132 line_coverage: &mut Option<HitMaps>,
133 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
134 progress: Option<&ProgressBar>,
135 early_exit: &EarlyExit,
136 position: Option<(usize, usize)>,
137) -> Result<ReplayErrorResult> {
138 let shrink_progress = ShrinkProgress::new(
141 &config,
142 progress,
143 &target_invariant.name,
144 position,
145 Some(&ided_contracts),
146 config.show_solidity,
147 );
148
149 let (calls, check_result) = if let Some(target) = target_value {
150 (
151 shrink_sequence_value(
152 &config,
153 invariant_contract,
154 target_invariant,
155 calls,
156 &executor,
157 target,
158 &shrink_progress,
159 early_exit,
160 )?,
161 None,
162 )
163 } else {
164 let shrunk = shrink_sequence(
165 &config,
166 invariant_contract,
167 target_invariant,
168 calls,
169 expect_assertion_failure,
170 &executor,
171 rd,
172 &shrink_progress,
173 early_exit,
174 )?;
175 (shrunk.calls, shrunk.result)
176 };
177
178 if let Some(sequence) = inner_sequence {
179 set_up_inner_replay(&mut executor, &sequence);
180 }
181
182 let mut replay = replay_run(
183 invariant_contract,
184 target_invariant,
185 executor,
186 known_contracts,
187 ided_contracts,
188 logs,
189 traces,
190 debug_bytecodes,
191 line_coverage,
192 deprecated_cheatcodes,
193 &calls,
194 config.show_solidity,
195 )?;
196
197 replay.check_result = check_result;
198 Ok(replay)
199}
200
201fn set_up_inner_replay<FEN: FoundryEvmNetwork>(
203 executor: &mut Executor<FEN>,
204 inner_sequence: &[Option<BasicTxDetails>],
205) {
206 if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer
207 && let Some(call_generator) = &mut fuzzer.call_generator
208 {
209 call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned()));
210 call_generator.set_replay(true);
211 }
212}