From b920b6889b2ba394b4957c8999f06b78e11c3f67 Mon Sep 17 00:00:00 2001 From: malik Date: Mon, 21 Oct 2024 21:54:56 +0100 Subject: [PATCH 01/25] native --- crates/evm/fuzz/src/strategies/param.rs | 8 ++- crates/evm/fuzz/src/strategies/uint.rs | 89 ++++++++++++++++++++++--- 2 files changed, 83 insertions(+), 14 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 7e5218fd872b..0c08a7776466 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -70,9 +70,11 @@ fn fuzz_param_inner( DynSolType::Int(n @ 8..=256) => super::IntStrategy::new(n, fuzz_fixtures) .prop_map(move |x| DynSolValue::Int(x, n)) .boxed(), - DynSolType::Uint(n @ 8..=256) => super::UintStrategy::new(n, fuzz_fixtures) - .prop_map(move |x| DynSolValue::Uint(x, n)) - .boxed(), + DynSolType::Uint(n @ 8..=256) => { + super::UintStrategy::new(n, fuzz_fixtures, None, None, false) + .prop_map(move |x| DynSolValue::Uint(x, n)) + .boxed() + } DynSolType::Function | DynSolType::Bool => DynSolValue::type_strategy(param).boxed(), DynSolType::Bytes => value(), DynSolType::FixedBytes(_size @ 1..=32) => value(), diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index af133efa0082..7bc6f06b6f45 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -16,6 +16,10 @@ pub struct UintValueTree { hi: U256, /// If true cannot be simplified or complexified fixed: bool, + /// Min Value + min_bound: U256, + /// Max Value + max_bound: U256, } impl UintValueTree { @@ -23,8 +27,8 @@ impl UintValueTree { /// # Arguments /// * `start` - Starting value for the tree /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. - fn new(start: U256, fixed: bool) -> Self { - Self { lo: U256::ZERO, curr: start, hi: start, fixed } + fn new(start: U256, fixed: bool, min_bound: U256, max_bound: U256) -> Self { + Self { lo: U256::ZERO, curr: start, hi: start, fixed, min_bound, max_bound } } fn reposition(&mut self) -> bool { @@ -44,7 +48,7 @@ impl ValueTree for UintValueTree { type Value = U256; fn current(&self) -> Self::Value { - self.curr + self.curr.clamp(self.min_bound, self.max_bound) } fn simplify(&mut self) -> bool { @@ -91,6 +95,12 @@ pub struct UintStrategy { fixtures_weight: usize, /// The weight for purely random values random_weight: usize, + /// Minimum bound for generated values + min_bound: U256, + /// Maximum bound for generated values + max_bound: U256, + /// Use logarithmic sampling for large ranges + use_log_sampling: bool, } impl UintStrategy { @@ -98,13 +108,27 @@ impl UintStrategy { /// #Arguments /// * `bits` - Size of uint in bits /// * `fixtures` - A set of fixed values to be generated (according to fixtures weight) - pub fn new(bits: usize, fixtures: Option<&[DynSolValue]>) -> Self { + pub fn new( + bits: usize, + fixtures: Option<&[DynSolValue]>, + min_bound: Option, + max_bound: Option, + use_log_sampling: bool, + ) -> Self { + let type_max = if bits < 256 { (U256::from(1) << bits) - U256::from(1) } else { U256::MAX }; + + let min = min_bound.unwrap_or(U256::ZERO); + let max = max_bound.unwrap_or(type_max); + Self { bits, fixtures: Vec::from(fixtures.unwrap_or_default()), edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, + min_bound: min, + max_bound: max, + use_log_sampling, } } @@ -113,8 +137,12 @@ impl UintStrategy { // Choose if we want values around 0 or max let is_min = rng.gen_bool(0.5); let offset = U256::from(rng.gen_range(0..4)); - let start = if is_min { offset } else { self.type_max().saturating_sub(offset) }; - Ok(UintValueTree::new(start, false)) + let start = if is_min { + self.min_bound.saturating_add(offset) + } else { + self.max_bound.saturating_sub(offset) + }; + Ok(UintValueTree::new(start, false, self.min_bound, self.max_bound)) } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -127,7 +155,8 @@ impl UintStrategy { 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)); + let fixture_value = uint_fixture.0.clamp(self.min_bound, self.max_bound); + return Ok(UintValueTree::new(fixture_value, false, self.min_bound, self.max_bound)); } } @@ -136,9 +165,8 @@ impl UintStrategy { self.generate_random_tree(runner) } - fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { + fn generate_random_values_uniformly(&self, runner: &mut TestRunner) -> U256 { let rng = runner.rng(); - // generate random number of bits uniformly let bits = rng.gen_range(0..=self.bits); @@ -163,9 +191,48 @@ impl UintStrategy { inner[1] = (lower >> 64) as u64; inner[2] = (higher & mask64) as u64; inner[3] = (higher >> 64) as u64; + let start: U256 = U256::from_limbs(inner); + start + } + + fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { + let start = if self.use_log_sampling { + self.generate_log_uniform(runner) + } else if self.max_bound > self.min_bound { + let range = self.max_bound - self.min_bound + U256::from(1); + let random = self.generate_random_values_uniformly(runner) % range; + self.min_bound + random + } else { + self.min_bound + }; + + let clamped_start = start.clamp(self.min_bound, self.max_bound); + Ok(UintValueTree::new(clamped_start, false, self.min_bound, self.max_bound)) + } + + fn generate_log_uniform(&self, runner: &mut TestRunner) -> U256 { + if self.max_bound <= self.min_bound { + return self.min_bound; + } + + let max_exp = 256; + let random_exp = runner.rng().gen_range(0..=max_exp); + + let mantissa = U256::from(runner.rng().gen::()); + + let mut value: U256 = (mantissa << random_exp) | (U256::from(1) << random_exp); + + value = value.clamp(self.min_bound, self.max_bound); + + if value == self.min_bound { + let range = self.max_bound - self.min_bound; + if range > U256::ZERO { + value += U256::from(runner.rng().gen::()) % range; + } + } - Ok(UintValueTree::new(start, false)) + value } fn type_max(&self) -> U256 { @@ -200,7 +267,7 @@ mod tests { #[test] fn test_uint_tree_complicate_max() { - let mut uint_tree = UintValueTree::new(U256::MAX, false); + let mut uint_tree = UintValueTree::new(U256::MAX, false, U256::MAX, U256::MIN); assert_eq!(uint_tree.hi, U256::MAX); assert_eq!(uint_tree.curr, U256::MAX); uint_tree.complicate(); From 7bf93bc4389c6afef5e5d814e8e2c7ddf837b97c Mon Sep 17 00:00:00 2001 From: malik Date: Tue, 22 Oct 2024 06:06:00 +0100 Subject: [PATCH 02/25] native --- crates/evm/fuzz/src/strategies/uint.rs | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 7bc6f06b6f45..57ee7f12ab43 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -234,14 +234,6 @@ impl UintStrategy { value } - - fn type_max(&self) -> U256 { - if self.bits < 256 { - (U256::from(1) << self.bits) - U256::from(1) - } else { - U256::MAX - } - } } impl Strategy for UintStrategy { @@ -263,7 +255,9 @@ impl Strategy for UintStrategy { mod tests { use crate::strategies::uint::UintValueTree; use alloy_primitives::U256; - use proptest::strategy::ValueTree; + use proptest::{prelude::Strategy, strategy::ValueTree, test_runner::TestRunner}; + + use super::UintStrategy; #[test] fn test_uint_tree_complicate_max() { @@ -273,4 +267,18 @@ mod tests { uint_tree.complicate(); assert_eq!(uint_tree.lo, U256::MIN); } + + #[test] + fn test_uint_strategy_respects_bounds() { + let min = U256::from(1000u64); + let max = U256::from(2000u64); + let strategy = UintStrategy::new(16, None, Some(min), Some(max), false); + let mut runner = TestRunner::default(); + + for _ in 0..1000 { + let value = strategy.new_tree(&mut runner).unwrap().current(); + assert!(value >= min && value <= max, "Generated value {} is out of bounds", value); + } + } + } From 7fc90ef6b37e5aa3324c301675e904f06b132534 Mon Sep 17 00:00:00 2001 From: malik Date: Tue, 22 Oct 2024 08:28:30 +0100 Subject: [PATCH 03/25] include tests and fix strategy --- crates/evm/fuzz/src/strategies/invariants.rs | 4 +- crates/evm/fuzz/src/strategies/param.rs | 203 ++++++++++++++++++- crates/evm/fuzz/src/strategies/uint.rs | 113 ++++++++++- 3 files changed, 309 insertions(+), 11 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/invariants.rs b/crates/evm/fuzz/src/strategies/invariants.rs index c7d04dd1ab02..4dc47eeada95 100644 --- a/crates/evm/fuzz/src/strategies/invariants.rs +++ b/crates/evm/fuzz/src/strategies/invariants.rs @@ -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; @@ -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()) diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 0c08a7776466..dd81d2772ca3 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -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}; @@ -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 +} + +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 { - fuzz_param_inner(param, None) +pub fn fuzz_param(param: &DynSolType, config: &FuzzConfig) -> BoxedStrategy { + fuzz_param_inner(param,config, None) } /// Given a parameter type and configured fixtures for param name, returns a strategy for generating @@ -33,13 +54,16 @@ pub fn fuzz_param_with_fixtures( fixtures: Option<&[DynSolValue]>, name: &str, ) -> BoxedStrategy { - 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 { + 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}"); @@ -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() } @@ -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::>() .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() } @@ -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)"; @@ -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(¶m, &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(¶m, &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(¶m, &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(¶m, &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(¶m, &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(¶m, &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"), + } + } + } + + } diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 57ee7f12ab43..c2dc16d33a2f 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -201,8 +201,12 @@ impl UintStrategy { self.generate_log_uniform(runner) } else if self.max_bound > self.min_bound { let range = self.max_bound - self.min_bound + U256::from(1); - let random = self.generate_random_values_uniformly(runner) % range; - self.min_bound + random + if range == U256::ZERO { + self.min_bound + } else { + let random = self.generate_random_values_uniformly(runner) % range; + self.min_bound + random + } } else { self.min_bound }; @@ -254,6 +258,7 @@ impl Strategy for UintStrategy { #[cfg(test)] mod tests { use crate::strategies::uint::UintValueTree; + use alloy_dyn_abi::DynSolValue; use alloy_primitives::U256; use proptest::{prelude::Strategy, strategy::ValueTree, test_runner::TestRunner}; @@ -281,4 +286,108 @@ mod tests { } } + #[test] + fn test_uint_value_tree_bounds() { + let min = U256::from(100u64); + let max = U256::from(200u64); + let start = U256::from(150u64); + + let mut tree = UintValueTree::new(start, false, min, max); + + assert_eq!(tree.current(), start); + + while tree.simplify() { + let curr = tree.current(); + assert!(curr >= min && curr <= max, + "Simplify produced out of bounds value: {}", curr); + } + + tree = UintValueTree::new(start, false, min, max); + + while tree.complicate() { + let curr = tree.current(); + assert!(curr >= min && curr <= max, + "Complicate produced out of bounds value: {}", curr); + } + } + + #[test] + fn test_edge_case_generation() { + let min = U256::from(100u64); + let max = U256::from(1000u64); + let strategy = UintStrategy::new(64, None, Some(min), Some(max), false); + let mut runner = TestRunner::default(); + + let mut found_min_area = false; + let mut found_max_area = false; + + for _ in 0..1000 { + let tree = strategy.generate_edge_tree(&mut runner).unwrap(); + let value = tree.current(); + + assert!(value >= min && value <= max, + "Edge case {} outside bounds [{}, {}]", value, min, max); + + if value <= min + U256::from(3) { + found_min_area = true; + } + if value >= max - U256::from(3) { + found_max_area = true; + } + } + + assert!(found_min_area, "Never generated values near minimum"); + assert!(found_max_area, "Never generated values near maximum"); + } + + + #[test] + fn test_fixture_generation() { + let min = U256::from(100u64); + let max = U256::from(1000u64); + let valid_fixture = U256::from(500u64); + let fixtures = vec![DynSolValue::Uint(valid_fixture, 64)]; + + let strategy = UintStrategy::new(64, Some(&fixtures), Some(min), Some(max), false); + let mut runner = TestRunner::default(); + + for _ in 0..100 { + let tree = strategy.generate_fixtures_tree(&mut runner).unwrap(); + let value = tree.current(); + assert!(value >= min && value <= max, + "Fixture value {} outside bounds [{}, {}]", value, min, max); + } + } + + #[test] + fn test_log_uniform_sampling() { + let strategy = UintStrategy::new(256, None, None, None, true); + let mut runner = TestRunner::default(); + let mut log2_buckets = vec![0; 256]; + let iterations = 100000; + + for _ in 0..iterations { + let tree = strategy.generate_random_tree(&mut runner).unwrap(); + let value = tree.current(); + + // Find the highest set bit (log2 bucket) + let mut highest_bit = 0; + for i in 0..256 { + if value >= (U256::from(1) << i) { + highest_bit = i; + } + } + log2_buckets[highest_bit] += 1; + } + + let mut populated_buckets = 0; + for &count in &log2_buckets { + if count > 0 { + populated_buckets += 1; + } + } + assert!(populated_buckets > 200, + "Log-uniform sampling didn't cover enough orders of magnitude"); + } + } From c98c93148e74544d0f9f2d6b9a8c1023fd6f8e33 Mon Sep 17 00:00:00 2001 From: malik Date: Tue, 22 Oct 2024 09:50:37 +0100 Subject: [PATCH 04/25] lints --- crates/evm/fuzz/src/strategies/param.rs | 79 +++++++++++-------------- crates/evm/fuzz/src/strategies/uint.rs | 46 +++++++------- 2 files changed, 58 insertions(+), 67 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index dd81d2772ca3..c7e3a1e93e4f 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -1,8 +1,8 @@ -use std::collections::HashMap; use super::state::EvmFuzzState; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::{Address, B256, I256, U256}; use proptest::prelude::*; +use std::collections::HashMap; /// The max length of arrays we fuzz for is 256. const MAX_ARRAY_LEN: usize = 256; @@ -10,7 +10,7 @@ const MAX_ARRAY_LEN: usize = 256; /// Struct to hold range configuration #[derive(Default, Clone)] pub struct FuzzConfig { - ranges: HashMap + ranges: HashMap, } impl FuzzConfig { @@ -26,12 +26,11 @@ impl FuzzConfig { } } - /// 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, config: &FuzzConfig) -> BoxedStrategy { - fuzz_param_inner(param,config, None) + fuzz_param_inner(param, config, None) } /// Given a parameter type and configured fixtures for param name, returns a strategy for generating @@ -96,13 +95,15 @@ fn fuzz_param_inner( .boxed(), DynSolType::Uint(n @ 8..=256) => { let (min, max) = if let Some(name) = param_name { - config.ranges.get(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() @@ -119,20 +120,21 @@ fn fuzz_param_inner( .boxed(), DynSolType::Tuple(ref params) => params .iter() - .map(|param| fuzz_param_inner(param,&FuzzConfig::new(), None)) + .map(|param| fuzz_param_inner(param, &FuzzConfig::new(), None)) .collect::>() .prop_map(DynSolValue::Tuple) .boxed(), DynSolType::FixedArray(ref param, size) => { - proptest::collection::vec(fuzz_param_inner(param, &FuzzConfig::new(), 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, &FuzzConfig::new(), None), 0..MAX_ARRAY_LEN) - .prop_map(DynSolValue::Array) - .boxed() - } + DynSolType::Array(ref param) => proptest::collection::vec( + fuzz_param_inner(param, &FuzzConfig::new(), None), + 0..MAX_ARRAY_LEN, + ) + .prop_map(DynSolValue::Array) + .boxed(), _ => panic!("unsupported fuzz param type: {param}"), } } @@ -282,10 +284,7 @@ mod tests { if let DynSolValue::Uint(value, _) = value { assert!( value >= min && value <= max, - "Generated value {} outside configured range [{}, {}]", - value, - min, - max + "Generated value {value} outside configured range [{min}, {max}]" ); } else { panic!("Expected Uint value"); @@ -303,11 +302,7 @@ mod tests { 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!(value <= U256::from(u8::MAX), "Generated value {value} exceeds uint8 max"); assert_eq!(bits, 8, "Incorrect bit size"); } else { panic!("Expected Uint value"); @@ -322,13 +317,13 @@ mod tests { DynSolValue::Uint(U256::from(500u64), 256), DynSolValue::Uint(U256::from(600u64), 256), ]; - + let param = DynSolType::Uint(256); let strategy = fuzz_param_inner(¶m, &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 { @@ -341,7 +336,6 @@ mod tests { assert!(found_fixture, "Never generated fixture value"); } - #[test] fn test_uint_param_with_range_and_fixtures() { let mut config = FuzzConfig::new(); @@ -350,11 +344,11 @@ mod tests { 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), + 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(¶m, &config, Some((&fixtures, "test"))); @@ -364,10 +358,7 @@ mod tests { if let DynSolValue::Uint(value, _) = value { assert!( value >= min && value <= max, - "Generated value {} outside configured range [{}, {}]", - value, - min, - max + "Generated value {value} outside configured range [{min}, {max}]" ); } } @@ -376,13 +367,15 @@ mod tests { #[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)); - + 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(¶m, &config, Some((&[], "amount"))); for _ in 0..100 { let value = strategy1.new_tree(&mut runner).unwrap().current(); @@ -391,14 +384,13 @@ mod tests { assert_eq!(bits, 256, "Incorrect bit size"); assert!( value >= U256::from(100u64) && value <= U256::from(1000u64), - "Generated value {} outside 'amount' range [100, 1000]", - value + "Generated value {value} outside 'amount' range [100, 1000]" ); } _ => panic!("Expected Uint value"), } } - + let strategy2 = fuzz_param_inner(¶m, &config, Some((&[], "nonexistent"))); for _ in 0..100 { let value = strategy2.new_tree(&mut runner).unwrap().current(); @@ -407,14 +399,11 @@ mod tests { assert_eq!(bits, 256, "Incorrect bit size"); assert!( value <= (U256::from(1) << 256) - U256::from(1), - "Generated value {} exceeds maximum uint256 value", - value + "Generated value {value} exceeds maximum uint256 value" ); } _ => panic!("Expected Uint value"), } } } - - } diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index c2dc16d33a2f..6935942d05b7 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -201,7 +201,7 @@ impl UintStrategy { self.generate_log_uniform(runner) } else if self.max_bound > self.min_bound { let range = self.max_bound - self.min_bound + U256::from(1); - if range == U256::ZERO { + if range == U256::ZERO { self.min_bound } else { let random = self.generate_random_values_uniformly(runner) % range; @@ -282,7 +282,7 @@ mod tests { for _ in 0..1000 { let value = strategy.new_tree(&mut runner).unwrap().current(); - assert!(value >= min && value <= max, "Generated value {} is out of bounds", value); + assert!(value >= min && value <= max, "Generated value {value} is out of bounds"); } } @@ -291,23 +291,21 @@ mod tests { let min = U256::from(100u64); let max = U256::from(200u64); let start = U256::from(150u64); - + let mut tree = UintValueTree::new(start, false, min, max); - + assert_eq!(tree.current(), start); - + while tree.simplify() { let curr = tree.current(); - assert!(curr >= min && curr <= max, - "Simplify produced out of bounds value: {}", curr); + assert!(curr >= min && curr <= max, "Simplify produced out of bounds value: {curr}"); } - + tree = UintValueTree::new(start, false, min, max); - + while tree.complicate() { let curr = tree.current(); - assert!(curr >= min && curr <= max, - "Complicate produced out of bounds value: {}", curr); + assert!(curr >= min && curr <= max, "Complicate produced out of bounds value: {curr}"); } } @@ -324,9 +322,11 @@ mod tests { for _ in 0..1000 { let tree = strategy.generate_edge_tree(&mut runner).unwrap(); let value = tree.current(); - - assert!(value >= min && value <= max, - "Edge case {} outside bounds [{}, {}]", value, min, max); + + assert!( + value >= min && value <= max, + "Edge case {value} outside bounds [{min}, {max}]" + ); if value <= min + U256::from(3) { found_min_area = true; @@ -340,22 +340,23 @@ mod tests { assert!(found_max_area, "Never generated values near maximum"); } - #[test] fn test_fixture_generation() { let min = U256::from(100u64); let max = U256::from(1000u64); let valid_fixture = U256::from(500u64); let fixtures = vec![DynSolValue::Uint(valid_fixture, 64)]; - + let strategy = UintStrategy::new(64, Some(&fixtures), Some(min), Some(max), false); let mut runner = TestRunner::default(); for _ in 0..100 { let tree = strategy.generate_fixtures_tree(&mut runner).unwrap(); let value = tree.current(); - assert!(value >= min && value <= max, - "Fixture value {} outside bounds [{}, {}]", value, min, max); + assert!( + value >= min && value <= max, + "Fixture value {value} outside bounds [{min}, {max}]" + ); } } @@ -369,7 +370,7 @@ mod tests { for _ in 0..iterations { let tree = strategy.generate_random_tree(&mut runner).unwrap(); let value = tree.current(); - + // Find the highest set bit (log2 bucket) let mut highest_bit = 0; for i in 0..256 { @@ -386,8 +387,9 @@ mod tests { populated_buckets += 1; } } - assert!(populated_buckets > 200, - "Log-uniform sampling didn't cover enough orders of magnitude"); + assert!( + populated_buckets > 200, + "Log-uniform sampling didn't cover enough orders of magnitude" + ); } - } From f539a263de2c0e4bcea3c18a3453a7317b1effae Mon Sep 17 00:00:00 2001 From: malik Date: Tue, 22 Oct 2024 13:08:16 +0100 Subject: [PATCH 05/25] lints --- crates/evm/fuzz/src/strategies/uint.rs | 86 +++++++++++++------------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 6935942d05b7..e0857e1824c2 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -167,39 +167,39 @@ impl UintStrategy { fn generate_random_values_uniformly(&self, runner: &mut TestRunner) -> U256 { let rng = runner.rng(); - // generate random number of bits uniformly - let bits = rng.gen_range(0..=self.bits); - - // init 2 128-bit randoms - let mut higher: u128 = rng.gen_range(0..=u128::MAX); - let mut lower: u128 = rng.gen_range(0..=u128::MAX); - - // cut 2 randoms according to bits size - match bits { - x if x < 128 => { - lower &= (1u128 << x) - 1; - higher = 0; - } - x if (128..256).contains(&x) => higher &= (1u128 << (x - 128)) - 1, - _ => {} + + //Generate the bits to use + let bits = self.bits; + + // Generate lower and higher parts + let lower: u128 = rng.gen(); + let higher: u128 = rng.gen(); + + // Apply masking + let (masked_lower, masked_higher) = if bits < 128 { + (lower & ((1u128 << bits) - 1), 0) + } else if bits < 256 { + (lower, higher & ((1u128 << (bits - 128)) - 1)) + } else { + (lower, higher) }; - // init U256 from 2 randoms + //Convert to U256 let mut inner: [u64; 4] = [0; 4]; - let mask64 = (1 << 65) - 1; - inner[0] = (lower & mask64) as u64; - inner[1] = (lower >> 64) as u64; - inner[2] = (higher & mask64) as u64; - inner[3] = (higher >> 64) as u64; - - let start: U256 = U256::from_limbs(inner); - start + inner[0] = (masked_lower & ((1u128 << 64) - 1)) as u64; + inner[1] = (masked_lower >> 64) as u64; + inner[2] = (masked_higher & ((1u128 << 64) - 1)) as u64; + inner[3] = (masked_higher >> 64) as u64; + + U256::from_limbs(inner) } fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { - let start = if self.use_log_sampling { + let start = if self.max_bound <= self.min_bound { + self.min_bound + } else if self.use_log_sampling { self.generate_log_uniform(runner) - } else if self.max_bound > self.min_bound { + } else { let range = self.max_bound - self.min_bound + U256::from(1); if range == U256::ZERO { self.min_bound @@ -207,12 +207,14 @@ impl UintStrategy { let random = self.generate_random_values_uniformly(runner) % range; self.min_bound + random } - } else { - self.min_bound }; - let clamped_start = start.clamp(self.min_bound, self.max_bound); - Ok(UintValueTree::new(clamped_start, false, self.min_bound, self.max_bound)) + Ok(UintValueTree::new( + start.clamp(self.min_bound, self.max_bound), + false, + self.min_bound, + self.max_bound + )) } fn generate_log_uniform(&self, runner: &mut TestRunner) -> U256 { @@ -220,20 +222,20 @@ impl UintStrategy { return self.min_bound; } - let max_exp = 256; - let random_exp = runner.rng().gen_range(0..=max_exp); - - let mantissa = U256::from(runner.rng().gen::()); - - let mut value: U256 = (mantissa << random_exp) | (U256::from(1) << random_exp); + let rng = runner.rng(); + + let exp = rng.gen::() % 256; + let mantissa = rng.gen::(); + let base = U256::from(1) << exp; + let mut value = base | (U256::from(mantissa) & (base - U256::from(1))); + value = value.clamp(self.min_bound, self.max_bound); - - if value == self.min_bound { + + if value == self.min_bound && self.max_bound > self.min_bound { let range = self.max_bound - self.min_bound; - if range > U256::ZERO { - value += U256::from(runner.rng().gen::()) % range; - } + let offset = U256::from(rng.gen::()) % range; + value = self.min_bound + offset; } value @@ -246,7 +248,7 @@ impl Strategy for UintStrategy { fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; let bias = runner.rng().gen_range(0..total_weight); - // randomly select one of 3 strategies + // randomly select one of 3 strategies match bias { x if x < self.edge_weight => self.generate_edge_tree(runner), x if x < self.edge_weight + self.fixtures_weight => self.generate_fixtures_tree(runner), From e85a1a7d03edca5296cb033d1129e6625b71f81c Mon Sep 17 00:00:00 2001 From: malik Date: Wed, 23 Oct 2024 11:32:56 +0100 Subject: [PATCH 06/25] preserve original behaviour --- crates/evm/fuzz/src/strategies/param.rs | 2 +- crates/evm/fuzz/src/strategies/uint.rs | 108 +++++++++++++++--------- 2 files changed, 70 insertions(+), 40 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index c7e3a1e93e4f..521835355140 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -104,7 +104,7 @@ fn fuzz_param_inner( (None, None) }; - super::UintStrategy::new(n, fuzz_fixtures, min, max, false) + super::UintStrategy::new(n, fuzz_fixtures, min, max) .prop_map(move |x| DynSolValue::Uint(x, n)) .boxed() } diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index e0857e1824c2..6a43d5f796d8 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -95,12 +95,10 @@ pub struct UintStrategy { fixtures_weight: usize, /// The weight for purely random values random_weight: usize, - /// Minimum bound for generated values - min_bound: U256, - /// Maximum bound for generated values - max_bound: U256, - /// Use logarithmic sampling for large ranges - use_log_sampling: bool, + /// Optional bounds for generated values + bounds: Option<(U256, U256)>, + + } impl UintStrategy { @@ -113,12 +111,12 @@ impl UintStrategy { fixtures: Option<&[DynSolValue]>, min_bound: Option, max_bound: Option, - use_log_sampling: bool, + ) -> Self { - let type_max = if bits < 256 { (U256::from(1) << bits) - U256::from(1) } else { U256::MAX }; - - let min = min_bound.unwrap_or(U256::ZERO); - let max = max_bound.unwrap_or(type_max); + let bounds = match (min_bound, max_bound) { + (Some(min), Some(max)) => Some((min, max)), + _ => None + }; Self { bits, @@ -126,23 +124,43 @@ impl UintStrategy { edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, - min_bound: min, - max_bound: max, - use_log_sampling, + bounds } } + pub fn use_log_sampling(&self) -> bool { + self.bits > 8 + } + fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); // Choose if we want values around 0 or max let is_min = rng.gen_bool(0.5); let offset = U256::from(rng.gen_range(0..4)); - let start = if is_min { - self.min_bound.saturating_add(offset) + + let start = if let Some((min, max)) = self.bounds { + // If bounds are set, use them + if is_min { + min.saturating_add(offset) + } else { + max.saturating_sub(offset) + } } else { - self.max_bound.saturating_sub(offset) + // If no bounds, use original behavior + let type_max = if self.bits < 256 { + (U256::from(1) << self.bits) - U256::from(1) + } else { + U256::MAX + }; + if is_min { + offset + } else { + type_max.saturating_sub(offset) + } }; - Ok(UintValueTree::new(start, false, self.min_bound, self.max_bound)) + + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + Ok(UintValueTree::new(start, false, min, max)) } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -153,10 +171,11 @@ impl UintStrategy { // Generate value tree from fixture. let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); if let Some(uint_fixture) = fixture.as_uint() { if uint_fixture.1 == self.bits { - let fixture_value = uint_fixture.0.clamp(self.min_bound, self.max_bound); - return Ok(UintValueTree::new(fixture_value, false, self.min_bound, self.max_bound)); + let fixture_value = uint_fixture.0.clamp(min, max); + return Ok(UintValueTree::new(fixture_value, false, min, max)); } } @@ -195,31 +214,34 @@ impl UintStrategy { } fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { - let start = if self.max_bound <= self.min_bound { - self.min_bound - } else if self.use_log_sampling { + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + let start = if max <= min { + min + } else if Self::use_log_sampling(self) { self.generate_log_uniform(runner) } else { - let range = self.max_bound - self.min_bound + U256::from(1); + let range = max - min + U256::from(1); if range == U256::ZERO { - self.min_bound + min } else { let random = self.generate_random_values_uniformly(runner) % range; - self.min_bound + random + min + random } }; Ok(UintValueTree::new( - start.clamp(self.min_bound, self.max_bound), + start.clamp(min, max), false, - self.min_bound, - self.max_bound + min, + max )) } fn generate_log_uniform(&self, runner: &mut TestRunner) -> U256 { - if self.max_bound <= self.min_bound { - return self.min_bound; + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + + if max <= min { + return min; } let rng = runner.rng(); @@ -230,16 +252,24 @@ impl UintStrategy { let base = U256::from(1) << exp; let mut value = base | (U256::from(mantissa) & (base - U256::from(1))); - value = value.clamp(self.min_bound, self.max_bound); + value = value.clamp(min, max); - if value == self.min_bound && self.max_bound > self.min_bound { - let range = self.max_bound - self.min_bound; + if value == min && max > min { + let range = max - min; let offset = U256::from(rng.gen::()) % range; - value = self.min_bound + offset; + value = min + offset; } value } + + pub fn type_max(&self) -> U256 { + if self.bits < 256 { + (U256::from(1) << self.bits) - U256::from(1) + } else { + U256::MAX + } + } } impl Strategy for UintStrategy { @@ -279,7 +309,7 @@ mod tests { fn test_uint_strategy_respects_bounds() { let min = U256::from(1000u64); let max = U256::from(2000u64); - let strategy = UintStrategy::new(16, None, Some(min), Some(max), false); + let strategy = UintStrategy::new(16, None, Some(min), Some(max)); let mut runner = TestRunner::default(); for _ in 0..1000 { @@ -315,7 +345,7 @@ mod tests { fn test_edge_case_generation() { let min = U256::from(100u64); let max = U256::from(1000u64); - let strategy = UintStrategy::new(64, None, Some(min), Some(max), false); + let strategy = UintStrategy::new(64, None, Some(min), Some(max)); let mut runner = TestRunner::default(); let mut found_min_area = false; @@ -349,7 +379,7 @@ mod tests { let valid_fixture = U256::from(500u64); let fixtures = vec![DynSolValue::Uint(valid_fixture, 64)]; - let strategy = UintStrategy::new(64, Some(&fixtures), Some(min), Some(max), false); + let strategy = UintStrategy::new(64, Some(&fixtures), Some(min), Some(max)); let mut runner = TestRunner::default(); for _ in 0..100 { @@ -364,7 +394,7 @@ mod tests { #[test] fn test_log_uniform_sampling() { - let strategy = UintStrategy::new(256, None, None, None, true); + let strategy = UintStrategy::new(256, None, None, None); let mut runner = TestRunner::default(); let mut log2_buckets = vec![0; 256]; let iterations = 100000; From 8ae626625563e1633c645e927ffdd39a23b84382 Mon Sep 17 00:00:00 2001 From: malik Date: Wed, 23 Oct 2024 14:03:02 +0100 Subject: [PATCH 07/25] preserve original behaviour --- crates/evm/fuzz/src/strategies/param.rs | 2 +- crates/evm/fuzz/src/strategies/uint.rs | 31 ++++++++++++++----------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 521835355140..7843e17b0a3e 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -104,7 +104,7 @@ fn fuzz_param_inner( (None, None) }; - super::UintStrategy::new(n, fuzz_fixtures, min, max) + super::UintStrategy::new(n, fuzz_fixtures, None) .prop_map(move |x| DynSolValue::Uint(x, n)) .boxed() } diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 6a43d5f796d8..dbf41cf86906 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -102,32 +102,35 @@ pub struct UintStrategy { } impl UintStrategy { - /// Create a new strategy. + /// Create a new strategy without bounds. /// #Arguments /// * `bits` - Size of uint in bits /// * `fixtures` - A set of fixed values to be generated (according to fixtures weight) pub fn new( bits: usize, fixtures: Option<&[DynSolValue]>, - min_bound: Option, - max_bound: Option, - + bounds: Option<(U256, U256)>, ) -> Self { - let bounds = match (min_bound, max_bound) { - (Some(min), Some(max)) => Some((min, max)), - _ => None - }; - Self { bits, fixtures: Vec::from(fixtures.unwrap_or_default()), edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, - bounds + bounds: None } } + + /// Add bounds to an existing strategy + pub fn with_bounds(self, min_bound: Option, max_bound: Option) -> Self { + let bounds = match (min_bound, max_bound) { + (Some(min), Some(max)) => Some((min, max)), + _ => None + }; + Self { bounds, ..self } + } + pub fn use_log_sampling(&self) -> bool { self.bits > 8 } @@ -309,7 +312,7 @@ mod tests { fn test_uint_strategy_respects_bounds() { let min = U256::from(1000u64); let max = U256::from(2000u64); - let strategy = UintStrategy::new(16, None, Some(min), Some(max)); + let strategy = UintStrategy::new(16, None, Some((min, max))); let mut runner = TestRunner::default(); for _ in 0..1000 { @@ -345,7 +348,7 @@ mod tests { fn test_edge_case_generation() { let min = U256::from(100u64); let max = U256::from(1000u64); - let strategy = UintStrategy::new(64, None, Some(min), Some(max)); + let strategy = UintStrategy::new(64, None, Some((min, max))); let mut runner = TestRunner::default(); let mut found_min_area = false; @@ -379,7 +382,7 @@ mod tests { let valid_fixture = U256::from(500u64); let fixtures = vec![DynSolValue::Uint(valid_fixture, 64)]; - let strategy = UintStrategy::new(64, Some(&fixtures), Some(min), Some(max)); + let strategy = UintStrategy::new(64, Some(&fixtures), Some((min, max))); let mut runner = TestRunner::default(); for _ in 0..100 { @@ -394,7 +397,7 @@ mod tests { #[test] fn test_log_uniform_sampling() { - let strategy = UintStrategy::new(256, None, None, None); + let strategy = UintStrategy::new(256, None, None); let mut runner = TestRunner::default(); let mut log2_buckets = vec![0; 256]; let iterations = 100000; From 34da162dbda8af4a73339130dfc886dc09cb9638 Mon Sep 17 00:00:00 2001 From: malik Date: Wed, 23 Oct 2024 14:15:36 +0100 Subject: [PATCH 08/25] preserve original behaviour --- crates/evm/fuzz/src/strategies/param.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 7843e17b0a3e..4b7d5faf1214 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -104,7 +104,8 @@ fn fuzz_param_inner( (None, None) }; - super::UintStrategy::new(n, fuzz_fixtures, None) + let strategy = super::UintStrategy::new(n, fuzz_fixtures, None); + strategy.with_bounds(Some(min.unwrap()), Some(max.unwrap())) .prop_map(move |x| DynSolValue::Uint(x, n)) .boxed() } From 65fe4075c542f3343ba4697043d3aa17168cda45 Mon Sep 17 00:00:00 2001 From: malik Date: Wed, 23 Oct 2024 15:19:08 +0100 Subject: [PATCH 09/25] make bounds optional --- crates/evm/fuzz/src/strategies/param.rs | 5 +- crates/evm/fuzz/src/strategies/uint.rs | 152 ++++++++++++++---------- 2 files changed, 90 insertions(+), 67 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 4b7d5faf1214..4355f2d7d292 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -104,8 +104,9 @@ fn fuzz_param_inner( (None, None) }; - let strategy = super::UintStrategy::new(n, fuzz_fixtures, None); - strategy.with_bounds(Some(min.unwrap()), Some(max.unwrap())) + let strategy = super::UintStrategy::new(n, fuzz_fixtures, None); + strategy + .with_bounds(Some(min.unwrap()), Some(max.unwrap())) .prop_map(move |x| DynSolValue::Uint(x, n)) .boxed() } diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index dbf41cf86906..c0f6cfaf35d2 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -16,10 +16,10 @@ pub struct UintValueTree { hi: U256, /// If true cannot be simplified or complexified fixed: bool, - /// Min Value - min_bound: U256, - /// Max Value - max_bound: U256, + ///Optional Min Value + min_bound: Option, + ///Optional Max Value + max_bound: Option, } impl UintValueTree { @@ -27,7 +27,7 @@ impl UintValueTree { /// # Arguments /// * `start` - Starting value for the tree /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. - fn new(start: U256, fixed: bool, min_bound: U256, max_bound: U256) -> Self { + fn new(start: U256, fixed: bool, min_bound: Option, max_bound: Option) -> Self { Self { lo: U256::ZERO, curr: start, hi: start, fixed, min_bound, max_bound } } @@ -48,7 +48,12 @@ impl ValueTree for UintValueTree { type Value = U256; fn current(&self) -> Self::Value { - self.curr.clamp(self.min_bound, self.max_bound) + match (self.min_bound, self.max_bound) { + (Some(min), Some(max)) => self.curr.clamp(min, max), + (Some(min), None) => self.curr.max(min), + (None, Some(max)) => self.curr.min(max), + (None, None) => self.curr, + } } fn simplify(&mut self) -> bool { @@ -97,8 +102,6 @@ pub struct UintStrategy { random_weight: usize, /// Optional bounds for generated values bounds: Option<(U256, U256)>, - - } impl UintStrategy { @@ -109,7 +112,7 @@ impl UintStrategy { pub fn new( bits: usize, fixtures: Option<&[DynSolValue]>, - bounds: Option<(U256, U256)>, + bounds: Option<(U256, U256)>, ) -> Self { Self { bits, @@ -117,16 +120,15 @@ impl UintStrategy { edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, - bounds: None + bounds: None, } } - /// Add bounds to an existing strategy pub fn with_bounds(self, min_bound: Option, max_bound: Option) -> Self { let bounds = match (min_bound, max_bound) { (Some(min), Some(max)) => Some((min, max)), - _ => None + _ => None, }; Self { bounds, ..self } } @@ -134,22 +136,21 @@ impl UintStrategy { pub fn use_log_sampling(&self) -> bool { self.bits > 8 } - + fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); - // Choose if we want values around 0 or max let is_min = rng.gen_bool(0.5); let offset = U256::from(rng.gen_range(0..4)); - + let start = if let Some((min, max)) = self.bounds { - // If bounds are set, use them + // If bounds are set,we use them if is_min { min.saturating_add(offset) } else { max.saturating_sub(offset) } } else { - // If no bounds, use original behavior + // If no bounds, we use original behavior let type_max = if self.bits < 256 { (U256::from(1) << self.bits) - U256::from(1) } else { @@ -161,9 +162,13 @@ impl UintStrategy { type_max.saturating_sub(offset) } }; - - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - Ok(UintValueTree::new(start, false, min, max)) + + Ok(UintValueTree::new( + start, + false, + self.bounds.map(|(min, _)| min), + self.bounds.map(|(_, max)| max), + )) } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -174,11 +179,20 @@ impl UintStrategy { // Generate value tree from fixture. let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + if let Some(uint_fixture) = fixture.as_uint() { if uint_fixture.1 == self.bits { - let fixture_value = uint_fixture.0.clamp(min, max); - return Ok(UintValueTree::new(fixture_value, false, min, max)); + let fixture_value = match self.bounds { + Some((min, max)) => uint_fixture.0.clamp(min, max), + None => uint_fixture.0, + }; + + return Ok(UintValueTree::new( + fixture_value, + false, + self.bounds.map(|(min, _)| min), + self.bounds.map(|(_, max)| max), + )); } } @@ -189,11 +203,11 @@ impl UintStrategy { fn generate_random_values_uniformly(&self, runner: &mut TestRunner) -> U256 { let rng = runner.rng(); - + //Generate the bits to use - let bits = self.bits; - - // Generate lower and higher parts + let bits = self.bits; + + // Generate lower and higher parts let lower: u128 = rng.gen(); let higher: u128 = rng.gen(); @@ -217,61 +231,69 @@ impl UintStrategy { } fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - let start = if max <= min { - min - } else if Self::use_log_sampling(self) { - self.generate_log_uniform(runner) - } else { - let range = max - min + U256::from(1); - if range == U256::ZERO { - min - } else { - let random = self.generate_random_values_uniformly(runner) % range; - min + random + let start = match self.bounds { + Some((min, max)) => { + if max <= min { + min + } else if Self::use_log_sampling(self) { + self.generate_log_uniform(runner) + } else { + let range = max - min + U256::from(1); + if range == U256::ZERO { + min + } else { + let random = self.generate_random_values_uniformly(runner) % range; + min + random + } + } + } + None => { + if Self::use_log_sampling(self) { + self.generate_log_uniform(runner) + } else { + // When no bounds are specified, generate within type bounds + let type_max = self.type_max(); + self.generate_random_values_uniformly(runner) % (type_max + U256::from(1)) + } } }; Ok(UintValueTree::new( - start.clamp(min, max), + start, false, - min, - max + self.bounds.map(|(min, _)| min), + self.bounds.map(|(_, max)| max), )) } fn generate_log_uniform(&self, runner: &mut TestRunner) -> U256 { - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - - if max <= min { - return min; - } - let rng = runner.rng(); - let exp = rng.gen::() % 256; let mantissa = rng.gen::(); let base = U256::from(1) << exp; let mut value = base | (U256::from(mantissa) & (base - U256::from(1))); - - value = value.clamp(min, max); - - if value == min && max > min { - let range = max - min; - let offset = U256::from(rng.gen::()) % range; - value = min + offset; + + // Only clamp if bounds are specified + if let Some((min, max)) = self.bounds { + value = value.clamp(min, max); + + if value == min && max > min { + let range = max - min; + let offset = U256::from(rng.gen::()) % range; + value = min + offset; + } } value } pub fn type_max(&self) -> U256 { - if self.bits < 256 { - (U256::from(1) << self.bits) - U256::from(1) - } else { - U256::MAX - } + if self.bits < 256 { + (U256::from(1) << self.bits) - U256::from(1) + } else { + U256::MAX + } } } @@ -281,7 +303,7 @@ impl Strategy for UintStrategy { fn new_tree(&self, runner: &mut TestRunner) -> NewTree { let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight; let bias = runner.rng().gen_range(0..total_weight); - // randomly select one of 3 strategies + // randomly select one of 3 strategies match bias { x if x < self.edge_weight => self.generate_edge_tree(runner), x if x < self.edge_weight + self.fixtures_weight => self.generate_fixtures_tree(runner), @@ -301,7 +323,7 @@ mod tests { #[test] fn test_uint_tree_complicate_max() { - let mut uint_tree = UintValueTree::new(U256::MAX, false, U256::MAX, U256::MIN); + let mut uint_tree = UintValueTree::new(U256::MAX, false, Some(U256::MAX), Some(U256::MIN)); assert_eq!(uint_tree.hi, U256::MAX); assert_eq!(uint_tree.curr, U256::MAX); uint_tree.complicate(); @@ -327,7 +349,7 @@ mod tests { let max = U256::from(200u64); let start = U256::from(150u64); - let mut tree = UintValueTree::new(start, false, min, max); + let mut tree = UintValueTree::new(start, false, Some(min), Some(max)); assert_eq!(tree.current(), start); @@ -336,7 +358,7 @@ mod tests { assert!(curr >= min && curr <= max, "Simplify produced out of bounds value: {curr}"); } - tree = UintValueTree::new(start, false, min, max); + tree = UintValueTree::new(start, false, Some(min), Some(max)); while tree.complicate() { let curr = tree.current(); From fdb585f2245fcc38648f3474e8f2cb4f9094243b Mon Sep 17 00:00:00 2001 From: malik Date: Wed, 23 Oct 2024 15:34:22 +0100 Subject: [PATCH 10/25] Revert "make bounds optional" This reverts commit 0853feae5dd6b6836d20e9acac30bc5d56148c37. --- crates/evm/fuzz/src/strategies/uint.rs | 114 +++++++++---------------- 1 file changed, 42 insertions(+), 72 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index c0f6cfaf35d2..9e8afd3e7c12 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -16,10 +16,10 @@ pub struct UintValueTree { hi: U256, /// If true cannot be simplified or complexified fixed: bool, - ///Optional Min Value - min_bound: Option, - ///Optional Max Value - max_bound: Option, + /// Min Value + min_bound: U256, + /// Max Value + max_bound: U256, } impl UintValueTree { @@ -27,7 +27,7 @@ impl UintValueTree { /// # Arguments /// * `start` - Starting value for the tree /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. - fn new(start: U256, fixed: bool, min_bound: Option, max_bound: Option) -> Self { + fn new(start: U256, fixed: bool, min_bound: U256, max_bound: U256) -> Self { Self { lo: U256::ZERO, curr: start, hi: start, fixed, min_bound, max_bound } } @@ -48,12 +48,7 @@ impl ValueTree for UintValueTree { type Value = U256; fn current(&self) -> Self::Value { - match (self.min_bound, self.max_bound) { - (Some(min), Some(max)) => self.curr.clamp(min, max), - (Some(min), None) => self.curr.max(min), - (None, Some(max)) => self.curr.min(max), - (None, None) => self.curr, - } + self.curr.clamp(self.min_bound, self.max_bound) } fn simplify(&mut self) -> bool { @@ -139,18 +134,19 @@ impl UintStrategy { fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); + // Choose if we want values around 0 or max let is_min = rng.gen_bool(0.5); let offset = U256::from(rng.gen_range(0..4)); let start = if let Some((min, max)) = self.bounds { - // If bounds are set,we use them + // If bounds are set, use them if is_min { min.saturating_add(offset) } else { max.saturating_sub(offset) } } else { - // If no bounds, we use original behavior + // If no bounds, use original behavior let type_max = if self.bits < 256 { (U256::from(1) << self.bits) - U256::from(1) } else { @@ -163,12 +159,8 @@ impl UintStrategy { } }; - Ok(UintValueTree::new( - start, - false, - self.bounds.map(|(min, _)| min), - self.bounds.map(|(_, max)| max), - )) + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + Ok(UintValueTree::new(start, false, min, max)) } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -179,20 +171,11 @@ impl UintStrategy { // Generate value tree from fixture. let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; - + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); if let Some(uint_fixture) = fixture.as_uint() { if uint_fixture.1 == self.bits { - let fixture_value = match self.bounds { - Some((min, max)) => uint_fixture.0.clamp(min, max), - None => uint_fixture.0, - }; - - return Ok(UintValueTree::new( - fixture_value, - false, - self.bounds.map(|(min, _)| min), - self.bounds.map(|(_, max)| max), - )); + let fixture_value = uint_fixture.0.clamp(min, max); + return Ok(UintValueTree::new(fixture_value, false, min, max)); } } @@ -231,58 +214,45 @@ impl UintStrategy { } fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { - let start = match self.bounds { - Some((min, max)) => { - if max <= min { - min - } else if Self::use_log_sampling(self) { - self.generate_log_uniform(runner) - } else { - let range = max - min + U256::from(1); - if range == U256::ZERO { - min - } else { - let random = self.generate_random_values_uniformly(runner) % range; - min + random - } - } - } - None => { - if Self::use_log_sampling(self) { - self.generate_log_uniform(runner) - } else { - // When no bounds are specified, generate within type bounds - let type_max = self.type_max(); - self.generate_random_values_uniformly(runner) % (type_max + U256::from(1)) - } + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + let start = if max <= min { + min + } else if Self::use_log_sampling(self) { + self.generate_log_uniform(runner) + } else { + let range = max - min + U256::from(1); + if range == U256::ZERO { + min + } else { + let random = self.generate_random_values_uniformly(runner) % range; + min + random } }; - Ok(UintValueTree::new( - start, - false, - self.bounds.map(|(min, _)| min), - self.bounds.map(|(_, max)| max), - )) + Ok(UintValueTree::new(start.clamp(min, max), false, min, max)) } fn generate_log_uniform(&self, runner: &mut TestRunner) -> U256 { + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + + if max <= min { + return min; + } + let rng = runner.rng(); + let exp = rng.gen::() % 256; let mantissa = rng.gen::(); let base = U256::from(1) << exp; let mut value = base | (U256::from(mantissa) & (base - U256::from(1))); - // Only clamp if bounds are specified - if let Some((min, max)) = self.bounds { - value = value.clamp(min, max); + value = value.clamp(min, max); - if value == min && max > min { - let range = max - min; - let offset = U256::from(rng.gen::()) % range; - value = min + offset; - } + if value == min && max > min { + let range = max - min; + let offset = U256::from(rng.gen::()) % range; + value = min + offset; } value @@ -323,7 +293,7 @@ mod tests { #[test] fn test_uint_tree_complicate_max() { - let mut uint_tree = UintValueTree::new(U256::MAX, false, Some(U256::MAX), Some(U256::MIN)); + let mut uint_tree = UintValueTree::new(U256::MAX, false, U256::MAX, U256::MIN); assert_eq!(uint_tree.hi, U256::MAX); assert_eq!(uint_tree.curr, U256::MAX); uint_tree.complicate(); @@ -349,7 +319,7 @@ mod tests { let max = U256::from(200u64); let start = U256::from(150u64); - let mut tree = UintValueTree::new(start, false, Some(min), Some(max)); + let mut tree = UintValueTree::new(start, false, min, max); assert_eq!(tree.current(), start); @@ -358,7 +328,7 @@ mod tests { assert!(curr >= min && curr <= max, "Simplify produced out of bounds value: {curr}"); } - tree = UintValueTree::new(start, false, Some(min), Some(max)); + tree = UintValueTree::new(start, false, min, max); while tree.complicate() { let curr = tree.current(); From f70b2fe52ee21f329449974eb4201a6c960f809f Mon Sep 17 00:00:00 2001 From: malik Date: Wed, 23 Oct 2024 16:43:53 +0100 Subject: [PATCH 11/25] make bounds optional --- crates/evm/fuzz/src/strategies/uint.rs | 114 ++++++++++++++++--------- 1 file changed, 72 insertions(+), 42 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 9e8afd3e7c12..c0f6cfaf35d2 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -16,10 +16,10 @@ pub struct UintValueTree { hi: U256, /// If true cannot be simplified or complexified fixed: bool, - /// Min Value - min_bound: U256, - /// Max Value - max_bound: U256, + ///Optional Min Value + min_bound: Option, + ///Optional Max Value + max_bound: Option, } impl UintValueTree { @@ -27,7 +27,7 @@ impl UintValueTree { /// # Arguments /// * `start` - Starting value for the tree /// * `fixed` - If `true` the tree would only contain one element and won't be simplified. - fn new(start: U256, fixed: bool, min_bound: U256, max_bound: U256) -> Self { + fn new(start: U256, fixed: bool, min_bound: Option, max_bound: Option) -> Self { Self { lo: U256::ZERO, curr: start, hi: start, fixed, min_bound, max_bound } } @@ -48,7 +48,12 @@ impl ValueTree for UintValueTree { type Value = U256; fn current(&self) -> Self::Value { - self.curr.clamp(self.min_bound, self.max_bound) + match (self.min_bound, self.max_bound) { + (Some(min), Some(max)) => self.curr.clamp(min, max), + (Some(min), None) => self.curr.max(min), + (None, Some(max)) => self.curr.min(max), + (None, None) => self.curr, + } } fn simplify(&mut self) -> bool { @@ -134,19 +139,18 @@ impl UintStrategy { fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { let rng = runner.rng(); - // Choose if we want values around 0 or max let is_min = rng.gen_bool(0.5); let offset = U256::from(rng.gen_range(0..4)); let start = if let Some((min, max)) = self.bounds { - // If bounds are set, use them + // If bounds are set,we use them if is_min { min.saturating_add(offset) } else { max.saturating_sub(offset) } } else { - // If no bounds, use original behavior + // If no bounds, we use original behavior let type_max = if self.bits < 256 { (U256::from(1) << self.bits) - U256::from(1) } else { @@ -159,8 +163,12 @@ impl UintStrategy { } }; - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - Ok(UintValueTree::new(start, false, min, max)) + Ok(UintValueTree::new( + start, + false, + self.bounds.map(|(min, _)| min), + self.bounds.map(|(_, max)| max), + )) } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -171,11 +179,20 @@ impl UintStrategy { // Generate value tree from fixture. let fixture = &self.fixtures[runner.rng().gen_range(0..self.fixtures.len())]; - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + if let Some(uint_fixture) = fixture.as_uint() { if uint_fixture.1 == self.bits { - let fixture_value = uint_fixture.0.clamp(min, max); - return Ok(UintValueTree::new(fixture_value, false, min, max)); + let fixture_value = match self.bounds { + Some((min, max)) => uint_fixture.0.clamp(min, max), + None => uint_fixture.0, + }; + + return Ok(UintValueTree::new( + fixture_value, + false, + self.bounds.map(|(min, _)| min), + self.bounds.map(|(_, max)| max), + )); } } @@ -214,45 +231,58 @@ impl UintStrategy { } fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree { - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - let start = if max <= min { - min - } else if Self::use_log_sampling(self) { - self.generate_log_uniform(runner) - } else { - let range = max - min + U256::from(1); - if range == U256::ZERO { - min - } else { - let random = self.generate_random_values_uniformly(runner) % range; - min + random + let start = match self.bounds { + Some((min, max)) => { + if max <= min { + min + } else if Self::use_log_sampling(self) { + self.generate_log_uniform(runner) + } else { + let range = max - min + U256::from(1); + if range == U256::ZERO { + min + } else { + let random = self.generate_random_values_uniformly(runner) % range; + min + random + } + } + } + None => { + if Self::use_log_sampling(self) { + self.generate_log_uniform(runner) + } else { + // When no bounds are specified, generate within type bounds + let type_max = self.type_max(); + self.generate_random_values_uniformly(runner) % (type_max + U256::from(1)) + } } }; - Ok(UintValueTree::new(start.clamp(min, max), false, min, max)) + Ok(UintValueTree::new( + start, + false, + self.bounds.map(|(min, _)| min), + self.bounds.map(|(_, max)| max), + )) } fn generate_log_uniform(&self, runner: &mut TestRunner) -> U256 { - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - - if max <= min { - return min; - } - let rng = runner.rng(); - let exp = rng.gen::() % 256; let mantissa = rng.gen::(); let base = U256::from(1) << exp; let mut value = base | (U256::from(mantissa) & (base - U256::from(1))); - value = value.clamp(min, max); + // Only clamp if bounds are specified + if let Some((min, max)) = self.bounds { + value = value.clamp(min, max); - if value == min && max > min { - let range = max - min; - let offset = U256::from(rng.gen::()) % range; - value = min + offset; + if value == min && max > min { + let range = max - min; + let offset = U256::from(rng.gen::()) % range; + value = min + offset; + } } value @@ -293,7 +323,7 @@ mod tests { #[test] fn test_uint_tree_complicate_max() { - let mut uint_tree = UintValueTree::new(U256::MAX, false, U256::MAX, U256::MIN); + let mut uint_tree = UintValueTree::new(U256::MAX, false, Some(U256::MAX), Some(U256::MIN)); assert_eq!(uint_tree.hi, U256::MAX); assert_eq!(uint_tree.curr, U256::MAX); uint_tree.complicate(); @@ -319,7 +349,7 @@ mod tests { let max = U256::from(200u64); let start = U256::from(150u64); - let mut tree = UintValueTree::new(start, false, min, max); + let mut tree = UintValueTree::new(start, false, Some(min), Some(max)); assert_eq!(tree.current(), start); @@ -328,7 +358,7 @@ mod tests { assert!(curr >= min && curr <= max, "Simplify produced out of bounds value: {curr}"); } - tree = UintValueTree::new(start, false, min, max); + tree = UintValueTree::new(start, false, Some(min), Some(max)); while tree.complicate() { let curr = tree.current(); From 0cf14ca49f5f6d73e8458e807f2a07dfab7c6abf Mon Sep 17 00:00:00 2001 From: malik Date: Wed, 23 Oct 2024 14:32:23 +0100 Subject: [PATCH 12/25] revert --- crates/evm/fuzz/src/strategies/param.rs | 4 +- crates/evm/fuzz/src/strategies/uint.rs | 57 +++++++++---------------- 2 files changed, 22 insertions(+), 39 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 4355f2d7d292..521835355140 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -104,9 +104,7 @@ fn fuzz_param_inner( (None, None) }; - let strategy = super::UintStrategy::new(n, fuzz_fixtures, None); - strategy - .with_bounds(Some(min.unwrap()), Some(max.unwrap())) + super::UintStrategy::new(n, fuzz_fixtures, min, max) .prop_map(move |x| DynSolValue::Uint(x, n)) .boxed() } diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index c0f6cfaf35d2..d0f012f8bf85 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -105,34 +105,31 @@ pub struct UintStrategy { } impl UintStrategy { - /// Create a new strategy without bounds. + /// Create a new strategy. /// #Arguments /// * `bits` - Size of uint in bits /// * `fixtures` - A set of fixed values to be generated (according to fixtures weight) pub fn new( bits: usize, fixtures: Option<&[DynSolValue]>, - bounds: Option<(U256, U256)>, + min_bound: Option, + max_bound: Option, ) -> Self { + let bounds = match (min_bound, max_bound) { + (Some(min), Some(max)) => Some((min, max)), + _ => None, + }; + Self { bits, fixtures: Vec::from(fixtures.unwrap_or_default()), edge_weight: 10usize, fixtures_weight: 40usize, random_weight: 50usize, - bounds: None, + bounds, } } - /// Add bounds to an existing strategy - pub fn with_bounds(self, min_bound: Option, max_bound: Option) -> Self { - let bounds = match (min_bound, max_bound) { - (Some(min), Some(max)) => Some((min, max)), - _ => None, - }; - Self { bounds, ..self } - } - pub fn use_log_sampling(&self) -> bool { self.bits > 8 } @@ -163,12 +160,8 @@ impl UintStrategy { } }; - Ok(UintValueTree::new( - start, - false, - self.bounds.map(|(min, _)| min), - self.bounds.map(|(_, max)| max), - )) + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + Ok(UintValueTree::new(start, false, min, max)) } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -258,12 +251,7 @@ impl UintStrategy { } }; - Ok(UintValueTree::new( - start, - false, - self.bounds.map(|(min, _)| min), - self.bounds.map(|(_, max)| max), - )) + Ok(UintValueTree::new(start.clamp(min, max), false, min, max)) } fn generate_log_uniform(&self, runner: &mut TestRunner) -> U256 { @@ -274,15 +262,12 @@ impl UintStrategy { let base = U256::from(1) << exp; let mut value = base | (U256::from(mantissa) & (base - U256::from(1))); - // Only clamp if bounds are specified - if let Some((min, max)) = self.bounds { - value = value.clamp(min, max); + value = value.clamp(min, max); - if value == min && max > min { - let range = max - min; - let offset = U256::from(rng.gen::()) % range; - value = min + offset; - } + if value == min && max > min { + let range = max - min; + let offset = U256::from(rng.gen::()) % range; + value = min + offset; } value @@ -334,7 +319,7 @@ mod tests { fn test_uint_strategy_respects_bounds() { let min = U256::from(1000u64); let max = U256::from(2000u64); - let strategy = UintStrategy::new(16, None, Some((min, max))); + let strategy = UintStrategy::new(16, None, Some(min), Some(max)); let mut runner = TestRunner::default(); for _ in 0..1000 { @@ -370,7 +355,7 @@ mod tests { fn test_edge_case_generation() { let min = U256::from(100u64); let max = U256::from(1000u64); - let strategy = UintStrategy::new(64, None, Some((min, max))); + let strategy = UintStrategy::new(64, None, Some(min), Some(max)); let mut runner = TestRunner::default(); let mut found_min_area = false; @@ -404,7 +389,7 @@ mod tests { let valid_fixture = U256::from(500u64); let fixtures = vec![DynSolValue::Uint(valid_fixture, 64)]; - let strategy = UintStrategy::new(64, Some(&fixtures), Some((min, max))); + let strategy = UintStrategy::new(64, Some(&fixtures), Some(min), Some(max)); let mut runner = TestRunner::default(); for _ in 0..100 { @@ -419,7 +404,7 @@ mod tests { #[test] fn test_log_uniform_sampling() { - let strategy = UintStrategy::new(256, None, None); + let strategy = UintStrategy::new(256, None, None, None); let mut runner = TestRunner::default(); let mut log2_buckets = vec![0; 256]; let iterations = 100000; From 840daf119469edda4cf23be8c7c927c5ad7ff369 Mon Sep 17 00:00:00 2001 From: malik Date: Wed, 23 Oct 2024 18:33:09 +0100 Subject: [PATCH 13/25] fixes --- crates/evm/fuzz/src/strategies/uint.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index d0f012f8bf85..e0e0dce76a67 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -161,7 +161,7 @@ impl UintStrategy { }; let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - Ok(UintValueTree::new(start, false, min, max)) + Ok(UintValueTree::new(start, false, Some(min), Some(max))) } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -251,7 +251,9 @@ impl UintStrategy { } }; - Ok(UintValueTree::new(start.clamp(min, max), false, min, max)) + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + + Ok(UintValueTree::new(start.clamp(min, max), false, Some(min), Some(max))) } fn generate_log_uniform(&self, runner: &mut TestRunner) -> U256 { @@ -262,6 +264,8 @@ impl UintStrategy { let base = U256::from(1) << exp; let mut value = base | (U256::from(mantissa) & (base - U256::from(1))); + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + value = value.clamp(min, max); if value == min && max > min { From 69360f16651c8b02816b1d70a0aeb7cc23b635ba Mon Sep 17 00:00:00 2001 From: malik Date: Wed, 23 Oct 2024 19:28:08 +0100 Subject: [PATCH 14/25] fixes --- crates/evm/fuzz/src/strategies/uint.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index e0e0dce76a67..a69b42edba4e 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -147,21 +147,25 @@ impl UintStrategy { max.saturating_sub(offset) } } else { - // If no bounds, we use original behavior - let type_max = if self.bits < 256 { - (U256::from(1) << self.bits) - U256::from(1) - } else { - U256::MAX - }; + let type_max = self.type_max(); if is_min { offset } else { - type_max.saturating_sub(offset) + if offset == U256::ZERO { + type_max + } else { + type_max.saturating_sub(offset) + } } }; - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - Ok(UintValueTree::new(start, false, Some(min), Some(max))) + let (_min, _max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + Ok(UintValueTree::new( + start, + false, + self.bounds.map(|(min, _)| min), + self.bounds.map(|(_, max)| max), + )) } fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree { From 127a8b97bb8d281db0a50f02a813b3d178a66004 Mon Sep 17 00:00:00 2001 From: malik Date: Wed, 23 Oct 2024 19:55:30 +0100 Subject: [PATCH 15/25] fixes --- crates/evm/fuzz/src/strategies/uint.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index a69b42edba4e..08f1e1e18eaa 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -163,8 +163,8 @@ impl UintStrategy { Ok(UintValueTree::new( start, false, - self.bounds.map(|(min, _)| min), - self.bounds.map(|(_, max)| max), + None, + None )) } From 898c4114ee74e8e2502949afd80e7e04bb144fa5 Mon Sep 17 00:00:00 2001 From: malik Date: Fri, 25 Oct 2024 06:53:09 +0100 Subject: [PATCH 16/25] natives --- crates/evm/fuzz/src/strategies/uint.rs | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 08f1e1e18eaa..6626aacc5c7f 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -115,9 +115,13 @@ impl UintStrategy { min_bound: Option, max_bound: Option, ) -> Self { + let type_max = if bits < 256 { (U256::from(1) << bits) - U256::from(1) } else { U256::MAX }; + let bounds = match (min_bound, max_bound) { - (Some(min), Some(max)) => Some((min, max)), - _ => None, + (Some(min), Some(max)) if min <= max => Some((min, max)), // Valid min-max bounds + (Some(min), None) => Some((min, type_max)), // Only min provided + (None, Some(max)) => Some((U256::ZERO, max)), // Only max provided + _ => Some((U256::ZERO, type_max)), // Default bounds }; Self { @@ -150,12 +154,10 @@ impl UintStrategy { let type_max = self.type_max(); if is_min { offset + } else if offset == U256::ZERO { + type_max } else { - if offset == U256::ZERO { - type_max - } else { - type_max.saturating_sub(offset) - } + type_max.saturating_sub(offset) } }; @@ -163,8 +165,8 @@ impl UintStrategy { Ok(UintValueTree::new( start, false, - None, - None + self.bounds.map(|(min, _)| min), + self.bounds.map(|(_, max)| max), )) } From e7cb2052b091bd3ad922bd5e3c3b5707bef4ddc5 Mon Sep 17 00:00:00 2001 From: malik Date: Fri, 25 Oct 2024 07:22:33 +0100 Subject: [PATCH 17/25] natives --- crates/evm/fuzz/src/strategies/uint.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 6626aacc5c7f..6370be4ac4d3 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -118,10 +118,10 @@ impl UintStrategy { let type_max = if bits < 256 { (U256::from(1) << bits) - U256::from(1) } else { U256::MAX }; let bounds = match (min_bound, max_bound) { - (Some(min), Some(max)) if min <= max => Some((min, max)), // Valid min-max bounds - (Some(min), None) => Some((min, type_max)), // Only min provided - (None, Some(max)) => Some((U256::ZERO, max)), // Only max provided - _ => Some((U256::ZERO, type_max)), // Default bounds + (Some(min), Some(max)) if min <= max => Some((min, max)), + (Some(min), None) => Some((min, type_max)), + (None, Some(max)) => Some((U256::ZERO, max)), + _ => Some((U256::ZERO, type_max)), }; Self { From 0017471480ba1a3db86b7e08e9c5150e8711ef24 Mon Sep 17 00:00:00 2001 From: malik Date: Fri, 25 Oct 2024 07:32:59 +0100 Subject: [PATCH 18/25] natives --- crates/evm/fuzz/src/strategies/uint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 6370be4ac4d3..00dce1792838 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -121,7 +121,7 @@ impl UintStrategy { (Some(min), Some(max)) if min <= max => Some((min, max)), (Some(min), None) => Some((min, type_max)), (None, Some(max)) => Some((U256::ZERO, max)), - _ => Some((U256::ZERO, type_max)), + _ => None, }; Self { From 74844f1ebd306320f26c70a36b88f3edda7835a1 Mon Sep 17 00:00:00 2001 From: malik Date: Fri, 25 Oct 2024 07:40:27 +0100 Subject: [PATCH 19/25] natives --- crates/evm/fuzz/src/strategies/param.rs | 24 +++++++++++------------- crates/evm/fuzz/src/strategies/uint.rs | 8 ++++---- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/param.rs b/crates/evm/fuzz/src/strategies/param.rs index 521835355140..05ef3dc67dfe 100644 --- a/crates/evm/fuzz/src/strategies/param.rs +++ b/crates/evm/fuzz/src/strategies/param.rs @@ -94,19 +94,17 @@ fn fuzz_param_inner( .prop_map(move |x| DynSolValue::Int(x, n)) .boxed(), DynSolType::Uint(n @ 8..=256) => { - 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) - .prop_map(move |x| DynSolValue::Uint(x, n)) - .boxed() + let bounds = param_name.and_then(|name| config.ranges.get(name)); + match bounds { + Some((min, max)) => { + super::UintStrategy::new(n, fuzz_fixtures, Some(*min), Some(*max)) + .prop_map(move |x| DynSolValue::Uint(x, n)) + .boxed() + } + None => super::UintStrategy::new(n, fuzz_fixtures, None, None) + .prop_map(move |x| DynSolValue::Uint(x, n)) + .boxed(), + } } DynSolType::Function | DynSolType::Bool => DynSolValue::type_strategy(param).boxed(), DynSolType::Bytes => value(), diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 00dce1792838..89027df81bb2 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -118,10 +118,10 @@ impl UintStrategy { let type_max = if bits < 256 { (U256::from(1) << bits) - U256::from(1) } else { U256::MAX }; let bounds = match (min_bound, max_bound) { - (Some(min), Some(max)) if min <= max => Some((min, max)), - (Some(min), None) => Some((min, type_max)), - (None, Some(max)) => Some((U256::ZERO, max)), - _ => None, + (Some(min), Some(max)) if min <= max => Some((min, max)), + (Some(min), None) => Some((min, type_max)), + (None, Some(max)) => Some((U256::ZERO, max)), + _ => None, }; Self { From 7e295cfd2500e7b6102c5bd92b09833deb917a6d Mon Sep 17 00:00:00 2001 From: malik Date: Fri, 25 Oct 2024 08:42:55 +0100 Subject: [PATCH 20/25] natives --- crates/evm/fuzz/src/strategies/uint.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 89027df81bb2..eed686ddc6e1 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -115,13 +115,16 @@ impl UintStrategy { min_bound: Option, max_bound: Option, ) -> Self { - let type_max = if bits < 256 { (U256::from(1) << bits) - U256::from(1) } else { U256::MAX }; - + let type_max = if bits < 256 { + (U256::from(1) << bits) - U256::from(1) + } else { + U256::MAX + }; let bounds = match (min_bound, max_bound) { (Some(min), Some(max)) if min <= max => Some((min, max)), (Some(min), None) => Some((min, type_max)), (None, Some(max)) => Some((U256::ZERO, max)), - _ => None, + _ => None }; Self { From 3ea16b0a16c42814edb47930474926abc8edae8c Mon Sep 17 00:00:00 2001 From: malik Date: Fri, 25 Oct 2024 08:51:08 +0100 Subject: [PATCH 21/25] natives --- crates/evm/fuzz/src/strategies/uint.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index eed686ddc6e1..89027df81bb2 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -115,16 +115,13 @@ impl UintStrategy { min_bound: Option, max_bound: Option, ) -> Self { - let type_max = if bits < 256 { - (U256::from(1) << bits) - U256::from(1) - } else { - U256::MAX - }; + let type_max = if bits < 256 { (U256::from(1) << bits) - U256::from(1) } else { U256::MAX }; + let bounds = match (min_bound, max_bound) { (Some(min), Some(max)) if min <= max => Some((min, max)), (Some(min), None) => Some((min, type_max)), (None, Some(max)) => Some((U256::ZERO, max)), - _ => None + _ => None, }; Self { From acfd24b3bc0ee899264c2c2c6731c25063897cfe Mon Sep 17 00:00:00 2001 From: malik Date: Fri, 25 Oct 2024 09:18:16 +0100 Subject: [PATCH 22/25] natives --- crates/evm/fuzz/src/strategies/uint.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 89027df81bb2..a6369287c6c6 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -135,7 +135,18 @@ impl UintStrategy { } pub fn use_log_sampling(&self) -> bool { - self.bits > 8 + + if self.bits <= 8 { + return false; + } + + if let Some((min, max)) = self.bounds { + let range = max - min; + + range > U256::from(256) + } else { + true + } } fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { From c1f916e7bdb5f9c010a70c742a5e2342c0a37e1d Mon Sep 17 00:00:00 2001 From: malik Date: Fri, 25 Oct 2024 09:57:39 +0100 Subject: [PATCH 23/25] natives --- crates/evm/fuzz/src/strategies/uint.rs | 44 ++++++++++---------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index a6369287c6c6..299e9a664112 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -118,10 +118,10 @@ impl UintStrategy { let type_max = if bits < 256 { (U256::from(1) << bits) - U256::from(1) } else { U256::MAX }; let bounds = match (min_bound, max_bound) { - (Some(min), Some(max)) if min <= max => Some((min, max)), - (Some(min), None) => Some((min, type_max)), - (None, Some(max)) => Some((U256::ZERO, max)), - _ => None, + (Some(min), Some(max)) if min <= max => Some((min, max)), + (Some(min), None) => Some((min, type_max)), + (None, Some(max)) => Some((U256::ZERO, max)), + _ => None, }; Self { @@ -135,18 +135,7 @@ impl UintStrategy { } pub fn use_log_sampling(&self) -> bool { - - if self.bits <= 8 { - return false; - } - - if let Some((min, max)) = self.bounds { - let range = max - min; - - range > U256::from(256) - } else { - true - } + self.bits > 8 } fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree { @@ -275,23 +264,24 @@ impl UintStrategy { fn generate_log_uniform(&self, runner: &mut TestRunner) -> U256 { let rng = runner.rng(); - let exp = rng.gen::() % 256; + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + + let min_bits = min.bits(); + let max_bits = max.bits(); + + let exp = min_bits + (rng.gen::() % (max_bits - min_bits + 1)); let mantissa = rng.gen::(); - + let base = U256::from(1) << exp; let mut value = base | (U256::from(mantissa) & (base - U256::from(1))); - - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - + value = value.clamp(min, max); - + if value == min && max > min { - let range = max - min; - let offset = U256::from(rng.gen::()) % range; - value = min + offset; + self.generate_log_uniform(runner) + } else { + value } - - value } pub fn type_max(&self) -> U256 { From c8eab41e11aeb108702cf0a716201dec14145ec4 Mon Sep 17 00:00:00 2001 From: malik Date: Fri, 25 Oct 2024 10:01:32 +0100 Subject: [PATCH 24/25] natives --- crates/evm/fuzz/src/strategies/uint.rs | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 299e9a664112..21cf68ba77cc 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -266,19 +266,32 @@ impl UintStrategy { let rng = runner.rng(); let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - let min_bits = min.bits(); - let max_bits = max.bits(); + let highest_bit = |mut num: U256| { + let mut bit = 0; + while num > U256::ZERO { + num >>= 1; + bit += 1; + } + bit + }; + + let min_bits = highest_bit(min); + let max_bits = highest_bit(max); - let exp = min_bits + (rng.gen::() % (max_bits - min_bits + 1)); + let exp = if max_bits > min_bits { + min_bits + (rng.gen::() % (max_bits - min_bits)) + } else { + min_bits + }; + let mantissa = rng.gen::(); - let base = U256::from(1) << exp; let mut value = base | (U256::from(mantissa) & (base - U256::from(1))); value = value.clamp(min, max); - + if value == min && max > min { - self.generate_log_uniform(runner) + self.generate_log_uniform(runner) } else { value } From 5ec0aca7ddaf9491d4d028a7fc3144365957a61d Mon Sep 17 00:00:00 2001 From: malik Date: Fri, 25 Oct 2024 10:32:20 +0100 Subject: [PATCH 25/25] natives --- crates/evm/fuzz/src/strategies/uint.rs | 36 ++++++++------------------ 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/crates/evm/fuzz/src/strategies/uint.rs b/crates/evm/fuzz/src/strategies/uint.rs index 21cf68ba77cc..00dce1792838 100644 --- a/crates/evm/fuzz/src/strategies/uint.rs +++ b/crates/evm/fuzz/src/strategies/uint.rs @@ -264,37 +264,23 @@ impl UintStrategy { fn generate_log_uniform(&self, runner: &mut TestRunner) -> U256 { let rng = runner.rng(); - let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); - - let highest_bit = |mut num: U256| { - let mut bit = 0; - while num > U256::ZERO { - num >>= 1; - bit += 1; - } - bit - }; - - let min_bits = highest_bit(min); - let max_bits = highest_bit(max); - - let exp = if max_bits > min_bits { - min_bits + (rng.gen::() % (max_bits - min_bits)) - } else { - min_bits - }; - + let exp = rng.gen::() % 256; let mantissa = rng.gen::(); + let base = U256::from(1) << exp; let mut value = base | (U256::from(mantissa) & (base - U256::from(1))); - + + let (min, max) = self.bounds.unwrap_or((U256::ZERO, self.type_max())); + value = value.clamp(min, max); - + if value == min && max > min { - self.generate_log_uniform(runner) - } else { - value + let range = max - min; + let offset = U256::from(rng.gen::()) % range; + value = min + offset; } + + value } pub fn type_max(&self) -> U256 {