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 #[cfg(test)]
167 fn record_observed_call(
168 &mut self,
169 caller: Address,
170 target: Address,
171 calldata: Bytes,
172 value: Option<U256>,
173 scheme: CallScheme,
174 ) {
175 if self.should_record_observed_call(scheme) {
176 self.observed_calls.push(ObservedCall {
177 depth: self.call_depth - 1,
178 caller,
179 target,
180 calldata,
181 value,
182 });
183 }
184 }
185
186 #[inline]
187 const fn should_record_observed_call(&self, scheme: CallScheme) -> bool {
188 self.record_calls && self.call_depth > 1 && scheme.is_call()
189 }
190
191 #[inline]
192 fn is_cheatcode_address(&self, address: Address) -> bool {
193 address == CHEATCODE_ADDRESS || self.extra_cheatcode_addresses.contains(&address)
194 }
195
196 #[cold]
198 fn collect_data(&mut self, interpreter: &Interpreter) {
199 let remaining = self.max_collected_values.saturating_sub(self.collected_values.len());
200 self.collected_values
201 .extend(interpreter.stack.data().iter().take(remaining).copied().map(B256::from));
202
203 self.collect = false;
212 }
213
214 fn override_call<CTX: ContextTr>(&mut self, ecx: &mut CTX, call: &mut CallInputs) {
228 let target_is_cheatcode = self.is_cheatcode_address(call.transfer_to());
229 let Some(ref mut call_generator) = self.call_generator else {
230 return;
231 };
232
233 if call.transfer_from() == call_generator.test_address
244 || !call.scheme.is_call()
245 || call_generator.override_depth > 0
246 || target_is_cheatcode
247 {
248 return;
249 }
250 {
251 let handlers = call_generator.handler_addresses.read();
252 if !handlers.contains(&call.transfer_from()) && !handlers.contains(&call.transfer_to())
253 {
254 return;
255 }
256 }
257
258 let Some(tx) = call_generator.next(call.transfer_from(), call.transfer_to()) else {
260 return;
261 };
262
263 let value = call.transfer_value().unwrap_or_default();
266 let has_value = !value.is_zero() && call.gas_limit > 2300;
267 if has_value
268 && ecx.journal_mut().transfer(call.transfer_from(), call.transfer_to(), value).is_err()
269 {
270 return;
271 }
272
273 call.input = CallInput::Bytes(tx.call_details.calldata);
275 call.caller = tx.sender;
276 call.target_address = tx.call_details.target;
277 call.bytecode_address = tx.call_details.target;
278 let target = ecx
279 .journal_mut()
280 .load_account_with_code(tx.call_details.target)
281 .expect("failed to load account");
282 call.known_bytecode =
286 (target.info.code_hash(), target.info.code.clone().unwrap_or_default());
287 call.value = CallValue::Transfer(alloy_primitives::U256::ZERO);
289
290 call_generator.override_depth = 1;
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298 use alloy_primitives::keccak256;
299 use foundry_evm_core::constants::MONAD_CHEATCODE_ADDRESS;
300 use revm::bytecode::Bytecode;
301
302 fn fuzzer(record_calls: bool) -> Fuzzer {
303 Fuzzer::new(16, None).with_call_recording(record_calls)
304 }
305
306 #[test]
307 fn network_cheatcode_addresses_are_opt_in() {
308 let ethereum = Fuzzer::new(16, None);
309 assert!(!ethereum.is_cheatcode_address(MONAD_CHEATCODE_ADDRESS));
310
311 let monad =
312 Fuzzer::new(16, None).with_extra_cheatcode_addresses(&[MONAD_CHEATCODE_ADDRESS]);
313 assert!(monad.is_cheatcode_address(MONAD_CHEATCODE_ADDRESS));
314 }
315
316 #[test]
317 fn mapping_hashes_are_recorded_outside_dictionary_collection() {
318 let key = B256::with_last_byte(1);
319 let parent = B256::with_last_byte(2);
320 let preimage = [key.as_slice(), parent.as_slice()].concat();
321 let result = keccak256(&preimage);
322 let mut interpreter =
323 Interpreter::default().with_bytecode(Bytecode::new_raw(Bytes::from_static(&[
324 revm::bytecode::opcode::KECCAK256,
325 ])));
326 interpreter.memory.resize(64);
327 interpreter.memory.set(0, &preimage);
328 assert!(interpreter.stack.push(U256::from(64)));
329 assert!(interpreter.stack.push(U256::ZERO));
330
331 let mut fuzzer = Fuzzer::new(16, Some(AddressMap::default()));
332 fuzzer.collect = false;
333 fuzzer.capture_mapping_hash(&interpreter);
334
335 interpreter.stack.pop().unwrap();
336 interpreter.stack.pop().unwrap();
337 assert!(interpreter.stack.push(result.into()));
338 fuzzer.record_mapping_hash(&interpreter);
339
340 let slots = fuzzer.mapping_slots.unwrap();
341 assert_eq!(slots[&Address::ZERO].seen_sha3.get(&result), Some(&(key, parent)));
342 }
343
344 #[test]
345 fn observed_calls_are_disabled_by_default() {
346 let mut fuzzer = Fuzzer::new(16, None);
347 fuzzer.call_depth = 2;
348
349 fuzzer.record_observed_call(
350 Address::repeat_byte(0xaa),
351 Address::repeat_byte(0x11),
352 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
353 Some(U256::ONE),
354 CallScheme::Call,
355 );
356
357 assert!(fuzzer.take_observed_calls().is_empty());
358 }
359
360 #[test]
361 fn observed_calls_skip_top_level_call() {
362 let mut fuzzer = fuzzer(true);
363 fuzzer.call_depth = 1;
364
365 fuzzer.record_observed_call(
366 Address::repeat_byte(0xaa),
367 Address::repeat_byte(0x11),
368 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
369 None,
370 CallScheme::Call,
371 );
372
373 assert!(fuzzer.take_observed_calls().is_empty());
374 }
375
376 #[test]
377 fn observed_calls_record_subcall_depth_target_calldata_and_value() {
378 let mut fuzzer = fuzzer(true);
379 let caller = Address::repeat_byte(0x11);
380 let target = Address::repeat_byte(0x22);
381 let calldata = Bytes::from_static(&[0xca, 0xfe, 0xba, 0xbe]);
382 let value = Some(U256::from(7));
383 fuzzer.call_depth = 3;
384
385 fuzzer.record_observed_call(caller, target, calldata.clone(), value, CallScheme::Call);
386
387 assert_eq!(
388 fuzzer.take_observed_calls(),
389 vec![ObservedCall { depth: 2, caller, target, calldata, value }]
390 );
391 }
392
393 #[test]
394 fn observed_calls_skip_non_call_schemes() {
395 let mut fuzzer = fuzzer(true);
396 fuzzer.call_depth = 2;
397
398 fuzzer.record_observed_call(
399 Address::repeat_byte(0x11),
400 Address::repeat_byte(0x22),
401 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
402 None,
403 CallScheme::DelegateCall,
404 );
405
406 assert!(fuzzer.take_observed_calls().is_empty());
407 }
408
409 #[test]
410 fn take_observed_calls_drains_buffer() {
411 let mut fuzzer = fuzzer(true);
412 fuzzer.call_depth = 2;
413 fuzzer.record_observed_call(
414 Address::repeat_byte(0xaa),
415 Address::repeat_byte(0x33),
416 Bytes::from_static(&[0x12, 0x34, 0x56, 0x78]),
417 None,
418 CallScheme::Call,
419 );
420
421 assert_eq!(fuzzer.take_observed_calls().len(), 1);
422 assert!(fuzzer.take_observed_calls().is_empty());
423 }
424}