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            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        // Propagate carry_up until it is zero or no more bytes left
499        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
514/// Returns mutated uint value if different from the original value and if it fits in the given
515/// size, otherwise None.
516fn validate_uint_mutation(original: U256, mutated: U256, size: usize) -> Option<U256> {
517    // Early return if mutated value is the same as original value.
518    if mutated == original {
519        return None;
520    }
521
522    // Check if mutated value fits the given size.
523    let max = if size < 256 { (U256::from(1) << size) - U256::from(1) } else { U256::MAX };
524    (mutated < max).then_some(mutated)
525}
526
527/// Returns mutated int value if different from the original value and if it fits in the given size,
528/// otherwise None.
529fn validate_int_mutation(original: I256, mutated: I256, size: usize) -> Option<I256> {
530    // Early return if mutated value is the same as original value.
531    if mutated == original {
532        return None;
533    }
534
535    // Check if mutated value fits the given size.
536    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        // Test bound in [min, max] range.
684        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        // Test bound in [min, max] range.
706        let result = original.bound(I256::MIN, I256::MAX, &mut runner);
707        assert!(result.is_some(), "Mutation should occur");
708    }
709}