Skip to main content

foundry_evm_fuzz/strategies/
mutators.rs

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
7// Interesting 8-bit values to inject.
8static INTERESTING_8: &[i8] = &[-128, -1, 0, 1, 16, 32, 64, 100, 127];
9
10/// Interesting 16-bit values to inject.
11static 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
15/// Interesting 32-bit values to inject.
16static 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
46/// Multipliers used to define the 3 standard deviation range of a Gaussian-like curve.
47/// For example, a multiplier of 0.25 means the +/-3 standard deviation bounds are +/-25% of the
48/// original value.
49static THREE_SIGMA_MULTIPLIERS: &[f64] = &[0.1, 0.25, 0.5, 1.0, 2.0, 5.0, 10.0];
50
51/// Mutator that randomly increments or decrements an uint or int.
52pub(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
98/// Mutator that changes the current value of an uint or int by applying gaussian noise.
99pub(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
133/// Mutator that bounds the current value of an uint or int in the given range.
134/// The mutated value is always different from the current value.
135pub 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            // Map to range.
154            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            // Map to range.
188            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
200/// Mutator that changes the current value by flipping a random bit.
201pub(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
242/// Mutator that changes the current value by randomly injecting interesting words (for uint, int,
243/// address and fixed bytes) - see <https://github.com/AFLplusplus/LibAFL/blob/90cb9a2919faf386e0678870e52784070cdac4b6/crates/libafl/src/mutators/mutations.rs#L88-L123>.
244pub(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
405/// Flips a random bit in the given mutable byte slice.
406fn 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
415/// Mutates a random byte in the given byte slice by replacing it with a randomly chosen
416/// interesting 8-bit value.
417fn 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
424/// Mutates a random 2-byte (16-bit) region in the byte slice with a randomly chosen interesting
425/// 16-bit value.
426fn 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
436/// Mutates a random 4-byte (32-bit) region in the byte slice with a randomly chosen interesting
437/// 32-bit value.
438fn 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
448/// Samples a scale factor from a pseudo-Gaussian distribution centered around 1.0.
449///
450/// - Select a random standard deviation multiplier from a predefined set.
451/// - Approximates a standard normal distribution using the Irwin-Hall method (sum of uniform
452///   samples).
453/// - Scales the normal value by the chosen standard deviation multiplier, divided by 3 to get
454///   standard deviation.
455/// - Adds 1.0 to center the scale factor around 1.0 (no mutation).
456///
457/// Returns a scale factor that, when applied to a number, mimics Gaussian noise.
458fn 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
478/// Applies a floating-point scale factor to a byte slice representing an unsigned or signed
479/// integer.
480fn 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            for b in bytes.iter_mut() {
489                *b = 0xFF;
490            }
491            return Some(());
492        }
493
494        bytes[i] = (scaled % 256.0).floor() as u8;
495
496        let mut carry_up = (scaled / 256.0).floor();
497        carry_down = (scaled % 1.0) / scale_factor;
498
499        let mut j = i;
500        // Propagate carry_up until it is zero or no more bytes left
501        while carry_up > 0.0 && j > 0 {
502            j -= 1;
503            let new_val = bytes[j] as f64 + carry_up;
504            if j == 0 && new_val >= 256.0 {
505                for b in bytes.iter_mut() {
506                    *b = 0xFF;
507                }
508                return Some(());
509            }
510            bytes[j] = (new_val % 256.0).floor() as u8;
511            carry_up = (new_val / 256.0).floor();
512        }
513    }
514
515    Some(())
516}
517
518/// Returns mutated uint value if different from the original value and if it fits in the given
519/// size, otherwise None.
520fn validate_uint_mutation(original: U256, mutated: U256, size: usize) -> Option<U256> {
521    // Early return if mutated value is the same as original value.
522    if mutated == original {
523        return None;
524    }
525
526    // Check if mutated value fits the given size.
527    let max = if size < 256 { (U256::from(1) << size) - U256::from(1) } else { U256::MAX };
528    (mutated <= max).then_some(mutated)
529}
530
531/// Returns mutated int value if different from the original value and if it fits in the given size,
532/// otherwise None.
533fn validate_int_mutation(original: I256, mutated: I256, size: usize) -> Option<I256> {
534    // Early return if mutated value is the same as original value.
535    if mutated == original {
536        return None;
537    }
538
539    // Check if mutated value fits the given size.
540    // Valid signed range for `size` bits is `[-2^(size-1), 2^(size-1) - 1]`.
541    let max = I256::overflowing_from_sign_and_abs(
542        Sign::Positive,
543        (U256::from(1) << (size - 1)) - U256::from(1),
544    )
545    .0;
546    let min = I256::overflowing_from_sign_and_abs(Sign::Negative, U256::from(1) << (size - 1)).0;
547    match mutated.sign() {
548        Sign::Positive => mutated <= max,
549        Sign::Negative => mutated >= min,
550    }
551    .then_some(mutated)
552}
553
554#[cfg(test)]
555mod tests {
556    use super::*;
557    use proptest::test_runner::Config;
558
559    #[test]
560    fn validate_uint_mutation_accepts_inclusive_max() {
561        // For width `size`, valid unsigned values are 0..=max where max = 2^size - 1.
562        // Regression: `< max` incorrectly rejected `mutated == max`; must use `<= max`.
563        for size in [8usize, 16, 32, 64, 128] {
564            let max = (U256::from(1) << size) - U256::from(1);
565            assert_eq!(
566                super::validate_uint_mutation(U256::ZERO, max, size),
567                Some(max),
568                "size={size}"
569            );
570        }
571        let original = U256::from(1);
572        assert_eq!(super::validate_uint_mutation(original, U256::MAX, 256), Some(U256::MAX));
573    }
574
575    #[test]
576    fn validate_int_mutation_accepts_inclusive_bounds() {
577        // For width `size`, valid signed values are [-2^(size-1), 2^(size-1) - 1].
578        // Regression: strict comparisons rejected the inclusive max and min bounds.
579        for size in [8usize, 16, 32, 64, 128] {
580            let max = I256::overflowing_from_sign_and_abs(
581                Sign::Positive,
582                (U256::from(1) << (size - 1)) - U256::from(1),
583            )
584            .0;
585            let min =
586                I256::overflowing_from_sign_and_abs(Sign::Negative, U256::from(1) << (size - 1)).0;
587            assert_eq!(
588                super::validate_int_mutation(I256::ZERO, max, size),
589                Some(max),
590                "size={size} max"
591            );
592            assert_eq!(
593                super::validate_int_mutation(I256::ZERO, min, size),
594                Some(min),
595                "size={size} min"
596            );
597        }
598        assert_eq!(super::validate_int_mutation(I256::ZERO, I256::MAX, 256), Some(I256::MAX));
599        assert_eq!(super::validate_int_mutation(I256::ZERO, I256::MIN, 256), Some(I256::MIN));
600    }
601
602    #[test]
603    fn test_mutate_uint() {
604        let mut runner = TestRunner::new(Config::default());
605        let size = 32;
606
607        let test_values =
608            vec![U256::ZERO, U256::ONE, U256::from(12345u64), U256::from(255), U256::MAX];
609
610        #[track_caller]
611        fn validate_mutation(value: U256, mutated: Option<U256>) {
612            assert!(
613                mutated.is_none() || mutated.is_some_and(|m| m != value),
614                "Mutation failed: value = {value:?}, mutated = {mutated:?}"
615            );
616        }
617
618        for value in test_values {
619            for _ in 0..100 {
620                validate_mutation(value, U256::increment_decrement(value, size, &mut runner));
621                validate_mutation(value, U256::flip_random_bit(value, size, &mut runner));
622                validate_mutation(value, U256::mutate_interesting_byte(value, size, &mut runner));
623                validate_mutation(value, U256::mutate_interesting_word(value, size, &mut runner));
624                validate_mutation(value, U256::mutate_interesting_dword(value, size, &mut runner));
625            }
626        }
627    }
628
629    #[test]
630    fn test_mutate_int() {
631        let mut runner = TestRunner::new(Config::default());
632        let size = 32;
633
634        let test_values = vec![
635            I256::ZERO,
636            I256::ONE,
637            I256::MINUS_ONE,
638            I256::from_dec_str("12345").unwrap(),
639            I256::from_dec_str("-54321").unwrap(),
640            I256::from_dec_str("340282366920938463463374607431768211455").unwrap(),
641            I256::from_dec_str("-340282366920938463463374607431768211455").unwrap(),
642        ];
643
644        #[track_caller]
645        fn validate_mutation(value: I256, mutated: Option<I256>) {
646            assert!(
647                mutated.is_none() || mutated.is_some_and(|m| m != value),
648                "Mutation failed: value = {value:?}, mutated = {mutated:?}"
649            );
650        }
651
652        for value in test_values {
653            for _ in 0..100 {
654                validate_mutation(value, I256::increment_decrement(value, size, &mut runner));
655                validate_mutation(value, I256::flip_random_bit(value, size, &mut runner));
656                validate_mutation(value, I256::mutate_interesting_byte(value, size, &mut runner));
657                validate_mutation(value, I256::mutate_interesting_word(value, size, &mut runner));
658                validate_mutation(value, I256::mutate_interesting_dword(value, size, &mut runner));
659            }
660        }
661    }
662
663    #[test]
664    fn test_mutate_address() {
665        let mut runner = TestRunner::new(Config::default());
666        let value = Address::random();
667
668        #[track_caller]
669        fn validate_mutation(value: Address, mutated: Option<Address>) {
670            assert!(
671                mutated.is_none() || mutated.is_some_and(|mutated| mutated != value),
672                "Mutation failed for value: {value:?}, result: {mutated:?}"
673            );
674        }
675
676        for _ in 0..100 {
677            validate_mutation(value, Address::flip_random_bit(value, 20, &mut runner));
678            validate_mutation(value, Address::mutate_interesting_byte(value, 20, &mut runner));
679            validate_mutation(value, Address::mutate_interesting_word(value, 20, &mut runner));
680            validate_mutation(value, Address::mutate_interesting_dword(value, 20, &mut runner));
681        }
682    }
683
684    #[test]
685    fn test_mutate_word() {
686        let mut runner = TestRunner::new(Config::default());
687        let value = Word::random();
688
689        #[track_caller]
690        fn validate_mutation(value: Word, mutated: Option<Word>) {
691            assert!(
692                mutated.is_none() || mutated.is_some_and(|mutated| mutated != value),
693                "Mutation failed for value: {value:?}, result: {mutated:?}"
694            );
695        }
696
697        for _ in 0..100 {
698            validate_mutation(value, Word::flip_random_bit(value, 32, &mut runner));
699            validate_mutation(value, Word::mutate_interesting_byte(value, 32, &mut runner));
700            validate_mutation(value, Word::mutate_interesting_word(value, 32, &mut runner));
701            validate_mutation(value, Word::mutate_interesting_dword(value, 32, &mut runner));
702        }
703    }
704
705    #[test]
706    fn test_mutate_interesting_word_too_small_returns_none() {
707        let mut runner = TestRunner::new(Config::default());
708        let value = U256::from(123);
709        assert!(U256::mutate_interesting_word(value, 8, &mut runner).is_none());
710    }
711
712    #[test]
713    fn test_mutate_interesting_dword_too_small_returns_none() {
714        let mut runner = TestRunner::new(Config::default());
715        let value = I256::from_dec_str("123").unwrap();
716        assert!(I256::mutate_interesting_dword(value, 16, &mut runner).is_none());
717    }
718
719    #[test]
720    fn test_u256_bound() {
721        let mut runner = TestRunner::new(Config::default());
722        let min = U256::from(0u64);
723        let max = U256::from(200u64);
724        let original = U256::from(100u64);
725
726        for _ in 0..50 {
727            let result = original.bound(min, max, &mut runner);
728            assert!(result.is_some(), "Mutation should occur");
729
730            let mutated = result.unwrap();
731            assert!(mutated >= min, "Mutated value >= min");
732            assert!(mutated <= max, "Mutated value <= max");
733            assert_ne!(mutated, original, "mutated value should differ from original");
734        }
735
736        // Test bound in [min, max] range.
737        let result = original.bound(U256::MIN, U256::MAX, &mut runner);
738        assert!(result.is_some(), "Mutation should occur");
739    }
740
741    #[test]
742    fn test_i256_bound() {
743        let mut runner = TestRunner::new(Config::default());
744        let min = I256::from_dec_str("-100").unwrap();
745        let max = I256::from_dec_str("100").unwrap();
746        let original = I256::from_dec_str("10").unwrap();
747
748        for _ in 0..50 {
749            let result = original.bound(min, max, &mut runner);
750            assert!(result.is_some(), "Mutation should occur");
751
752            let mutated = result.unwrap();
753            assert!(mutated >= min, "Mutated value >= min");
754            assert!(mutated <= max, "Mutated value <= max");
755            assert_ne!(mutated, original, "Mutated value should not equal current");
756        }
757
758        // Test bound in [min, max] range.
759        let result = original.bound(I256::MIN, I256::MAX, &mut runner);
760        assert!(result.is_some(), "Mutation should occur");
761    }
762}