foundry_evm_fuzz/strategies/
int.rs1use alloy_dyn_abi::{DynSolType, DynSolValue};
2use alloy_primitives::{I256, Sign, U256};
3use proptest::{
4 prelude::Rng,
5 strategy::{NewTree, Strategy, ValueTree},
6 test_runner::TestRunner,
7};
8
9pub fn clamp(value: I256, max: U256) -> I256 {
12 let max_i256 = I256::from_raw(max);
13 let min_i256 = I256::overflowing_from_sign_and_abs(Sign::Negative, max + U256::from(1)).0;
14 if value > max_i256 {
15 max_i256
16 } else if value < min_i256 {
17 min_i256
18 } else {
19 value
20 }
21}
22
23pub struct IntValueTree {
25 lo: I256,
27 curr: I256,
29 hi: I256,
31 fixed: bool,
33}
34
35impl IntValueTree {
36 fn new(start: I256, fixed: bool) -> Self {
41 Self { lo: I256::ZERO, curr: start, hi: start, fixed }
42 }
43
44 fn reposition(&mut self) -> bool {
45 let interval = self.hi - self.lo;
46 let new_mid = self.lo + interval / I256::from_raw(U256::from(2));
47
48 if new_mid == self.curr {
49 false
50 } else {
51 self.curr = new_mid;
52 true
53 }
54 }
55
56 fn magnitude_greater(lhs: I256, rhs: I256) -> bool {
57 if lhs.is_zero() {
58 return false;
59 }
60 (lhs > rhs) ^ (lhs.is_negative())
61 }
62}
63
64impl ValueTree for IntValueTree {
65 type Value = I256;
66
67 fn current(&self) -> Self::Value {
68 self.curr
69 }
70
71 fn simplify(&mut self) -> bool {
72 if self.fixed || !Self::magnitude_greater(self.hi, self.lo) {
73 return false;
74 }
75 self.hi = self.curr;
76 self.reposition()
77 }
78
79 fn complicate(&mut self) -> bool {
80 if self.fixed || !Self::magnitude_greater(self.hi, self.lo) {
81 return false;
82 }
83
84 self.lo = if self.curr != I256::MIN && self.curr != I256::MAX {
85 self.curr + if self.hi.is_negative() { I256::MINUS_ONE } else { I256::ONE }
86 } else {
87 self.curr
88 };
89
90 self.reposition()
91 }
92}
93
94#[derive(Debug)]
109pub struct IntStrategy {
110 bits: usize,
112 fixtures: Vec<DynSolValue>,
114 edge_weight: usize,
116 fixtures_weight: usize,
118 random_weight: usize,
120 max_value: Option<U256>,
124}
125
126impl IntStrategy {
127 pub fn new(bits: usize, fixtures: Option<&[DynSolValue]>, max_value: Option<U256>) -> Self {
134 Self {
135 bits,
136 fixtures: Vec::from(fixtures.unwrap_or_default()),
137 edge_weight: 10usize,
138 fixtures_weight: 40usize,
139 random_weight: 50usize,
140 max_value,
141 }
142 }
143
144 fn effective_max(&self) -> U256 {
145 let type_max: U256 = (U256::from(1) << (self.bits - 1)) - U256::from(1);
146 self.max_value.map(|m| type_max.min(m)).unwrap_or(type_max)
147 }
148
149 fn generate_edge_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
150 let rng = runner.rng();
151
152 let offset = I256::from_raw(U256::from(rng.random_range(0..4)));
153 let umax = self.effective_max();
154 let kind = rng.random_range(0..4);
156 let start = match kind {
157 0 => {
158 I256::overflowing_from_sign_and_abs(Sign::Negative, umax + U256::from(1)).0 + offset
159 }
160 1 => -offset - I256::ONE,
161 2 => offset,
162 3 => I256::overflowing_from_sign_and_abs(Sign::Positive, umax).0 - offset,
163 _ => unreachable!(),
164 };
165 Ok(IntValueTree::new(clamp(start, self.effective_max()), false))
166 }
167
168 fn generate_fixtures_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
169 if self.fixtures.is_empty() {
171 return self.generate_random_tree(runner);
172 }
173
174 let fixture = &self.fixtures[runner.rng().random_range(0..self.fixtures.len())];
176 if let Some(int_fixture) = fixture.as_int()
177 && int_fixture.1 == self.bits
178 {
179 return Ok(IntValueTree::new(clamp(int_fixture.0, self.effective_max()), false));
180 }
181
182 error!("{:?} is not a valid {} fixture", fixture, DynSolType::Int(self.bits));
184 self.generate_random_tree(runner)
185 }
186
187 fn generate_random_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
188 let rng = runner.rng();
189
190 let bits = rng.random_range(0..=self.bits);
192
193 if bits == 0 {
194 return Ok(IntValueTree::new(I256::ZERO, false));
195 }
196
197 let mut higher: u128 = rng.random_range(0..=u128::MAX);
199 let mut lower: u128 = rng.random_range(0..=u128::MAX);
200
201 match bits - 1 {
203 x if x < 128 => {
204 lower &= (1u128 << x) - 1;
205 higher = 0;
206 }
207 x if (128..256).contains(&x) => higher &= (1u128 << (x - 128)) - 1,
208 _ => {}
209 };
210
211 let mut inner: [u64; 4] = [0; 4];
213 let mask64 = (1 << 65) - 1;
214 inner[0] = (lower & mask64) as u64;
215 inner[1] = (lower >> 64) as u64;
216 inner[2] = (higher & mask64) as u64;
217 inner[3] = (higher >> 64) as u64;
218
219 let sign = if rng.random::<bool>() { Sign::Positive } else { Sign::Negative };
222 let (start, _) = I256::overflowing_from_sign_and_abs(sign, U256::from_limbs(inner));
223
224 Ok(IntValueTree::new(clamp(start, self.effective_max()), false))
225 }
226}
227
228impl Strategy for IntStrategy {
229 type Tree = IntValueTree;
230 type Value = I256;
231
232 fn new_tree(&self, runner: &mut TestRunner) -> NewTree<Self> {
233 let total_weight = self.random_weight + self.fixtures_weight + self.edge_weight;
234 let bias = runner.rng().random_range(0..total_weight);
235 match bias {
237 x if x < self.edge_weight => self.generate_edge_tree(runner),
238 x if x < self.edge_weight + self.fixtures_weight => self.generate_fixtures_tree(runner),
239 _ => self.generate_random_tree(runner),
240 }
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use crate::strategies::int::IntValueTree;
247 use alloy_primitives::I256;
248 use proptest::strategy::ValueTree;
249
250 #[test]
251 fn test_int_tree_complicate_should_not_overflow() {
252 let mut int_tree = IntValueTree::new(I256::MAX, false);
253 assert_eq!(int_tree.hi, I256::MAX);
254 assert_eq!(int_tree.curr, I256::MAX);
255 int_tree.complicate();
256 assert_eq!(int_tree.lo, I256::MAX);
257
258 let mut int_tree = IntValueTree::new(I256::MIN, false);
259 assert_eq!(int_tree.hi, I256::MIN);
260 assert_eq!(int_tree.curr, I256::MIN);
261 int_tree.complicate();
262 assert_eq!(int_tree.lo, I256::MIN);
263 }
264}