foundry_evm/executors/invariant/
replay.rs1use super::{
2 call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData,
3 shrink_sequence,
4};
5use crate::executors::{EarlyExit, Executor};
6use alloy_dyn_abi::JsonAbiExt;
7use alloy_primitives::{Log, U256, map::HashMap};
8use eyre::Result;
9use foundry_common::{ContractsByAddress, ContractsByArtifact};
10use foundry_evm_coverage::HitMaps;
11use foundry_evm_fuzz::{BaseCounterExample, BasicTxDetails, invariant::InvariantContract};
12use foundry_evm_traces::{TraceKind, TraceMode, Traces, load_contracts};
13use indicatif::ProgressBar;
14use parking_lot::RwLock;
15use proptest::test_runner::TestError;
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 call_result = executor.transact_raw(
43 tx.sender,
44 tx.call_details.target,
45 tx.call_details.calldata.clone(),
46 U256::ZERO,
47 )?;
48
49 logs.extend(call_result.logs);
50 traces.push((TraceKind::Execution, call_result.traces.clone().unwrap()));
51 HitMaps::merge_opt(line_coverage, call_result.line_coverage);
52
53 ided_contracts
55 .extend(load_contracts(call_result.traces.iter().map(|a| &a.arena), known_contracts));
56
57 counterexample_sequence.push(BaseCounterExample::from_invariant_call(
59 tx.sender,
60 tx.call_details.target,
61 &tx.call_details.calldata,
62 &ided_contracts,
63 call_result.traces,
64 show_solidity,
65 ));
66 }
67
68 let (invariant_result, invariant_success) = call_invariant_function(
74 &executor,
75 invariant_contract.address,
76 invariant_contract.invariant_function.abi_encode_input(&[])?.into(),
77 )?;
78 traces.push((TraceKind::Execution, invariant_result.traces.clone().unwrap()));
79 logs.extend(invariant_result.logs);
80 deprecated_cheatcodes.extend(
81 invariant_result
82 .cheatcodes
83 .as_ref()
84 .map_or_else(Default::default, |cheats| cheats.deprecated.clone()),
85 );
86
87 if invariant_contract.call_after_invariant && invariant_success {
89 let (after_invariant_result, _) =
90 call_after_invariant_function(&executor, invariant_contract.address)?;
91 traces.push((TraceKind::Execution, after_invariant_result.traces.clone().unwrap()));
92 logs.extend(after_invariant_result.logs);
93 }
94
95 Ok(counterexample_sequence)
96}
97
98#[expect(clippy::too_many_arguments)]
100pub fn replay_error(
101 failed_case: &FailedInvariantCaseData,
102 invariant_contract: &InvariantContract<'_>,
103 mut executor: Executor,
104 known_contracts: &ContractsByArtifact,
105 ided_contracts: ContractsByAddress,
106 logs: &mut Vec<Log>,
107 traces: &mut Traces,
108 line_coverage: &mut Option<HitMaps>,
109 deprecated_cheatcodes: &mut HashMap<&'static str, Option<&'static str>>,
110 progress: Option<&ProgressBar>,
111 show_solidity: bool,
112 early_exit: &EarlyExit,
113) -> Result<Vec<BaseCounterExample>> {
114 match failed_case.test_error {
115 TestError::Abort(_) => Ok(vec![]),
117 TestError::Fail(_, ref calls) => {
118 let calls = shrink_sequence(
120 failed_case,
121 calls,
122 &executor,
123 invariant_contract.call_after_invariant,
124 progress,
125 early_exit,
126 )?;
127
128 set_up_inner_replay(&mut executor, &failed_case.inner_sequence);
129
130 replay_run(
132 invariant_contract,
133 executor,
134 known_contracts,
135 ided_contracts,
136 logs,
137 traces,
138 line_coverage,
139 deprecated_cheatcodes,
140 &calls,
141 show_solidity,
142 )
143 }
144 }
145}
146
147fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option<BasicTxDetails>]) {
149 if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer
150 && let Some(call_generator) = &mut fuzzer.call_generator
151 {
152 call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned()));
153 call_generator.set_replay(true);
154 }
155}