Skip to main content

forge/
symbolic_minimizer.rs

1use crate::result::SymbolicCounterexampleCall;
2use alloy_dyn_abi::{DynSolValue, JsonAbiExt};
3use alloy_json_abi::Function;
4use alloy_primitives::{Address, B256, Bytes, Function as SolFunction, I256, U256};
5use foundry_evm::executors::invariant::{
6    SequenceShrink, ShrinkCandidateKeys, ShrinkRun, shrink_sequence_by_removing,
7};
8use itertools::Itertools;
9
10const MAX_SUBSET_CANDIDATES_PER_PASS: usize = 256;
11
12#[derive(Clone, Debug, PartialEq, Eq, Hash)]
13struct ReplayCallKey {
14    warp: Option<U256>,
15    roll: Option<U256>,
16    sender: Address,
17    target: Address,
18    calldata: Bytes,
19    value: Option<U256>,
20}
21
22fn replay_call_key(call: &SymbolicCounterexampleCall) -> ReplayCallKey {
23    ReplayCallKey {
24        warp: call.warp,
25        roll: call.roll,
26        sender: call.sender,
27        target: call.target,
28        calldata: call.calldata.clone(),
29        value: call.value.filter(|value| !value.is_zero()),
30    }
31}
32
33fn replay_sequence_key(calls: &[SymbolicCounterexampleCall]) -> Vec<ReplayCallKey> {
34    calls.iter().map(replay_call_key).collect()
35}
36
37/// Result of deterministic single-call counterexample minimization.
38#[derive(Clone, Debug)]
39pub(crate) struct MinimizedSingleCall {
40    pub original_call: SymbolicCounterexampleCall,
41    pub minimized_call: SymbolicCounterexampleCall,
42    pub attempts: usize,
43    pub accepted: usize,
44}
45
46impl MinimizedSingleCall {
47    pub(crate) fn changed(&self) -> bool {
48        self.original_call.calldata != self.minimized_call.calldata
49    }
50}
51
52/// Result of deterministic stateful sequence counterexample minimization.
53#[derive(Clone, Debug)]
54pub(crate) struct MinimizedSequence {
55    pub original_calls: Vec<SymbolicCounterexampleCall>,
56    pub minimized_calls: Vec<SymbolicCounterexampleCall>,
57    pub attempts: usize,
58    pub accepted: usize,
59}
60
61impl MinimizedSequence {
62    #[cfg(test)]
63    pub(crate) fn changed(&self) -> bool {
64        self.original_calls != self.minimized_calls
65    }
66
67    pub(crate) fn original_calldata_bytes(&self) -> usize {
68        sequence_calldata_bytes(&self.original_calls)
69    }
70
71    pub(crate) fn minimized_calldata_bytes(&self) -> usize {
72        sequence_calldata_bytes(&self.minimized_calls)
73    }
74}
75
76fn sequence_calldata_bytes(calls: &[SymbolicCounterexampleCall]) -> usize {
77    calls.iter().map(|call| call.calldata.len()).sum()
78}
79
80/// Minimizes a replay-confirmed stateful sequence while preserving the concrete failure.
81///
82/// The caller's `still_fails` predicate must replay the whole candidate sequence and return true
83/// only when it preserves the already-confirmed failure identity.
84pub(crate) fn minimize_sequence_counterexample(
85    calls: &[SymbolicCounterexampleCall],
86    sender_candidates: &[Address],
87    max_attempts: usize,
88    mut still_fails: impl FnMut(&[SymbolicCounterexampleCall]) -> bool,
89) -> Option<MinimizedSequence> {
90    if calls.is_empty() {
91        return None;
92    }
93
94    let original_calls = calls.to_vec();
95    let mut minimizer =
96        SequenceMinimizer::new(original_calls.clone(), max_attempts, &mut still_fails);
97
98    minimizer.minimize_len();
99    minimizer.minimize_calldata();
100    minimizer.minimize_senders(sender_candidates);
101    minimizer.minimize_values();
102
103    let (minimized_calls, attempts, accepted) = minimizer.finish();
104    Some(MinimizedSequence { original_calls, minimized_calls, attempts, accepted })
105}
106
107struct SequenceMinimizer<'a> {
108    current_calls: Vec<SymbolicCounterexampleCall>,
109    tried_candidates: ShrinkCandidateKeys<Vec<ReplayCallKey>>,
110    run: ShrinkRun,
111    still_fails: &'a mut dyn FnMut(&[SymbolicCounterexampleCall]) -> bool,
112}
113
114impl<'a> SequenceMinimizer<'a> {
115    fn new(
116        current_calls: Vec<SymbolicCounterexampleCall>,
117        max_attempts: usize,
118        still_fails: &'a mut dyn FnMut(&[SymbolicCounterexampleCall]) -> bool,
119    ) -> Self {
120        let tried_candidates = ShrinkCandidateKeys::new(replay_sequence_key(&current_calls));
121        Self { current_calls, tried_candidates, run: ShrinkRun::new(max_attempts), still_fails }
122    }
123
124    const fn can_try(&self) -> bool {
125        self.run.can_try()
126    }
127
128    const fn remaining_attempts(&self) -> usize {
129        self.run.remaining_attempts()
130    }
131
132    fn finish(self) -> (Vec<SymbolicCounterexampleCall>, usize, usize) {
133        let stats = self.run.finish();
134        (self.current_calls, stats.attempts, stats.accepted)
135    }
136
137    fn try_candidate(&mut self, candidate_calls: Vec<SymbolicCounterexampleCall>) -> bool {
138        if !self.can_try() || candidate_calls == self.current_calls {
139            return false;
140        }
141
142        if !self.tried_candidates.insert(replay_sequence_key(&candidate_calls)) {
143            return false;
144        }
145
146        if self.run.try_candidate(|| (self.still_fails)(&candidate_calls)) {
147            self.current_calls = candidate_calls;
148            true
149        } else {
150            false
151        }
152    }
153
154    fn minimize_len(&mut self) {
155        if self.current_calls.len() <= 1 || !self.can_try() {
156            return;
157        }
158
159        let base_calls = self.current_calls.clone();
160        let current_calls = &mut self.current_calls;
161        let tried_candidates = &mut self.tried_candidates;
162        let still_fails = &mut self.still_fails;
163
164        shrink_sequence_by_removing(
165            base_calls.len(),
166            &mut self.run,
167            || false,
168            || {},
169            |shrinker| {
170                let candidate_calls = sequence_from_shrink(&base_calls, shrinker);
171                if candidate_calls == *current_calls {
172                    return None;
173                }
174
175                if !tried_candidates.insert(replay_sequence_key(&candidate_calls)) {
176                    return None;
177                }
178
179                if (*still_fails)(&candidate_calls) {
180                    *current_calls = candidate_calls;
181                    Some(true)
182                } else {
183                    Some(false)
184                }
185            },
186        );
187    }
188
189    fn minimize_calldata(&mut self) {
190        let mut idx = 0usize;
191        while idx < self.current_calls.len() && self.can_try() {
192            let Some(function) = self.current_calls[idx]
193                .signature
194                .as_deref()
195                .and_then(|signature| Function::parse(signature).ok())
196            else {
197                idx += 1;
198                continue;
199            };
200            let template = self.current_calls[idx].clone();
201            let remaining_attempts = self.remaining_attempts();
202            let minimized = minimize_single_call_counterexample(
203                &function,
204                &template,
205                remaining_attempts,
206                |candidate_call| {
207                    let mut candidate_calls = self.current_calls.clone();
208                    candidate_calls[idx] = candidate_call.clone();
209                    self.try_candidate(candidate_calls)
210                },
211            );
212            if let Some(minimized) = minimized
213                && minimized.changed()
214                && let Some(call) = self.current_calls.get_mut(idx)
215            {
216                *call = minimized.minimized_call;
217            }
218            idx += 1;
219        }
220    }
221
222    fn minimize_senders(&mut self, sender_candidates: &[Address]) {
223        let mut idx = 0usize;
224        while idx < self.current_calls.len() && self.can_try() {
225            for sender in sender_candidates.iter().copied() {
226                if !self.can_try() || self.current_calls[idx].sender == sender {
227                    continue;
228                }
229                let mut candidate_calls = self.current_calls.clone();
230                candidate_calls[idx].sender = sender;
231                self.try_candidate(candidate_calls);
232            }
233            idx += 1;
234        }
235    }
236
237    fn minimize_values(&mut self) {
238        let mut idx = 0usize;
239        while idx < self.current_calls.len() && self.can_try() {
240            self.minimize_call_value(idx);
241            idx += 1;
242        }
243    }
244
245    fn minimize_call_value(&mut self, idx: usize) {
246        let Some(mut accepted_value) = self.current_calls[idx].value else {
247            return;
248        };
249
250        let mut zero_candidate = self.current_calls.clone();
251        zero_candidate[idx].value = None;
252        if self.try_candidate(zero_candidate) {
253            return;
254        }
255
256        if accepted_value.is_zero() {
257            return;
258        }
259
260        let mut rejected_value = U256::ZERO;
261        while accepted_value > rejected_value + U256::ONE && self.can_try() {
262            let candidate_value = rejected_value + ((accepted_value - rejected_value) >> 1usize);
263            let mut candidate_calls = self.current_calls.clone();
264            candidate_calls[idx].value = Some(candidate_value);
265            if self.try_candidate(candidate_calls) {
266                accepted_value = candidate_value;
267            } else {
268                rejected_value = candidate_value;
269            }
270        }
271    }
272}
273
274fn sequence_from_shrink(
275    calls: &[SymbolicCounterexampleCall],
276    shrinker: &SequenceShrink,
277) -> Vec<SymbolicCounterexampleCall> {
278    shrinker.apply_with_accumulated_delay(
279        calls,
280        |call| (call.warp, call.roll),
281        |mut call, warp, roll| {
282            if !warp.is_zero() {
283                call.warp = Some(warp);
284            }
285            if !roll.is_zero() {
286                call.roll = Some(roll);
287            }
288            call
289        },
290    )
291}
292
293/// Minimizes a stateless symbolic counterexample with ABI-valid candidates only.
294///
295/// `still_fails` must concretely replay the candidate and return `true` only when it preserves the
296/// already-confirmed failure.
297pub(crate) fn minimize_single_call_counterexample(
298    function: &Function,
299    call: &SymbolicCounterexampleCall,
300    max_attempts: usize,
301    mut still_fails: impl FnMut(&SymbolicCounterexampleCall) -> bool,
302) -> Option<MinimizedSingleCall> {
303    if call.calldata.get(..4).is_none_or(|selector| selector != function.selector()) {
304        return None;
305    }
306
307    let original_args = function.abi_decode_input(&call.calldata[4..]).ok()?;
308    let mut current_args = original_args;
309    let mut current_call = call_with_args(function, call, &current_args)?;
310    let mut run = ShrinkRun::new(max_attempts);
311    let mut tried_calldata = ShrinkCandidateKeys::new(current_call.calldata.clone());
312
313    let mut try_args = |candidate_args: &[DynSolValue]| {
314        if !run.can_try() {
315            return false;
316        }
317        let Some(candidate_call) = call_with_args(function, call, candidate_args) else {
318            return false;
319        };
320        if candidate_call.calldata == current_call.calldata {
321            return false;
322        }
323
324        if !tried_calldata.insert(candidate_call.calldata.clone()) {
325            return false;
326        }
327
328        if run.try_candidate(|| still_fails(&candidate_call)) {
329            current_call = candidate_call;
330            true
331        } else {
332            false
333        }
334    };
335    minimize_values_batch(&mut current_args, &mut try_args);
336    minimize_value_subsets(&mut current_args, &mut try_args);
337    minimize_value_pairs(&mut current_args, &mut try_args);
338    minimize_values(&mut current_args, &mut try_args);
339
340    current_call = with_formatted_args(current_call, &current_args);
341    let stats = run.finish();
342
343    Some(MinimizedSingleCall {
344        original_call: call.clone(),
345        minimized_call: current_call,
346        attempts: stats.attempts,
347        accepted: stats.accepted,
348    })
349}
350
351fn call_with_args(
352    function: &Function,
353    template: &SymbolicCounterexampleCall,
354    args: &[DynSolValue],
355) -> Option<SymbolicCounterexampleCall> {
356    let calldata = Bytes::from(function.abi_encode_input(args).ok()?);
357    Some(SymbolicCounterexampleCall { calldata, args: None, raw_args: None, ..template.clone() })
358}
359
360fn with_formatted_args(
361    mut call: SymbolicCounterexampleCall,
362    args: &[DynSolValue],
363) -> SymbolicCounterexampleCall {
364    call.args = Some(foundry_common::fmt::format_tokens(args).format(", ").to_string());
365    call.raw_args = Some(foundry_common::fmt::format_tokens_raw(args).format(", ").to_string());
366    call
367}
368
369fn minimize_values_batch(
370    values: &mut Vec<DynSolValue>,
371    try_values: &mut dyn FnMut(&[DynSolValue]) -> bool,
372) -> bool {
373    let candidate_values = values.iter().cloned().map(minimally_simple_value).collect::<Vec<_>>();
374    if candidate_values == *values {
375        return false;
376    }
377    if try_values(&candidate_values) {
378        *values = candidate_values;
379        true
380    } else {
381        false
382    }
383}
384
385fn minimize_value_subsets(
386    values: &mut Vec<DynSolValue>,
387    try_values: &mut dyn FnMut(&[DynSolValue]) -> bool,
388) -> bool {
389    let mut changed = false;
390    loop {
391        let simple_values = values.iter().cloned().map(minimally_simple_value).collect::<Vec<_>>();
392        let shrinkable_idxs = values
393            .iter()
394            .zip(&simple_values)
395            .enumerate()
396            .filter_map(|(idx, (current, simple))| (current != simple).then_some(idx))
397            .collect::<Vec<_>>();
398        if shrinkable_idxs.len() < 2 {
399            break;
400        }
401
402        let mut pass_changed = false;
403        for subset_size in subset_sizes(shrinkable_idxs.len()) {
404            let mut subset = Vec::with_capacity(subset_size);
405            if try_value_subset(
406                values,
407                &simple_values,
408                &shrinkable_idxs,
409                subset_size,
410                0,
411                &mut subset,
412                try_values,
413            ) {
414                pass_changed = true;
415                break;
416            }
417        }
418
419        if !pass_changed {
420            break;
421        }
422        changed = true;
423    }
424    changed
425}
426
427fn try_value_subset(
428    values: &mut Vec<DynSolValue>,
429    simple_values: &[DynSolValue],
430    shrinkable_idxs: &[usize],
431    subset_size: usize,
432    start: usize,
433    subset: &mut Vec<usize>,
434    try_values: &mut dyn FnMut(&[DynSolValue]) -> bool,
435) -> bool {
436    if subset.len() == subset_size {
437        let mut candidate_values = values.clone();
438        for idx in subset.iter().copied() {
439            candidate_values[idx] = simple_values[idx].clone();
440        }
441        if try_values(&candidate_values) {
442            *values = candidate_values;
443            return true;
444        }
445        return false;
446    }
447
448    let remaining = subset_size - subset.len();
449    for choice_idx in start..=shrinkable_idxs.len() - remaining {
450        subset.push(shrinkable_idxs[choice_idx]);
451        if try_value_subset(
452            values,
453            simple_values,
454            shrinkable_idxs,
455            subset_size,
456            choice_idx + 1,
457            subset,
458            try_values,
459        ) {
460            return true;
461        }
462        subset.pop();
463    }
464    false
465}
466
467fn minimally_simple_value(mut value: DynSolValue) -> DynSolValue {
468    minimize_value(&mut value, &mut |_| true);
469    value
470}
471
472fn minimize_u256_pair_candidates(
473    current_left: U256,
474    current_right: U256,
475    mut try_candidate: impl FnMut(U256, U256) -> bool,
476) -> bool {
477    if current_left.is_zero() || current_right.is_zero() {
478        return false;
479    }
480
481    let mut accepted_left = current_left;
482    let mut accepted_right = current_right;
483    let mut rejected_left = U256::ZERO;
484    let mut rejected_right = U256::ZERO;
485    let mut changed = false;
486    while accepted_left > rejected_left + U256::ONE || accepted_right > rejected_right + U256::ONE {
487        let candidate_left = if accepted_left > rejected_left + U256::ONE {
488            rejected_left + ((accepted_left - rejected_left) >> 1usize)
489        } else {
490            accepted_left
491        };
492        let candidate_right = if accepted_right > rejected_right + U256::ONE {
493            rejected_right + ((accepted_right - rejected_right) >> 1usize)
494        } else {
495            accepted_right
496        };
497
498        if try_candidate(candidate_left, candidate_right) {
499            accepted_left = candidate_left;
500            accepted_right = candidate_right;
501            changed = true;
502        } else {
503            rejected_left = candidate_left;
504            rejected_right = candidate_right;
505        }
506    }
507    changed
508}
509
510fn minimize_values(values: &mut [DynSolValue], try_values: &mut dyn FnMut(&[DynSolValue]) -> bool) {
511    loop {
512        let mut changed = false;
513        for idx in 0..values.len() {
514            let mut value = values[idx].clone();
515            let value_changed = minimize_value(&mut value, &mut |candidate| {
516                let mut candidate_values = values.to_vec();
517                candidate_values[idx] = candidate.clone();
518                try_values(&candidate_values)
519            });
520            if value_changed {
521                values[idx] = value;
522                changed = true;
523            }
524        }
525        if !changed {
526            break;
527        }
528    }
529}
530
531fn minimize_value_pairs(
532    values: &mut Vec<DynSolValue>,
533    try_values: &mut dyn FnMut(&[DynSolValue]) -> bool,
534) -> bool {
535    let mut changed = false;
536    loop {
537        let mut pass_changed = false;
538        for left_idx in 0..values.len() {
539            for right_idx in left_idx + 1..values.len() {
540                let left = values[left_idx].clone();
541                let right = values[right_idx].clone();
542                if minimize_numeric_value_pair(&left, &right, |left, right| {
543                    let mut candidate_values = values.clone();
544                    candidate_values[left_idx] = left;
545                    candidate_values[right_idx] = right;
546                    if try_values(&candidate_values) {
547                        *values = candidate_values;
548                        true
549                    } else {
550                        false
551                    }
552                }) {
553                    pass_changed = true;
554                    break;
555                }
556            }
557            if pass_changed {
558                break;
559            }
560        }
561        if !pass_changed {
562            break;
563        }
564        changed = true;
565    }
566    changed
567}
568
569fn minimize_value(
570    value: &mut DynSolValue,
571    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
572) -> bool {
573    let mut changed = false;
574    loop {
575        let pass_changed =
576            minimize_scalar_value(value, try_value) || minimize_compound_value(value, try_value);
577        if !pass_changed {
578            break;
579        }
580        changed = true;
581    }
582    changed
583}
584
585fn minimize_scalar_value(
586    value: &mut DynSolValue,
587    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
588) -> bool {
589    match value.clone() {
590        DynSolValue::Bool(true) => accept_candidate(value, DynSolValue::Bool(false), try_value),
591        DynSolValue::Bool(false) => false,
592        DynSolValue::Uint(current, bits) => minimize_uint(value, current, bits, try_value),
593        DynSolValue::Int(current, bits) => minimize_int(value, current, bits, try_value),
594        DynSolValue::Address(current) => minimize_address(value, current, try_value),
595        DynSolValue::FixedBytes(current, size) => {
596            minimize_fixed_bytes(value, current, size, try_value)
597        }
598        DynSolValue::Function(current) => {
599            if current == SolFunction::ZERO {
600                false
601            } else {
602                accept_candidate(value, DynSolValue::Function(SolFunction::ZERO), try_value)
603            }
604        }
605        DynSolValue::Bytes(current) => minimize_bytes(value, current, try_value),
606        DynSolValue::String(current) => minimize_string(value, current, try_value),
607        DynSolValue::Array(_)
608        | DynSolValue::FixedArray(_)
609        | DynSolValue::Tuple(_)
610        | DynSolValue::CustomStruct { .. } => false,
611    }
612}
613
614fn minimize_compound_value(
615    value: &mut DynSolValue,
616    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
617) -> bool {
618    match value.clone() {
619        DynSolValue::Array(mut elements) => {
620            if minimize_array_len(value, &mut elements, try_value) {
621                return true;
622            }
623            if let Some(candidate) = minimize_elements_batch(
624                &mut elements,
625                |items| DynSolValue::Array(items.to_vec()),
626                try_value,
627            ) {
628                *value = candidate;
629                return true;
630            }
631            if let Some(candidate) = minimize_element_subsets(
632                &mut elements,
633                |items| DynSolValue::Array(items.to_vec()),
634                try_value,
635            ) {
636                *value = candidate;
637                return true;
638            }
639            if let Some(candidate) = minimize_element_pairs(
640                &mut elements,
641                |items| DynSolValue::Array(items.to_vec()),
642                try_value,
643            ) {
644                *value = candidate;
645                return true;
646            }
647            minimize_elements(&mut elements, |items| DynSolValue::Array(items.to_vec()), try_value)
648                .is_some_and(|candidate| {
649                    *value = candidate;
650                    true
651                })
652        }
653        DynSolValue::FixedArray(mut elements) => {
654            if let Some(candidate) = minimize_elements_batch(
655                &mut elements,
656                |items| DynSolValue::FixedArray(items.to_vec()),
657                try_value,
658            ) {
659                *value = candidate;
660                return true;
661            }
662            if let Some(candidate) = minimize_element_subsets(
663                &mut elements,
664                |items| DynSolValue::FixedArray(items.to_vec()),
665                try_value,
666            ) {
667                *value = candidate;
668                return true;
669            }
670            if let Some(candidate) = minimize_element_pairs(
671                &mut elements,
672                |items| DynSolValue::FixedArray(items.to_vec()),
673                try_value,
674            ) {
675                *value = candidate;
676                return true;
677            }
678            minimize_elements(
679                &mut elements,
680                |items| DynSolValue::FixedArray(items.to_vec()),
681                try_value,
682            )
683            .is_some_and(|candidate| {
684                *value = candidate;
685                true
686            })
687        }
688        DynSolValue::Tuple(mut elements) => {
689            if let Some(candidate) = minimize_elements_batch(
690                &mut elements,
691                |items| DynSolValue::Tuple(items.to_vec()),
692                try_value,
693            ) {
694                *value = candidate;
695                return true;
696            }
697            if let Some(candidate) = minimize_element_subsets(
698                &mut elements,
699                |items| DynSolValue::Tuple(items.to_vec()),
700                try_value,
701            ) {
702                *value = candidate;
703                return true;
704            }
705            if let Some(candidate) = minimize_element_pairs(
706                &mut elements,
707                |items| DynSolValue::Tuple(items.to_vec()),
708                try_value,
709            ) {
710                *value = candidate;
711                return true;
712            }
713            minimize_elements(&mut elements, |items| DynSolValue::Tuple(items.to_vec()), try_value)
714                .is_some_and(|candidate| {
715                    *value = candidate;
716                    true
717                })
718        }
719        DynSolValue::CustomStruct { name, prop_names, mut tuple } => {
720            if let Some(candidate) = minimize_elements_batch(
721                &mut tuple,
722                |items| DynSolValue::CustomStruct {
723                    name: name.clone(),
724                    prop_names: prop_names.clone(),
725                    tuple: items.to_vec(),
726                },
727                try_value,
728            ) {
729                *value = candidate;
730                return true;
731            }
732            if let Some(candidate) = minimize_element_subsets(
733                &mut tuple,
734                |items| DynSolValue::CustomStruct {
735                    name: name.clone(),
736                    prop_names: prop_names.clone(),
737                    tuple: items.to_vec(),
738                },
739                try_value,
740            ) {
741                *value = candidate;
742                return true;
743            }
744            if let Some(candidate) = minimize_element_pairs(
745                &mut tuple,
746                |items| DynSolValue::CustomStruct {
747                    name: name.clone(),
748                    prop_names: prop_names.clone(),
749                    tuple: items.to_vec(),
750                },
751                try_value,
752            ) {
753                *value = candidate;
754                return true;
755            }
756            minimize_elements(
757                &mut tuple,
758                |items| DynSolValue::CustomStruct {
759                    name: name.clone(),
760                    prop_names: prop_names.clone(),
761                    tuple: items.to_vec(),
762                },
763                try_value,
764            )
765            .is_some_and(|candidate| {
766                *value = candidate;
767                true
768            })
769        }
770        _ => false,
771    }
772}
773
774fn minimize_uint(
775    value: &mut DynSolValue,
776    current: U256,
777    bits: usize,
778    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
779) -> bool {
780    if !current.is_zero() && accept_candidate(value, DynSolValue::Uint(U256::ZERO, bits), try_value)
781    {
782        return true;
783    }
784
785    let one = U256::ONE;
786    if current > one && accept_candidate(value, DynSolValue::Uint(one, bits), try_value) {
787        return true;
788    }
789
790    let bit_limit = bits.min(256);
791    for bit in (0..bit_limit).rev() {
792        let mask = U256::ONE << bit;
793        if (current & mask).is_zero() {
794            continue;
795        }
796        let candidate = current & !mask;
797        if accept_candidate(value, DynSolValue::Uint(candidate, bits), try_value) {
798            return true;
799        }
800    }
801
802    minimize_uint_by_search(value, current, bits, try_value)
803}
804
805fn minimize_uint_by_search(
806    value: &mut DynSolValue,
807    current: U256,
808    bits: usize,
809    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
810) -> bool {
811    if current <= U256::ONE {
812        return false;
813    }
814
815    let mut accepted = current;
816    let mut rejected = U256::ZERO;
817    let mut changed = false;
818    while accepted > rejected + U256::ONE {
819        let candidate: U256 = rejected + ((accepted - rejected) >> 1usize);
820        if accept_candidate(value, DynSolValue::Uint(candidate, bits), try_value) {
821            accepted = candidate;
822            changed = true;
823        } else {
824            rejected = candidate;
825        }
826    }
827
828    changed
829}
830
831fn minimize_int(
832    value: &mut DynSolValue,
833    current: I256,
834    bits: usize,
835    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
836) -> bool {
837    if current != I256::ZERO
838        && accept_candidate(value, DynSolValue::Int(I256::ZERO, bits), try_value)
839    {
840        return true;
841    }
842    if current.is_negative()
843        && current != I256::MINUS_ONE
844        && accept_candidate(value, DynSolValue::Int(I256::MINUS_ONE, bits), try_value)
845    {
846        return true;
847    }
848
849    minimize_int_by_search(value, current, bits, try_value)
850}
851
852fn minimize_int_by_search(
853    value: &mut DynSolValue,
854    current: I256,
855    bits: usize,
856    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
857) -> bool {
858    let mut accepted_abs = current.unsigned_abs();
859    if accepted_abs <= U256::ONE {
860        return false;
861    }
862
863    let mut rejected_abs = U256::ZERO;
864    let mut changed = false;
865    while accepted_abs > rejected_abs + U256::ONE {
866        let candidate_abs: U256 = rejected_abs + ((accepted_abs - rejected_abs) >> 1usize);
867        let candidate = signed_candidate_with_abs(current, candidate_abs);
868        if accept_candidate(value, DynSolValue::Int(candidate, bits), try_value) {
869            accepted_abs = candidate_abs;
870            changed = true;
871        } else {
872            rejected_abs = candidate_abs;
873        }
874    }
875
876    changed
877}
878
879fn minimize_address(
880    value: &mut DynSolValue,
881    current: Address,
882    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
883) -> bool {
884    for candidate in address_candidates(current) {
885        if accept_candidate(value, DynSolValue::Address(candidate), try_value) {
886            return true;
887        }
888    }
889    false
890}
891
892fn address_candidates(current: Address) -> Vec<Address> {
893    if current.is_zero() {
894        return Vec::new();
895    }
896
897    let mut candidates = Vec::new();
898    candidates.push(Address::ZERO);
899
900    let deadbeef = Address::from_word(B256::from(U256::from(0xdeadbeefu64)));
901    if current != deadbeef {
902        candidates.push(deadbeef);
903    }
904
905    let bytes = current.into_array();
906    for idx in 0..bytes.len() {
907        if bytes[idx] == 0 {
908            continue;
909        }
910        let mut candidate = bytes;
911        candidate[idx] = 0;
912        candidates.push(Address::from(candidate));
913    }
914
915    candidates
916}
917
918fn minimize_fixed_bytes(
919    value: &mut DynSolValue,
920    current: B256,
921    size: usize,
922    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
923) -> bool {
924    if !current.is_zero()
925        && accept_candidate(value, DynSolValue::FixedBytes(B256::ZERO, size), try_value)
926    {
927        return true;
928    }
929
930    let mut bytes = [0u8; 32];
931    bytes.copy_from_slice(current.as_slice());
932    for idx in (0..size.min(bytes.len())).rev() {
933        if bytes[idx] == 0 {
934            continue;
935        }
936        let mut candidate = bytes;
937        candidate[idx] = 0;
938        if accept_candidate(value, DynSolValue::FixedBytes(B256::from(candidate), size), try_value)
939        {
940            return true;
941        }
942    }
943    false
944}
945
946fn minimize_bytes(
947    value: &mut DynSolValue,
948    current: Vec<u8>,
949    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
950) -> bool {
951    for len in 0..current.len() {
952        if accept_candidate(value, DynSolValue::Bytes(current[..len].to_vec()), try_value) {
953            return true;
954        }
955    }
956    if try_delete_vec_range(&current, |candidate| {
957        accept_candidate(value, DynSolValue::Bytes(candidate), try_value)
958    }) {
959        return true;
960    }
961    if try_slice_vec_range(&current, |candidate| {
962        accept_candidate(value, DynSolValue::Bytes(candidate), try_value)
963    }) {
964        return true;
965    }
966
967    for idx in (0..current.len()).rev() {
968        if current[idx] == 0 {
969            continue;
970        }
971        let mut candidate = current.clone();
972        candidate[idx] = 0;
973        if accept_candidate(value, DynSolValue::Bytes(candidate), try_value) {
974            return true;
975        }
976    }
977    false
978}
979
980fn minimize_string(
981    value: &mut DynSolValue,
982    current: String,
983    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
984) -> bool {
985    for len in 0..current.len() {
986        if current.is_char_boundary(len)
987            && accept_candidate(value, DynSolValue::String(current[..len].to_string()), try_value)
988        {
989            return true;
990        }
991    }
992    if try_delete_string_range(&current, |candidate| {
993        accept_candidate(value, DynSolValue::String(candidate), try_value)
994    }) {
995        return true;
996    }
997    if try_slice_string_range(&current, |candidate| {
998        accept_candidate(value, DynSolValue::String(candidate), try_value)
999    }) {
1000        return true;
1001    }
1002
1003    let current_bytes = current.as_bytes();
1004    for idx in (0..current_bytes.len()).rev() {
1005        if current_bytes[idx] == 0 {
1006            continue;
1007        }
1008        let mut candidate = current_bytes.to_vec();
1009        candidate[idx] = 0;
1010        if let Ok(candidate) = String::from_utf8(candidate)
1011            && accept_candidate(value, DynSolValue::String(candidate), try_value)
1012        {
1013            return true;
1014        }
1015    }
1016    false
1017}
1018
1019fn minimize_array_len(
1020    value: &mut DynSolValue,
1021    current: &mut Vec<DynSolValue>,
1022    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1023) -> bool {
1024    for len in 0..current.len() {
1025        let candidate = DynSolValue::Array(current[..len].to_vec());
1026        if accept_candidate(value, candidate, try_value) {
1027            current.truncate(len);
1028            return true;
1029        }
1030    }
1031    if try_delete_vec_range(current, |candidate| {
1032        accept_candidate(value, DynSolValue::Array(candidate), try_value)
1033    }) {
1034        return true;
1035    }
1036    if try_slice_vec_range(current, |candidate| {
1037        accept_candidate(value, DynSolValue::Array(candidate), try_value)
1038    }) {
1039        return true;
1040    }
1041    false
1042}
1043
1044fn try_delete_vec_range<T: Clone>(
1045    current: &[T],
1046    mut try_candidate: impl FnMut(Vec<T>) -> bool,
1047) -> bool {
1048    for range_len in deletion_lengths(current.len()) {
1049        for start in 0..=current.len() - range_len {
1050            let mut candidate = Vec::with_capacity(current.len() - range_len);
1051            candidate.extend_from_slice(&current[..start]);
1052            candidate.extend_from_slice(&current[start + range_len..]);
1053            if try_candidate(candidate) {
1054                return true;
1055            }
1056        }
1057    }
1058    false
1059}
1060
1061fn try_delete_string_range(current: &str, mut try_candidate: impl FnMut(String) -> bool) -> bool {
1062    let mut boundaries = current.char_indices().map(|(idx, _)| idx).collect::<Vec<_>>();
1063    boundaries.push(current.len());
1064
1065    for range_len in deletion_lengths(boundaries.len().saturating_sub(1)) {
1066        for start_idx in 0..=boundaries.len() - range_len - 1 {
1067            let start = boundaries[start_idx];
1068            let end = boundaries[start_idx + range_len];
1069            let mut candidate = String::with_capacity(current.len() - (end - start));
1070            candidate.push_str(&current[..start]);
1071            candidate.push_str(&current[end..]);
1072            if try_candidate(candidate) {
1073                return true;
1074            }
1075        }
1076    }
1077    false
1078}
1079
1080fn try_slice_vec_range<T: Clone>(
1081    current: &[T],
1082    mut try_candidate: impl FnMut(Vec<T>) -> bool,
1083) -> bool {
1084    for len in 1..current.len() {
1085        for start in 1..=current.len() - len {
1086            let candidate = current[start..start + len].to_vec();
1087            if try_candidate(candidate) {
1088                return true;
1089            }
1090        }
1091    }
1092    false
1093}
1094
1095fn try_slice_string_range(current: &str, mut try_candidate: impl FnMut(String) -> bool) -> bool {
1096    let mut boundaries = current.char_indices().map(|(idx, _)| idx).collect::<Vec<_>>();
1097    boundaries.push(current.len());
1098    let char_len = boundaries.len().saturating_sub(1);
1099
1100    for len in 1..char_len {
1101        for start_idx in 1..=char_len - len {
1102            let start = boundaries[start_idx];
1103            let end = boundaries[start_idx + len];
1104            if try_candidate(current[start..end].to_string()) {
1105                return true;
1106            }
1107        }
1108    }
1109    false
1110}
1111
1112fn deletion_lengths(len: usize) -> Vec<usize> {
1113    if len == 0 {
1114        return Vec::new();
1115    }
1116
1117    let mut lengths = Vec::new();
1118    let mut range_len = len;
1119    while range_len > 0 {
1120        lengths.push(range_len);
1121        range_len /= 2;
1122    }
1123    lengths.sort_unstable();
1124    lengths.dedup();
1125    lengths.reverse();
1126    lengths
1127}
1128
1129fn minimize_elements(
1130    elements: &mut [DynSolValue],
1131    rebuild: impl Fn(&[DynSolValue]) -> DynSolValue,
1132    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1133) -> Option<DynSolValue> {
1134    for idx in 0..elements.len() {
1135        let mut element = elements[idx].clone();
1136        let changed = minimize_value(&mut element, &mut |candidate| {
1137            let mut candidate_elements = elements.to_vec();
1138            candidate_elements[idx] = candidate.clone();
1139            try_value(&rebuild(&candidate_elements))
1140        });
1141        if changed {
1142            elements[idx] = element;
1143            return Some(rebuild(elements));
1144        }
1145    }
1146    None
1147}
1148
1149fn minimize_elements_batch(
1150    elements: &mut Vec<DynSolValue>,
1151    rebuild: impl Fn(&[DynSolValue]) -> DynSolValue,
1152    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1153) -> Option<DynSolValue> {
1154    let simple_elements = elements.iter().cloned().map(minimally_simple_value).collect::<Vec<_>>();
1155    if simple_elements == *elements {
1156        return None;
1157    }
1158    let candidate = rebuild(&simple_elements);
1159    try_value(&candidate).then(|| {
1160        *elements = simple_elements;
1161        candidate
1162    })
1163}
1164
1165fn minimize_element_pairs(
1166    elements: &mut Vec<DynSolValue>,
1167    rebuild: impl Fn(&[DynSolValue]) -> DynSolValue,
1168    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1169) -> Option<DynSolValue> {
1170    for left_idx in 0..elements.len() {
1171        for right_idx in left_idx + 1..elements.len() {
1172            let left = elements[left_idx].clone();
1173            let right = elements[right_idx].clone();
1174            if minimize_numeric_value_pair(&left, &right, |left, right| {
1175                let mut candidate_elements = elements.clone();
1176                candidate_elements[left_idx] = left;
1177                candidate_elements[right_idx] = right;
1178                if try_value(&rebuild(&candidate_elements)) {
1179                    *elements = candidate_elements;
1180                    true
1181                } else {
1182                    false
1183                }
1184            }) {
1185                return Some(rebuild(elements));
1186            }
1187        }
1188    }
1189    None
1190}
1191
1192fn minimize_element_subsets(
1193    elements: &mut Vec<DynSolValue>,
1194    rebuild: impl Fn(&[DynSolValue]) -> DynSolValue,
1195    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1196) -> Option<DynSolValue> {
1197    let simple_elements = elements.iter().cloned().map(minimally_simple_value).collect::<Vec<_>>();
1198    let shrinkable_idxs = elements
1199        .iter()
1200        .zip(&simple_elements)
1201        .enumerate()
1202        .filter_map(|(idx, (current, simple))| (current != simple).then_some(idx))
1203        .collect::<Vec<_>>();
1204    if shrinkable_idxs.len() < 2 {
1205        return None;
1206    }
1207
1208    for subset_size in subset_sizes(shrinkable_idxs.len()) {
1209        let mut search = ElementSubsetSearch {
1210            elements,
1211            simple_elements: &simple_elements,
1212            shrinkable_idxs: &shrinkable_idxs,
1213            rebuild: &rebuild,
1214            try_value,
1215        };
1216        let mut subset = Vec::with_capacity(subset_size);
1217        if let Some(candidate_elements) =
1218            try_element_subset(&mut search, subset_size, 0, &mut subset)
1219        {
1220            *elements = candidate_elements.clone();
1221            return Some(rebuild(&candidate_elements));
1222        }
1223    }
1224    None
1225}
1226
1227fn minimize_numeric_value_pair(
1228    left: &DynSolValue,
1229    right: &DynSolValue,
1230    mut try_pair: impl FnMut(DynSolValue, DynSolValue) -> bool,
1231) -> bool {
1232    match (left, right) {
1233        (DynSolValue::Uint(left, left_bits), DynSolValue::Uint(right, right_bits)) => {
1234            let mut try_uint_pair = |left, right| {
1235                try_pair(DynSolValue::Uint(left, *left_bits), DynSolValue::Uint(right, *right_bits))
1236            };
1237            minimize_u256_pair_delta_candidates(*left, *right, &mut try_uint_pair)
1238        }
1239        (DynSolValue::Int(left, left_bits), DynSolValue::Int(right, right_bits)) => {
1240            minimize_i256_pair_candidates(*left, *right, |left, right| {
1241                try_pair(DynSolValue::Int(left, *left_bits), DynSolValue::Int(right, *right_bits))
1242            })
1243        }
1244        _ => false,
1245    }
1246}
1247
1248fn minimize_u256_pair_delta_candidates(
1249    current_left: U256,
1250    current_right: U256,
1251    try_candidate: &mut impl FnMut(U256, U256) -> bool,
1252) -> bool {
1253    let one = U256::ONE;
1254    if (current_left, current_right) != (one, one)
1255        && !current_left.is_zero()
1256        && !current_right.is_zero()
1257        && try_candidate(one, one)
1258    {
1259        return true;
1260    }
1261
1262    match current_left.cmp(&current_right) {
1263        std::cmp::Ordering::Equal => {
1264            !current_left.is_zero() && try_candidate(U256::ZERO, U256::ZERO)
1265        }
1266        std::cmp::Ordering::Greater => {
1267            let delta = current_left - current_right;
1268            !current_right.is_zero() && try_candidate(delta, U256::ZERO)
1269        }
1270        std::cmp::Ordering::Less => {
1271            let delta = current_right - current_left;
1272            !current_left.is_zero() && try_candidate(U256::ZERO, delta)
1273        }
1274    }
1275}
1276
1277fn minimize_i256_pair_candidates(
1278    current_left: I256,
1279    current_right: I256,
1280    mut try_candidate: impl FnMut(I256, I256) -> bool,
1281) -> bool {
1282    if current_left == I256::ZERO || current_right == I256::ZERO {
1283        return false;
1284    }
1285    if current_left.is_negative() != current_right.is_negative() {
1286        return false;
1287    }
1288
1289    minimize_u256_pair_candidates(
1290        current_left.unsigned_abs(),
1291        current_right.unsigned_abs(),
1292        |left_abs, right_abs| {
1293            let left = signed_candidate_with_abs(current_left, left_abs);
1294            let right = signed_candidate_with_abs(current_right, right_abs);
1295            try_candidate(left, right)
1296        },
1297    )
1298}
1299
1300const fn signed_candidate_with_abs(current: I256, abs: U256) -> I256 {
1301    if current.is_negative() { I256::from_raw(abs.wrapping_neg()) } else { I256::from_raw(abs) }
1302}
1303
1304fn subset_sizes(shrinkable_len: usize) -> Vec<usize> {
1305    let mut sizes = Vec::new();
1306    for subset_size in 2..shrinkable_len {
1307        if bounded_combination_count(shrinkable_len, subset_size, MAX_SUBSET_CANDIDATES_PER_PASS)
1308            .is_some()
1309        {
1310            sizes.push(subset_size);
1311        }
1312    }
1313    sizes
1314}
1315
1316fn bounded_combination_count(n: usize, k: usize, max: usize) -> Option<usize> {
1317    if k > n {
1318        return None;
1319    }
1320    let k = k.min(n - k);
1321    let mut count = 1usize;
1322    for step in 1..=k {
1323        count = count.checked_mul(n + 1 - step)?;
1324        count /= step;
1325        if count > max {
1326            return None;
1327        }
1328    }
1329    Some(count)
1330}
1331
1332struct ElementSubsetSearch<'a, R>
1333where
1334    R: Fn(&[DynSolValue]) -> DynSolValue,
1335{
1336    elements: &'a [DynSolValue],
1337    simple_elements: &'a [DynSolValue],
1338    shrinkable_idxs: &'a [usize],
1339    rebuild: &'a R,
1340    try_value: &'a mut dyn FnMut(&DynSolValue) -> bool,
1341}
1342
1343fn try_element_subset<R>(
1344    search: &mut ElementSubsetSearch<'_, R>,
1345    subset_size: usize,
1346    start: usize,
1347    subset: &mut Vec<usize>,
1348) -> Option<Vec<DynSolValue>>
1349where
1350    R: Fn(&[DynSolValue]) -> DynSolValue,
1351{
1352    if subset.len() == subset_size {
1353        let mut candidate_elements = search.elements.to_vec();
1354        for idx in subset.iter().copied() {
1355            candidate_elements[idx] = search.simple_elements[idx].clone();
1356        }
1357        if (search.try_value)(&(search.rebuild)(&candidate_elements)) {
1358            return Some(candidate_elements);
1359        }
1360        return None;
1361    }
1362
1363    let remaining = subset_size - subset.len();
1364    for choice_idx in start..=search.shrinkable_idxs.len() - remaining {
1365        subset.push(search.shrinkable_idxs[choice_idx]);
1366        if let Some(candidate) = try_element_subset(search, subset_size, choice_idx + 1, subset) {
1367            return Some(candidate);
1368        }
1369        subset.pop();
1370    }
1371    None
1372}
1373
1374fn accept_candidate(
1375    value: &mut DynSolValue,
1376    candidate: DynSolValue,
1377    try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1378) -> bool {
1379    if *value == candidate {
1380        return false;
1381    }
1382    if try_value(&candidate) {
1383        *value = candidate;
1384        true
1385    } else {
1386        false
1387    }
1388}
1389
1390#[cfg(test)]
1391mod tests {
1392    use super::*;
1393    use alloy_json_abi::JsonAbi;
1394    use std::collections::HashSet;
1395
1396    const TEST_MAX_MINIMIZATION_ATTEMPTS: usize = 5000;
1397
1398    fn call(function: &Function, args: Vec<DynSolValue>) -> SymbolicCounterexampleCall {
1399        SymbolicCounterexampleCall {
1400            warp: None,
1401            roll: None,
1402            sender: Address::ZERO,
1403            target: Address::repeat_byte(0x11),
1404            calldata: Bytes::from(function.abi_encode_input(&args).unwrap()),
1405            value: Some(U256::ZERO),
1406            contract_name: Some("Target".to_string()),
1407            function_name: Some(function.name.clone()),
1408            signature: Some(function.signature()),
1409            args: Some(foundry_common::fmt::format_tokens(&args).format(", ").to_string()),
1410            raw_args: Some(foundry_common::fmt::format_tokens_raw(&args).format(", ").to_string()),
1411        }
1412    }
1413
1414    fn decoded(function: &Function, call: &SymbolicCounterexampleCall) -> Vec<DynSolValue> {
1415        function.abi_decode_input(&call.calldata[4..]).unwrap()
1416    }
1417
1418    fn address(value: u64) -> Address {
1419        Address::from_word(B256::from(U256::from(value)))
1420    }
1421
1422    #[test]
1423    fn minimizes_common_abi_values_with_replay_predicate() {
1424        let abi =
1425            JsonAbi::parse(["function check(uint256,address,bytes,string,uint256[]) external"])
1426                .unwrap();
1427        let function = abi.functions().next().unwrap();
1428        let start = call(
1429            function,
1430            vec![
1431                DynSolValue::Uint(U256::from(0xff), 256),
1432                DynSolValue::Address(Address::repeat_byte(0xaa)),
1433                DynSolValue::Bytes(vec![0x99, 0x42, 0x88]),
1434                DynSolValue::String("abc".to_string()),
1435                DynSolValue::Array(vec![
1436                    DynSolValue::Uint(U256::ZERO, 256),
1437                    DynSolValue::Uint(U256::from(7), 256),
1438                    DynSolValue::Uint(U256::from(9), 256),
1439                ]),
1440            ],
1441        );
1442
1443        let minimized =
1444            minimize_single_call_counterexample(function, &start, TEST_MAX_MINIMIZATION_ATTEMPTS, |candidate| {
1445                let args = decoded(function, candidate);
1446                matches!(&args[0], DynSolValue::Uint(value, _) if *value & U256::from(0x2a) == U256::from(0x2a))
1447                    && matches!(&args[1], DynSolValue::Address(address) if address.as_slice()[19] == 0xaa)
1448                    && matches!(&args[2], DynSolValue::Bytes(bytes) if bytes.get(1) == Some(&0x42))
1449                    && matches!(&args[3], DynSolValue::String(value) if value.starts_with('a'))
1450                    && matches!(&args[4], DynSolValue::Array(values) if values.iter().any(|value| matches!(value, DynSolValue::Uint(uint, _) if *uint == U256::from(7))))
1451            })
1452            .unwrap();
1453
1454        let args = decoded(function, &minimized.minimized_call);
1455        assert_eq!(args[0], DynSolValue::Uint(U256::from(0x2a), 256));
1456        assert_eq!(args[1], DynSolValue::Address(Address::with_last_byte(0xaa)));
1457        assert_eq!(args[2], DynSolValue::Bytes(vec![0, 0x42]));
1458        assert_eq!(args[3], DynSolValue::String("a".to_string()));
1459        assert_eq!(args[4], DynSolValue::Array(vec![DynSolValue::Uint(U256::from(7), 256)]));
1460        assert!(minimized.changed());
1461        assert!(minimized.attempts > minimized.accepted);
1462        assert!(minimized.accepted > 0);
1463    }
1464
1465    #[test]
1466    fn minimizes_with_echidna_style_range_deletion_and_numeric_lowering() {
1467        let abi =
1468            JsonAbi::parse(["function check(uint256,int256,bytes,string,uint256[]) external"])
1469                .unwrap();
1470        let function = abi.functions().next().unwrap();
1471        let start = call(
1472            function,
1473            vec![
1474                DynSolValue::Uint(U256::from(100), 256),
1475                DynSolValue::Int(I256::try_from(-100).unwrap(), 256),
1476                DynSolValue::Bytes(vec![1, 2, 3, 0x42, 4]),
1477                DynSolValue::String("abcZ".to_string()),
1478                DynSolValue::Array(vec![
1479                    DynSolValue::Uint(U256::ONE, 256),
1480                    DynSolValue::Uint(U256::from(2), 256),
1481                    DynSolValue::Uint(U256::from(7), 256),
1482                    DynSolValue::Uint(U256::from(3), 256),
1483                ]),
1484            ],
1485        );
1486
1487        let minimized = minimize_single_call_counterexample(function, &start, TEST_MAX_MINIMIZATION_ATTEMPTS, |candidate| {
1488            let args = decoded(function, candidate);
1489            matches!(&args[0], DynSolValue::Uint(value, _) if *value > U256::from(42))
1490                && matches!(&args[1], DynSolValue::Int(value, _) if *value < I256::try_from(-42).unwrap())
1491                && matches!(&args[2], DynSolValue::Bytes(bytes) if bytes.contains(&0x42))
1492                && matches!(&args[3], DynSolValue::String(value) if value.contains('Z'))
1493                && matches!(&args[4], DynSolValue::Array(values) if values.iter().any(|value| matches!(value, DynSolValue::Uint(uint, _) if *uint == U256::from(7))))
1494        })
1495        .unwrap();
1496
1497        let args = decoded(function, &minimized.minimized_call);
1498        assert_eq!(args[0], DynSolValue::Uint(U256::from(43), 256));
1499        assert_eq!(args[1], DynSolValue::Int(I256::try_from(-43).unwrap(), 256));
1500        assert_eq!(args[2], DynSolValue::Bytes(vec![0x42]));
1501        assert_eq!(args[3], DynSolValue::String("Z".to_string()));
1502        assert_eq!(args[4], DynSolValue::Array(vec![DynSolValue::Uint(U256::from(7), 256)]));
1503        assert!(minimized.changed());
1504        assert!(minimized.accepted > 0);
1505    }
1506
1507    #[test]
1508    fn matches_echidna_uint8_threshold_shrink_result() {
1509        let abi = JsonAbi::parse(["function check(uint8) external"]).unwrap();
1510        let function = abi.functions().next().unwrap();
1511        let start = call(function, vec![DynSolValue::Uint(U256::from(246), 8)]);
1512
1513        let minimized = minimize_single_call_counterexample(
1514            function,
1515            &start,
1516            TEST_MAX_MINIMIZATION_ATTEMPTS,
1517            |candidate| {
1518                let args = decoded(function, candidate);
1519                matches!(&args[0], DynSolValue::Uint(value, 8) if *value > U256::from(42))
1520            },
1521        )
1522        .unwrap();
1523
1524        assert_eq!(
1525            decoded(function, &minimized.minimized_call),
1526            vec![DynSolValue::Uint(U256::from(43), 8)]
1527        );
1528        assert!(minimized.attempts < TEST_MAX_MINIMIZATION_ATTEMPTS);
1529    }
1530
1531    #[test]
1532    fn uint_minimization_prefers_bit_clearing_before_binary_search() {
1533        let mut value = DynSolValue::Uint(U256::from(100), 256);
1534        let accepted = [U256::from(100), U256::from(50), U256::from(36)];
1535
1536        loop {
1537            let (current, bits) = match &value {
1538                DynSolValue::Uint(current, bits) => (*current, *bits),
1539                _ => unreachable!(),
1540            };
1541            if !minimize_uint(
1542                &mut value,
1543                current,
1544                bits,
1545                &mut |candidate| matches!(candidate, DynSolValue::Uint(candidate, _) if accepted.contains(candidate)),
1546            ) {
1547                break;
1548            }
1549        }
1550
1551        assert_eq!(value, DynSolValue::Uint(U256::from(36), 256));
1552    }
1553
1554    #[test]
1555    fn uint_pair_delta_minimization_tries_simple_nonzero_pair_first() {
1556        let mut candidates = Vec::new();
1557
1558        assert!(minimize_u256_pair_delta_candidates(
1559            U256::from(100),
1560            U256::from(50),
1561            &mut |left, right| {
1562                candidates.push((left, right));
1563                (left, right) == (U256::ONE, U256::ONE)
1564            },
1565        ));
1566
1567        assert_eq!(candidates, vec![(U256::ONE, U256::ONE)]);
1568    }
1569
1570    #[test]
1571    fn skips_duplicate_single_call_replay_candidates() {
1572        let abi = JsonAbi::parse(["function check(uint256,uint256) external"]).unwrap();
1573        let function = abi.functions().next().unwrap();
1574        let start = call(
1575            function,
1576            vec![DynSolValue::Uint(U256::ONE, 256), DynSolValue::Uint(U256::ONE, 256)],
1577        );
1578        let mut replayed = HashSet::new();
1579
1580        let minimized = minimize_single_call_counterexample(
1581            function,
1582            &start,
1583            TEST_MAX_MINIMIZATION_ATTEMPTS,
1584            |candidate| {
1585                assert!(replayed.insert(candidate.calldata.clone()), "duplicate candidate replay");
1586                false
1587            },
1588        )
1589        .unwrap();
1590
1591        assert!(!minimized.changed());
1592        assert_eq!(minimized.accepted, 0);
1593        assert_eq!(minimized.attempts, 3);
1594        assert_eq!(replayed.len(), minimized.attempts);
1595    }
1596
1597    #[test]
1598    fn matches_echidna_contiguous_slice_examples() {
1599        let bytes_abi = JsonAbi::parse(["function check(bytes) external"]).unwrap();
1600        let bytes_function = bytes_abi.functions().next().unwrap();
1601        let bytes_start =
1602            call(bytes_function, vec![DynSolValue::Bytes(vec![0x99, 0x41, 0x42, 0x88])]);
1603        let bytes_minimized = minimize_single_call_counterexample(
1604            bytes_function,
1605            &bytes_start,
1606            TEST_MAX_MINIMIZATION_ATTEMPTS,
1607            |candidate| {
1608                decoded(bytes_function, candidate) == vec![DynSolValue::Bytes(vec![0x41, 0x42])]
1609            },
1610        )
1611        .unwrap();
1612        assert_eq!(
1613            decoded(bytes_function, &bytes_minimized.minimized_call),
1614            vec![DynSolValue::Bytes(vec![0x41, 0x42])]
1615        );
1616
1617        let string_abi = JsonAbi::parse(["function check(string) external"]).unwrap();
1618        let string_function = string_abi.functions().next().unwrap();
1619        let string_start = call(string_function, vec![DynSolValue::String("xOKy".to_string())]);
1620        let string_minimized = minimize_single_call_counterexample(
1621            string_function,
1622            &string_start,
1623            TEST_MAX_MINIMIZATION_ATTEMPTS,
1624            |candidate| {
1625                decoded(string_function, candidate) == vec![DynSolValue::String("OK".to_string())]
1626            },
1627        )
1628        .unwrap();
1629        assert_eq!(
1630            decoded(string_function, &string_minimized.minimized_call),
1631            vec![DynSolValue::String("OK".to_string())]
1632        );
1633
1634        let array_abi = JsonAbi::parse(["function check(uint256[]) external"]).unwrap();
1635        let array_function = array_abi.functions().next().unwrap();
1636        let array_start = call(
1637            array_function,
1638            vec![DynSolValue::Array(vec![
1639                DynSolValue::Uint(U256::from(9), 256),
1640                DynSolValue::Uint(U256::from(4), 256),
1641                DynSolValue::Uint(U256::from(2), 256),
1642                DynSolValue::Uint(U256::from(8), 256),
1643            ])],
1644        );
1645        let array_minimized = minimize_single_call_counterexample(
1646            array_function,
1647            &array_start,
1648            TEST_MAX_MINIMIZATION_ATTEMPTS,
1649            |candidate| {
1650                let args = decoded(array_function, candidate);
1651                matches!(&args[0], DynSolValue::Array(values) if values == &[
1652                    DynSolValue::Uint(U256::from(4), 256),
1653                    DynSolValue::Uint(U256::from(2), 256),
1654                ])
1655            },
1656        )
1657        .unwrap();
1658        assert_eq!(
1659            decoded(array_function, &array_minimized.minimized_call),
1660            vec![DynSolValue::Array(vec![
1661                DynSolValue::Uint(U256::from(4), 256),
1662                DynSolValue::Uint(U256::from(2), 256),
1663            ])]
1664        );
1665    }
1666
1667    #[test]
1668    fn matches_echidna_address_deadbeef_and_bool_examples() {
1669        let deadbeef = address(0xdeadbeef);
1670
1671        let address_abi = JsonAbi::parse(["function check(address) external"]).unwrap();
1672        let address_function = address_abi.functions().next().unwrap();
1673        let address_start =
1674            call(address_function, vec![DynSolValue::Address(Address::repeat_byte(0xaa))]);
1675        let address_minimized = minimize_single_call_counterexample(
1676            address_function,
1677            &address_start,
1678            TEST_MAX_MINIMIZATION_ATTEMPTS,
1679            |candidate| {
1680                let args = decoded(address_function, candidate);
1681                matches!(&args[..], [DynSolValue::Address(address)] if *address == deadbeef)
1682            },
1683        )
1684        .unwrap();
1685        assert_eq!(
1686            decoded(address_function, &address_minimized.minimized_call),
1687            vec![DynSolValue::Address(deadbeef)]
1688        );
1689
1690        let bool_abi = JsonAbi::parse(["function check(bool) external"]).unwrap();
1691        let bool_function = bool_abi.functions().next().unwrap();
1692        let bool_start = call(bool_function, vec![DynSolValue::Bool(true)]);
1693        let bool_minimized = minimize_single_call_counterexample(
1694            bool_function,
1695            &bool_start,
1696            TEST_MAX_MINIMIZATION_ATTEMPTS,
1697            |candidate| decoded(bool_function, candidate) == vec![DynSolValue::Bool(false)],
1698        )
1699        .unwrap();
1700        assert_eq!(
1701            decoded(bool_function, &bool_minimized.minimized_call),
1702            vec![DynSolValue::Bool(false)]
1703        );
1704    }
1705
1706    #[test]
1707    fn minimizes_correlated_multi_arg_slice_examples() {
1708        let abi = JsonAbi::parse(["function check(bytes,string) external"]).unwrap();
1709        let function = abi.functions().next().unwrap();
1710        let start = call(
1711            function,
1712            vec![
1713                DynSolValue::Bytes(vec![0x99, 0x41, 0x42, 0x88]),
1714                DynSolValue::String("xOKy".to_string()),
1715            ],
1716        );
1717
1718        let minimized = minimize_single_call_counterexample(
1719            function,
1720            &start,
1721            TEST_MAX_MINIMIZATION_ATTEMPTS,
1722            |candidate| {
1723                let args = decoded(function, candidate);
1724                matches!(&args[..], [
1725                DynSolValue::Bytes(bytes),
1726                DynSolValue::String(string),
1727            ] if bytes == &[0x41, 0x42] && string.contains("OK"))
1728            },
1729        )
1730        .unwrap();
1731
1732        assert_eq!(
1733            decoded(function, &minimized.minimized_call),
1734            vec![DynSolValue::Bytes(vec![0x41, 0x42]), DynSolValue::String("OK".to_string()),]
1735        );
1736        assert!(minimized.changed());
1737    }
1738
1739    #[test]
1740    fn adapts_echidna_values_darray_fixture() {
1741        let abi = JsonAbi::parse(["function add_darray(address[]) external"]).unwrap();
1742        let function = abi.functions().next().unwrap();
1743        let target = address(0x123456);
1744        let start = call(
1745            function,
1746            vec![DynSolValue::Array(vec![
1747                DynSolValue::Address(address(0xaaaa)),
1748                DynSolValue::Address(target),
1749                DynSolValue::Address(address(0xbbbb)),
1750            ])],
1751        );
1752
1753        let minimized = minimize_single_call_counterexample(
1754            function,
1755            &start,
1756            TEST_MAX_MINIMIZATION_ATTEMPTS,
1757            |candidate| {
1758                let args = decoded(function, candidate);
1759                matches!(&args[0], DynSolValue::Array(values) if values.iter().any(|value| {
1760                    matches!(value, DynSolValue::Address(candidate) if *candidate == target)
1761                }))
1762            },
1763        )
1764        .unwrap();
1765
1766        assert_eq!(
1767            decoded(function, &minimized.minimized_call),
1768            vec![DynSolValue::Array(vec![DynSolValue::Address(target)])]
1769        );
1770        assert!(minimized.changed());
1771    }
1772
1773    #[test]
1774    fn adapts_echidna_abiv2_dynamic_struct_fixture() {
1775        let abi = JsonAbi::parse(["function yolo((uint256,string,address)) external"]).unwrap();
1776        let function = abi.functions().next().unwrap();
1777        let start = call(
1778            function,
1779            vec![DynSolValue::Tuple(vec![
1780                DynSolValue::Uint(U256::from(137), 256),
1781                DynSolValue::String("xyoloy".to_string()),
1782                DynSolValue::Address(Address::repeat_byte(0xaa)),
1783            ])],
1784        );
1785
1786        let minimized = minimize_single_call_counterexample(
1787            function,
1788            &start,
1789            TEST_MAX_MINIMIZATION_ATTEMPTS,
1790            |candidate| {
1791                let args = decoded(function, candidate);
1792                matches!(&args[0], DynSolValue::Tuple(values)
1793                if matches!(&values[..], [
1794                    DynSolValue::Uint(_, _),
1795                    DynSolValue::String(value),
1796                    DynSolValue::Address(_),
1797                ] if value.contains("yolo")))
1798            },
1799        )
1800        .unwrap();
1801
1802        assert_eq!(
1803            decoded(function, &minimized.minimized_call),
1804            vec![DynSolValue::Tuple(vec![
1805                DynSolValue::Uint(U256::ZERO, 256),
1806                DynSolValue::String("yolo".to_string()),
1807                DynSolValue::Address(Address::ZERO),
1808            ])]
1809        );
1810        assert!(minimized.changed());
1811    }
1812
1813    #[test]
1814    fn adapts_echidna_abiv2_multituple_fixture() {
1815        let abi = JsonAbi::parse(["function f(((bytes)),((bytes))) external"]).unwrap();
1816        let function = abi.functions().next().unwrap();
1817        let start = call(
1818            function,
1819            vec![
1820                DynSolValue::Tuple(vec![DynSolValue::Tuple(vec![DynSolValue::Bytes(vec![
1821                    0x99, 0x42, 0x88,
1822                ])])]),
1823                DynSolValue::Tuple(vec![DynSolValue::Tuple(vec![DynSolValue::Bytes(vec![
1824                    0xaa, 0xbb,
1825                ])])]),
1826            ],
1827        );
1828
1829        let minimized = minimize_single_call_counterexample(
1830            function,
1831            &start,
1832            TEST_MAX_MINIMIZATION_ATTEMPTS,
1833            |candidate| {
1834                let args = decoded(function, candidate);
1835                matches!(&args[0], DynSolValue::Tuple(outer)
1836                if matches!(&outer[0], DynSolValue::Tuple(inner)
1837                    if matches!(&inner[0], DynSolValue::Bytes(bytes) if bytes.contains(&0x42))))
1838            },
1839        )
1840        .unwrap();
1841
1842        assert_eq!(
1843            decoded(function, &minimized.minimized_call),
1844            vec![
1845                DynSolValue::Tuple(vec![DynSolValue::Tuple(vec![DynSolValue::Bytes(vec![0x42])])]),
1846                DynSolValue::Tuple(vec![DynSolValue::Tuple(vec![DynSolValue::Bytes(Vec::new())])]),
1847            ]
1848        );
1849        assert!(minimized.changed());
1850    }
1851
1852    #[test]
1853    fn minimizes_correlated_top_level_abi_value_subsets() {
1854        let abi = JsonAbi::parse(["function check(uint256,uint256,bytes) external"]).unwrap();
1855        let function = abi.functions().next().unwrap();
1856        let start = call(
1857            function,
1858            vec![
1859                DynSolValue::Uint(U256::ONE, 256),
1860                DynSolValue::Uint(U256::ONE, 256),
1861                DynSolValue::Bytes(vec![0x99, 0x42, 0x88]),
1862            ],
1863        );
1864
1865        let minimized = minimize_single_call_counterexample(
1866            function,
1867            &start,
1868            TEST_MAX_MINIMIZATION_ATTEMPTS,
1869            |candidate| {
1870                let args = decoded(function, candidate);
1871                matches!(&args[..], [
1872                DynSolValue::Uint(left, _),
1873                DynSolValue::Uint(right, _),
1874                DynSolValue::Bytes(bytes),
1875            ] if left.is_zero() && right.is_zero() && bytes.contains(&0x42))
1876            },
1877        )
1878        .unwrap();
1879
1880        assert_eq!(
1881            decoded(function, &minimized.minimized_call),
1882            vec![
1883                DynSolValue::Uint(U256::ZERO, 256),
1884                DynSolValue::Uint(U256::ZERO, 256),
1885                DynSolValue::Bytes(vec![0x42]),
1886            ]
1887        );
1888        assert!(minimized.accepted > 0);
1889    }
1890
1891    #[test]
1892    fn minimizes_correlated_nested_abi_value_subsets() {
1893        let abi = JsonAbi::parse(["function check((uint256,uint256,bytes)) external"]).unwrap();
1894        let function = abi.functions().next().unwrap();
1895        let start = call(
1896            function,
1897            vec![DynSolValue::Tuple(vec![
1898                DynSolValue::Uint(U256::ONE, 256),
1899                DynSolValue::Uint(U256::ONE, 256),
1900                DynSolValue::Bytes(vec![0x99, 0x42, 0x88]),
1901            ])],
1902        );
1903
1904        let minimized = minimize_single_call_counterexample(
1905            function,
1906            &start,
1907            TEST_MAX_MINIMIZATION_ATTEMPTS,
1908            |candidate| {
1909                let args = decoded(function, candidate);
1910                matches!(&args[0], DynSolValue::Tuple(values)
1911                if matches!(&values[..], [
1912                    DynSolValue::Uint(left, _),
1913                    DynSolValue::Uint(right, _),
1914                    DynSolValue::Bytes(bytes),
1915                ] if left.is_zero() && right.is_zero() && bytes.contains(&0x42)))
1916            },
1917        )
1918        .unwrap();
1919
1920        assert_eq!(
1921            decoded(function, &minimized.minimized_call),
1922            vec![DynSolValue::Tuple(vec![
1923                DynSolValue::Uint(U256::ZERO, 256),
1924                DynSolValue::Uint(U256::ZERO, 256),
1925                DynSolValue::Bytes(vec![0x42]),
1926            ]),]
1927        );
1928        assert!(minimized.accepted > 0);
1929    }
1930
1931    #[test]
1932    fn adapts_echidna_symbolic_fixed_array_relation_fixture() {
1933        let abi = JsonAbi::parse(["function array(uint256[3]) external"]).unwrap();
1934        let function = abi.functions().next().unwrap();
1935        let start = call(
1936            function,
1937            vec![DynSolValue::FixedArray(vec![
1938                DynSolValue::Uint(U256::from(4_370_001), 256),
1939                DynSolValue::Uint(U256::from(1_524_785_991), 256),
1940                DynSolValue::Uint(U256::from(4_370_000), 256),
1941            ])],
1942        );
1943
1944        let minimized = minimize_single_call_counterexample(
1945            function,
1946            &start,
1947            TEST_MAX_MINIMIZATION_ATTEMPTS,
1948            |candidate| {
1949                let args = decoded(function, candidate);
1950                matches!(&args[0], DynSolValue::FixedArray(values)
1951                if matches!(&values[..], [
1952                    DynSolValue::Uint(left, _),
1953                    DynSolValue::Uint(_, _),
1954                    DynSolValue::Uint(right, _),
1955                ] if *left == *right + U256::ONE))
1956            },
1957        )
1958        .unwrap();
1959
1960        assert_eq!(
1961            decoded(function, &minimized.minimized_call),
1962            vec![DynSolValue::FixedArray(vec![
1963                DynSolValue::Uint(U256::ONE, 256),
1964                DynSolValue::Uint(U256::ZERO, 256),
1965                DynSolValue::Uint(U256::ZERO, 256),
1966            ])]
1967        );
1968        assert!(minimized.changed());
1969        assert!(minimized.accepted > 0);
1970    }
1971
1972    #[test]
1973    fn adapts_echidna_addressarrayutils_duplicate_fixture() {
1974        let abi = JsonAbi::parse(["function checkNoDuplicate(address[]) external"]).unwrap();
1975        let function = abi.functions().next().unwrap();
1976        let start = call(
1977            function,
1978            vec![DynSolValue::Array(vec![
1979                DynSolValue::Address(address(0x20000)),
1980                DynSolValue::Address(address(0xffff_ffff)),
1981                DynSolValue::Address(Address::ZERO),
1982                DynSolValue::Address(address(0x20000)),
1983                DynSolValue::Address(address(0x0001_ffff_fffe)),
1984                DynSolValue::Address(address(0x30000)),
1985            ])],
1986        );
1987
1988        let minimized = minimize_single_call_counterexample(function, &start, TEST_MAX_MINIMIZATION_ATTEMPTS, |candidate| {
1989            let args = decoded(function, candidate);
1990            matches!(&args[0], DynSolValue::Array(values) if values.iter().array_combinations().any(|[left, right]| left == right))
1991        })
1992        .unwrap();
1993
1994        assert_eq!(
1995            decoded(function, &minimized.minimized_call),
1996            vec![DynSolValue::Array(vec![
1997                DynSolValue::Address(Address::ZERO),
1998                DynSolValue::Address(Address::ZERO),
1999            ])]
2000        );
2001        assert!(minimized.changed());
2002        assert!(minimized.accepted > 0);
2003    }
2004
2005    #[test]
2006    fn skips_duplicate_sequence_replay_candidates() {
2007        let abi = JsonAbi::parse(["function noop() external"]).unwrap();
2008        let function = abi.functions().next().unwrap();
2009        let mut start = call(function, Vec::new());
2010        start.sender = address(0xaaaa);
2011        start.value = Some(U256::ZERO);
2012        let sender = address(0x100);
2013        let mut replays = 0usize;
2014
2015        let minimized = minimize_sequence_counterexample(
2016            &[start],
2017            &[sender, sender],
2018            TEST_MAX_MINIMIZATION_ATTEMPTS,
2019            |calls| {
2020                replays += 1;
2021                assert_eq!(calls.len(), 1);
2022                assert_eq!(calls[0].sender, sender);
2023                assert_eq!(calls[0].value, Some(U256::ZERO));
2024                false
2025            },
2026        )
2027        .unwrap();
2028
2029        assert!(!minimized.changed());
2030        assert_eq!(minimized.accepted, 0);
2031        assert_eq!(minimized.attempts, 1);
2032        assert_eq!(replays, minimized.attempts);
2033    }
2034
2035    #[test]
2036    fn minimizes_stateful_sequence_length_calldata_senders_and_values() {
2037        let abi = JsonAbi::parse([
2038            "function noise(uint256) external",
2039            "function prime(uint256) external",
2040            "function fire(uint256) external payable",
2041        ])
2042        .unwrap();
2043        let noise = abi.functions().find(|function| function.name == "noise").unwrap();
2044        let prime = abi.functions().find(|function| function.name == "prime").unwrap();
2045        let fire = abi.functions().find(|function| function.name == "fire").unwrap();
2046        let smaller_sender = address(0x100);
2047        let original_sender = address(0xaaaa);
2048        let mut sequence = vec![
2049            call(noise, vec![DynSolValue::Uint(U256::from(999), 256)]),
2050            call(prime, vec![DynSolValue::Uint(U256::from(1_000), 256)]),
2051            call(noise, vec![DynSolValue::Uint(U256::from(123), 256)]),
2052            call(fire, vec![DynSolValue::Uint(U256::from(5_000), 256)]),
2053        ];
2054        for call in &mut sequence {
2055            call.sender = original_sender;
2056            call.value = Some(U256::from(200));
2057        }
2058
2059        let minimized = minimize_sequence_counterexample(
2060            &sequence,
2061            &[smaller_sender],
2062            TEST_MAX_MINIMIZATION_ATTEMPTS,
2063            |candidate| {
2064                let mut primed = false;
2065                for call in candidate {
2066                    if call.calldata.get(..4) == Some(prime.selector().as_slice()) {
2067                        let args = decoded(prime, call);
2068                        primed |= matches!(&args[0], DynSolValue::Uint(value, _) if *value > U256::from(40));
2069                    }
2070                    if call.calldata.get(..4) == Some(fire.selector().as_slice()) {
2071                        let args = decoded(fire, call);
2072                        let enough_value = call.value.unwrap_or_default() > U256::from(10);
2073                        if primed
2074                            && enough_value
2075                            && matches!(&args[0], DynSolValue::Uint(value, _) if *value > U256::from(100))
2076                        {
2077                            return true;
2078                        }
2079                    }
2080                }
2081                false
2082            },
2083        )
2084        .unwrap();
2085
2086        assert!(minimized.changed());
2087        assert_eq!(minimized.minimized_calls.len(), 2);
2088        assert_eq!(
2089            decoded(prime, &minimized.minimized_calls[0]),
2090            vec![DynSolValue::Uint(U256::from(41), 256)]
2091        );
2092        assert_eq!(
2093            decoded(fire, &minimized.minimized_calls[1]),
2094            vec![DynSolValue::Uint(U256::from(101), 256)]
2095        );
2096        assert_eq!(minimized.minimized_calls[0].sender, smaller_sender);
2097        assert_eq!(minimized.minimized_calls[1].sender, smaller_sender);
2098        assert_eq!(minimized.minimized_calls[0].value, None);
2099        assert_eq!(minimized.minimized_calls[1].value, Some(U256::from(11)));
2100        assert_eq!(minimized.original_calldata_bytes(), sequence_calldata_bytes(&sequence));
2101        assert!(minimized.minimized_calldata_bytes() < minimized.original_calldata_bytes());
2102        assert!(minimized.accepted > 0);
2103    }
2104
2105    #[test]
2106    fn leaves_already_minimal_counterexample_replayable() {
2107        let abi = JsonAbi::parse(["function check(int256,bool,bytes3) external"]).unwrap();
2108        let function = abi.functions().next().unwrap();
2109        let mut fixed_bytes = [0u8; 32];
2110        fixed_bytes[2] = 0x42;
2111        let start = call(
2112            function,
2113            vec![
2114                DynSolValue::Int(I256::MINUS_ONE, 256),
2115                DynSolValue::Bool(false),
2116                DynSolValue::FixedBytes(B256::from(fixed_bytes), 3),
2117            ],
2118        );
2119
2120        let minimized = minimize_single_call_counterexample(
2121            function,
2122            &start,
2123            TEST_MAX_MINIMIZATION_ATTEMPTS,
2124            |candidate| {
2125                let args = decoded(function, candidate);
2126                matches!(&args[0], DynSolValue::Int(value, _) if *value == I256::MINUS_ONE)
2127                    && matches!(&args[1], DynSolValue::Bool(false))
2128                    && matches!(&args[2], DynSolValue::FixedBytes(bytes, 3) if bytes[2] == 0x42)
2129            },
2130        )
2131        .unwrap();
2132
2133        assert_eq!(decoded(function, &minimized.minimized_call), decoded(function, &start));
2134        assert!(!minimized.changed());
2135        assert_eq!(minimized.accepted, 0);
2136    }
2137}