Skip to content

Commit

Permalink
include tests and fix strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
malik672 committed Oct 22, 2024
1 parent 7bf93bc commit 7fc90ef
Show file tree
Hide file tree
Showing 3 changed files with 309 additions and 11 deletions.
4 changes: 2 additions & 2 deletions crates/evm/fuzz/src/strategies/invariants.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::{fuzz_calldata, fuzz_param_from_state};
use crate::{
invariant::{BasicTxDetails, CallDetails, FuzzRunIdentifiedContracts, SenderFilters},
strategies::{fuzz_calldata_from_state, fuzz_param, EvmFuzzState},
strategies::{fuzz_calldata_from_state, fuzz_param, param::FuzzConfig, EvmFuzzState},
FuzzFixtures,
};
use alloy_json_abi::Function;
Expand Down Expand Up @@ -94,7 +94,7 @@ fn select_random_sender(
} else {
assert!(dictionary_weight <= 100, "dictionary_weight must be <= 100");
proptest::prop_oneof![
100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address),
100 - dictionary_weight => fuzz_param(&alloy_dyn_abi::DynSolType::Address, &FuzzConfig::new()),
dictionary_weight => fuzz_param_from_state(&alloy_dyn_abi::DynSolType::Address, fuzz_state),
]
.prop_map(move |addr| addr.as_address().unwrap())
Expand Down
203 changes: 196 additions & 7 deletions crates/evm/fuzz/src/strategies/param.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::collections::HashMap;
use super::state::EvmFuzzState;
use alloy_dyn_abi::{DynSolType, DynSolValue};
use alloy_primitives::{Address, B256, I256, U256};
Expand All @@ -6,11 +7,31 @@ use proptest::prelude::*;
/// The max length of arrays we fuzz for is 256.
const MAX_ARRAY_LEN: usize = 256;

/// Struct to hold range configuration
#[derive(Default, Clone)]
pub struct FuzzConfig {
ranges: HashMap<String, (U256, U256)>
}

impl FuzzConfig {
/// Initiates a new range configuration
pub fn new() -> Self {
Self { ranges: HashMap::new() }
}

/// Adds a range
pub fn with_range(mut self, param_name: &str, min: U256, max: U256) -> Self {
self.ranges.insert(param_name.to_string(), (min, max));
self
}
}


/// Given a parameter type, returns a strategy for generating values for that type.
///
/// See [`fuzz_param_with_fixtures`] for more information.
pub fn fuzz_param(param: &DynSolType) -> BoxedStrategy<DynSolValue> {
fuzz_param_inner(param, None)
pub fn fuzz_param(param: &DynSolType, config: &FuzzConfig) -> BoxedStrategy<DynSolValue> {
fuzz_param_inner(param,config, None)
}

/// Given a parameter type and configured fixtures for param name, returns a strategy for generating
Expand All @@ -33,13 +54,16 @@ pub fn fuzz_param_with_fixtures(
fixtures: Option<&[DynSolValue]>,
name: &str,
) -> BoxedStrategy<DynSolValue> {
fuzz_param_inner(param, fixtures.map(|f| (f, name)))
fuzz_param_inner(param, &FuzzConfig::new(), fixtures.map(|f| (f, name)))
}

fn fuzz_param_inner(
param: &DynSolType,
config: &FuzzConfig,
mut fuzz_fixtures: Option<(&[DynSolValue], &str)>,
) -> BoxedStrategy<DynSolValue> {
let param_name = fuzz_fixtures.as_ref().map(|(_, name)| *name);

if let Some((fixtures, name)) = fuzz_fixtures {
if !fixtures.iter().all(|f| f.matches(param)) {
error!("fixtures for {name:?} do not match type {param}");
Expand Down Expand Up @@ -71,7 +95,15 @@ fn fuzz_param_inner(
.prop_map(move |x| DynSolValue::Int(x, n))
.boxed(),
DynSolType::Uint(n @ 8..=256) => {
super::UintStrategy::new(n, fuzz_fixtures, None, None, false)
let (min, max) = if let Some(name) = param_name {
config.ranges.get(name)
.map(|(min, max)| (Some(*min), Some(*max)))
.unwrap_or((None, None))
} else {
(None, None)
};

super::UintStrategy::new(n, fuzz_fixtures, min, max, false)
.prop_map(move |x| DynSolValue::Uint(x, n))
.boxed()
}
Expand All @@ -87,17 +119,17 @@ fn fuzz_param_inner(
.boxed(),
DynSolType::Tuple(ref params) => params
.iter()
.map(|param| fuzz_param_inner(param, None))
.map(|param| fuzz_param_inner(param,&FuzzConfig::new(), None))
.collect::<Vec<_>>()
.prop_map(DynSolValue::Tuple)
.boxed(),
DynSolType::FixedArray(ref param, size) => {
proptest::collection::vec(fuzz_param_inner(param, None), size)
proptest::collection::vec(fuzz_param_inner(param, &FuzzConfig::new(), None), size)
.prop_map(DynSolValue::FixedArray)
.boxed()
}
DynSolType::Array(ref param) => {
proptest::collection::vec(fuzz_param_inner(param, None), 0..MAX_ARRAY_LEN)
proptest::collection::vec(fuzz_param_inner(param, &FuzzConfig::new(), None), 0..MAX_ARRAY_LEN)
.prop_map(DynSolValue::Array)
.boxed()
}
Expand Down Expand Up @@ -210,10 +242,15 @@ mod tests {
strategies::{fuzz_calldata, fuzz_calldata_from_state, EvmFuzzState},
FuzzFixtures,
};
use alloy_dyn_abi::{DynSolType, DynSolValue};
use alloy_primitives::U256;
use foundry_common::abi::get_func;
use foundry_config::FuzzDictionaryConfig;
use proptest::{prelude::Strategy, test_runner::TestRunner};
use revm::db::{CacheDB, EmptyDB};

use super::{fuzz_param_inner, FuzzConfig};

#[test]
fn can_fuzz_array() {
let f = "testArray(uint64[2] calldata values)";
Expand All @@ -228,4 +265,156 @@ mod tests {
let mut runner = proptest::test_runner::TestRunner::new(cfg);
let _ = runner.run(&strategy, |_| Ok(()));
}

#[test]
fn test_uint_param_with_range() {
let mut config = FuzzConfig::new();
let min = U256::from(100u64);
let max = U256::from(1000u64);
config = config.with_range("amount", min, max);

let param = DynSolType::Uint(256);
let strategy = fuzz_param_inner(&param, &config, Some((&[], "amount")));

let mut runner = TestRunner::default();
for _ in 0..1000 {
let value = strategy.new_tree(&mut runner).unwrap().current();
if let DynSolValue::Uint(value, _) = value {
assert!(
value >= min && value <= max,
"Generated value {} outside configured range [{}, {}]",
value,
min,
max
);
} else {
panic!("Expected Uint value");
}
}
}

#[test]
fn test_uint_param_without_range() {
let config = FuzzConfig::new();
let param = DynSolType::Uint(8);
let strategy = fuzz_param_inner(&param, &config, None);

let mut runner = TestRunner::default();
for _ in 0..1000 {
let value = strategy.new_tree(&mut runner).unwrap().current();
if let DynSolValue::Uint(value, bits) = value {
assert!(
value <= U256::from(u8::MAX),
"Generated value {} exceeds uint8 max",
value
);
assert_eq!(bits, 8, "Incorrect bit size");
} else {
panic!("Expected Uint value");
}
}
}

#[test]
fn test_uint_param_with_fixtures() {
let config = FuzzConfig::new();
let fixtures = vec![
DynSolValue::Uint(U256::from(500u64), 256),
DynSolValue::Uint(U256::from(600u64), 256),
];

let param = DynSolType::Uint(256);
let strategy = fuzz_param_inner(&param, &config, Some((&fixtures, "test")));

let mut runner = TestRunner::default();
let mut found_fixture = false;

for _ in 0..1000 {
let value = strategy.new_tree(&mut runner).unwrap().current();
if let DynSolValue::Uint(value, _) = value {
if value == U256::from(500u64) || value == U256::from(600u64) {
found_fixture = true;
break;
}
}
}
assert!(found_fixture, "Never generated fixture value");
}


#[test]
fn test_uint_param_with_range_and_fixtures() {
let mut config = FuzzConfig::new();
let min = U256::from(100u64);
let max = U256::from(1000u64);
config = config.with_range("test", min, max);

let fixtures = vec![
DynSolValue::Uint(U256::from(50u64), 256),
DynSolValue::Uint(U256::from(500u64), 256),
DynSolValue::Uint(U256::from(1500u64), 256),
];

let param = DynSolType::Uint(256);
let strategy = fuzz_param_inner(&param, &config, Some((&fixtures, "test")));

let mut runner = TestRunner::default();
for _ in 0..1000 {
let value = strategy.new_tree(&mut runner).unwrap().current();
if let DynSolValue::Uint(value, _) = value {
assert!(
value >= min && value <= max,
"Generated value {} outside configured range [{}, {}]",
value,
min,
max
);
}
}
}

#[test]
fn test_param_range_matching() {
let mut config = FuzzConfig::new();
config = config
.with_range("amount", U256::from(100u64), U256::from(1000u64))
.with_range("other", U256::from(2000u64), U256::from(3000u64));

let param = DynSolType::Uint(256);
let mut runner = TestRunner::default();

let strategy1 = fuzz_param_inner(&param, &config, Some((&[], "amount")));
for _ in 0..100 {
let value = strategy1.new_tree(&mut runner).unwrap().current();
match value {
DynSolValue::Uint(value, bits) => {
assert_eq!(bits, 256, "Incorrect bit size");
assert!(
value >= U256::from(100u64) && value <= U256::from(1000u64),
"Generated value {} outside 'amount' range [100, 1000]",
value
);
}
_ => panic!("Expected Uint value"),
}
}

let strategy2 = fuzz_param_inner(&param, &config, Some((&[], "nonexistent")));
for _ in 0..100 {
let value = strategy2.new_tree(&mut runner).unwrap().current();
match value {
DynSolValue::Uint(value, bits) => {
assert_eq!(bits, 256, "Incorrect bit size");
assert!(
value <= (U256::from(1) << 256) - U256::from(1),
"Generated value {} exceeds maximum uint256 value",
value
);
}
_ => panic!("Expected Uint value"),
}
}
}


}
Loading

0 comments on commit 7fc90ef

Please sign in to comment.