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::{shrink_sequence, shrink_sequence_value},
5};
6use alloy_dyn_abi::JsonAbiExt;
7use alloy_primitives::{I256, Log, map::HashMap};
8use eyre::Result;
9use foundry_common::{ContractsByAddress, ContractsByArtifact};
10use foundry_config::InvariantConfig;
11use foundry_evm_coverage::HitMaps;
12use foundry_evm_fuzz::{BaseCounterExample, BasicTxDetails, invariant::InvariantContract};
13use foundry_evm_traces::{TraceKind, TraceMode, Traces, load_contracts};
14use indicatif::ProgressBar;
15use parking_lot::RwLock;
16use std::sync::Arc;
17
18#[expect(clippy::too_many_arguments)]
21pub fn replay_run(
22 invariant_contract: &InvariantContract<'_>,
23 mut executor: Executor,
24 known_contracts: &ContractsByArtifact,
25 mut ided_contracts: ContractsByAddress,
26 logs: &mut Vec<Log>,
27 traces: &mut Traces,
28 line_coverage: &mut Option<HitMaps>,
29 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
30 inputs: &[BasicTxDetails],
31 show_solidity: bool,
32) -> Result<Vec<BaseCounterExample>> {
33 if executor.inspector().tracer.is_none() {
35 executor.set_tracing(TraceMode::Call);
36 }
37
38 let mut counterexample_sequence = vec![];
39
40 for tx in inputs {
42 let mut call_result = execute_tx(&mut executor, tx)?;
43 logs.extend(call_result.logs.clone());
44 traces.push((TraceKind::Execution, call_result.traces.clone().unwrap()));
45 HitMaps::merge_opt(line_coverage, call_result.line_coverage.clone());
46
47 executor.commit(&mut call_result);
49
50 ided_contracts
52 .extend(load_contracts(call_result.traces.iter().map(|a| &a.arena), known_contracts));
53
54 counterexample_sequence.push(BaseCounterExample::from_invariant_call(
56 tx,
57 &ided_contracts,
58 call_result.traces,
59 show_solidity,
60 ));
61 }
62
63 let (invariant_result, invariant_success) = call_invariant_function(
69 &executor,
70 invariant_contract.address,
71 invariant_contract.invariant_function.abi_encode_input(&[])?.into(),
72 )?;
73 traces.push((TraceKind::Execution, invariant_result.traces.clone().unwrap()));
74 logs.extend(invariant_result.logs);
75 deprecated_cheatcodes.extend(
76 invariant_result
77 .cheatcodes
78 .as_ref()
79 .map_or_else(Default::default, |cheats| cheats.deprecated.clone()),
80 );
81
82 if invariant_contract.call_after_invariant && invariant_success {
84 let (after_invariant_result, _) =
85 call_after_invariant_function(&executor, invariant_contract.address)?;
86 traces.push((TraceKind::Execution, after_invariant_result.traces.clone().unwrap()));
87 logs.extend(after_invariant_result.logs);
88 }
89
90 Ok(counterexample_sequence)
91}
92
93#[expect(clippy::too_many_arguments)]
98pub fn replay_error(
99 config: InvariantConfig,
100 mut executor: Executor,
101 calls: &[BasicTxDetails],
102 inner_sequence: Option<Vec<Option<BasicTxDetails>>>,
103 target_value: Option<I256>,
104 invariant_contract: &InvariantContract<'_>,
105 known_contracts: &ContractsByArtifact,
106 ided_contracts: ContractsByAddress,
107 logs: &mut Vec<Log>,
108 traces: &mut Traces,
109 line_coverage: &mut Option<HitMaps>,
110 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
111 progress: Option<&ProgressBar>,
112 early_exit: &EarlyExit,
113) -> Result<Vec<BaseCounterExample>> {
114 let calls = if let Some(target) = target_value {
115 shrink_sequence_value(
116 &config,
117 invariant_contract,
118 calls,
119 &executor,
120 target,
121 progress,
122 early_exit,
123 )?
124 } else {
125 shrink_sequence(&config, invariant_contract, calls, &executor, progress, early_exit)?
126 };
127
128 if let Some(sequence) = inner_sequence {
129 set_up_inner_replay(&mut executor, &sequence);
130 }
131
132 replay_run(
133 invariant_contract,
134 executor,
135 known_contracts,
136 ided_contracts,
137 logs,
138 traces,
139 line_coverage,
140 deprecated_cheatcodes,
141 &calls,
142 config.show_solidity,
143 )
144}
145
146fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option<BasicTxDetails>]) {
148 if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer
149 && let Some(call_generator) = &mut fuzzer.call_generator
150 {
151 call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned()));
152 call_generator.set_replay(true);
153 }
154}