1use crate::invariant::RandomCallGenerator;
2use alloy_primitives::{Address, B256, Bytes, U256, map::AddressMap};
3use foundry_common::mapping_slots::{
4 MappingSlots, PendingMappingHash, capture_hash as capture_mapping_hash,
5 record_hash as record_mapping_hash, step as mapping_step,
6};
7use foundry_evm_core::constants::CHEATCODE_ADDRESS;
8use revm::{
9 Inspector,
10 context::{ContextTr, JournalTr, Transaction},
11 interpreter::{CallInput, CallInputs, CallOutcome, CallScheme, CallValue, Interpreter},
12};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
20pub struct ObservedCall {
21 pub depth: u32,
22 pub caller: Address,
23 pub target: Address,
24 pub calldata: Bytes,
25 pub value: Option<U256>,
26}
27
28#[derive(Clone, Debug)]
30pub struct Fuzzer {
31 pub collect: bool,
33 pub call_generator: Option<RandomCallGenerator>,
35 pub collected_values: Vec<B256>,
37 pub max_collected_values: usize,
39 pub mapping_slots: Option<AddressMap<MappingSlots>>,
41 pending_mapping_hash: Option<PendingMappingHash>,
43 record_calls: bool,
45 observed_calls: Vec<ObservedCall>,
47 call_depth: u32,
49 extra_cheatcode_addresses: &'static [Address],
51}
52
53impl<CTX: ContextTr> Inspector<CTX> for Fuzzer {
54 #[inline]
55 fn step(&mut self, interp: &mut Interpreter, _context: &mut CTX) {
56 self.capture_mapping_hash(interp);
57 if self.collect {
59 self.collect_data(interp);
60 }
61 }
62
63 #[inline]
64 fn step_end(&mut self, interp: &mut Interpreter, _context: &mut CTX) {
65 self.record_mapping_hash(interp);
66 }
67
68 fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
69 if self.call_generator.is_some() && ecx.tx().caller() != inputs.transfer_from() {
71 self.override_call(ecx, inputs);
72 }
73
74 self.call_depth = self.call_depth.saturating_add(1);
75 if self.should_record_observed_call(inputs.scheme) {
76 self.observed_calls.push(ObservedCall {
77 depth: self.call_depth - 1,
78 caller: inputs.transfer_from(),
79 target: inputs.transfer_to(),
80 calldata: inputs.input.bytes(ecx),
81 value: inputs.transfer_value().filter(|value| !value.is_zero()),
82 });
83 }
84
85 self.collect = true;
88
89 None
90 }
91
92 fn call_end(&mut self, _context: &mut CTX, _inputs: &CallInputs, _outcome: &mut CallOutcome) {
93 if let Some(ref mut call_generator) = self.call_generator {
94 if call_generator.override_depth > 0 {
96 call_generator.override_depth -= 1;
97 }
98 }
99
100 self.collect = true;
103
104 self.call_depth = self.call_depth.saturating_sub(1);
105 }
106}
107
108impl Fuzzer {
109 fn capture_mapping_hash(&mut self, interpreter: &Interpreter) {
110 if let Some(mapping_slots) = &mut self.mapping_slots {
111 mapping_step(mapping_slots, interpreter);
112 self.pending_mapping_hash = capture_mapping_hash(interpreter);
113 }
114 }
115
116 fn record_mapping_hash(&mut self, interpreter: &Interpreter) {
117 if let Some(pending) = self.pending_mapping_hash.take()
118 && interpreter.bytecode.action.is_none()
119 && let Some(mapping_slots) = &mut self.mapping_slots
120 {
121 record_mapping_hash(mapping_slots, interpreter, pending);
122 }
123 }
124
125 pub const fn new(
127 max_collected_values: usize,
128 mapping_slots: Option<AddressMap<MappingSlots>>,
129 ) -> Self {
130 Self {
131 collect: true,
132 call_generator: None,
133 collected_values: Vec::new(),
134 max_collected_values,
135 mapping_slots,
136 pending_mapping_hash: None,
137 record_calls: false,
138 observed_calls: Vec::new(),
139 call_depth: 0,
140 extra_cheatcode_addresses: &[],
141 }
142 }
143
144 pub const fn with_extra_cheatcode_addresses(mut self, addresses: &'static [Address]) -> Self {
146 self.extra_cheatcode_addresses = addresses;
147 self
148 }
149
150 pub const fn with_call_recording(mut self, record_calls: bool) -> Self {
152 self.record_calls = record_calls;
153 self
154 }
155
156 pub const fn set_call_recording(&mut self, record_calls: bool) {
158 self.record_calls = record_calls;
159 }
160
161 pub fn take_observed_calls(&mut self) -> Vec<ObservedCall> {
163 std::mem::take(&mut self.observed_calls)
164 }
165
166 #[inline]
167 const fn should_record_observed_call(&self, scheme: CallScheme) -> bool {
168 self.record_calls && self.call_depth > 1 && scheme.is_call()
169 }
170
171 #[inline]
172 fn is_cheatcode_address(&self, address: Address) -> bool {
173 address == CHEATCODE_ADDRESS || self.extra_cheatcode_addresses.contains(&address)
174 }
175
176 #[cold]
178 fn collect_data(&mut self, interpreter: &Interpreter) {
179 let remaining = self.max_collected_values.saturating_sub(self.collected_values.len());
180 self.collected_values
181 .extend(interpreter.stack.data().iter().take(remaining).copied().map(B256::from));
182
183 self.collect = false;
192 }
193
194 fn override_call<CTX: ContextTr>(&mut self, ecx: &mut CTX, call: &mut CallInputs) {
208 let target_is_cheatcode = self.is_cheatcode_address(call.transfer_to());
209 let Some(ref mut call_generator) = self.call_generator else {
210 return;
211 };
212
213 if call.transfer_from() == call_generator.test_address
224 || !call.scheme.is_call()
225 || call_generator.override_depth > 0
226 || target_is_cheatcode
227 {
228 return;
229 }
230 {
231 let handlers = call_generator.handler_addresses.read();
232 if !handlers.contains(&call.transfer_from()) && !handlers.contains(&call.transfer_to())
233 {
234 return;
235 }
236 }
237
238 let Some(tx) = call_generator.next(call.transfer_from(), call.transfer_to()) else {
240 return;
241 };
242
243 let value = call.transfer_value().unwrap_or_default();
246 let has_value = !value.is_zero() && call.gas_limit > 2300;
247 if has_value
248 && ecx.journal_mut().transfer(call.transfer_from(), call.transfer_to(), value).is_err()
249 {
250 return;
251 }
252
253 call.input = CallInput::Bytes(tx.call_details.calldata);
255 call.caller = tx.sender;
256 call.target_address = tx.call_details.target;
257 call.bytecode_address = tx.call_details.target;
258 let target = ecx
259 .journal_mut()
260 .load_account_with_code(tx.call_details.target)
261 .expect("failed to load account");
262 call.known_bytecode =
266 (target.info.code_hash(), target.info.code.clone().unwrap_or_default());
267 call.value = CallValue::Transfer(alloy_primitives::U256::ZERO);
269
270 call_generator.override_depth = 1;
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278 use alloy_primitives::keccak256;
279 use foundry_evm_core::constants::MONAD_CHEATCODE_ADDRESS;
280 use revm::{Context, MainContext, bytecode::Bytecode};
281
282 fn fuzzer(record_calls: bool) -> Fuzzer {
283 Fuzzer::new(16, None).with_call_recording(record_calls)
284 }
285
286 fn observe_call(
288 fuzzer: &mut Fuzzer,
289 caller: Address,
290 target: Address,
291 calldata: Bytes,
292 value: U256,
293 scheme: CallScheme,
294 ) {
295 let mut inputs = CallInputs {
296 input: CallInput::Bytes(calldata),
297 return_memory_offset: 0..0,
298 gas_limit: 0,
299 reservoir: 0,
300 bytecode_address: target,
301 known_bytecode: Default::default(),
302 target_address: target,
303 caller,
304 value: CallValue::Transfer(value),
305 scheme,
306 is_static: false,
307 charged_new_account_state_gas: false,
308 };
309 fuzzer.call_depth -= 1;
311 fuzzer.call(&mut Context::mainnet(), &mut inputs);
312 }
313
314 #[test]
315 fn network_cheatcode_addresses_are_opt_in() {
316 let ethereum = Fuzzer::new(16, None);
317 assert!(!ethereum.is_cheatcode_address(MONAD_CHEATCODE_ADDRESS));
318
319 let monad =
320 Fuzzer::new(16, None).with_extra_cheatcode_addresses(&[MONAD_CHEATCODE_ADDRESS]);
321 assert!(monad.is_cheatcode_address(MONAD_CHEATCODE_ADDRESS));
322 }
323
324 #[test]
325 fn mapping_hashes_are_recorded_outside_dictionary_collection() {
326 let key = B256::with_last_byte(1);
327 let parent = B256::with_last_byte(2);
328 let preimage = [key.as_slice(), parent.as_slice()].concat();
329 let result = keccak256(&preimage);
330 let mut interpreter =
331 Interpreter::default().with_bytecode(Bytecode::new_raw(Bytes::from_static(&[
332 revm::bytecode::opcode::KECCAK256,
333 ])));
334 interpreter.memory.resize(64);
335 interpreter.memory.set(0, &preimage);
336 assert!(interpreter.stack.push(U256::from(64)));
337 assert!(interpreter.stack.push(U256::ZERO));
338
339 let mut fuzzer = Fuzzer::new(16, Some(AddressMap::default()));
340 fuzzer.collect = false;
341 fuzzer.capture_mapping_hash(&interpreter);
342
343 interpreter.stack.pop().unwrap();
344 interpreter.stack.pop().unwrap();
345 assert!(interpreter.stack.push(result.into()));
346 fuzzer.record_mapping_hash(&interpreter);
347
348 let slots = fuzzer.mapping_slots.unwrap();
349 assert_eq!(slots[&Address::ZERO].seen_sha3.get(&result), Some(&(key, parent)));
350 }
351
352 #[test]
353 fn observed_calls_are_disabled_by_default() {
354 let mut fuzzer = Fuzzer::new(16, None);
355 fuzzer.call_depth = 2;
356
357 observe_call(
358 &mut fuzzer,
359 Address::repeat_byte(0xaa),
360 Address::repeat_byte(0x11),
361 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
362 U256::ONE,
363 CallScheme::Call,
364 );
365
366 assert!(fuzzer.take_observed_calls().is_empty());
367 }
368
369 #[test]
370 fn observed_calls_skip_top_level_call() {
371 let mut fuzzer = fuzzer(true);
372 fuzzer.call_depth = 1;
373
374 observe_call(
375 &mut fuzzer,
376 Address::repeat_byte(0xaa),
377 Address::repeat_byte(0x11),
378 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
379 U256::ZERO,
380 CallScheme::Call,
381 );
382
383 assert!(fuzzer.take_observed_calls().is_empty());
384 }
385
386 #[test]
387 fn observed_calls_record_subcall_depth_target_calldata_and_value() {
388 let mut fuzzer = fuzzer(true);
389 let caller = Address::repeat_byte(0x11);
390 let target = Address::repeat_byte(0x22);
391 let calldata = Bytes::from_static(&[0xca, 0xfe, 0xba, 0xbe]);
392 let value = U256::from(7);
393 fuzzer.call_depth = 3;
394
395 observe_call(&mut fuzzer, caller, target, calldata.clone(), value, CallScheme::Call);
396
397 assert_eq!(
398 fuzzer.take_observed_calls(),
399 vec![ObservedCall { depth: 2, caller, target, calldata, value: Some(value) }]
400 );
401 }
402
403 #[test]
404 fn observed_calls_drop_zero_value() {
405 let mut fuzzer = fuzzer(true);
406 fuzzer.call_depth = 2;
407
408 observe_call(
409 &mut fuzzer,
410 Address::repeat_byte(0x11),
411 Address::repeat_byte(0x22),
412 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
413 U256::ZERO,
414 CallScheme::Call,
415 );
416
417 assert_eq!(fuzzer.take_observed_calls()[0].value, None);
418 }
419
420 #[test]
421 fn observed_calls_skip_non_call_schemes() {
422 let mut fuzzer = fuzzer(true);
423 fuzzer.call_depth = 2;
424
425 observe_call(
426 &mut fuzzer,
427 Address::repeat_byte(0x11),
428 Address::repeat_byte(0x22),
429 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
430 U256::ZERO,
431 CallScheme::DelegateCall,
432 );
433
434 assert!(fuzzer.take_observed_calls().is_empty());
435 }
436
437 #[test]
438 fn take_observed_calls_drains_buffer() {
439 let mut fuzzer = fuzzer(true);
440 fuzzer.call_depth = 2;
441 observe_call(
442 &mut fuzzer,
443 Address::repeat_byte(0xaa),
444 Address::repeat_byte(0x33),
445 Bytes::from_static(&[0x12, 0x34, 0x56, 0x78]),
446 U256::ZERO,
447 CallScheme::Call,
448 );
449
450 assert_eq!(fuzzer.take_observed_calls().len(), 1);
451 assert!(fuzzer.take_observed_calls().is_empty());
452 }
453}