foundry_evm_fuzz/strategies/
uint.rs1use alloy_dyn_abi::{DynSolType, DynSolValue};
2use alloy_primitives::U256;
3use proptest::{
4 prelude::Rng,
5 strategy::{NewTree, Strategy, ValueTree},
6 test_runner::TestRunner,
7};
8
9pub struct UintValueTree {
11 lo: U256,
13 curr: U256,
15 hi: U256,
17 fixed: bool,
19}
20
21impl UintValueTree {
22 fn new(start: U256, fixed: bool) -> Self {
27 Self { lo: U256::ZERO, curr: start, hi: start, fixed }
28 }
29
30 fn reposition(&mut self) -> bool {
31 let interval = self.hi - self.lo;
32 let new_mid = self.lo + interval / U256::from(2);
33
34 if new_mid == self.curr {
35 false
36 } else {
37 self.curr = new_mid;
38 true
39 }
40 }
41}
42
43impl ValueTree for UintValueTree {
44 type Value = U256;
45
46 fn current(&self) -> Self::Value {
47 self.curr
48 }
49
50 fn simplify(&mut self) -> bool {
51 if self.fixed || (self.hi <= self.lo) {
52 return false;
53 }
54 self.hi = self.curr;
55 self.reposition()
56 }
57
58 fn complicate(&mut self) -> bool {
59 if self.fixed || (self.hi <= self.lo) {
60 return false;
61 }
62
63 self.lo = self.curr + U256::from(1);
64 self.reposition()
65 }
66}
67
68#[derive(Debug)]
83pub struct UintStrategy {
84 bits: usize,
86 fixtures: Vec<DynSolValue>,
88 edge_weight: usize,
90 fixtures_weight: usize,
92 random_weight: usize,
94 max_value: Option<U256>,
97}
98
99impl UintStrategy {
100 pub fn new(bits: usize, fixtures: Option<&[DynSolValue]>, max_value: Option<U256>) -> Self {
107 Self {
108 bits,
109 fixtures: Vec::from(fixtures.unwrap_or_default()),
110 edge_weight: 10usize,
111 fixtures_weight: 40usize,
112 random_weight: 50usize,
113 max_value,
114 }
115 }
116
117 fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
118 let rng = runner.rng();
119 let is_min = rng.random::<bool>();
121 let offset = U256::from(rng.random_range(0..4));
122 let start = if is_min { offset } else { self.type_max().saturating_sub(offset) };
123 Ok(UintValueTree::new(start, false))
124 }
125
126 fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
127 if self.fixtures.is_empty() {
129 return self.generate_random_tree(runner);
130 }
131
132 let fixture = &self.fixtures[runner.rng().random_range(0..self.fixtures.len())];
134 if let Some(uint_fixture) = fixture.as_uint()
135 && uint_fixture.1 == self.bits
136 {
137 let value = uint_fixture.0.min(self.type_max());
138 return Ok(UintValueTree::new(value, false));
139 }
140
141 error!("{:?} is not a valid {} fixture", fixture, DynSolType::Uint(self.bits));
143 self.generate_random_tree(runner)
144 }
145
146 fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
147 let rng = runner.rng();
148
149 let bits = rng.random_range(0..=self.bits);
151
152 let mut higher: u128 = rng.random_range(0..=u128::MAX);
154 let mut lower: u128 = rng.random_range(0..=u128::MAX);
155
156 match bits {
158 x if x < 128 => {
159 lower &= (1u128 << x) - 1;
160 higher = 0;
161 }
162 x if (128..256).contains(&x) => higher &= (1u128 << (x - 128)) - 1,
163 _ => {}
164 };
165
166 let mut inner: [u64; 4] = [0; 4];
168 let mask64 = (1 << 65) - 1;
169 inner[0] = (lower & mask64) as u64;
170 inner[1] = (lower >> 64) as u64;
171 inner[2] = (higher & mask64) as u64;
172 inner[3] = (higher >> 64) as u64;
173 let start: U256 = U256::from_limbs(inner);
174
175 let start = start.min(self.type_max());
177
178 Ok(UintValueTree::new(start, false))
179 }
180
181 fn type_max(&self) -> U256 {
182 let type_max =
183 if self.bits < 256 { (U256::from(1) << self.bits) - U256::from(1) } else { U256::MAX };
184 self.max_value.map(|m| type_max.min(m)).unwrap_or(type_max)
185 }
186}
187
188impl Strategy for UintStrategy {
189 type Tree = UintValueTree;
190 type Value = U256;
191 fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
192 let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight;
193 let bias = runner.rng().random_range(0..total_weight);
194 match bias {
196 x if x < self.edge_weight => self.generate_edge_tree(runner),
197 x if x < self.edge_weight + self.fixtures_weight => self.generate_fixtures_tree(runner),
198 _ => self.generate_random_tree(runner),
199 }
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use crate::strategies::uint::UintValueTree;
206 use alloy_primitives::U256;
207 use proptest::strategy::ValueTree;
208
209 #[test]
210 fn test_uint_tree_complicate_max() {
211 let mut uint_tree = UintValueTree::new(U256::MAX, false);
212 assert_eq!(uint_tree.hi, U256::MAX);
213 assert_eq!(uint_tree.curr, U256::MAX);
214 uint_tree.complicate();
215 assert_eq!(uint_tree.lo, U256::MIN);
216 }
217}