foundry_evm/executors/invariant/
replay.rs1use super::{
2 call_after_invariant_function, call_invariant_function, error::FailedInvariantCaseData,
3 shrink_sequence,
4};
5use crate::executors::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) -> Result<Vec<BaseCounterExample>> {
113 match failed_case.test_error {
114 TestError::Abort(_) => Ok(vec![]),
116 TestError::Fail(_, ref calls) => {
117 let calls = shrink_sequence(
119 failed_case,
120 calls,
121 &executor,
122 invariant_contract.call_after_invariant,
123 progress,
124 )?;
125
126 set_up_inner_replay(&mut executor, &failed_case.inner_sequence);
127
128 replay_run(
130 invariant_contract,
131 executor,
132 known_contracts,
133 ided_contracts,
134 logs,
135 traces,
136 line_coverage,
137 deprecated_cheatcodes,
138 &calls,
139 show_solidity,
140 )
141 }
142 }
143}
144
145fn set_up_inner_replay(executor: &mut Executor, inner_sequence: &[Option<BasicTxDetails>]) {
147 if let Some(fuzzer) = &mut executor.inspector_mut().fuzzer
148 && let Some(call_generator) = &mut fuzzer.call_generator
149 {
150 call_generator.last_sequence = Arc::new(RwLock::new(inner_sequence.to_owned()));
151 call_generator.set_replay(true);
152 }
153}