1use crate::invariant::RandomCallGenerator;
2use alloy_primitives::{Address, B256, Bytes, U256, map::AddressMap};
3use foundry_common::mapping_slots::{MappingSlots, step as mapping_step};
4use foundry_evm_core::constants::CHEATCODE_ADDRESS;
5use revm::{
6 Inspector,
7 context::{ContextTr, JournalTr, Transaction},
8 interpreter::{CallInput, CallInputs, CallOutcome, CallScheme, CallValue, Interpreter},
9};
10
11#[derive(Clone, Debug, PartialEq, Eq)]
17pub struct ObservedCall {
18 pub depth: u32,
19 pub caller: Address,
20 pub target: Address,
21 pub calldata: Bytes,
22 pub value: Option<U256>,
23}
24
25#[derive(Clone, Debug)]
27pub struct Fuzzer {
28 pub collect: bool,
30 pub call_generator: Option<RandomCallGenerator>,
32 pub collected_values: Vec<B256>,
34 pub max_collected_values: usize,
36 pub mapping_slots: Option<AddressMap<MappingSlots>>,
38 record_calls: bool,
40 observed_calls: Vec<ObservedCall>,
42 call_depth: u32,
44}
45
46impl<CTX: ContextTr> Inspector<CTX> for Fuzzer {
47 #[inline]
48 fn step(&mut self, interp: &mut Interpreter, _context: &mut CTX) {
49 if self.collect {
51 self.collect_data(interp);
52 if let Some(mapping_slots) = &mut self.mapping_slots {
53 mapping_step(mapping_slots, interp);
54 }
55 }
56 }
57
58 fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
59 if self.call_generator.is_some() && ecx.tx().caller() != inputs.caller {
61 self.override_call(ecx, inputs);
62 }
63
64 self.call_depth = self.call_depth.saturating_add(1);
65 if self.should_record_observed_call(inputs.scheme) {
66 self.observed_calls.push(ObservedCall {
67 depth: self.call_depth - 1,
68 caller: inputs.caller,
69 target: inputs.target_address,
70 calldata: inputs.input.bytes(ecx),
71 value: inputs.transfer_value().filter(|value| !value.is_zero()),
72 });
73 }
74
75 self.collect = true;
78
79 None
80 }
81
82 fn call_end(&mut self, _context: &mut CTX, _inputs: &CallInputs, _outcome: &mut CallOutcome) {
83 if let Some(ref mut call_generator) = self.call_generator {
84 if call_generator.override_depth > 0 {
86 call_generator.override_depth -= 1;
87 }
88 }
89
90 self.collect = true;
93
94 self.call_depth = self.call_depth.saturating_sub(1);
95 }
96}
97
98impl Fuzzer {
99 pub const fn new(
101 max_collected_values: usize,
102 mapping_slots: Option<AddressMap<MappingSlots>>,
103 ) -> Self {
104 Self {
105 collect: true,
106 call_generator: None,
107 collected_values: Vec::new(),
108 max_collected_values,
109 mapping_slots,
110 record_calls: false,
111 observed_calls: Vec::new(),
112 call_depth: 0,
113 }
114 }
115
116 pub const fn with_call_recording(mut self, record_calls: bool) -> Self {
118 self.record_calls = record_calls;
119 self
120 }
121
122 pub const fn set_call_recording(&mut self, record_calls: bool) {
124 self.record_calls = record_calls;
125 }
126
127 pub fn take_observed_calls(&mut self) -> Vec<ObservedCall> {
129 std::mem::take(&mut self.observed_calls)
130 }
131
132 #[cfg(test)]
133 fn record_observed_call(
134 &mut self,
135 caller: Address,
136 target: Address,
137 calldata: Bytes,
138 value: Option<U256>,
139 scheme: CallScheme,
140 ) {
141 if self.should_record_observed_call(scheme) {
142 self.observed_calls.push(ObservedCall {
143 depth: self.call_depth - 1,
144 caller,
145 target,
146 calldata,
147 value,
148 });
149 }
150 }
151
152 #[inline]
153 const fn should_record_observed_call(&self, scheme: CallScheme) -> bool {
154 self.record_calls && self.call_depth > 1 && matches!(scheme, CallScheme::Call)
155 }
156
157 #[cold]
159 fn collect_data(&mut self, interpreter: &Interpreter) {
160 let remaining = self.max_collected_values.saturating_sub(self.collected_values.len());
161 self.collected_values
162 .extend(interpreter.stack.data().iter().take(remaining).copied().map(B256::from));
163
164 self.collect = false;
173 }
174
175 fn override_call<CTX: ContextTr>(&mut self, ecx: &mut CTX, call: &mut CallInputs) {
189 let Some(ref mut call_generator) = self.call_generator else {
190 return;
191 };
192
193 if call.caller == call_generator.test_address
204 || call.scheme != CallScheme::Call
205 || call_generator.override_depth > 0
206 || call.target_address == CHEATCODE_ADDRESS
207 {
208 return;
209 }
210 {
211 let handlers = call_generator.handler_addresses.read();
212 if !handlers.contains(&call.caller) && !handlers.contains(&call.target_address) {
213 return;
214 }
215 }
216
217 let Some(tx) = call_generator.next(call.caller, call.target_address) else {
219 return;
220 };
221
222 let value = call.transfer_value().unwrap_or_default();
225 let has_value = !value.is_zero() && call.gas_limit > 2300;
226 if has_value && ecx.journal_mut().transfer(call.caller, call.target_address, value).is_err()
227 {
228 return;
229 }
230
231 call.input = CallInput::Bytes(tx.call_details.calldata);
233 call.caller = tx.sender;
234 call.target_address = tx.call_details.target;
235 call.bytecode_address = tx.call_details.target;
236 let target = ecx
237 .journal_mut()
238 .load_account_with_code(tx.call_details.target)
239 .expect("failed to load account");
240 call.known_bytecode = (target.info.code_hash, target.info.code.clone().unwrap_or_default());
244 call.value = CallValue::Transfer(alloy_primitives::U256::ZERO);
246
247 call_generator.override_depth = 1;
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 fn fuzzer(record_calls: bool) -> Fuzzer {
257 Fuzzer::new(16, None).with_call_recording(record_calls)
258 }
259
260 #[test]
261 fn observed_calls_are_disabled_by_default() {
262 let mut fuzzer = Fuzzer::new(16, None);
263 fuzzer.call_depth = 2;
264
265 fuzzer.record_observed_call(
266 Address::from([0xaa; 20]),
267 Address::from([0x11; 20]),
268 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
269 Some(U256::from(1)),
270 CallScheme::Call,
271 );
272
273 assert!(fuzzer.take_observed_calls().is_empty());
274 }
275
276 #[test]
277 fn observed_calls_skip_top_level_call() {
278 let mut fuzzer = fuzzer(true);
279 fuzzer.call_depth = 1;
280
281 fuzzer.record_observed_call(
282 Address::from([0xaa; 20]),
283 Address::from([0x11; 20]),
284 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
285 None,
286 CallScheme::Call,
287 );
288
289 assert!(fuzzer.take_observed_calls().is_empty());
290 }
291
292 #[test]
293 fn observed_calls_record_subcall_depth_target_calldata_and_value() {
294 let mut fuzzer = fuzzer(true);
295 let caller = Address::from([0x11; 20]);
296 let target = Address::from([0x22; 20]);
297 let calldata = Bytes::from_static(&[0xca, 0xfe, 0xba, 0xbe]);
298 let value = Some(U256::from(7));
299 fuzzer.call_depth = 3;
300
301 fuzzer.record_observed_call(caller, target, calldata.clone(), value, CallScheme::Call);
302
303 assert_eq!(
304 fuzzer.take_observed_calls(),
305 vec![ObservedCall { depth: 2, caller, target, calldata, value }]
306 );
307 }
308
309 #[test]
310 fn observed_calls_skip_non_call_schemes() {
311 let mut fuzzer = fuzzer(true);
312 fuzzer.call_depth = 2;
313
314 fuzzer.record_observed_call(
315 Address::from([0x11; 20]),
316 Address::from([0x22; 20]),
317 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
318 None,
319 CallScheme::DelegateCall,
320 );
321
322 assert!(fuzzer.take_observed_calls().is_empty());
323 }
324
325 #[test]
326 fn take_observed_calls_drains_buffer() {
327 let mut fuzzer = fuzzer(true);
328 fuzzer.call_depth = 2;
329 fuzzer.record_observed_call(
330 Address::from([0xaa; 20]),
331 Address::from([0x33; 20]),
332 Bytes::from_static(&[0x12, 0x34, 0x56, 0x78]),
333 None,
334 CallScheme::Call,
335 );
336
337 assert_eq!(fuzzer.take_observed_calls().len(), 1);
338 assert!(fuzzer.take_observed_calls().is_empty());
339 }
340}