foundry_evm_fuzz/
inspector.rs

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