foundry_evm_fuzz/strategies/
int.rsuse alloy_dyn_abi::{DynSolType, DynSolValue};
use alloy_primitives::{Sign, I256, U256};
use proptest::{
strategy::{NewTree, Strategy, ValueTree},
test_runner::TestRunner,
};
use rand::Rng;
pub struct IntValueTree {
lo: I256,
curr: I256,
hi: I256,
fixed: bool,
}
impl IntValueTree {
fn new(start: I256, fixed: bool) -> Self {
Self { lo: I256::ZERO, curr: start, hi: start, fixed }
}
fn reposition(&mut self) -> bool {
let interval = self.hi - self.lo;
let new_mid = self.lo + interval / I256::from_raw(U256::from(2));
if new_mid == self.curr {
false
} else {
self.curr = new_mid;
true
}
}
fn magnitude_greater(lhs: I256, rhs: I256) -> bool {
if lhs.is_zero() {
return false
}
(lhs > rhs) ^ (lhs.is_negative())
}
}
impl ValueTree for IntValueTree {
type Value = I256;
fn current(&self) -> Self::Value {
self.curr
}
fn simplify(&mut self) -> bool {
if self.fixed || !Self::magnitude_greater(self.hi, self.lo) {
return false
}
self.hi = self.curr;
self.reposition()
}
fn complicate(&mut self) -> bool {
if self.fixed || !Self::magnitude_greater(self.hi, self.lo) {
return false
}
self.lo = if self.curr != I256::MIN && self.curr != I256::MAX {
self.curr + if self.hi.is_negative() { I256::MINUS_ONE } else { I256::ONE }
} else {
self.curr
};
self.reposition()
}
}
#[derive(Debug)]
pub struct IntStrategy {
bits: usize,
fixtures: Vec<DynSolValue>,
edge_weight: usize,
fixtures_weight: usize,
random_weight: usize,
}
impl IntStrategy {
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 offset = I256::from_raw(U256::from(rng.gen_range(0..4)));
let umax: U256 = (U256::from(1) << (self.bits - 1)) - U256::from(1);
let kind = rng.gen_range(0..4);
let start = match kind {
0 => {
I256::overflowing_from_sign_and_abs(Sign::Negative, umax + U256::from(1)).0 + offset
}
1 => -offset - I256::ONE,
2 => offset,
3 => I256::overflowing_from_sign_and_abs(Sign::Positive, umax).0 - offset,
_ => unreachable!(),
};
Ok(IntValueTree::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(int_fixture) = fixture.as_int() {
if int_fixture.1 == self.bits {
return Ok(IntValueTree::new(int_fixture.0, false));
}
}
error!("{:?} is not a valid {} fixture", fixture, DynSolType::Int(self.bits));
self.generate_random_tree(runner)
}
fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
let rng = runner.rng();
let bits = rng.gen_range(0..=self.bits);
if bits == 0 {
return Ok(IntValueTree::new(I256::ZERO, false))
}
let mut higher: u128 = rng.gen_range(0..=u128::MAX);
let mut lower: u128 = rng.gen_range(0..=u128::MAX);
match bits - 1 {
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 sign = if rng.gen_bool(0.5) { Sign::Positive } else { Sign::Negative };
let (start, _) = I256::overflowing_from_sign_and_abs(sign, U256::from_limbs(inner));
Ok(IntValueTree::new(start, false))
}
}
impl Strategy for IntStrategy {
type Tree = IntValueTree;
type Value = I256;
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::int::IntValueTree;
use alloy_primitives::I256;
use proptest::strategy::ValueTree;
#[test]
fn test_int_tree_complicate_should_not_overflow() {
let mut int_tree = IntValueTree::new(I256::MAX, false);
assert_eq!(int_tree.hi, I256::MAX);
assert_eq!(int_tree.curr, I256::MAX);
int_tree.complicate();
assert_eq!(int_tree.lo, I256::MAX);
let mut int_tree = IntValueTree::new(I256::MIN, false);
assert_eq!(int_tree.hi, I256::MIN);
assert_eq!(int_tree.curr, I256::MIN);
int_tree.complicate();
assert_eq!(int_tree.lo, I256::MIN);
}
}