Skip to content

Commit

Permalink
refactor: simplify fuzzing code (#7420)
Browse files Browse the repository at this point in the history
* refactor: simplify fuzzing code

* chore: use shl

* chore: unlock earlier
  • Loading branch information
DaniPopes authored Mar 18, 2024
1 parent bc821ef commit 63ea108
Show file tree
Hide file tree
Showing 8 changed files with 185 additions and 266 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 5 additions & 18 deletions crates/evm/evm/src/executors/fuzz/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,19 +82,11 @@ impl FuzzedExecutor {

let state = self.build_fuzz_state();

let mut weights = vec![];
let dictionary_weight = self.config.dictionary.dictionary_weight.min(100);
if self.config.dictionary.dictionary_weight < 100 {
weights.push((100 - dictionary_weight, fuzz_calldata(func.clone())));
}
if dictionary_weight > 0 {
weights.push((
self.config.dictionary.dictionary_weight,
fuzz_calldata_from_state(func.clone(), state.clone()),
));
}

let strat = proptest::strategy::Union::new_weighted(weights);
let strat = proptest::prop_oneof![
100 - dictionary_weight => fuzz_calldata(func.clone()),
dictionary_weight => fuzz_calldata_from_state(func.clone(), &state),
];
debug!(func=?func.name, should_fail, "fuzzing");
let run_result = self.runner.clone().run(&strat, |calldata| {
let fuzz_res = self.single_fuzz(&state, address, should_fail, calldata)?;
Expand Down Expand Up @@ -215,12 +207,7 @@ impl FuzzedExecutor {
let state_changeset = call.state_changeset.take().unwrap();

// Build fuzzer state
collect_state_from_call(
&call.logs,
&state_changeset,
state.clone(),
&self.config.dictionary,
);
collect_state_from_call(&call.logs, &state_changeset, state, &self.config.dictionary);

// When the `assume` cheatcode is called it returns a special string
if call.result.as_ref() == MAGIC_ASSUME {
Expand Down
6 changes: 3 additions & 3 deletions crates/evm/evm/src/executors/invariant/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ impl<'a> InvariantExecutor<'a> {
&mut state_changeset,
sender,
&call_result,
fuzz_state.clone(),
&fuzz_state,
&self.config.dictionary,
);

Expand Down Expand Up @@ -369,7 +369,7 @@ impl<'a> InvariantExecutor<'a> {
Arc::new(Mutex::new(targeted_contracts));

let calldata_fuzz_config =
CalldataFuzzDictionary::new(&self.config.dictionary, fuzz_state.clone());
CalldataFuzzDictionary::new(&self.config.dictionary, &fuzz_state);

// Creates the invariant strategy.
let strat = invariant_strat(
Expand Down Expand Up @@ -667,7 +667,7 @@ fn collect_data(
state_changeset: &mut HashMap<Address, revm::primitives::Account>,
sender: &Address,
call_result: &RawCallResult,
fuzz_state: EvmFuzzState,
fuzz_state: &EvmFuzzState,
config: &FuzzDictionaryConfig,
) {
// Verify it has no code.
Expand Down
1 change: 0 additions & 1 deletion crates/evm/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ revm = { workspace = true, default-features = false, features = [
"arbitrary",
] }

arbitrary = "1.3.1"
eyre = "0.6"
hashbrown = { version = "0.14", features = ["serde"] }
itertools.workspace = true
Expand Down
60 changes: 25 additions & 35 deletions crates/evm/fuzz/src/strategies/calldata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,27 @@ use alloy_json_abi::Function;
use alloy_primitives::{Address, Bytes};
use foundry_config::FuzzDictionaryConfig;
use hashbrown::HashSet;
use proptest::prelude::{BoxedStrategy, Strategy};
use std::{fmt, sync::Arc};
use proptest::prelude::Strategy;
use std::sync::Arc;

/// Clonable wrapper around [CalldataFuzzDictionary].
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
pub struct CalldataFuzzDictionary {
pub inner: Arc<CalldataFuzzDictionaryConfig>,
}

impl CalldataFuzzDictionary {
pub fn new(config: &FuzzDictionaryConfig, state: EvmFuzzState) -> Self {
pub fn new(config: &FuzzDictionaryConfig, state: &EvmFuzzState) -> Self {
Self { inner: Arc::new(CalldataFuzzDictionaryConfig::new(config, state)) }
}
}

#[derive(Clone)]
#[derive(Clone, Debug)]
pub struct CalldataFuzzDictionaryConfig {
/// Addresses that can be used for fuzzing calldata.
pub addresses: Vec<Address>,
}

impl fmt::Debug for CalldataFuzzDictionaryConfig {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CalldataFuzzDictionaryConfig").field("addresses", &self.addresses).finish()
}
}

/// Represents custom configuration for invariant fuzzed calldata strategies.
///
/// At the moment only the dictionary of addresses to be used for a fuzzed `function(address)` can
Expand All @@ -41,51 +35,47 @@ impl CalldataFuzzDictionaryConfig {
/// The set of addresses contains a number of `max_calldata_fuzz_dictionary_addresses` random
/// addresses plus all addresses that already had their PUSH bytes collected (retrieved from
/// `EvmFuzzState`, if `include_push_bytes` config enabled).
pub fn new(config: &FuzzDictionaryConfig, state: EvmFuzzState) -> Self {
let mut addresses: HashSet<Address> = HashSet::new();
let dict_size = config.max_calldata_fuzz_dictionary_addresses;
pub fn new(config: &FuzzDictionaryConfig, state: &EvmFuzzState) -> Self {
let mut addresses = HashSet::<Address>::new();

let dict_size = config.max_calldata_fuzz_dictionary_addresses;
if dict_size > 0 {
loop {
if addresses.len() == dict_size {
break
}
addresses.insert(Address::random());
}

addresses.extend(std::iter::repeat_with(Address::random).take(dict_size));
// Add all addresses that already had their PUSH bytes collected.
let mut state = state.write();
addresses.extend(state.addresses());
addresses.extend(state.read().addresses());
}

Self { addresses: Vec::from_iter(addresses) }
Self { addresses: addresses.into_iter().collect() }
}
}

/// Given a function, it returns a strategy which generates valid calldata
/// for that function's input types.
pub fn fuzz_calldata(func: Function) -> BoxedStrategy<Bytes> {
pub fn fuzz_calldata(func: Function) -> impl Strategy<Value = Bytes> {
fuzz_calldata_with_config(func, None)
}

/// Given a function, it returns a strategy which generates valid calldata
/// for that function's input types, following custom configuration rules.
pub fn fuzz_calldata_with_config(
func: Function,
config: Option<CalldataFuzzDictionary>,
) -> BoxedStrategy<Bytes> {
config: Option<&CalldataFuzzDictionary>,
) -> impl Strategy<Value = Bytes> {
// We need to compose all the strategies generated for each parameter in all
// possible combinations
let strats = func
.inputs
.iter()
.map(|input| fuzz_param(&input.selector_type().parse().unwrap(), config.clone()))
.map(|input| fuzz_param(&input.selector_type().parse().unwrap(), config))
.collect::<Vec<_>>();

strats
.prop_map(move |tokens| {
trace!(input=?tokens);
func.abi_encode_input(&tokens).unwrap().into()
})
.boxed()
strats.prop_map(move |values| {
func.abi_encode_input(&values)
.unwrap_or_else(|_| {
panic!(
"Fuzzer generated invalid arguments for function `{}` with inputs {:?}: {:?}",
func.name, func.inputs, values
)
})
.into()
})
}
Loading

0 comments on commit 63ea108

Please sign in to comment.