foundry_evm_fuzz/
inspector.rs

1use crate::{invariant::RandomCallGenerator, strategies::EvmFuzzState};
2use foundry_common::mapping_slots::step as mapping_step;
3use revm::{
4    Inspector,
5    context::{ContextTr, Transaction},
6    inspector::JournalExt,
7    interpreter::{CallInput, CallInputs, CallOutcome, CallScheme, Interpreter},
8};
9
10/// An inspector that can fuzz and collect data for that effect.
11#[derive(Clone, Debug)]
12pub struct Fuzzer {
13    /// If set, it collects `stack` and `memory` values for fuzzing purposes.
14    pub collect: bool,
15    /// Given a strategy, it generates a random call.
16    pub call_generator: Option<RandomCallGenerator>,
17    /// If `collect` is set, we store the collected values in this fuzz dictionary.
18    pub fuzz_state: EvmFuzzState,
19}
20
21impl<CTX> Inspector<CTX> for Fuzzer
22where
23    CTX: ContextTr<Journal: JournalExt>,
24{
25    #[inline]
26    fn step(&mut self, interp: &mut Interpreter, _context: &mut CTX) {
27        // We only collect `stack` and `memory` data before and after calls.
28        if self.collect {
29            self.collect_data(interp);
30            if let Some(mapping_slots) = &mut self.fuzz_state.mapping_slots {
31                mapping_step(mapping_slots, interp);
32            }
33        }
34    }
35
36    fn call(&mut self, ecx: &mut CTX, inputs: &mut CallInputs) -> Option<CallOutcome> {
37        // We don't want to override the very first call made to the test contract.
38        if self.call_generator.is_some() && ecx.tx().caller() != inputs.caller {
39            self.override_call(inputs);
40        }
41
42        // We only collect `stack` and `memory` data before and after calls.
43        // this will be turned off on the next `step`
44        self.collect = true;
45
46        None
47    }
48
49    fn call_end(&mut self, _context: &mut CTX, _inputs: &CallInputs, _outcome: &mut CallOutcome) {
50        if let Some(ref mut call_generator) = self.call_generator {
51            call_generator.used = false;
52        }
53
54        // We only collect `stack` and `memory` data before and after calls.
55        // this will be turned off on the next `step`
56        self.collect = true;
57    }
58}
59
60impl Fuzzer {
61    /// Collects `stack` and `memory` values into the fuzz dictionary.
62    #[cold]
63    fn collect_data(&mut self, interpreter: &Interpreter) {
64        self.fuzz_state.collect_values(interpreter.stack.data().iter().copied().map(Into::into));
65
66        // TODO: disabled for now since it's flooding the dictionary
67        // for index in 0..interpreter.shared_memory.len() / 32 {
68        //     let mut slot = [0u8; 32];
69        //     slot.clone_from_slice(interpreter.shared_memory.get_slice(index * 32, 32));
70
71        //     state.insert(slot);
72        // }
73
74        self.collect = false;
75    }
76
77    /// Overrides an external call and tries to call any method of msg.sender.
78    fn override_call(&mut self, call: &mut CallInputs) {
79        if let Some(ref mut call_generator) = self.call_generator {
80            // We only override external calls which are not coming from the test contract.
81            if call.caller != call_generator.test_address
82                && call.scheme == CallScheme::Call
83                && !call_generator.used
84            {
85                // There's only a 30% chance that an override happens.
86                if let Some(tx) = call_generator.next(call.caller, call.target_address) {
87                    call.input = CallInput::Bytes(tx.call_details.calldata.0.into());
88                    call.caller = tx.sender;
89                    call.target_address = tx.call_details.target;
90
91                    // TODO: in what scenarios can the following be problematic
92                    call.bytecode_address = tx.call_details.target;
93                    call_generator.used = true;
94                }
95            }
96        }
97    }
98}