1use alloy_dyn_abi::Word;
2use alloy_primitives::{Address, I256, Sign, U256};
3use proptest::{prelude::*, test_runner::TestRunner};
4use rand::seq::IndexedRandom;
5use std::fmt::Debug;
6
7static INTERESTING_8: &[i8] = &[-128, -1, 0, 1, 16, 32, 64, 100, 127];
9
10static INTERESTING_16: &[i16] = &[
12 -128, -1, 0, 1, 16, 32, 64, 100, 127, -32768, -129, 128, 255, 256, 512, 1000, 1024, 4096, 32767,
13];
14
15static INTERESTING_32: &[i32] = &[
17 -128,
18 -1,
19 0,
20 1,
21 16,
22 32,
23 64,
24 100,
25 127,
26 -32768,
27 -129,
28 128,
29 255,
30 256,
31 512,
32 1000,
33 1024,
34 4096,
35 32767,
36 -2147483648,
37 -100663046,
38 -32769,
39 32768,
40 65535,
41 65536,
42 100663045,
43 2147483647,
44];
45
46static THREE_SIGMA_MULTIPLIERS: &[f64] = &[0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0];
50
51pub(crate) trait IncrementDecrementMutator: Sized + Copy + Debug {
53 fn validate(old: Self, new: Self, size: usize) -> Option<Self>;
54
55 #[instrument(
56 name = "mutator::increment_decrement",
57 level = "trace",
58 skip(size, test_runner),
59 ret
60 )]
61 fn increment_decrement(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
62 let mutated = if test_runner.rng().random::<bool>() {
63 self.wrapping_add(Self::ONE)
64 } else {
65 self.wrapping_sub(Self::ONE)
66 };
67 Self::validate(self, mutated, size)
68 }
69
70 fn wrapping_add(self, rhs: Self) -> Self;
71 fn wrapping_sub(self, rhs: Self) -> Self;
72 const ONE: Self;
73}
74
75macro_rules! impl_increment_decrement_mutator {
76 ($ty:ty, $validate_fn:path) => {
77 impl IncrementDecrementMutator for $ty {
78 fn validate(old: Self, new: Self, size: usize) -> Option<Self> {
79 $validate_fn(old, new, size)
80 }
81
82 fn wrapping_add(self, rhs: Self) -> Self {
83 Self::wrapping_add(self, rhs)
84 }
85
86 fn wrapping_sub(self, rhs: Self) -> Self {
87 Self::wrapping_sub(self, rhs)
88 }
89
90 const ONE: Self = Self::ONE;
91 }
92 };
93}
94
95impl_increment_decrement_mutator!(U256, validate_uint_mutation);
96impl_increment_decrement_mutator!(I256, validate_int_mutation);
97
98pub(crate) trait GaussianNoiseMutator: Sized + Copy + Debug {
100 fn mutate_with_gaussian_noise(self, size: usize, test_runner: &mut TestRunner) -> Option<Self>;
101}
102
103impl GaussianNoiseMutator for U256 {
104 #[instrument(
105 name = "U256::mutate_with_gaussian_noise",
106 level = "trace",
107 skip(size, test_runner),
108 ret
109 )]
110 fn mutate_with_gaussian_noise(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
111 let scale_factor = sample_gaussian_scale(&mut test_runner.rng())?;
112 let mut bytes: [u8; 32] = self.to_be_bytes();
113 apply_scale_to_bytes(&mut bytes[32 - size / 8..], scale_factor)?;
114 validate_uint_mutation(self, Self::from_be_bytes(bytes), size)
115 }
116}
117
118impl GaussianNoiseMutator for I256 {
119 #[instrument(
120 name = "I256::mutate_with_gaussian_noise",
121 level = "trace",
122 skip(size, test_runner),
123 ret
124 )]
125 fn mutate_with_gaussian_noise(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
126 let scale_factor = sample_gaussian_scale(&mut test_runner.rng())?;
127 let mut bytes: [u8; 32] = self.to_be_bytes();
128 apply_scale_to_bytes(&mut bytes[32 - size / 8..], scale_factor)?;
129 validate_int_mutation(self, Self::from_be_bytes(bytes), size)
130 }
131}
132
133pub trait BoundMutator: Sized + Copy + Debug {
136 fn bound(self, min: Self, max: Self, test_runner: &mut TestRunner) -> Option<Self>;
137}
138
139impl BoundMutator for U256 {
140 #[instrument(name = "U256::bound", level = "trace", skip(test_runner), ret)]
141 fn bound(self, min: Self, max: Self, test_runner: &mut TestRunner) -> Option<Self> {
142 if min > max || self < min || self > max || min == max {
143 return None;
144 }
145
146 let rng = test_runner.rng();
147
148 loop {
149 let bits = rng.random_range(8..=256);
150 let mask = (Self::ONE << bits) - Self::ONE;
151 let candidate = Self::from(rng.random::<u128>()) & mask;
152
153 let candidate = min + (candidate % ((max - min).saturating_add(Self::ONE)));
155
156 if candidate != self {
157 return Some(candidate);
158 }
159 }
160 }
161}
162
163impl BoundMutator for I256 {
164 #[instrument(name = "I256::bound", level = "trace", skip(test_runner), ret)]
165 fn bound(self, min: Self, max: Self, test_runner: &mut TestRunner) -> Option<Self> {
166 if min > max || self < min || self > max || min == max {
167 return None;
168 }
169
170 let rng = test_runner.rng();
171
172 loop {
173 let bits = rng.random_range(8..=255);
174 let mask = (U256::ONE << bits) - U256::ONE;
175 let rand_u = U256::from(rng.next_u64()) | (U256::from(rng.next_u64()) << 64);
176 let unsigned_candidate = rand_u & mask;
177
178 let signed_candidate = {
179 let midpoint = U256::ONE << (bits - 1);
180 if unsigned_candidate < midpoint {
181 Self::from_raw(unsigned_candidate)
182 } else {
183 Self::from_raw(unsigned_candidate) - Self::from_raw(U256::ONE << bits)
184 }
185 };
186
187 let range = max.saturating_sub(min).saturating_add(Self::ONE).unsigned_abs();
189 let wrapped = Self::from_raw(U256::from(signed_candidate.unsigned_abs()) % range);
190 let candidate =
191 if signed_candidate.is_negative() { max - wrapped } else { min + wrapped };
192
193 if candidate != self {
194 return Some(candidate);
195 }
196 }
197 }
198}
199
200pub(crate) trait BitMutator: Sized + Copy + Debug {
202 fn flip_random_bit(self, size: usize, test_runner: &mut TestRunner) -> Option<Self>;
203}
204
205impl BitMutator for U256 {
206 #[instrument(name = "U256::flip_random_bit", level = "trace", skip(size, test_runner), ret)]
207 fn flip_random_bit(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
208 let mut bytes: [u8; 32] = self.to_be_bytes();
209 flip_random_bit_in_slice(&mut bytes[32 - size / 8..], test_runner)?;
210 validate_uint_mutation(self, Self::from_be_bytes(bytes), size)
211 }
212}
213
214impl BitMutator for I256 {
215 #[instrument(name = "I256::flip_random_bit", level = "trace", skip(size, test_runner), ret)]
216 fn flip_random_bit(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
217 let mut bytes: [u8; 32] = self.to_be_bytes();
218 flip_random_bit_in_slice(&mut bytes[32 - size / 8..], test_runner)?;
219 validate_int_mutation(self, Self::from_be_bytes(bytes), size)
220 }
221}
222
223impl BitMutator for Address {
224 #[instrument(name = "Address::flip_random_bit", level = "trace", skip(_size, test_runner), ret)]
225 fn flip_random_bit(self, _size: usize, test_runner: &mut TestRunner) -> Option<Self> {
226 let mut mutated = self;
227 flip_random_bit_in_slice(mutated.as_mut_slice(), test_runner)?;
228 (self != mutated).then_some(mutated)
229 }
230}
231
232impl BitMutator for Word {
233 #[instrument(name = "Word::flip_random_bit", level = "trace", skip(size, test_runner), ret)]
234 fn flip_random_bit(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
235 let mut bytes = self;
236 let slice = &mut bytes[..size];
237 flip_random_bit_in_slice(slice, test_runner)?;
238 (self != bytes).then_some(bytes)
239 }
240}
241
242pub(crate) trait InterestingWordMutator: Sized + Copy + Debug {
245 fn mutate_interesting_byte(self, size: usize, test_runner: &mut TestRunner) -> Option<Self>;
246 fn mutate_interesting_word(self, size: usize, test_runner: &mut TestRunner) -> Option<Self>;
247 fn mutate_interesting_dword(self, size: usize, test_runner: &mut TestRunner) -> Option<Self>;
248}
249
250impl InterestingWordMutator for U256 {
251 #[instrument(
252 name = "U256::mutate_interesting_byte",
253 level = "trace",
254 skip(size, test_runner),
255 ret
256 )]
257 fn mutate_interesting_byte(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
258 let mut bytes: [u8; 32] = self.to_be_bytes();
259 mutate_interesting_byte_slice(&mut bytes[32 - size / 8..], test_runner)?;
260 validate_uint_mutation(self, Self::from_be_bytes(bytes), size)
261 }
262
263 #[instrument(
264 name = "U256::mutate_interesting_word",
265 level = "trace",
266 skip(size, test_runner),
267 ret
268 )]
269 fn mutate_interesting_word(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
270 let mut bytes: [u8; 32] = self.to_be_bytes();
271 mutate_interesting_word_slice(&mut bytes[32 - size / 8..], test_runner)?;
272 validate_uint_mutation(self, Self::from_be_bytes(bytes), size)
273 }
274
275 #[instrument(
276 name = "U256::mutate_interesting_dword",
277 level = "trace",
278 skip(size, test_runner),
279 ret
280 )]
281 fn mutate_interesting_dword(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
282 let mut bytes: [u8; 32] = self.to_be_bytes();
283 mutate_interesting_dword_slice(&mut bytes[32 - size / 8..], test_runner)?;
284 validate_uint_mutation(self, Self::from_be_bytes(bytes), size)
285 }
286}
287
288impl InterestingWordMutator for I256 {
289 #[instrument(
290 name = "I256::mutate_interesting_byte",
291 level = "trace",
292 skip(size, test_runner),
293 ret
294 )]
295 fn mutate_interesting_byte(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
296 let mut bytes: [u8; 32] = self.to_be_bytes();
297 mutate_interesting_byte_slice(&mut bytes[32 - size / 8..], test_runner)?;
298 validate_int_mutation(self, Self::from_be_bytes(bytes), size)
299 }
300
301 #[instrument(
302 name = "I256::mutate_interesting_word",
303 level = "trace",
304 skip(size, test_runner),
305 ret
306 )]
307 fn mutate_interesting_word(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
308 let mut bytes: [u8; 32] = self.to_be_bytes();
309 mutate_interesting_word_slice(&mut bytes[32 - size / 8..], test_runner)?;
310 validate_int_mutation(self, Self::from_be_bytes(bytes), size)
311 }
312
313 #[instrument(
314 name = "I256::mutate_interesting_dword",
315 level = "trace",
316 skip(size, test_runner),
317 ret
318 )]
319 fn mutate_interesting_dword(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
320 let mut bytes: [u8; 32] = self.to_be_bytes();
321 mutate_interesting_dword_slice(&mut bytes[32 - size / 8..], test_runner)?;
322 validate_int_mutation(self, Self::from_be_bytes(bytes), size)
323 }
324}
325
326impl InterestingWordMutator for Address {
327 #[instrument(
328 name = "Address::mutate_interesting_byte",
329 level = "trace",
330 skip(_size, test_runner),
331 ret
332 )]
333 fn mutate_interesting_byte(self, _size: usize, test_runner: &mut TestRunner) -> Option<Self> {
334 let mut mutated = self;
335 mutate_interesting_byte_slice(mutated.as_mut_slice(), test_runner)?;
336 (self != mutated).then_some(mutated)
337 }
338
339 #[instrument(
340 name = "Address::mutate_interesting_word",
341 level = "trace",
342 skip(_size, test_runner),
343 ret
344 )]
345 fn mutate_interesting_word(self, _size: usize, test_runner: &mut TestRunner) -> Option<Self> {
346 let mut mutated = self;
347 mutate_interesting_word_slice(mutated.as_mut_slice(), test_runner)?;
348 (self != mutated).then_some(mutated)
349 }
350
351 #[instrument(
352 name = "Address::mutate_interesting_dword",
353 level = "trace",
354 skip(_size, test_runner),
355 ret
356 )]
357 fn mutate_interesting_dword(self, _size: usize, test_runner: &mut TestRunner) -> Option<Self> {
358 let mut mutated = self;
359 mutate_interesting_dword_slice(mutated.as_mut_slice(), test_runner)?;
360 (self != mutated).then_some(mutated)
361 }
362}
363
364impl InterestingWordMutator for Word {
365 #[instrument(
366 name = "Word::mutate_interesting_byte",
367 level = "trace",
368 skip(size, test_runner),
369 ret
370 )]
371 fn mutate_interesting_byte(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
372 let mut bytes = self;
373 let slice = &mut bytes[..size];
374 mutate_interesting_byte_slice(slice, test_runner)?;
375 (self != bytes).then_some(bytes)
376 }
377
378 #[instrument(
379 name = "Word::mutate_interesting_word",
380 level = "trace",
381 skip(size, test_runner),
382 ret
383 )]
384 fn mutate_interesting_word(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
385 let mut bytes = self;
386 let slice = &mut bytes[..size];
387 mutate_interesting_word_slice(slice, test_runner)?;
388 (self != bytes).then_some(bytes)
389 }
390
391 #[instrument(
392 name = "Word::mutate_interesting_dword",
393 level = "trace",
394 skip(size, test_runner),
395 ret
396 )]
397 fn mutate_interesting_dword(self, size: usize, test_runner: &mut TestRunner) -> Option<Self> {
398 let mut bytes = self;
399 let slice = &mut bytes[..size];
400 mutate_interesting_dword_slice(slice, test_runner)?;
401 (self != bytes).then_some(bytes)
402 }
403}
404
405fn flip_random_bit_in_slice(bytes: &mut [u8], test_runner: &mut TestRunner) -> Option<()> {
407 if bytes.is_empty() {
408 return None;
409 }
410 let bit_index = test_runner.rng().random_range(0..(bytes.len() * 8));
411 bytes[bit_index / 8] ^= 1 << (bit_index % 8);
412 Some(())
413}
414
415fn mutate_interesting_byte_slice(bytes: &mut [u8], test_runner: &mut TestRunner) -> Option<()> {
418 let index = test_runner.rng().random_range(0..bytes.len());
419 let val = *INTERESTING_8.choose(&mut test_runner.rng())? as u8;
420 bytes[index] = val;
421 Some(())
422}
423
424fn mutate_interesting_word_slice(bytes: &mut [u8], test_runner: &mut TestRunner) -> Option<()> {
427 if bytes.len() < 2 {
428 return None;
429 }
430 let index = test_runner.rng().random_range(0..=bytes.len() - 2);
431 let val = *INTERESTING_16.choose(&mut test_runner.rng())? as u16;
432 bytes[index..index + 2].copy_from_slice(&val.to_be_bytes());
433 Some(())
434}
435
436fn mutate_interesting_dword_slice(bytes: &mut [u8], test_runner: &mut TestRunner) -> Option<()> {
439 if bytes.len() < 4 {
440 return None;
441 }
442 let index = test_runner.rng().random_range(0..=bytes.len() - 4);
443 let val = *INTERESTING_32.choose(&mut test_runner.rng())? as u32;
444 bytes[index..index + 4].copy_from_slice(&val.to_be_bytes());
445 Some(())
446}
447
448fn sample_gaussian_scale<R: Rng>(rng: &mut R) -> Option<f64> {
459 let num_samples = 8;
460 let chosen_3rd_sigma = *THREE_SIGMA_MULTIPLIERS.choose(rng).unwrap_or(&1.0);
461
462 let mut sum = 0.0;
463 for _ in 0..num_samples {
464 sum += rng.random::<f64>();
465 }
466
467 let standard_normal = sum - (num_samples as f64 / 2.0);
468 let mut scale_factor = (chosen_3rd_sigma / 3.0) * standard_normal;
469 scale_factor += 1.0;
470
471 if scale_factor < 0.0 || (scale_factor - 1.0).abs() < f64::EPSILON {
472 None
473 } else {
474 Some(scale_factor)
475 }
476}
477
478fn apply_scale_to_bytes(bytes: &mut [u8], scale_factor: f64) -> Option<()> {
481 let mut carry_down = 0.0;
482
483 for i in (0..bytes.len()).rev() {
484 let byte_val = bytes[i] as f64;
485 let scaled = (byte_val + carry_down * 256.0) * scale_factor;
486
487 if i == 0 && scaled >= 256.0 {
488 bytes.iter_mut().for_each(|b| *b = 0xFF);
489 return Some(());
490 }
491
492 bytes[i] = (scaled % 256.0).floor() as u8;
493
494 let mut carry_up = (scaled / 256.0).floor();
495 carry_down = (scaled % 1.0) / scale_factor;
496
497 let mut j = i;
498 while carry_up > 0.0 && j > 0 {
500 j -= 1;
501 let new_val = bytes[j] as f64 + carry_up;
502 if j == 0 && new_val >= 256.0 {
503 bytes.iter_mut().for_each(|b| *b = 0xFF);
504 return Some(());
505 }
506 bytes[j] = (new_val % 256.0).floor() as u8;
507 carry_up = (new_val / 256.0).floor();
508 }
509 }
510
511 Some(())
512}
513
514fn validate_uint_mutation(original: U256, mutated: U256, size: usize) -> Option<U256> {
517 if mutated == original {
519 return None;
520 }
521
522 let max = if size < 256 { (U256::from(1) << size) - U256::from(1) } else { U256::MAX };
524 (mutated < max).then_some(mutated)
525}
526
527fn validate_int_mutation(original: I256, mutated: I256, size: usize) -> Option<I256> {
530 if mutated == original {
532 return None;
533 }
534
535 let max_abs = (U256::from(1) << (size - 1)) - U256::from(1);
537 match mutated.sign() {
538 Sign::Positive => mutated < I256::overflowing_from_sign_and_abs(Sign::Positive, max_abs).0,
539 Sign::Negative => mutated > I256::overflowing_from_sign_and_abs(Sign::Negative, max_abs).0,
540 }
541 .then_some(mutated)
542}
543
544#[cfg(test)]
545mod tests {
546 use super::*;
547 use proptest::test_runner::Config;
548
549 #[test]
550 fn test_mutate_uint() {
551 let mut runner = TestRunner::new(Config::default());
552 let size = 32;
553
554 let test_values =
555 vec![U256::ZERO, U256::ONE, U256::from(12345u64), U256::from(255), U256::MAX];
556
557 #[track_caller]
558 fn validate_mutation(value: U256, mutated: Option<U256>) {
559 assert!(
560 mutated.is_none() || mutated.is_some_and(|m| m != value),
561 "Mutation failed: value = {value:?}, mutated = {mutated:?}"
562 );
563 }
564
565 for value in test_values {
566 for _ in 0..100 {
567 validate_mutation(value, U256::increment_decrement(value, size, &mut runner));
568 validate_mutation(value, U256::flip_random_bit(value, size, &mut runner));
569 validate_mutation(value, U256::mutate_interesting_byte(value, size, &mut runner));
570 validate_mutation(value, U256::mutate_interesting_word(value, size, &mut runner));
571 validate_mutation(value, U256::mutate_interesting_dword(value, size, &mut runner));
572 }
573 }
574 }
575
576 #[test]
577 fn test_mutate_int() {
578 let mut runner = TestRunner::new(Config::default());
579 let size = 32;
580
581 let test_values = vec![
582 I256::ZERO,
583 I256::ONE,
584 I256::MINUS_ONE,
585 I256::from_dec_str("12345").unwrap(),
586 I256::from_dec_str("-54321").unwrap(),
587 I256::from_dec_str("340282366920938463463374607431768211455").unwrap(),
588 I256::from_dec_str("-340282366920938463463374607431768211455").unwrap(),
589 ];
590
591 #[track_caller]
592 fn validate_mutation(value: I256, mutated: Option<I256>) {
593 assert!(
594 mutated.is_none() || mutated.is_some_and(|m| m != value),
595 "Mutation failed: value = {value:?}, mutated = {mutated:?}"
596 );
597 }
598
599 for value in test_values {
600 for _ in 0..100 {
601 validate_mutation(value, I256::increment_decrement(value, size, &mut runner));
602 validate_mutation(value, I256::flip_random_bit(value, size, &mut runner));
603 validate_mutation(value, I256::mutate_interesting_byte(value, size, &mut runner));
604 validate_mutation(value, I256::mutate_interesting_word(value, size, &mut runner));
605 validate_mutation(value, I256::mutate_interesting_dword(value, size, &mut runner));
606 }
607 }
608 }
609
610 #[test]
611 fn test_mutate_address() {
612 let mut runner = TestRunner::new(Config::default());
613 let value = Address::random();
614
615 #[track_caller]
616 fn validate_mutation(value: Address, mutated: Option<Address>) {
617 assert!(
618 mutated.is_none() || mutated.is_some_and(|mutated| mutated != value),
619 "Mutation failed for value: {value:?}, result: {mutated:?}"
620 );
621 }
622
623 for _ in 0..100 {
624 validate_mutation(value, Address::flip_random_bit(value, 20, &mut runner));
625 validate_mutation(value, Address::mutate_interesting_byte(value, 20, &mut runner));
626 validate_mutation(value, Address::mutate_interesting_word(value, 20, &mut runner));
627 validate_mutation(value, Address::mutate_interesting_dword(value, 20, &mut runner));
628 }
629 }
630
631 #[test]
632 fn test_mutate_word() {
633 let mut runner = TestRunner::new(Config::default());
634 let value = Word::random();
635
636 #[track_caller]
637 fn validate_mutation(value: Word, mutated: Option<Word>) {
638 assert!(
639 mutated.is_none() || mutated.is_some_and(|mutated| mutated != value),
640 "Mutation failed for value: {value:?}, result: {mutated:?}"
641 );
642 }
643
644 for _ in 0..100 {
645 validate_mutation(value, Word::flip_random_bit(value, 32, &mut runner));
646 validate_mutation(value, Word::mutate_interesting_byte(value, 32, &mut runner));
647 validate_mutation(value, Word::mutate_interesting_word(value, 32, &mut runner));
648 validate_mutation(value, Word::mutate_interesting_dword(value, 32, &mut runner));
649 }
650 }
651
652 #[test]
653 fn test_mutate_interesting_word_too_small_returns_none() {
654 let mut runner = TestRunner::new(Config::default());
655 let value = U256::from(123);
656 assert!(U256::mutate_interesting_word(value, 8, &mut runner).is_none());
657 }
658
659 #[test]
660 fn test_mutate_interesting_dword_too_small_returns_none() {
661 let mut runner = TestRunner::new(Config::default());
662 let value = I256::from_dec_str("123").unwrap();
663 assert!(I256::mutate_interesting_dword(value, 16, &mut runner).is_none());
664 }
665
666 #[test]
667 fn test_u256_bound() {
668 let mut runner = TestRunner::new(Config::default());
669 let min = U256::from(0u64);
670 let max = U256::from(200u64);
671 let original = U256::from(100u64);
672
673 for _ in 0..50 {
674 let result = original.bound(min, max, &mut runner);
675 assert!(result.is_some(), "Mutation should occur");
676
677 let mutated = result.unwrap();
678 assert!(mutated >= min, "Mutated value >= min");
679 assert!(mutated <= max, "Mutated value <= max");
680 assert_ne!(mutated, original, "mutated value should differ from original");
681 }
682
683 let result = original.bound(U256::MIN, U256::MAX, &mut runner);
685 assert!(result.is_some(), "Mutation should occur");
686 }
687
688 #[test]
689 fn test_i256_bound() {
690 let mut runner = TestRunner::new(Config::default());
691 let min = I256::from_dec_str("-100").unwrap();
692 let max = I256::from_dec_str("100").unwrap();
693 let original = I256::from_dec_str("10").unwrap();
694
695 for _ in 0..50 {
696 let result = original.bound(min, max, &mut runner);
697 assert!(result.is_some(), "Mutation should occur");
698
699 let mutated = result.unwrap();
700 assert!(mutated >= min, "Mutated value >= min");
701 assert!(mutated <= max, "Mutated value <= max");
702 assert_ne!(mutated, original, "Mutated value should not equal current");
703 }
704
705 let result = original.bound(I256::MIN, I256::MAX, &mut runner);
707 assert!(result.is_some(), "Mutation should occur");
708 }
709}