foundry_evm_fuzz/strategies/
uint.rsuse alloy_dyn_abi::{DynSolType, DynSolValue};
use alloy_primitives::U256;
use proptest::{
strategy::{NewTree, Strategy, ValueTree},
test_runner::TestRunner,
};
use rand::Rng;
pub struct UintValueTree {
lo: U256,
curr: U256,
hi: U256,
fixed: bool,
}
impl UintValueTree {
fn new(start: U256, fixed: bool) -> Self {
Self { lo: U256::ZERO, curr: start, hi: start, fixed }
}
fn reposition(&mut self) -> bool {
let interval = self.hi - self.lo;
let new_mid = self.lo + interval / U256::from(2);
if new_mid == self.curr {
false
} else {
self.curr = new_mid;
true
}
}
}
impl ValueTree for UintValueTree {
type Value = U256;
fn current(&self) -> Self::Value {
self.curr
}
fn simplify(&mut self) -> bool {
if self.fixed || (self.hi <= self.lo) {
return false
}
self.hi = self.curr;
self.reposition()
}
fn complicate(&mut self) -> bool {
if self.fixed || (self.hi <= self.lo) {
return false
}
self.lo = self.curr + U256::from(1);
self.reposition()
}
}
#[derive(Debug)]
pub struct UintStrategy {
bits: usize,
fixtures: Vec<DynSolValue>,
edge_weight: usize,
fixtures_weight: usize,
random_weight: usize,
}
impl UintStrategy {
pub fn new(bits: usize, fixtures: Option<&[DynSolValue]>) -> Self {
Self {
bits,
fixtures: Vec::from(fixtures.unwrap_or_default()),
edge_weight: 10usize,
fixtures_weight: 40usize,
random_weight: 50usize,
}
}
fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
let rng = runner.rng();
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))
}
fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
if self.fixtures.is_empty() {
return self.generate_random_tree(runner)
}
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));
}
}
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> {
let rng = runner.rng();
let bits = rng.gen_range(0..=self.bits);
let mut higher: u128 = rng.gen_range(0..=u128::MAX);
let mut lower: u128 = rng.gen_range(0..=u128::MAX);
match bits {
x if x < 128 => {
lower &= (1u128 << x) - 1;
higher = 0;
}
x if (128..256).contains(&x) => higher &= (1u128 << (x - 128)) - 1,
_ => {}
};
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);
Ok(UintValueTree::new(start, false))
}
fn type_max(&self) -> U256 {
if self.bits < 256 {
(U256::from(1) << self.bits) - U256::from(1)
} else {
U256::MAX
}
}
}
impl Strategy for UintStrategy {
type Tree = UintValueTree;
type Value = U256;
fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight;
let bias = runner.rng().gen_range(0..total_weight);
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),
_ => self.generate_random_tree(runner),
}
}
}
#[cfg(test)]
mod tests {
use crate::strategies::uint::UintValueTree;
use alloy_primitives::U256;
use proptest::strategy::ValueTree;
#[test]
fn test_uint_tree_complicate_max() {
let mut uint_tree = UintValueTree::new(U256::MAX, false);
assert_eq!(uint_tree.hi, U256::MAX);
assert_eq!(uint_tree.curr, U256::MAX);
uint_tree.complicate();
assert_eq!(uint_tree.lo, U256::MIN);
}
}