Skip to main content

foundry_evm_fuzz/
inspector.rs

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