1use super::{BasicTxDetails, CallDetails};
2use alloy_primitives::Address;
3use parking_lot::{Mutex, RwLock};
4use proptest::{
5 option::weighted,
6 strategy::{SBoxedStrategy, Strategy, ValueTree},
7 test_runner::TestRunner,
8};
9use std::sync::Arc;
1011/// Given a TestRunner and a strategy, it generates calls. Used inside the Fuzzer inspector to
12/// override external calls to test for potential reentrancy vulnerabilities..
13#[derive(Clone, Debug)]
14pub struct RandomCallGenerator {
15/// Address of the test contract.
16pub test_address: Address,
17/// Runner that will generate the call from the strategy.
18pub runner: Arc<Mutex<TestRunner>>,
19/// Strategy to be used to generate calls from `target_reference`.
20pub strategy: SBoxedStrategy<Option<CallDetails>>,
21/// Reference to which contract we want a fuzzed calldata from.
22pub target_reference: Arc<RwLock<Address>>,
23/// Flag to know if a call has been overridden. Don't allow nesting for now.
24pub used: bool,
25/// If set to `true`, consumes the next call from `last_sequence`, otherwise queries it from
26 /// the strategy.
27pub replay: bool,
28/// Saves the sequence of generated calls that can be replayed later on.
29pub last_sequence: Arc<RwLock<Vec<Option<BasicTxDetails>>>>,
30}
3132impl RandomCallGenerator {
33pub fn new(
34 test_address: Address,
35 runner: TestRunner,
36 strategy: impl Strategy<Value = CallDetails> + Send + Sync + 'static,
37 target_reference: Arc<RwLock<Address>>,
38 ) -> Self {
39Self {
40test_address,
41 runner: Arc::new(Mutex::new(runner)),
42 strategy: weighted(0.9, strategy).sboxed(),
43target_reference,
44 last_sequence: Arc::default(),
45 replay: false,
46 used: false,
47 }
48 }
4950/// All `self.next()` calls will now pop `self.last_sequence`. Used to replay an invariant
51 /// failure.
52pub fn set_replay(&mut self, status: bool) {
53self.replay = status;
54if status {
55// So it can later be popped.
56self.last_sequence.write().reverse();
57 }
58 }
5960/// Gets the next call. Random if replay is not set. Otherwise, it pops from `last_sequence`.
61pub fn next(
62&mut self,
63 original_caller: Address,
64 original_target: Address,
65 ) -> Option<BasicTxDetails> {
66if self.replay {
67self.last_sequence.write().pop().expect(
68"to have same size as the number of (unsafe) external calls of the sequence.",
69 )
70 } else {
71// TODO: Do we want it to be 80% chance only too ?
72let sender = original_target;
7374// Set which contract we mostly (80% chance) want to generate calldata from.
75*self.target_reference.write() = original_caller;
7677// `original_caller` has a 80% chance of being the `new_target`.
78let choice = self79.strategy
80 .new_tree(&mut self.runner.lock())
81 .unwrap()
82 .current()
83 .map(|call_details| BasicTxDetails { sender, call_details });
8485self.last_sequence.write().push(choice.clone());
86choice87 }
88 }
89}