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::from(1) && 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::from(1)
487 || accepted_right > rejected_right + U256::from(1)
488 {
489 let candidate_left = if accepted_left > rejected_left + U256::from(1) {
490 rejected_left + ((accepted_left - rejected_left) >> 1usize)
491 } else {
492 accepted_left
493 };
494 let candidate_right = if accepted_right > rejected_right + U256::from(1) {
495 rejected_right + ((accepted_right - rejected_right) >> 1usize)
496 } else {
497 accepted_right
498 };
499
500 if try_candidate(candidate_left, candidate_right) {
501 accepted_left = candidate_left;
502 accepted_right = candidate_right;
503 changed = true;
504 } else {
505 rejected_left = candidate_left;
506 rejected_right = candidate_right;
507 }
508 }
509 changed
510}
511
512fn minimize_values(values: &mut [DynSolValue], try_values: &mut dyn FnMut(&[DynSolValue]) -> bool) {
513 loop {
514 let mut changed = false;
515 for idx in 0..values.len() {
516 let mut value = values[idx].clone();
517 let value_changed = minimize_value(&mut value, &mut |candidate| {
518 let mut candidate_values = values.to_vec();
519 candidate_values[idx] = candidate.clone();
520 try_values(&candidate_values)
521 });
522 if value_changed {
523 values[idx] = value;
524 changed = true;
525 }
526 }
527 if !changed {
528 break;
529 }
530 }
531}
532
533fn minimize_value_pairs(
534 values: &mut Vec<DynSolValue>,
535 try_values: &mut dyn FnMut(&[DynSolValue]) -> bool,
536) -> bool {
537 let mut changed = false;
538 loop {
539 let mut pass_changed = false;
540 for left_idx in 0..values.len() {
541 for right_idx in left_idx + 1..values.len() {
542 let left = values[left_idx].clone();
543 let right = values[right_idx].clone();
544 if minimize_numeric_value_pair(&left, &right, |left, right| {
545 let mut candidate_values = values.clone();
546 candidate_values[left_idx] = left;
547 candidate_values[right_idx] = right;
548 if try_values(&candidate_values) {
549 *values = candidate_values;
550 true
551 } else {
552 false
553 }
554 }) {
555 pass_changed = true;
556 break;
557 }
558 }
559 if pass_changed {
560 break;
561 }
562 }
563 if !pass_changed {
564 break;
565 }
566 changed = true;
567 }
568 changed
569}
570
571fn minimize_value(
572 value: &mut DynSolValue,
573 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
574) -> bool {
575 let mut changed = false;
576 loop {
577 let pass_changed =
578 minimize_scalar_value(value, try_value) || minimize_compound_value(value, try_value);
579 if !pass_changed {
580 break;
581 }
582 changed = true;
583 }
584 changed
585}
586
587fn minimize_scalar_value(
588 value: &mut DynSolValue,
589 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
590) -> bool {
591 match value.clone() {
592 DynSolValue::Bool(true) => accept_candidate(value, DynSolValue::Bool(false), try_value),
593 DynSolValue::Bool(false) => false,
594 DynSolValue::Uint(current, bits) => minimize_uint(value, current, bits, try_value),
595 DynSolValue::Int(current, bits) => minimize_int(value, current, bits, try_value),
596 DynSolValue::Address(current) => minimize_address(value, current, try_value),
597 DynSolValue::FixedBytes(current, size) => {
598 minimize_fixed_bytes(value, current, size, try_value)
599 }
600 DynSolValue::Function(current) => {
601 if current == SolFunction::ZERO {
602 false
603 } else {
604 accept_candidate(value, DynSolValue::Function(SolFunction::ZERO), try_value)
605 }
606 }
607 DynSolValue::Bytes(current) => minimize_bytes(value, current, try_value),
608 DynSolValue::String(current) => minimize_string(value, current, try_value),
609 DynSolValue::Array(_)
610 | DynSolValue::FixedArray(_)
611 | DynSolValue::Tuple(_)
612 | DynSolValue::CustomStruct { .. } => false,
613 }
614}
615
616fn minimize_compound_value(
617 value: &mut DynSolValue,
618 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
619) -> bool {
620 match value.clone() {
621 DynSolValue::Array(mut elements) => {
622 if minimize_array_len(value, &mut elements, try_value) {
623 return true;
624 }
625 if let Some(candidate) = minimize_elements_batch(
626 &mut elements,
627 |items| DynSolValue::Array(items.to_vec()),
628 try_value,
629 ) {
630 *value = candidate;
631 return true;
632 }
633 if let Some(candidate) = minimize_element_subsets(
634 &mut elements,
635 |items| DynSolValue::Array(items.to_vec()),
636 try_value,
637 ) {
638 *value = candidate;
639 return true;
640 }
641 if let Some(candidate) = minimize_element_pairs(
642 &mut elements,
643 |items| DynSolValue::Array(items.to_vec()),
644 try_value,
645 ) {
646 *value = candidate;
647 return true;
648 }
649 minimize_elements(&mut elements, |items| DynSolValue::Array(items.to_vec()), try_value)
650 .map(|candidate| {
651 *value = candidate;
652 true
653 })
654 .unwrap_or(false)
655 }
656 DynSolValue::FixedArray(mut elements) => {
657 if let Some(candidate) = minimize_elements_batch(
658 &mut elements,
659 |items| DynSolValue::FixedArray(items.to_vec()),
660 try_value,
661 ) {
662 *value = candidate;
663 return true;
664 }
665 if let Some(candidate) = minimize_element_subsets(
666 &mut elements,
667 |items| DynSolValue::FixedArray(items.to_vec()),
668 try_value,
669 ) {
670 *value = candidate;
671 return true;
672 }
673 if let Some(candidate) = minimize_element_pairs(
674 &mut elements,
675 |items| DynSolValue::FixedArray(items.to_vec()),
676 try_value,
677 ) {
678 *value = candidate;
679 return true;
680 }
681 minimize_elements(
682 &mut elements,
683 |items| DynSolValue::FixedArray(items.to_vec()),
684 try_value,
685 )
686 .map(|candidate| {
687 *value = candidate;
688 true
689 })
690 .unwrap_or(false)
691 }
692 DynSolValue::Tuple(mut elements) => {
693 if let Some(candidate) = minimize_elements_batch(
694 &mut elements,
695 |items| DynSolValue::Tuple(items.to_vec()),
696 try_value,
697 ) {
698 *value = candidate;
699 return true;
700 }
701 if let Some(candidate) = minimize_element_subsets(
702 &mut elements,
703 |items| DynSolValue::Tuple(items.to_vec()),
704 try_value,
705 ) {
706 *value = candidate;
707 return true;
708 }
709 if let Some(candidate) = minimize_element_pairs(
710 &mut elements,
711 |items| DynSolValue::Tuple(items.to_vec()),
712 try_value,
713 ) {
714 *value = candidate;
715 return true;
716 }
717 minimize_elements(&mut elements, |items| DynSolValue::Tuple(items.to_vec()), try_value)
718 .map(|candidate| {
719 *value = candidate;
720 true
721 })
722 .unwrap_or(false)
723 }
724 DynSolValue::CustomStruct { name, prop_names, mut tuple } => {
725 if let Some(candidate) = minimize_elements_batch(
726 &mut tuple,
727 |items| DynSolValue::CustomStruct {
728 name: name.clone(),
729 prop_names: prop_names.clone(),
730 tuple: items.to_vec(),
731 },
732 try_value,
733 ) {
734 *value = candidate;
735 return true;
736 }
737 if let Some(candidate) = minimize_element_subsets(
738 &mut tuple,
739 |items| DynSolValue::CustomStruct {
740 name: name.clone(),
741 prop_names: prop_names.clone(),
742 tuple: items.to_vec(),
743 },
744 try_value,
745 ) {
746 *value = candidate;
747 return true;
748 }
749 if let Some(candidate) = minimize_element_pairs(
750 &mut tuple,
751 |items| DynSolValue::CustomStruct {
752 name: name.clone(),
753 prop_names: prop_names.clone(),
754 tuple: items.to_vec(),
755 },
756 try_value,
757 ) {
758 *value = candidate;
759 return true;
760 }
761 minimize_elements(
762 &mut tuple,
763 |items| DynSolValue::CustomStruct {
764 name: name.clone(),
765 prop_names: prop_names.clone(),
766 tuple: items.to_vec(),
767 },
768 try_value,
769 )
770 .map(|candidate| {
771 *value = candidate;
772 true
773 })
774 .unwrap_or(false)
775 }
776 _ => false,
777 }
778}
779
780fn minimize_uint(
781 value: &mut DynSolValue,
782 current: U256,
783 bits: usize,
784 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
785) -> bool {
786 if !current.is_zero() && accept_candidate(value, DynSolValue::Uint(U256::ZERO, bits), try_value)
787 {
788 return true;
789 }
790
791 let one = U256::from(1);
792 if current > one && accept_candidate(value, DynSolValue::Uint(one, bits), try_value) {
793 return true;
794 }
795
796 let bit_limit = bits.min(256);
797 for bit in (0..bit_limit).rev() {
798 let mask = U256::from(1) << bit;
799 if current & mask == U256::ZERO {
800 continue;
801 }
802 let candidate = current & !mask;
803 if accept_candidate(value, DynSolValue::Uint(candidate, bits), try_value) {
804 return true;
805 }
806 }
807
808 minimize_uint_by_search(value, current, bits, try_value)
809}
810
811fn minimize_uint_by_search(
812 value: &mut DynSolValue,
813 current: U256,
814 bits: usize,
815 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
816) -> bool {
817 if current <= U256::from(1) {
818 return false;
819 }
820
821 let mut accepted = current;
822 let mut rejected = U256::ZERO;
823 let mut changed = false;
824 while accepted > rejected + U256::from(1) {
825 let candidate: U256 = rejected + ((accepted - rejected) >> 1usize);
826 if accept_candidate(value, DynSolValue::Uint(candidate, bits), try_value) {
827 accepted = candidate;
828 changed = true;
829 } else {
830 rejected = candidate;
831 }
832 }
833
834 changed
835}
836
837fn minimize_int(
838 value: &mut DynSolValue,
839 current: I256,
840 bits: usize,
841 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
842) -> bool {
843 if current != I256::ZERO
844 && accept_candidate(value, DynSolValue::Int(I256::ZERO, bits), try_value)
845 {
846 return true;
847 }
848 if current.is_negative()
849 && current != I256::MINUS_ONE
850 && accept_candidate(value, DynSolValue::Int(I256::MINUS_ONE, bits), try_value)
851 {
852 return true;
853 }
854
855 minimize_int_by_search(value, current, bits, try_value)
856}
857
858fn minimize_int_by_search(
859 value: &mut DynSolValue,
860 current: I256,
861 bits: usize,
862 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
863) -> bool {
864 let mut accepted_abs = current.unsigned_abs();
865 if accepted_abs <= U256::from(1) {
866 return false;
867 }
868
869 let mut rejected_abs = U256::ZERO;
870 let mut changed = false;
871 while accepted_abs > rejected_abs + U256::from(1) {
872 let candidate_abs: U256 = rejected_abs + ((accepted_abs - rejected_abs) >> 1usize);
873 let candidate = if current.is_negative() {
874 I256::from_raw(candidate_abs.wrapping_neg())
875 } else {
876 I256::from_raw(candidate_abs)
877 };
878 if accept_candidate(value, DynSolValue::Int(candidate, bits), try_value) {
879 accepted_abs = candidate_abs;
880 changed = true;
881 } else {
882 rejected_abs = candidate_abs;
883 }
884 }
885
886 changed
887}
888
889fn minimize_address(
890 value: &mut DynSolValue,
891 current: Address,
892 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
893) -> bool {
894 for candidate in address_candidates(current) {
895 if accept_candidate(value, DynSolValue::Address(candidate), try_value) {
896 return true;
897 }
898 }
899 false
900}
901
902fn address_candidates(current: Address) -> Vec<Address> {
903 if current == Address::ZERO {
904 return Vec::new();
905 }
906
907 let mut candidates = Vec::new();
908 candidates.push(Address::ZERO);
909
910 let deadbeef = Address::from_word(B256::from(U256::from(0xdeadbeefu64)));
911 if current != deadbeef {
912 candidates.push(deadbeef);
913 }
914
915 let bytes = current.into_array();
916 for idx in 0..bytes.len() {
917 if bytes[idx] == 0 {
918 continue;
919 }
920 let mut candidate = bytes;
921 candidate[idx] = 0;
922 candidates.push(Address::from(candidate));
923 }
924
925 candidates
926}
927
928fn minimize_fixed_bytes(
929 value: &mut DynSolValue,
930 current: B256,
931 size: usize,
932 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
933) -> bool {
934 if current != B256::ZERO
935 && accept_candidate(value, DynSolValue::FixedBytes(B256::ZERO, size), try_value)
936 {
937 return true;
938 }
939
940 let mut bytes = [0u8; 32];
941 bytes.copy_from_slice(current.as_slice());
942 for idx in (0..size.min(bytes.len())).rev() {
943 if bytes[idx] == 0 {
944 continue;
945 }
946 let mut candidate = bytes;
947 candidate[idx] = 0;
948 if accept_candidate(value, DynSolValue::FixedBytes(B256::from(candidate), size), try_value)
949 {
950 return true;
951 }
952 }
953 false
954}
955
956fn minimize_bytes(
957 value: &mut DynSolValue,
958 current: Vec<u8>,
959 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
960) -> bool {
961 for len in 0..current.len() {
962 if accept_candidate(value, DynSolValue::Bytes(current[..len].to_vec()), try_value) {
963 return true;
964 }
965 }
966 if try_delete_vec_range(¤t, |candidate| {
967 accept_candidate(value, DynSolValue::Bytes(candidate), try_value)
968 }) {
969 return true;
970 }
971 if try_slice_vec_range(¤t, |candidate| {
972 accept_candidate(value, DynSolValue::Bytes(candidate), try_value)
973 }) {
974 return true;
975 }
976
977 for idx in (0..current.len()).rev() {
978 if current[idx] == 0 {
979 continue;
980 }
981 let mut candidate = current.clone();
982 candidate[idx] = 0;
983 if accept_candidate(value, DynSolValue::Bytes(candidate), try_value) {
984 return true;
985 }
986 }
987 false
988}
989
990fn minimize_string(
991 value: &mut DynSolValue,
992 current: String,
993 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
994) -> bool {
995 for len in 0..current.len() {
996 if current.is_char_boundary(len)
997 && accept_candidate(value, DynSolValue::String(current[..len].to_string()), try_value)
998 {
999 return true;
1000 }
1001 }
1002 if try_delete_string_range(¤t, |candidate| {
1003 accept_candidate(value, DynSolValue::String(candidate), try_value)
1004 }) {
1005 return true;
1006 }
1007 if try_slice_string_range(¤t, |candidate| {
1008 accept_candidate(value, DynSolValue::String(candidate), try_value)
1009 }) {
1010 return true;
1011 }
1012
1013 let current_bytes = current.as_bytes();
1014 for idx in (0..current_bytes.len()).rev() {
1015 if current_bytes[idx] == 0 {
1016 continue;
1017 }
1018 let mut candidate = current_bytes.to_vec();
1019 candidate[idx] = 0;
1020 if let Ok(candidate) = String::from_utf8(candidate)
1021 && accept_candidate(value, DynSolValue::String(candidate), try_value)
1022 {
1023 return true;
1024 }
1025 }
1026 false
1027}
1028
1029fn minimize_array_len(
1030 value: &mut DynSolValue,
1031 current: &mut Vec<DynSolValue>,
1032 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1033) -> bool {
1034 for len in 0..current.len() {
1035 let candidate = DynSolValue::Array(current[..len].to_vec());
1036 if accept_candidate(value, candidate, try_value) {
1037 current.truncate(len);
1038 return true;
1039 }
1040 }
1041 if try_delete_vec_range(current, |candidate| {
1042 accept_candidate(value, DynSolValue::Array(candidate), try_value)
1043 }) {
1044 return true;
1045 }
1046 if try_slice_vec_range(current, |candidate| {
1047 accept_candidate(value, DynSolValue::Array(candidate), try_value)
1048 }) {
1049 return true;
1050 }
1051 false
1052}
1053
1054fn try_delete_vec_range<T: Clone>(
1055 current: &[T],
1056 mut try_candidate: impl FnMut(Vec<T>) -> bool,
1057) -> bool {
1058 for range_len in deletion_lengths(current.len()) {
1059 for start in 0..=current.len() - range_len {
1060 let mut candidate = Vec::with_capacity(current.len() - range_len);
1061 candidate.extend_from_slice(¤t[..start]);
1062 candidate.extend_from_slice(¤t[start + range_len..]);
1063 if try_candidate(candidate) {
1064 return true;
1065 }
1066 }
1067 }
1068 false
1069}
1070
1071fn try_delete_string_range(current: &str, mut try_candidate: impl FnMut(String) -> bool) -> bool {
1072 let mut boundaries = current.char_indices().map(|(idx, _)| idx).collect::<Vec<_>>();
1073 boundaries.push(current.len());
1074
1075 for range_len in deletion_lengths(boundaries.len().saturating_sub(1)) {
1076 for start_idx in 0..=boundaries.len() - range_len - 1 {
1077 let start = boundaries[start_idx];
1078 let end = boundaries[start_idx + range_len];
1079 let mut candidate = String::with_capacity(current.len() - (end - start));
1080 candidate.push_str(¤t[..start]);
1081 candidate.push_str(¤t[end..]);
1082 if try_candidate(candidate) {
1083 return true;
1084 }
1085 }
1086 }
1087 false
1088}
1089
1090fn try_slice_vec_range<T: Clone>(
1091 current: &[T],
1092 mut try_candidate: impl FnMut(Vec<T>) -> bool,
1093) -> bool {
1094 for len in 1..current.len() {
1095 for start in 1..=current.len() - len {
1096 let candidate = current[start..start + len].to_vec();
1097 if try_candidate(candidate) {
1098 return true;
1099 }
1100 }
1101 }
1102 false
1103}
1104
1105fn try_slice_string_range(current: &str, mut try_candidate: impl FnMut(String) -> bool) -> bool {
1106 let mut boundaries = current.char_indices().map(|(idx, _)| idx).collect::<Vec<_>>();
1107 boundaries.push(current.len());
1108 let char_len = boundaries.len().saturating_sub(1);
1109
1110 for len in 1..char_len {
1111 for start_idx in 1..=char_len - len {
1112 let start = boundaries[start_idx];
1113 let end = boundaries[start_idx + len];
1114 if try_candidate(current[start..end].to_string()) {
1115 return true;
1116 }
1117 }
1118 }
1119 false
1120}
1121
1122fn deletion_lengths(len: usize) -> Vec<usize> {
1123 if len == 0 {
1124 return Vec::new();
1125 }
1126
1127 let mut lengths = Vec::new();
1128 let mut range_len = len;
1129 while range_len > 0 {
1130 lengths.push(range_len);
1131 range_len /= 2;
1132 }
1133 lengths.sort_unstable();
1134 lengths.dedup();
1135 lengths.reverse();
1136 lengths
1137}
1138
1139fn minimize_elements(
1140 elements: &mut [DynSolValue],
1141 rebuild: impl Fn(&[DynSolValue]) -> DynSolValue,
1142 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1143) -> Option<DynSolValue> {
1144 for idx in 0..elements.len() {
1145 let mut element = elements[idx].clone();
1146 let changed = minimize_value(&mut element, &mut |candidate| {
1147 let mut candidate_elements = elements.to_vec();
1148 candidate_elements[idx] = candidate.clone();
1149 try_value(&rebuild(&candidate_elements))
1150 });
1151 if changed {
1152 elements[idx] = element;
1153 return Some(rebuild(elements));
1154 }
1155 }
1156 None
1157}
1158
1159fn minimize_elements_batch(
1160 elements: &mut Vec<DynSolValue>,
1161 rebuild: impl Fn(&[DynSolValue]) -> DynSolValue,
1162 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1163) -> Option<DynSolValue> {
1164 let simple_elements = elements.iter().cloned().map(minimally_simple_value).collect::<Vec<_>>();
1165 if simple_elements == *elements {
1166 return None;
1167 }
1168 let candidate = rebuild(&simple_elements);
1169 try_value(&candidate).then(|| {
1170 *elements = simple_elements;
1171 candidate
1172 })
1173}
1174
1175fn minimize_element_pairs(
1176 elements: &mut Vec<DynSolValue>,
1177 rebuild: impl Fn(&[DynSolValue]) -> DynSolValue,
1178 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1179) -> Option<DynSolValue> {
1180 for left_idx in 0..elements.len() {
1181 for right_idx in left_idx + 1..elements.len() {
1182 let left = elements[left_idx].clone();
1183 let right = elements[right_idx].clone();
1184 if minimize_numeric_value_pair(&left, &right, |left, right| {
1185 let mut candidate_elements = elements.clone();
1186 candidate_elements[left_idx] = left;
1187 candidate_elements[right_idx] = right;
1188 if try_value(&rebuild(&candidate_elements)) {
1189 *elements = candidate_elements;
1190 true
1191 } else {
1192 false
1193 }
1194 }) {
1195 return Some(rebuild(elements));
1196 }
1197 }
1198 }
1199 None
1200}
1201
1202fn minimize_element_subsets(
1203 elements: &mut Vec<DynSolValue>,
1204 rebuild: impl Fn(&[DynSolValue]) -> DynSolValue,
1205 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1206) -> Option<DynSolValue> {
1207 let simple_elements = elements.iter().cloned().map(minimally_simple_value).collect::<Vec<_>>();
1208 let shrinkable_idxs = elements
1209 .iter()
1210 .zip(&simple_elements)
1211 .enumerate()
1212 .filter_map(|(idx, (current, simple))| (current != simple).then_some(idx))
1213 .collect::<Vec<_>>();
1214 if shrinkable_idxs.len() < 2 {
1215 return None;
1216 }
1217
1218 for subset_size in subset_sizes(shrinkable_idxs.len()) {
1219 let mut search = ElementSubsetSearch {
1220 elements,
1221 simple_elements: &simple_elements,
1222 shrinkable_idxs: &shrinkable_idxs,
1223 rebuild: &rebuild,
1224 try_value,
1225 };
1226 let mut subset = Vec::with_capacity(subset_size);
1227 if let Some(candidate_elements) =
1228 try_element_subset(&mut search, subset_size, 0, &mut subset)
1229 {
1230 *elements = candidate_elements.clone();
1231 return Some(rebuild(&candidate_elements));
1232 }
1233 }
1234 None
1235}
1236
1237fn minimize_numeric_value_pair(
1238 left: &DynSolValue,
1239 right: &DynSolValue,
1240 mut try_pair: impl FnMut(DynSolValue, DynSolValue) -> bool,
1241) -> bool {
1242 match (left, right) {
1243 (DynSolValue::Uint(left, left_bits), DynSolValue::Uint(right, right_bits)) => {
1244 let mut try_uint_pair = |left, right| {
1245 try_pair(DynSolValue::Uint(left, *left_bits), DynSolValue::Uint(right, *right_bits))
1246 };
1247 minimize_u256_pair_delta_candidates(*left, *right, &mut try_uint_pair)
1248 }
1249 (DynSolValue::Int(left, left_bits), DynSolValue::Int(right, right_bits)) => {
1250 minimize_i256_pair_candidates(*left, *right, |left, right| {
1251 try_pair(DynSolValue::Int(left, *left_bits), DynSolValue::Int(right, *right_bits))
1252 })
1253 }
1254 _ => false,
1255 }
1256}
1257
1258fn minimize_u256_pair_delta_candidates(
1259 current_left: U256,
1260 current_right: U256,
1261 try_candidate: &mut impl FnMut(U256, U256) -> bool,
1262) -> bool {
1263 let one = U256::from(1);
1264 if (current_left, current_right) != (one, one)
1265 && !current_left.is_zero()
1266 && !current_right.is_zero()
1267 && try_candidate(one, one)
1268 {
1269 return true;
1270 }
1271
1272 match current_left.cmp(¤t_right) {
1273 std::cmp::Ordering::Equal => {
1274 !current_left.is_zero() && try_candidate(U256::ZERO, U256::ZERO)
1275 }
1276 std::cmp::Ordering::Greater => {
1277 let delta = current_left - current_right;
1278 !current_right.is_zero() && try_candidate(delta, U256::ZERO)
1279 }
1280 std::cmp::Ordering::Less => {
1281 let delta = current_right - current_left;
1282 !current_left.is_zero() && try_candidate(U256::ZERO, delta)
1283 }
1284 }
1285}
1286
1287fn minimize_i256_pair_candidates(
1288 current_left: I256,
1289 current_right: I256,
1290 mut try_candidate: impl FnMut(I256, I256) -> bool,
1291) -> bool {
1292 if current_left == I256::ZERO || current_right == I256::ZERO {
1293 return false;
1294 }
1295 if current_left.is_negative() != current_right.is_negative() {
1296 return false;
1297 }
1298
1299 minimize_u256_pair_candidates(
1300 current_left.unsigned_abs(),
1301 current_right.unsigned_abs(),
1302 |left_abs, right_abs| {
1303 let left = signed_candidate_with_abs(current_left, left_abs);
1304 let right = signed_candidate_with_abs(current_right, right_abs);
1305 try_candidate(left, right)
1306 },
1307 )
1308}
1309
1310const fn signed_candidate_with_abs(current: I256, abs: U256) -> I256 {
1311 if current.is_negative() { I256::from_raw(abs.wrapping_neg()) } else { I256::from_raw(abs) }
1312}
1313
1314fn subset_sizes(shrinkable_len: usize) -> Vec<usize> {
1315 let mut sizes = Vec::new();
1316 for subset_size in 2..shrinkable_len {
1317 if bounded_combination_count(shrinkable_len, subset_size, MAX_SUBSET_CANDIDATES_PER_PASS)
1318 .is_some()
1319 {
1320 sizes.push(subset_size);
1321 }
1322 }
1323 sizes
1324}
1325
1326fn bounded_combination_count(n: usize, k: usize, max: usize) -> Option<usize> {
1327 if k > n {
1328 return None;
1329 }
1330 let k = k.min(n - k);
1331 let mut count = 1usize;
1332 for step in 1..=k {
1333 count = count.checked_mul(n + 1 - step)?;
1334 count /= step;
1335 if count > max {
1336 return None;
1337 }
1338 }
1339 Some(count)
1340}
1341
1342struct ElementSubsetSearch<'a, R>
1343where
1344 R: Fn(&[DynSolValue]) -> DynSolValue,
1345{
1346 elements: &'a [DynSolValue],
1347 simple_elements: &'a [DynSolValue],
1348 shrinkable_idxs: &'a [usize],
1349 rebuild: &'a R,
1350 try_value: &'a mut dyn FnMut(&DynSolValue) -> bool,
1351}
1352
1353fn try_element_subset<R>(
1354 search: &mut ElementSubsetSearch<'_, R>,
1355 subset_size: usize,
1356 start: usize,
1357 subset: &mut Vec<usize>,
1358) -> Option<Vec<DynSolValue>>
1359where
1360 R: Fn(&[DynSolValue]) -> DynSolValue,
1361{
1362 if subset.len() == subset_size {
1363 let mut candidate_elements = search.elements.to_vec();
1364 for idx in subset.iter().copied() {
1365 candidate_elements[idx] = search.simple_elements[idx].clone();
1366 }
1367 if (search.try_value)(&(search.rebuild)(&candidate_elements)) {
1368 return Some(candidate_elements);
1369 }
1370 return None;
1371 }
1372
1373 let remaining = subset_size - subset.len();
1374 for choice_idx in start..=search.shrinkable_idxs.len() - remaining {
1375 subset.push(search.shrinkable_idxs[choice_idx]);
1376 if let Some(candidate) = try_element_subset(search, subset_size, choice_idx + 1, subset) {
1377 return Some(candidate);
1378 }
1379 subset.pop();
1380 }
1381 None
1382}
1383
1384fn accept_candidate(
1385 value: &mut DynSolValue,
1386 candidate: DynSolValue,
1387 try_value: &mut dyn FnMut(&DynSolValue) -> bool,
1388) -> bool {
1389 if *value == candidate {
1390 return false;
1391 }
1392 if try_value(&candidate) {
1393 *value = candidate;
1394 true
1395 } else {
1396 false
1397 }
1398}
1399
1400#[cfg(test)]
1401mod tests {
1402 use super::*;
1403 use alloy_json_abi::JsonAbi;
1404 use std::collections::HashSet;
1405
1406 const TEST_MAX_MINIMIZATION_ATTEMPTS: usize = 5000;
1407
1408 fn call(function: &Function, args: Vec<DynSolValue>) -> SymbolicCounterexampleCall {
1409 SymbolicCounterexampleCall {
1410 warp: None,
1411 roll: None,
1412 sender: Address::ZERO,
1413 target: Address::repeat_byte(0x11),
1414 calldata: Bytes::from(function.abi_encode_input(&args).unwrap()),
1415 value: Some(U256::ZERO),
1416 contract_name: Some("Target".to_string()),
1417 function_name: Some(function.name.clone()),
1418 signature: Some(function.signature()),
1419 args: Some(foundry_common::fmt::format_tokens(&args).format(", ").to_string()),
1420 raw_args: Some(foundry_common::fmt::format_tokens_raw(&args).format(", ").to_string()),
1421 }
1422 }
1423
1424 fn decoded(function: &Function, call: &SymbolicCounterexampleCall) -> Vec<DynSolValue> {
1425 function.abi_decode_input(&call.calldata[4..]).unwrap()
1426 }
1427
1428 fn address(value: u64) -> Address {
1429 Address::from_word(B256::from(U256::from(value)))
1430 }
1431
1432 #[test]
1433 fn minimizes_common_abi_values_with_replay_predicate() {
1434 let abi =
1435 JsonAbi::parse(["function check(uint256,address,bytes,string,uint256[]) external"])
1436 .unwrap();
1437 let function = abi.functions().next().unwrap();
1438 let start = call(
1439 function,
1440 vec![
1441 DynSolValue::Uint(U256::from(0xff), 256),
1442 DynSolValue::Address(Address::from([0xaa; 20])),
1443 DynSolValue::Bytes(vec![0x99, 0x42, 0x88]),
1444 DynSolValue::String("abc".to_string()),
1445 DynSolValue::Array(vec![
1446 DynSolValue::Uint(U256::from(0), 256),
1447 DynSolValue::Uint(U256::from(7), 256),
1448 DynSolValue::Uint(U256::from(9), 256),
1449 ]),
1450 ],
1451 );
1452
1453 let minimized =
1454 minimize_single_call_counterexample(function, &start, TEST_MAX_MINIMIZATION_ATTEMPTS, |candidate| {
1455 let args = decoded(function, candidate);
1456 matches!(&args[0], DynSolValue::Uint(value, _) if *value & U256::from(0x2a) == U256::from(0x2a))
1457 && matches!(&args[1], DynSolValue::Address(address) if address.as_slice()[19] == 0xaa)
1458 && matches!(&args[2], DynSolValue::Bytes(bytes) if bytes.get(1) == Some(&0x42))
1459 && matches!(&args[3], DynSolValue::String(value) if value.starts_with('a'))
1460 && matches!(&args[4], DynSolValue::Array(values) if values.iter().any(|value| matches!(value, DynSolValue::Uint(uint, _) if *uint == U256::from(7))))
1461 })
1462 .unwrap();
1463
1464 let args = decoded(function, &minimized.minimized_call);
1465 assert_eq!(args[0], DynSolValue::Uint(U256::from(0x2a), 256));
1466 assert_eq!(
1467 args[1],
1468 DynSolValue::Address(Address::from([
1469 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xaa,
1470 ]))
1471 );
1472 assert_eq!(args[2], DynSolValue::Bytes(vec![0, 0x42]));
1473 assert_eq!(args[3], DynSolValue::String("a".to_string()));
1474 assert_eq!(args[4], DynSolValue::Array(vec![DynSolValue::Uint(U256::from(7), 256)]));
1475 assert!(minimized.changed());
1476 assert!(minimized.attempts > minimized.accepted);
1477 assert!(minimized.accepted > 0);
1478 }
1479
1480 #[test]
1481 fn minimizes_with_echidna_style_range_deletion_and_numeric_lowering() {
1482 let abi =
1483 JsonAbi::parse(["function check(uint256,int256,bytes,string,uint256[]) external"])
1484 .unwrap();
1485 let function = abi.functions().next().unwrap();
1486 let start = call(
1487 function,
1488 vec![
1489 DynSolValue::Uint(U256::from(100), 256),
1490 DynSolValue::Int(I256::try_from(-100).unwrap(), 256),
1491 DynSolValue::Bytes(vec![1, 2, 3, 0x42, 4]),
1492 DynSolValue::String("abcZ".to_string()),
1493 DynSolValue::Array(vec![
1494 DynSolValue::Uint(U256::from(1), 256),
1495 DynSolValue::Uint(U256::from(2), 256),
1496 DynSolValue::Uint(U256::from(7), 256),
1497 DynSolValue::Uint(U256::from(3), 256),
1498 ]),
1499 ],
1500 );
1501
1502 let minimized = minimize_single_call_counterexample(function, &start, TEST_MAX_MINIMIZATION_ATTEMPTS, |candidate| {
1503 let args = decoded(function, candidate);
1504 matches!(&args[0], DynSolValue::Uint(value, _) if *value > U256::from(42))
1505 && matches!(&args[1], DynSolValue::Int(value, _) if *value < I256::try_from(-42).unwrap())
1506 && matches!(&args[2], DynSolValue::Bytes(bytes) if bytes.contains(&0x42))
1507 && matches!(&args[3], DynSolValue::String(value) if value.contains('Z'))
1508 && matches!(&args[4], DynSolValue::Array(values) if values.iter().any(|value| matches!(value, DynSolValue::Uint(uint, _) if *uint == U256::from(7))))
1509 })
1510 .unwrap();
1511
1512 let args = decoded(function, &minimized.minimized_call);
1513 assert_eq!(args[0], DynSolValue::Uint(U256::from(43), 256));
1514 assert_eq!(args[1], DynSolValue::Int(I256::try_from(-43).unwrap(), 256));
1515 assert_eq!(args[2], DynSolValue::Bytes(vec![0x42]));
1516 assert_eq!(args[3], DynSolValue::String("Z".to_string()));
1517 assert_eq!(args[4], DynSolValue::Array(vec![DynSolValue::Uint(U256::from(7), 256)]));
1518 assert!(minimized.changed());
1519 assert!(minimized.accepted > 0);
1520 }
1521
1522 #[test]
1523 fn matches_echidna_uint8_threshold_shrink_result() {
1524 let abi = JsonAbi::parse(["function check(uint8) external"]).unwrap();
1525 let function = abi.functions().next().unwrap();
1526 let start = call(function, vec![DynSolValue::Uint(U256::from(246), 8)]);
1527
1528 let minimized = minimize_single_call_counterexample(
1529 function,
1530 &start,
1531 TEST_MAX_MINIMIZATION_ATTEMPTS,
1532 |candidate| {
1533 let args = decoded(function, candidate);
1534 matches!(&args[0], DynSolValue::Uint(value, 8) if *value > U256::from(42))
1535 },
1536 )
1537 .unwrap();
1538
1539 assert_eq!(
1540 decoded(function, &minimized.minimized_call),
1541 vec![DynSolValue::Uint(U256::from(43), 8)]
1542 );
1543 assert!(minimized.attempts < TEST_MAX_MINIMIZATION_ATTEMPTS);
1544 }
1545
1546 #[test]
1547 fn uint_minimization_prefers_bit_clearing_before_binary_search() {
1548 let mut value = DynSolValue::Uint(U256::from(100), 256);
1549 let accepted = [U256::from(100), U256::from(50), U256::from(36)];
1550
1551 loop {
1552 let (current, bits) = match &value {
1553 DynSolValue::Uint(current, bits) => (*current, *bits),
1554 _ => unreachable!(),
1555 };
1556 if !minimize_uint(
1557 &mut value,
1558 current,
1559 bits,
1560 &mut |candidate| matches!(candidate, DynSolValue::Uint(candidate, _) if accepted.contains(candidate)),
1561 ) {
1562 break;
1563 }
1564 }
1565
1566 assert_eq!(value, DynSolValue::Uint(U256::from(36), 256));
1567 }
1568
1569 #[test]
1570 fn uint_pair_delta_minimization_tries_simple_nonzero_pair_first() {
1571 let mut candidates = Vec::new();
1572
1573 assert!(minimize_u256_pair_delta_candidates(
1574 U256::from(100),
1575 U256::from(50),
1576 &mut |left, right| {
1577 candidates.push((left, right));
1578 (left, right) == (U256::from(1), U256::from(1))
1579 },
1580 ));
1581
1582 assert_eq!(candidates, vec![(U256::from(1), U256::from(1))]);
1583 }
1584
1585 #[test]
1586 fn skips_duplicate_single_call_replay_candidates() {
1587 let abi = JsonAbi::parse(["function check(uint256,uint256) external"]).unwrap();
1588 let function = abi.functions().next().unwrap();
1589 let start = call(
1590 function,
1591 vec![DynSolValue::Uint(U256::from(1), 256), DynSolValue::Uint(U256::from(1), 256)],
1592 );
1593 let mut replayed = HashSet::new();
1594
1595 let minimized = minimize_single_call_counterexample(
1596 function,
1597 &start,
1598 TEST_MAX_MINIMIZATION_ATTEMPTS,
1599 |candidate| {
1600 assert!(replayed.insert(candidate.calldata.clone()), "duplicate candidate replay");
1601 false
1602 },
1603 )
1604 .unwrap();
1605
1606 assert!(!minimized.changed());
1607 assert_eq!(minimized.accepted, 0);
1608 assert_eq!(minimized.attempts, 3);
1609 assert_eq!(replayed.len(), minimized.attempts);
1610 }
1611
1612 #[test]
1613 fn matches_echidna_contiguous_slice_examples() {
1614 let bytes_abi = JsonAbi::parse(["function check(bytes) external"]).unwrap();
1615 let bytes_function = bytes_abi.functions().next().unwrap();
1616 let bytes_start =
1617 call(bytes_function, vec![DynSolValue::Bytes(vec![0x99, 0x41, 0x42, 0x88])]);
1618 let bytes_minimized = minimize_single_call_counterexample(
1619 bytes_function,
1620 &bytes_start,
1621 TEST_MAX_MINIMIZATION_ATTEMPTS,
1622 |candidate| {
1623 decoded(bytes_function, candidate) == vec![DynSolValue::Bytes(vec![0x41, 0x42])]
1624 },
1625 )
1626 .unwrap();
1627 assert_eq!(
1628 decoded(bytes_function, &bytes_minimized.minimized_call),
1629 vec![DynSolValue::Bytes(vec![0x41, 0x42])]
1630 );
1631
1632 let string_abi = JsonAbi::parse(["function check(string) external"]).unwrap();
1633 let string_function = string_abi.functions().next().unwrap();
1634 let string_start = call(string_function, vec![DynSolValue::String("xOKy".to_string())]);
1635 let string_minimized = minimize_single_call_counterexample(
1636 string_function,
1637 &string_start,
1638 TEST_MAX_MINIMIZATION_ATTEMPTS,
1639 |candidate| {
1640 decoded(string_function, candidate) == vec![DynSolValue::String("OK".to_string())]
1641 },
1642 )
1643 .unwrap();
1644 assert_eq!(
1645 decoded(string_function, &string_minimized.minimized_call),
1646 vec![DynSolValue::String("OK".to_string())]
1647 );
1648
1649 let array_abi = JsonAbi::parse(["function check(uint256[]) external"]).unwrap();
1650 let array_function = array_abi.functions().next().unwrap();
1651 let array_start = call(
1652 array_function,
1653 vec![DynSolValue::Array(vec![
1654 DynSolValue::Uint(U256::from(9), 256),
1655 DynSolValue::Uint(U256::from(4), 256),
1656 DynSolValue::Uint(U256::from(2), 256),
1657 DynSolValue::Uint(U256::from(8), 256),
1658 ])],
1659 );
1660 let array_minimized = minimize_single_call_counterexample(
1661 array_function,
1662 &array_start,
1663 TEST_MAX_MINIMIZATION_ATTEMPTS,
1664 |candidate| {
1665 let args = decoded(array_function, candidate);
1666 matches!(&args[0], DynSolValue::Array(values) if values == &[
1667 DynSolValue::Uint(U256::from(4), 256),
1668 DynSolValue::Uint(U256::from(2), 256),
1669 ])
1670 },
1671 )
1672 .unwrap();
1673 assert_eq!(
1674 decoded(array_function, &array_minimized.minimized_call),
1675 vec![DynSolValue::Array(vec![
1676 DynSolValue::Uint(U256::from(4), 256),
1677 DynSolValue::Uint(U256::from(2), 256),
1678 ])]
1679 );
1680 }
1681
1682 #[test]
1683 fn matches_echidna_address_deadbeef_and_bool_examples() {
1684 let deadbeef = address(0xdeadbeef);
1685
1686 let address_abi = JsonAbi::parse(["function check(address) external"]).unwrap();
1687 let address_function = address_abi.functions().next().unwrap();
1688 let address_start =
1689 call(address_function, vec![DynSolValue::Address(Address::from([0xaa; 20]))]);
1690 let address_minimized = minimize_single_call_counterexample(
1691 address_function,
1692 &address_start,
1693 TEST_MAX_MINIMIZATION_ATTEMPTS,
1694 |candidate| {
1695 let args = decoded(address_function, candidate);
1696 matches!(&args[..], [DynSolValue::Address(address)] if *address == deadbeef)
1697 },
1698 )
1699 .unwrap();
1700 assert_eq!(
1701 decoded(address_function, &address_minimized.minimized_call),
1702 vec![DynSolValue::Address(deadbeef)]
1703 );
1704
1705 let bool_abi = JsonAbi::parse(["function check(bool) external"]).unwrap();
1706 let bool_function = bool_abi.functions().next().unwrap();
1707 let bool_start = call(bool_function, vec![DynSolValue::Bool(true)]);
1708 let bool_minimized = minimize_single_call_counterexample(
1709 bool_function,
1710 &bool_start,
1711 TEST_MAX_MINIMIZATION_ATTEMPTS,
1712 |candidate| decoded(bool_function, candidate) == vec![DynSolValue::Bool(false)],
1713 )
1714 .unwrap();
1715 assert_eq!(
1716 decoded(bool_function, &bool_minimized.minimized_call),
1717 vec![DynSolValue::Bool(false)]
1718 );
1719 }
1720
1721 #[test]
1722 fn minimizes_correlated_multi_arg_slice_examples() {
1723 let abi = JsonAbi::parse(["function check(bytes,string) external"]).unwrap();
1724 let function = abi.functions().next().unwrap();
1725 let start = call(
1726 function,
1727 vec![
1728 DynSolValue::Bytes(vec![0x99, 0x41, 0x42, 0x88]),
1729 DynSolValue::String("xOKy".to_string()),
1730 ],
1731 );
1732
1733 let minimized = minimize_single_call_counterexample(
1734 function,
1735 &start,
1736 TEST_MAX_MINIMIZATION_ATTEMPTS,
1737 |candidate| {
1738 let args = decoded(function, candidate);
1739 matches!(&args[..], [
1740 DynSolValue::Bytes(bytes),
1741 DynSolValue::String(string),
1742 ] if bytes == &[0x41, 0x42] && string.contains("OK"))
1743 },
1744 )
1745 .unwrap();
1746
1747 assert_eq!(
1748 decoded(function, &minimized.minimized_call),
1749 vec![DynSolValue::Bytes(vec![0x41, 0x42]), DynSolValue::String("OK".to_string()),]
1750 );
1751 assert!(minimized.changed());
1752 }
1753
1754 #[test]
1755 fn adapts_echidna_values_darray_fixture() {
1756 let abi = JsonAbi::parse(["function add_darray(address[]) external"]).unwrap();
1757 let function = abi.functions().next().unwrap();
1758 let target = address(0x123456);
1759 let start = call(
1760 function,
1761 vec![DynSolValue::Array(vec![
1762 DynSolValue::Address(address(0xaaaa)),
1763 DynSolValue::Address(target),
1764 DynSolValue::Address(address(0xbbbb)),
1765 ])],
1766 );
1767
1768 let minimized = minimize_single_call_counterexample(
1769 function,
1770 &start,
1771 TEST_MAX_MINIMIZATION_ATTEMPTS,
1772 |candidate| {
1773 let args = decoded(function, candidate);
1774 matches!(&args[0], DynSolValue::Array(values) if values.iter().any(|value| {
1775 matches!(value, DynSolValue::Address(candidate) if *candidate == target)
1776 }))
1777 },
1778 )
1779 .unwrap();
1780
1781 assert_eq!(
1782 decoded(function, &minimized.minimized_call),
1783 vec![DynSolValue::Array(vec![DynSolValue::Address(target)])]
1784 );
1785 assert!(minimized.changed());
1786 }
1787
1788 #[test]
1789 fn adapts_echidna_abiv2_dynamic_struct_fixture() {
1790 let abi = JsonAbi::parse(["function yolo((uint256,string,address)) external"]).unwrap();
1791 let function = abi.functions().next().unwrap();
1792 let start = call(
1793 function,
1794 vec![DynSolValue::Tuple(vec![
1795 DynSolValue::Uint(U256::from(137), 256),
1796 DynSolValue::String("xyoloy".to_string()),
1797 DynSolValue::Address(Address::from([0xaa; 20])),
1798 ])],
1799 );
1800
1801 let minimized = minimize_single_call_counterexample(
1802 function,
1803 &start,
1804 TEST_MAX_MINIMIZATION_ATTEMPTS,
1805 |candidate| {
1806 let args = decoded(function, candidate);
1807 matches!(&args[0], DynSolValue::Tuple(values)
1808 if matches!(&values[..], [
1809 DynSolValue::Uint(_, _),
1810 DynSolValue::String(value),
1811 DynSolValue::Address(_),
1812 ] if value.contains("yolo")))
1813 },
1814 )
1815 .unwrap();
1816
1817 assert_eq!(
1818 decoded(function, &minimized.minimized_call),
1819 vec![DynSolValue::Tuple(vec![
1820 DynSolValue::Uint(U256::ZERO, 256),
1821 DynSolValue::String("yolo".to_string()),
1822 DynSolValue::Address(Address::ZERO),
1823 ])]
1824 );
1825 assert!(minimized.changed());
1826 }
1827
1828 #[test]
1829 fn adapts_echidna_abiv2_multituple_fixture() {
1830 let abi = JsonAbi::parse(["function f(((bytes)),((bytes))) external"]).unwrap();
1831 let function = abi.functions().next().unwrap();
1832 let start = call(
1833 function,
1834 vec![
1835 DynSolValue::Tuple(vec![DynSolValue::Tuple(vec![DynSolValue::Bytes(vec![
1836 0x99, 0x42, 0x88,
1837 ])])]),
1838 DynSolValue::Tuple(vec![DynSolValue::Tuple(vec![DynSolValue::Bytes(vec![
1839 0xaa, 0xbb,
1840 ])])]),
1841 ],
1842 );
1843
1844 let minimized = minimize_single_call_counterexample(
1845 function,
1846 &start,
1847 TEST_MAX_MINIMIZATION_ATTEMPTS,
1848 |candidate| {
1849 let args = decoded(function, candidate);
1850 matches!(&args[0], DynSolValue::Tuple(outer)
1851 if matches!(&outer[0], DynSolValue::Tuple(inner)
1852 if matches!(&inner[0], DynSolValue::Bytes(bytes) if bytes.contains(&0x42))))
1853 },
1854 )
1855 .unwrap();
1856
1857 assert_eq!(
1858 decoded(function, &minimized.minimized_call),
1859 vec![
1860 DynSolValue::Tuple(vec![DynSolValue::Tuple(vec![DynSolValue::Bytes(vec![0x42])])]),
1861 DynSolValue::Tuple(vec![DynSolValue::Tuple(vec![DynSolValue::Bytes(Vec::new())])]),
1862 ]
1863 );
1864 assert!(minimized.changed());
1865 }
1866
1867 #[test]
1868 fn minimizes_correlated_top_level_abi_value_subsets() {
1869 let abi = JsonAbi::parse(["function check(uint256,uint256,bytes) external"]).unwrap();
1870 let function = abi.functions().next().unwrap();
1871 let start = call(
1872 function,
1873 vec![
1874 DynSolValue::Uint(U256::from(1), 256),
1875 DynSolValue::Uint(U256::from(1), 256),
1876 DynSolValue::Bytes(vec![0x99, 0x42, 0x88]),
1877 ],
1878 );
1879
1880 let minimized = minimize_single_call_counterexample(
1881 function,
1882 &start,
1883 TEST_MAX_MINIMIZATION_ATTEMPTS,
1884 |candidate| {
1885 let args = decoded(function, candidate);
1886 matches!(&args[..], [
1887 DynSolValue::Uint(left, _),
1888 DynSolValue::Uint(right, _),
1889 DynSolValue::Bytes(bytes),
1890 ] if left.is_zero() && right.is_zero() && bytes.contains(&0x42))
1891 },
1892 )
1893 .unwrap();
1894
1895 assert_eq!(
1896 decoded(function, &minimized.minimized_call),
1897 vec![
1898 DynSolValue::Uint(U256::ZERO, 256),
1899 DynSolValue::Uint(U256::ZERO, 256),
1900 DynSolValue::Bytes(vec![0x42]),
1901 ]
1902 );
1903 assert!(minimized.accepted > 0);
1904 }
1905
1906 #[test]
1907 fn minimizes_correlated_nested_abi_value_subsets() {
1908 let abi = JsonAbi::parse(["function check((uint256,uint256,bytes)) external"]).unwrap();
1909 let function = abi.functions().next().unwrap();
1910 let start = call(
1911 function,
1912 vec![DynSolValue::Tuple(vec![
1913 DynSolValue::Uint(U256::from(1), 256),
1914 DynSolValue::Uint(U256::from(1), 256),
1915 DynSolValue::Bytes(vec![0x99, 0x42, 0x88]),
1916 ])],
1917 );
1918
1919 let minimized = minimize_single_call_counterexample(
1920 function,
1921 &start,
1922 TEST_MAX_MINIMIZATION_ATTEMPTS,
1923 |candidate| {
1924 let args = decoded(function, candidate);
1925 matches!(&args[0], DynSolValue::Tuple(values)
1926 if matches!(&values[..], [
1927 DynSolValue::Uint(left, _),
1928 DynSolValue::Uint(right, _),
1929 DynSolValue::Bytes(bytes),
1930 ] if left.is_zero() && right.is_zero() && bytes.contains(&0x42)))
1931 },
1932 )
1933 .unwrap();
1934
1935 assert_eq!(
1936 decoded(function, &minimized.minimized_call),
1937 vec![DynSolValue::Tuple(vec![
1938 DynSolValue::Uint(U256::ZERO, 256),
1939 DynSolValue::Uint(U256::ZERO, 256),
1940 DynSolValue::Bytes(vec![0x42]),
1941 ]),]
1942 );
1943 assert!(minimized.accepted > 0);
1944 }
1945
1946 #[test]
1947 fn adapts_echidna_symbolic_fixed_array_relation_fixture() {
1948 let abi = JsonAbi::parse(["function array(uint256[3]) external"]).unwrap();
1949 let function = abi.functions().next().unwrap();
1950 let start = call(
1951 function,
1952 vec![DynSolValue::FixedArray(vec![
1953 DynSolValue::Uint(U256::from(4_370_001), 256),
1954 DynSolValue::Uint(U256::from(1_524_785_991), 256),
1955 DynSolValue::Uint(U256::from(4_370_000), 256),
1956 ])],
1957 );
1958
1959 let minimized = minimize_single_call_counterexample(
1960 function,
1961 &start,
1962 TEST_MAX_MINIMIZATION_ATTEMPTS,
1963 |candidate| {
1964 let args = decoded(function, candidate);
1965 matches!(&args[0], DynSolValue::FixedArray(values)
1966 if matches!(&values[..], [
1967 DynSolValue::Uint(left, _),
1968 DynSolValue::Uint(_, _),
1969 DynSolValue::Uint(right, _),
1970 ] if *left == *right + U256::from(1)))
1971 },
1972 )
1973 .unwrap();
1974
1975 assert_eq!(
1976 decoded(function, &minimized.minimized_call),
1977 vec![DynSolValue::FixedArray(vec![
1978 DynSolValue::Uint(U256::from(1), 256),
1979 DynSolValue::Uint(U256::ZERO, 256),
1980 DynSolValue::Uint(U256::ZERO, 256),
1981 ])]
1982 );
1983 assert!(minimized.changed());
1984 assert!(minimized.accepted > 0);
1985 }
1986
1987 #[test]
1988 fn adapts_echidna_addressarrayutils_duplicate_fixture() {
1989 let abi = JsonAbi::parse(["function checkNoDuplicate(address[]) external"]).unwrap();
1990 let function = abi.functions().next().unwrap();
1991 let start = call(
1992 function,
1993 vec![DynSolValue::Array(vec![
1994 DynSolValue::Address(address(0x20000)),
1995 DynSolValue::Address(address(0xffff_ffff)),
1996 DynSolValue::Address(Address::ZERO),
1997 DynSolValue::Address(address(0x20000)),
1998 DynSolValue::Address(address(0x0001_ffff_fffe)),
1999 DynSolValue::Address(address(0x30000)),
2000 ])],
2001 );
2002
2003 let minimized = minimize_single_call_counterexample(function, &start, TEST_MAX_MINIMIZATION_ATTEMPTS, |candidate| {
2004 let args = decoded(function, candidate);
2005 matches!(&args[0], DynSolValue::Array(values) if values.iter().array_combinations().any(|[left, right]| left == right))
2006 })
2007 .unwrap();
2008
2009 assert_eq!(
2010 decoded(function, &minimized.minimized_call),
2011 vec![DynSolValue::Array(vec![
2012 DynSolValue::Address(Address::ZERO),
2013 DynSolValue::Address(Address::ZERO),
2014 ])]
2015 );
2016 assert!(minimized.changed());
2017 assert!(minimized.accepted > 0);
2018 }
2019
2020 #[test]
2021 fn skips_duplicate_sequence_replay_candidates() {
2022 let abi = JsonAbi::parse(["function noop() external"]).unwrap();
2023 let function = abi.functions().next().unwrap();
2024 let mut start = call(function, Vec::new());
2025 start.sender = address(0xaaaa);
2026 start.value = Some(U256::ZERO);
2027 let sender = address(0x100);
2028 let mut replays = 0usize;
2029
2030 let minimized = minimize_sequence_counterexample(
2031 &[start],
2032 &[sender, sender],
2033 TEST_MAX_MINIMIZATION_ATTEMPTS,
2034 |calls| {
2035 replays += 1;
2036 assert_eq!(calls.len(), 1);
2037 assert_eq!(calls[0].sender, sender);
2038 assert_eq!(calls[0].value, Some(U256::ZERO));
2039 false
2040 },
2041 )
2042 .unwrap();
2043
2044 assert!(!minimized.changed());
2045 assert_eq!(minimized.accepted, 0);
2046 assert_eq!(minimized.attempts, 1);
2047 assert_eq!(replays, minimized.attempts);
2048 }
2049
2050 #[test]
2051 fn minimizes_stateful_sequence_length_calldata_senders_and_values() {
2052 let abi = JsonAbi::parse([
2053 "function noise(uint256) external",
2054 "function prime(uint256) external",
2055 "function fire(uint256) external payable",
2056 ])
2057 .unwrap();
2058 let noise = abi.functions().find(|function| function.name == "noise").unwrap();
2059 let prime = abi.functions().find(|function| function.name == "prime").unwrap();
2060 let fire = abi.functions().find(|function| function.name == "fire").unwrap();
2061 let smaller_sender = address(0x100);
2062 let original_sender = address(0xaaaa);
2063 let mut sequence = vec![
2064 call(noise, vec![DynSolValue::Uint(U256::from(999), 256)]),
2065 call(prime, vec![DynSolValue::Uint(U256::from(1_000), 256)]),
2066 call(noise, vec![DynSolValue::Uint(U256::from(123), 256)]),
2067 call(fire, vec![DynSolValue::Uint(U256::from(5_000), 256)]),
2068 ];
2069 for call in &mut sequence {
2070 call.sender = original_sender;
2071 call.value = Some(U256::from(200));
2072 }
2073
2074 let minimized = minimize_sequence_counterexample(
2075 &sequence,
2076 &[smaller_sender],
2077 TEST_MAX_MINIMIZATION_ATTEMPTS,
2078 |candidate| {
2079 let mut primed = false;
2080 for call in candidate {
2081 if call.calldata.get(..4) == Some(prime.selector().as_slice()) {
2082 let args = decoded(prime, call);
2083 primed |= matches!(&args[0], DynSolValue::Uint(value, _) if *value > U256::from(40));
2084 }
2085 if call.calldata.get(..4) == Some(fire.selector().as_slice()) {
2086 let args = decoded(fire, call);
2087 let enough_value = call.value.unwrap_or_default() > U256::from(10);
2088 if primed
2089 && enough_value
2090 && matches!(&args[0], DynSolValue::Uint(value, _) if *value > U256::from(100))
2091 {
2092 return true;
2093 }
2094 }
2095 }
2096 false
2097 },
2098 )
2099 .unwrap();
2100
2101 assert!(minimized.changed());
2102 assert_eq!(minimized.minimized_calls.len(), 2);
2103 assert_eq!(
2104 decoded(prime, &minimized.minimized_calls[0]),
2105 vec![DynSolValue::Uint(U256::from(41), 256)]
2106 );
2107 assert_eq!(
2108 decoded(fire, &minimized.minimized_calls[1]),
2109 vec![DynSolValue::Uint(U256::from(101), 256)]
2110 );
2111 assert_eq!(minimized.minimized_calls[0].sender, smaller_sender);
2112 assert_eq!(minimized.minimized_calls[1].sender, smaller_sender);
2113 assert_eq!(minimized.minimized_calls[0].value, None);
2114 assert_eq!(minimized.minimized_calls[1].value, Some(U256::from(11)));
2115 assert_eq!(minimized.original_calldata_bytes(), sequence_calldata_bytes(&sequence));
2116 assert!(minimized.minimized_calldata_bytes() < minimized.original_calldata_bytes());
2117 assert!(minimized.accepted > 0);
2118 }
2119
2120 #[test]
2121 fn leaves_already_minimal_counterexample_replayable() {
2122 let abi = JsonAbi::parse(["function check(int256,bool,bytes3) external"]).unwrap();
2123 let function = abi.functions().next().unwrap();
2124 let mut fixed_bytes = [0u8; 32];
2125 fixed_bytes[2] = 0x42;
2126 let start = call(
2127 function,
2128 vec![
2129 DynSolValue::Int(I256::MINUS_ONE, 256),
2130 DynSolValue::Bool(false),
2131 DynSolValue::FixedBytes(B256::from(fixed_bytes), 3),
2132 ],
2133 );
2134
2135 let minimized = minimize_single_call_counterexample(
2136 function,
2137 &start,
2138 TEST_MAX_MINIMIZATION_ATTEMPTS,
2139 |candidate| {
2140 let args = decoded(function, candidate);
2141 matches!(&args[0], DynSolValue::Int(value, _) if *value == I256::MINUS_ONE)
2142 && matches!(&args[1], DynSolValue::Bool(false))
2143 && matches!(&args[2], DynSolValue::FixedBytes(bytes, 3) if bytes[2] == 0x42)
2144 },
2145 )
2146 .unwrap();
2147
2148 assert_eq!(decoded(function, &minimized.minimized_call), decoded(function, &start));
2149 assert!(!minimized.changed());
2150 assert_eq!(minimized.accepted, 0);
2151 }
2152}