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.caller {
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.caller,
79 target: inputs.target_address,
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 && matches!(scheme, CallScheme::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.target_address);
229 let Some(ref mut call_generator) = self.call_generator else {
230 return;
231 };
232
233 if call.caller == call_generator.test_address
244 || call.scheme != CallScheme::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.caller) && !handlers.contains(&call.target_address) {
253 return;
254 }
255 }
256
257 let Some(tx) = call_generator.next(call.caller, call.target_address) else {
259 return;
260 };
261
262 let value = call.transfer_value().unwrap_or_default();
265 let has_value = !value.is_zero() && call.gas_limit > 2300;
266 if has_value && ecx.journal_mut().transfer(call.caller, call.target_address, value).is_err()
267 {
268 return;
269 }
270
271 call.input = CallInput::Bytes(tx.call_details.calldata);
273 call.caller = tx.sender;
274 call.target_address = tx.call_details.target;
275 call.bytecode_address = tx.call_details.target;
276 let target = ecx
277 .journal_mut()
278 .load_account_with_code(tx.call_details.target)
279 .expect("failed to load account");
280 call.known_bytecode = (target.info.code_hash, target.info.code.clone().unwrap_or_default());
284 call.value = CallValue::Transfer(alloy_primitives::U256::ZERO);
286
287 call_generator.override_depth = 1;
289 }
290}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295 use alloy_primitives::keccak256;
296 use foundry_evm_core::constants::MONAD_CHEATCODE_ADDRESS;
297 use revm::bytecode::Bytecode;
298
299 fn fuzzer(record_calls: bool) -> Fuzzer {
300 Fuzzer::new(16, None).with_call_recording(record_calls)
301 }
302
303 #[test]
304 fn network_cheatcode_addresses_are_opt_in() {
305 let ethereum = Fuzzer::new(16, None);
306 assert!(!ethereum.is_cheatcode_address(MONAD_CHEATCODE_ADDRESS));
307
308 let monad =
309 Fuzzer::new(16, None).with_extra_cheatcode_addresses(&[MONAD_CHEATCODE_ADDRESS]);
310 assert!(monad.is_cheatcode_address(MONAD_CHEATCODE_ADDRESS));
311 }
312
313 #[test]
314 fn mapping_hashes_are_recorded_outside_dictionary_collection() {
315 let key = B256::with_last_byte(1);
316 let parent = B256::with_last_byte(2);
317 let preimage = [key.as_slice(), parent.as_slice()].concat();
318 let result = keccak256(&preimage);
319 let mut interpreter =
320 Interpreter::default().with_bytecode(Bytecode::new_raw(Bytes::from_static(&[
321 revm::bytecode::opcode::KECCAK256,
322 ])));
323 interpreter.memory.resize(64);
324 interpreter.memory.set(0, &preimage);
325 assert!(interpreter.stack.push(U256::from(64)));
326 assert!(interpreter.stack.push(U256::ZERO));
327
328 let mut fuzzer = Fuzzer::new(16, Some(AddressMap::default()));
329 fuzzer.collect = false;
330 fuzzer.capture_mapping_hash(&interpreter);
331
332 interpreter.stack.pop().unwrap();
333 interpreter.stack.pop().unwrap();
334 assert!(interpreter.stack.push(result.into()));
335 fuzzer.record_mapping_hash(&interpreter);
336
337 let slots = fuzzer.mapping_slots.unwrap();
338 assert_eq!(slots[&Address::ZERO].seen_sha3.get(&result), Some(&(key, parent)));
339 }
340
341 #[test]
342 fn observed_calls_are_disabled_by_default() {
343 let mut fuzzer = Fuzzer::new(16, None);
344 fuzzer.call_depth = 2;
345
346 fuzzer.record_observed_call(
347 Address::from([0xaa; 20]),
348 Address::from([0x11; 20]),
349 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
350 Some(U256::from(1)),
351 CallScheme::Call,
352 );
353
354 assert!(fuzzer.take_observed_calls().is_empty());
355 }
356
357 #[test]
358 fn observed_calls_skip_top_level_call() {
359 let mut fuzzer = fuzzer(true);
360 fuzzer.call_depth = 1;
361
362 fuzzer.record_observed_call(
363 Address::from([0xaa; 20]),
364 Address::from([0x11; 20]),
365 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
366 None,
367 CallScheme::Call,
368 );
369
370 assert!(fuzzer.take_observed_calls().is_empty());
371 }
372
373 #[test]
374 fn observed_calls_record_subcall_depth_target_calldata_and_value() {
375 let mut fuzzer = fuzzer(true);
376 let caller = Address::from([0x11; 20]);
377 let target = Address::from([0x22; 20]);
378 let calldata = Bytes::from_static(&[0xca, 0xfe, 0xba, 0xbe]);
379 let value = Some(U256::from(7));
380 fuzzer.call_depth = 3;
381
382 fuzzer.record_observed_call(caller, target, calldata.clone(), value, CallScheme::Call);
383
384 assert_eq!(
385 fuzzer.take_observed_calls(),
386 vec![ObservedCall { depth: 2, caller, target, calldata, value }]
387 );
388 }
389
390 #[test]
391 fn observed_calls_skip_non_call_schemes() {
392 let mut fuzzer = fuzzer(true);
393 fuzzer.call_depth = 2;
394
395 fuzzer.record_observed_call(
396 Address::from([0x11; 20]),
397 Address::from([0x22; 20]),
398 Bytes::from_static(&[0xde, 0xad, 0xbe, 0xef]),
399 None,
400 CallScheme::DelegateCall,
401 );
402
403 assert!(fuzzer.take_observed_calls().is_empty());
404 }
405
406 #[test]
407 fn take_observed_calls_drains_buffer() {
408 let mut fuzzer = fuzzer(true);
409 fuzzer.call_depth = 2;
410 fuzzer.record_observed_call(
411 Address::from([0xaa; 20]),
412 Address::from([0x33; 20]),
413 Bytes::from_static(&[0x12, 0x34, 0x56, 0x78]),
414 None,
415 CallScheme::Call,
416 );
417
418 assert_eq!(fuzzer.take_observed_calls().len(), 1);
419 assert!(fuzzer.take_observed_calls().is_empty());
420 }
421}