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#[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#[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
80pub(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(¤t_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
293pub(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, ¤t_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, ¤t_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(¤t, |candidate| {
957 accept_candidate(value, DynSolValue::Bytes(candidate), try_value)
958 }) {
959 return true;
960 }
961 if try_slice_vec_range(¤t, |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(¤t, |candidate| {
993 accept_candidate(value, DynSolValue::String(candidate), try_value)
994 }) {
995 return true;
996 }
997 if try_slice_string_range(¤t, |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(¤t[..start]);
1052 candidate.extend_from_slice(¤t[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(¤t[..start]);
1071 candidate.push_str(¤t[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(¤t_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}