Skip to content

Commit

Permalink
Review changes: don't panic when invalid fixture, use prop_filter_map…
Browse files Browse the repository at this point in the history
… for fixture strategy and raise error
  • Loading branch information
grandizzy committed Apr 18, 2024
1 parent 53c64d3 commit 997c5d4
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 14 deletions.
8 changes: 5 additions & 3 deletions crates/evm/fuzz/src/strategies/int.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ impl ValueTree for IntValueTree {
/// - use `amount` named parameter in fuzzed test in order to include fixtures in fuzzed values
/// `function testFuzz_int32(int32 amount)`.
///
/// If fixture is not a valid int type then fuzzer will panic.
/// If fixture is not a valid int type then error is raised and random value generated.
#[derive(Debug)]
pub struct IntStrategy {
/// Bit size of int (e.g. 256)
Expand Down Expand Up @@ -147,14 +147,16 @@ impl IntStrategy {
}

// Generate value tree from fixture.
// If fixture is not a valid type, raise error and generate random value.
let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())];
if let Some(int_fixture) = fixture.as_int() {
if int_fixture.1 == self.bits {
return Ok(IntValueTree::new(int_fixture.0, false));
}
}
panic!("{:?} is not a valid {} fixture", fixture, DynSolType::Int(self.bits));

// If fixture is not a valid type, raise error and generate random value.
error!("{:?} is not a valid {} fixture", fixture, DynSolType::Int(self.bits));
self.generate_random_tree(runner)
}

fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
Expand Down
10 changes: 5 additions & 5 deletions crates/evm/fuzz/src/strategies/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ pub use invariants::{fuzz_contract_with_calldata, invariant_strat, override_call
/// Macro to create strategy with fixtures.
/// 1. A default strategy if no fixture defined for current parameter.
/// 2. A weighted strategy that use fixtures and default strategy values for current parameter.
/// If fixture is not of the same type as fuzzed parameter then fuzzer will panic.
/// If fixture is not of the same type as fuzzed parameter then value is rejected and error raised.
macro_rules! fixture_strategy {
($fixtures:ident, $default_strategy:expr) => {
($fixtures:ident, $strategy_value:expr, $default_strategy:expr) => {
if let Some(fixtures) = $fixtures {
proptest::prop_oneof![
50 => {
let custom_fixtures: Vec<DynSolValue> =
fixtures.iter().enumerate().map(|(_, value)| value.to_owned()).collect();
let custom_fixtures_len = custom_fixtures.len();
any::<prop::sample::Index>()
.prop_map(move |index| {
.prop_filter_map("invalid fixture", move |index| {
let index = index.index(custom_fixtures_len);
custom_fixtures.get(index).unwrap().clone()
})
$strategy_value(custom_fixtures.get(index))
})
},
50 => $default_strategy
].boxed()
Expand Down
48 changes: 45 additions & 3 deletions crates/evm/fuzz/src/strategies/param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ const MAX_ARRAY_LEN: usize = 256;
/// `fixture_owner` function can be used in a fuzzed test function with a signature like
/// `function testFuzz_ownerAddress(address owner, uint amount)`.
///
/// Fuzzer will panic if the type of fixtures is different than the parameter type.
/// Fuzzer will reject value and raise error if the fixture type is not of the same type as
/// parameter to fuzz.
///
/// Works with ABI Encoder v2 tuples.
pub fn fuzz_param(
Expand All @@ -27,7 +28,18 @@ pub fn fuzz_param(
) -> BoxedStrategy<DynSolValue> {
match *param {
DynSolType::Address => {
fixture_strategy!(fuzz_fixtures, DynSolValue::type_strategy(&DynSolType::Address))
fixture_strategy!(
fuzz_fixtures,
|fixture: Option<&DynSolValue>| {
if let Some(val @ DynSolValue::Address(_)) = fixture {
Some(val.clone())
} else {
error!("{:?} is not a valid address fixture", fixture.unwrap());
None
}
},
DynSolValue::type_strategy(&DynSolType::Address)
)
}
DynSolType::Int(n @ 8..=256) => super::IntStrategy::new(n, fuzz_fixtures)
.prop_map(move |x| DynSolValue::Int(x, n))
Expand All @@ -37,14 +49,44 @@ pub fn fuzz_param(
.boxed(),
DynSolType::Function | DynSolType::Bool => DynSolValue::type_strategy(param).boxed(),
DynSolType::Bytes => {
fixture_strategy!(fuzz_fixtures, DynSolValue::type_strategy(&DynSolType::Bytes))
fixture_strategy!(
fuzz_fixtures,
|fixture: Option<&DynSolValue>| {
if let Some(val @ DynSolValue::Bytes(_)) = fixture {
Some(val.clone())
} else {
error!("{:?} is not a valid bytes fixture", fixture.unwrap());
None
}
},
DynSolValue::type_strategy(&DynSolType::Bytes)
)
}
DynSolType::FixedBytes(size @ 1..=32) => fixture_strategy!(
fuzz_fixtures,
|fixture: Option<&DynSolValue>| {
if let Some(val @ DynSolValue::FixedBytes(_, _)) = fixture {
if let Some(val) = val.as_fixed_bytes() {
if val.1 == size {
return Some(DynSolValue::FixedBytes(B256::from_slice(val.0), val.1))
}
}
}
error!("{:?} is not a valid fixed bytes fixture", fixture.unwrap());
None
},
DynSolValue::type_strategy(&DynSolType::FixedBytes(size))
),
DynSolType::String => fixture_strategy!(
fuzz_fixtures,
|fixture: Option<&DynSolValue>| {
if let Some(val @ DynSolValue::String(_)) = fixture {
Some(val.clone())
} else {
error!("{:?} is not a valid string fixture", fixture.unwrap());
None
}
},
DynSolValue::type_strategy(&DynSolType::String).prop_map(move |value| {
DynSolValue::String(
value.as_str().unwrap().trim().trim_end_matches('\0').to_string(),
Expand Down
8 changes: 5 additions & 3 deletions crates/evm/fuzz/src/strategies/uint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ impl ValueTree for UintValueTree {
/// - use `amount` named parameter in fuzzed test in order to include fixtures in fuzzed values
/// `function testFuzz_uint32(uint32 amount)`.
///
/// If fixture is not a valid uint type then fuzzer will panic.
/// If fixture is not a valid uint type then error is raised and random value generated.
#[derive(Debug)]
pub struct UintStrategy {
/// Bit size of uint (e.g. 256)
Expand Down Expand Up @@ -125,14 +125,16 @@ impl UintStrategy {
}

// Generate value tree from fixture.
// If fixture is not a valid type, raise error and generate random value.
let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())];
if let Some(uint_fixture) = fixture.as_uint() {
if uint_fixture.1 == self.bits {
return Ok(UintValueTree::new(uint_fixture.0, false));
}
}
panic!("{:?} is not a valid {} fixture", fixture, DynSolType::Uint(self.bits));

// If fixture is not a valid type, raise error and generate random value.
error!("{:?} is not a valid {} fixture", fixture, DynSolType::Uint(self.bits));
self.generate_random_tree(runner)
}

fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
Expand Down

0 comments on commit 997c5d4

Please sign in to comment.