foundry_evm_symbolic/runtime/solver/reasoning/
monotonic_product.rs1use super::*;
2
3type LessThanFacts<'a> = HashSet<(&'a SymExpr, &'a SymExpr)>;
4type LessOrEqualFacts<'a> = HashSet<(&'a SymExpr, &'a SymExpr)>;
5type PositiveFacts<'a> = HashSet<&'a SymExpr>;
6
7#[derive(Default)]
8struct OrderFacts<'a> {
9 less_than: LessThanFacts<'a>,
10 less_or_equal: LessOrEqualFacts<'a>,
11 positive: PositiveFacts<'a>,
12}
13
14pub(crate) fn product_monotonic_unsat_normalized(constraints: &[SymBoolExpr]) -> bool {
16 let facts = order_facts(constraints.iter());
17 let bounds = ConstraintContext::new(constraints);
18
19 constraints.iter().any(|constraint| {
20 reversed_strict_comparison(constraint)
21 .is_some_and(|(left, right)| expr_less_or_equal(right, left, &facts, &bounds))
22 || product_less_than_negation(constraint).is_some_and(
23 |(left_a, left_b, right_a, right_b)| {
24 product_less_than_known(
25 left_a,
26 left_b,
27 right_a,
28 right_b,
29 &facts.less_than,
30 &facts.positive,
31 &bounds,
32 )
33 },
34 )
35 })
36}
37
38pub(in super::super) fn remove_implied_monotonic_constraints(
43 mut constraints: Vec<SymBoolExpr>,
44) -> Vec<SymBoolExpr> {
45 let mut index = 0;
48 while index < constraints.len() {
49 if !constraints[index].contains_hard_arith() {
50 index += 1;
51 continue;
52 }
53 let Some((left, right)) = less_or_equal_comparison(&constraints[index]) else {
54 index += 1;
55 continue;
56 };
57 let base = constraints
58 .iter()
59 .enumerate()
60 .filter(|(candidate, _)| *candidate != index)
61 .map(|(_, constraint)| constraint.clone())
62 .collect::<Vec<_>>();
63 let facts = order_facts(base.iter());
64 let bounds = ConstraintContext::new(&base);
65 if expr_less_or_equal(left, right, &facts, &bounds) {
66 constraints.remove(index);
67 } else {
68 index += 1;
69 }
70 }
71 constraints
72}
73
74fn order_facts<'a>(constraints: impl IntoIterator<Item = &'a SymBoolExpr>) -> OrderFacts<'a> {
75 let mut facts = OrderFacts::default();
76 for constraint in constraints {
77 collect_order_facts(constraint, &mut facts);
78 }
79 facts
80}
81
82fn collect_order_facts<'a>(expr: &'a SymBoolExpr, facts: &mut OrderFacts<'a>) {
83 match expr.kind() {
84 SymBoolExprKind::And(values) => {
85 for value in values.iter() {
86 collect_order_facts(value, facts);
87 }
88 }
89 SymBoolExprKind::Cmp(SymCmpOp::Ult, left, right) => {
90 facts.less_than.insert((left, right));
91 facts.less_or_equal.insert((left, right));
92 if left.as_const().is_some_and(|value| value.is_zero()) {
93 facts.positive.insert(right);
94 }
95 }
96 SymBoolExprKind::Cmp(SymCmpOp::Ugt, left, right) => {
97 facts.less_than.insert((right, left));
98 facts.less_or_equal.insert((right, left));
99 if right.as_const().is_some_and(|value| value.is_zero()) {
100 facts.positive.insert(left);
101 }
102 }
103 SymBoolExprKind::Cmp(SymCmpOp::Ule, left, right) => {
104 facts.less_or_equal.insert((left, right));
105 }
106 SymBoolExprKind::Cmp(SymCmpOp::Uge, left, right) => {
107 facts.less_or_equal.insert((right, left));
108 }
109 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
110 facts.less_or_equal.insert((left, right));
111 facts.less_or_equal.insert((right, left));
112 }
113 SymBoolExprKind::Not(value) => {
114 if let Some(expr) = nonzero_expr(value) {
115 facts.positive.insert(expr);
116 }
117 if let SymBoolExprKind::Cmp(op, left, right) = value.kind() {
118 match op {
119 SymCmpOp::Ult => {
121 facts.less_or_equal.insert((right, left));
122 }
123 SymCmpOp::Ugt => {
125 facts.less_or_equal.insert((left, right));
126 }
127 SymCmpOp::Ule => {
129 facts.less_than.insert((right, left));
130 facts.less_or_equal.insert((right, left));
131 }
132 SymCmpOp::Uge => {
134 facts.less_than.insert((left, right));
135 facts.less_or_equal.insert((left, right));
136 }
137 SymCmpOp::Eq | SymCmpOp::Slt | SymCmpOp::Sgt => {}
138 }
139 }
140 }
141 SymBoolExprKind::Const(_) | SymBoolExprKind::Cmp(SymCmpOp::Slt | SymCmpOp::Sgt, _, _) => {}
142 }
143}
144
145fn reversed_strict_comparison(expr: &SymBoolExpr) -> Option<(&SymExpr, &SymExpr)> {
147 match expr.kind() {
148 SymBoolExprKind::Cmp(SymCmpOp::Ult, left, right) => Some((left, right)),
149 SymBoolExprKind::Cmp(SymCmpOp::Ugt, left, right) => Some((right, left)),
150 SymBoolExprKind::Not(value) => match value.kind() {
151 SymBoolExprKind::Cmp(SymCmpOp::Ule, left, right) => Some((right, left)),
152 SymBoolExprKind::Cmp(SymCmpOp::Uge, left, right) => Some((left, right)),
153 _ => None,
154 },
155 _ => None,
156 }
157}
158
159fn less_or_equal_comparison(expr: &SymBoolExpr) -> Option<(&SymExpr, &SymExpr)> {
161 match expr.kind() {
162 SymBoolExprKind::Cmp(SymCmpOp::Ule, left, right) => Some((left, right)),
163 SymBoolExprKind::Cmp(SymCmpOp::Uge, left, right) => Some((right, left)),
164 SymBoolExprKind::Not(value) => match value.kind() {
165 SymBoolExprKind::Cmp(SymCmpOp::Ult, left, right) => Some((right, left)),
166 SymBoolExprKind::Cmp(SymCmpOp::Ugt, left, right) => Some((left, right)),
167 _ => None,
168 },
169 _ => None,
170 }
171}
172
173fn expr_less_or_equal<'a>(
175 left: &'a SymExpr,
176 right: &'a SymExpr,
177 facts: &OrderFacts<'a>,
178 bounds: &ConstraintContext,
179) -> bool {
180 if left == right || facts.less_or_equal.contains(&(left, right)) {
181 return true;
182 }
183
184 match (left.kind(), right.kind()) {
185 (
186 SymExprKind::BinOp(SymBinOp::UDiv, left_num, left_den),
187 SymExprKind::BinOp(SymBinOp::UDiv, right_num, right_den),
188 ) if left_den == right_den => expr_less_or_equal(left_num, right_num, facts, bounds),
189 (
190 SymExprKind::BinOp(SymBinOp::Mul, left_a, left_b),
191 SymExprKind::BinOp(SymBinOp::Mul, right_a, right_b),
192 ) if bounds.mul_cannot_overflow_256(left_a, left_b)
193 && bounds.mul_cannot_overflow_256(right_a, right_b) =>
194 {
195 product_less_or_equal_known(left_a, left_b, right_a, right_b, facts, bounds)
196 }
197 _ => false,
198 }
199}
200
201fn product_less_or_equal_known<'a>(
202 left_a: &'a SymExpr,
203 left_b: &'a SymExpr,
204 right_a: &'a SymExpr,
205 right_b: &'a SymExpr,
206 facts: &OrderFacts<'a>,
207 bounds: &ConstraintContext,
208) -> bool {
209 product_less_or_equal_known_ordered(left_a, left_b, right_a, right_b, facts, bounds)
210 || product_less_or_equal_known_ordered(left_b, left_a, right_a, right_b, facts, bounds)
211 || product_less_or_equal_known_ordered(left_a, left_b, right_b, right_a, facts, bounds)
212 || product_less_or_equal_known_ordered(left_b, left_a, right_b, right_a, facts, bounds)
213}
214
215fn product_less_or_equal_known_ordered<'a>(
216 left_a: &'a SymExpr,
217 left_b: &'a SymExpr,
218 right_a: &'a SymExpr,
219 right_b: &'a SymExpr,
220 facts: &OrderFacts<'a>,
221 bounds: &ConstraintContext,
222) -> bool {
223 expr_less_or_equal(left_a, right_a, facts, bounds)
224 && expr_less_or_equal(left_b, right_b, facts, bounds)
225}
226
227fn nonzero_expr(expr: &SymBoolExpr) -> Option<&SymExpr> {
228 let SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) = expr.kind() else { return None };
229 if left.as_const().is_some_and(|value| value.is_zero()) {
230 Some(right)
231 } else if right.as_const().is_some_and(|value| value.is_zero()) {
232 Some(left)
233 } else {
234 None
235 }
236}
237
238fn product_less_than_negation(
239 expr: &SymBoolExpr,
240) -> Option<(&SymExpr, &SymExpr, &SymExpr, &SymExpr)> {
241 let SymBoolExprKind::Not(value) = expr.kind() else { return None };
242 let SymBoolExprKind::Cmp(SymCmpOp::Ult, left, right) = value.kind() else {
243 return None;
244 };
245 let (left_a, left_b) = mul_operands(left)?;
246 let (right_a, right_b) = mul_operands(right)?;
247 Some((left_a, left_b, right_a, right_b))
248}
249
250fn product_less_than_known<'a>(
251 left_a: &'a SymExpr,
252 left_b: &'a SymExpr,
253 right_a: &'a SymExpr,
254 right_b: &'a SymExpr,
255 less_than: &LessThanFacts<'a>,
256 positive: &PositiveFacts<'a>,
257 bounds: &ConstraintContext,
258) -> bool {
259 product_less_than_known_ordered(left_a, left_b, right_a, right_b, less_than, positive, bounds)
260 || product_less_than_known_ordered(
261 left_b, left_a, right_a, right_b, less_than, positive, bounds,
262 )
263 || product_less_than_known_ordered(
264 left_a, left_b, right_b, right_a, less_than, positive, bounds,
265 )
266 || product_less_than_known_ordered(
267 left_b, left_a, right_b, right_a, less_than, positive, bounds,
268 )
269}
270
271fn product_less_than_known_ordered<'a>(
272 left_a: &'a SymExpr,
273 left_b: &'a SymExpr,
274 right_a: &'a SymExpr,
275 right_b: &'a SymExpr,
276 less_than: &LessThanFacts<'a>,
277 positive: &PositiveFacts<'a>,
278 bounds: &ConstraintContext,
279) -> bool {
280 positive.contains(left_a)
281 && positive.contains(left_b)
282 && less_than.contains(&(left_a, right_a))
283 && less_than.contains(&(left_b, right_b))
284 && bounds.mul_cannot_overflow_256(left_a, left_b)
285 && bounds.mul_cannot_overflow_256(right_a, right_b)
286}
287
288fn mul_operands(expr: &SymExpr) -> Option<(&SymExpr, &SymExpr)> {
289 match expr.kind() {
290 SymExprKind::BinOp(SymBinOp::Mul, left, right) => Some((left, right)),
291 _ => None,
292 }
293}