1use super::{hashcons::HashConsed, *};
2use foundry_evm::revm::interpreter::instructions::i256::{i256_div, i256_mod};
3
4const MAX_BITWISE_BOOL_WORD_VISITS: usize = 256;
7const MAX_CONSTANT_DIFFERENCE_VISITS: usize = 256;
8
9impl SymExpr {
10 pub(crate) fn select_storage_write(
11 self,
12 cx: &mut SymCx,
13 write_key: Self,
14 write_value: Self,
15 base: Self,
16 ) -> Self {
17 if write_value == base {
18 return base;
19 }
20 let condition = self.storage_key_eq(cx, &write_key);
21 match condition.as_const() {
22 Some(true) => write_value,
23 Some(false) => base,
24 None => Self::ite(cx, condition, write_value, base),
25 }
26 }
27
28 pub(crate) fn storage_key_eq(&self, cx: &mut SymCx, write_key: &Self) -> SymBoolExpr {
29 if let (Some(read_root), Some(write_root)) =
30 (self.storage_mapping_root_slot(cx), write_key.storage_mapping_root_slot(cx))
31 && read_root != write_root
32 {
33 return SymBoolExpr::constant(cx, false);
34 }
35 match (self.storage_layout_key(cx), write_key.storage_layout_key(cx)) {
36 (Some((read_base, read_offset)), Some((write_base, write_offset))) => {
37 let read_base = read_base
38 .storage_base_eq(cx, &write_base)
39 .unwrap_or_else(|| SymBoolExpr::eq(cx, read_base, write_base));
40 let read_offset = SymBoolExpr::eq(cx, read_offset, write_offset);
41 SymBoolExpr::and(cx, vec![read_base, read_offset])
42 }
43 (Some(_), None) if write_key.as_const().is_some() => SymBoolExpr::constant(cx, false),
44 (None, Some(_)) if self.as_const().is_some() => SymBoolExpr::constant(cx, false),
45 _ => SymBoolExpr::eq(cx, self.clone(), write_key.clone()),
46 }
47 }
48
49 fn storage_base_eq(&self, cx: &mut SymCx, other: &Self) -> Option<SymBoolExpr> {
50 let read = self.storage_mapping_key(cx)?;
51 let write = other.storage_mapping_key(cx)?;
52
53 let key_eq = storage_mapping_key_eq(cx, &read, &write);
54 let slot_eq = read
55 .slot
56 .storage_base_eq(cx, &write.slot)
57 .unwrap_or_else(|| SymBoolExpr::eq(cx, read.slot, write.slot));
58 Some(SymBoolExpr::and(cx, vec![key_eq, slot_eq]))
59 }
60
61 pub(crate) fn storage_mapping_key(&self, cx: &mut SymCx) -> Option<StorageMappingKey> {
62 let bytes = self.storage_mapping_key_bytes(cx)?;
63 let key_bytes = &bytes[..32];
64 let preserve_key_bytes = (!key_bytes.iter().all(|byte| byte.as_const().is_some())
65 && word_from_extracted_bytes(key_bytes).is_none())
66 .then(|| key_bytes.to_vec());
67 let key = Self::from_bytes(cx, key_bytes.iter().cloned());
68 let slot = Self::from_bytes(cx, bytes[32..64].iter().cloned());
69 Some(StorageMappingKey { key, key_bytes: preserve_key_bytes, slot })
70 }
71
72 pub(crate) fn storage_mapping_provenance_observed_with(
73 &self,
74 cx: &mut SymCx,
75 mut observed_preimage: impl FnMut(&Self) -> Option<Arc<[Self]>>,
76 ) -> Option<SymbolicMappingProvenance> {
77 let mut current = self.clone();
78 let mut keys = Vec::new();
79 let mut visited = Vec::new();
80 loop {
81 if visited.contains(¤t) {
82 return None;
83 }
84 visited.push(current.clone());
85 let bytes = observed_preimage(¤t)?;
86 if bytes.len() != 64 {
87 return None;
88 }
89 let key = Self::from_bytes(cx, bytes[..32].iter().cloned());
90 keys.push(key);
91 current = Self::from_bytes(cx, bytes[32..64].iter().cloned());
92 match current.kind() {
93 SymExprKind::Const(root_slot) if observed_preimage(¤t).is_none() => {
94 keys.reverse();
95 return Some(SymbolicMappingProvenance { root_slot: *root_slot, keys });
96 }
97 SymExprKind::Const(_) | SymExprKind::Keccak { .. } => {}
98 _ => return None,
99 }
100 }
101 }
102
103 fn storage_mapping_root_slot(&self, cx: &mut SymCx) -> Option<U256> {
104 let bytes = self.storage_mapping_key_bytes(cx)?;
105 let slot = Self::from_bytes(cx, bytes[32..64].iter().cloned());
106 match slot.kind() {
107 SymExprKind::Const(value) if cx.concrete_keccak_preimage(*value).is_some() => {
108 slot.storage_mapping_root_slot(cx)
109 }
110 SymExprKind::Const(slot) => Some(*slot),
111 SymExprKind::Keccak { .. } => slot.storage_mapping_root_slot(cx),
112 _ => None,
113 }
114 }
115
116 fn storage_mapping_key_bytes(&self, cx: &SymCx) -> Option<Arc<[Self]>> {
117 match self.kind() {
118 SymExprKind::Keccak { len, bytes, .. }
119 if len.as_const() == Some(U256::from(64)) && bytes.len() >= 64 =>
120 {
121 Some(bytes.clone())
122 }
123 SymExprKind::Const(hash) => cx.concrete_keccak_preimage(*hash),
124 _ => None,
125 }
126 }
127
128 fn storage_layout_key(&self, cx: &mut SymCx) -> Option<(Self, Self)> {
129 match self.kind() {
130 SymExprKind::Keccak { .. } => Some((self.clone(), Self::zero(cx))),
131 SymExprKind::Const(hash) if cx.concrete_keccak_preimage(*hash).is_some() => {
132 Some((self.clone(), Self::zero(cx)))
133 }
134 SymExprKind::BinOp(SymBinOp::Add, left, right) => {
135 if let Some((base, offset)) = left.storage_layout_key(cx)
136 && !right.contains_keccak()
137 {
138 let offset = Self::binop(cx, SymBinOp::Add, offset, right.clone());
139 return Some((base, offset));
140 }
141 if let Some((base, offset)) = right.storage_layout_key(cx)
142 && !left.contains_keccak()
143 {
144 let offset = Self::binop(cx, SymBinOp::Add, offset, left.clone());
145 return Some((base, offset));
146 }
147 None
148 }
149 _ => None,
150 }
151 }
152}
153
154pub(crate) struct StorageMappingKey {
155 key: SymExpr,
156 key_bytes: Option<Vec<SymExpr>>,
157 slot: SymExpr,
158}
159
160#[derive(Clone, Debug, PartialEq, Eq)]
161pub(crate) struct SymbolicMappingProvenance {
162 pub(crate) root_slot: U256,
163 pub(crate) keys: Vec<SymExpr>,
164}
165
166fn storage_mapping_key_eq(
167 cx: &mut SymCx,
168 read: &StorageMappingKey,
169 write: &StorageMappingKey,
170) -> SymBoolExpr {
171 if read.key_bytes.is_some() || write.key_bytes.is_some() {
172 let read_owned;
173 let read_bytes = if let Some(bytes) = read.key_bytes.as_deref() {
174 bytes
175 } else {
176 read_owned = read.key.clone().into_byte_exprs(cx);
177 &read_owned
178 };
179 let write_owned;
180 let write_bytes = if let Some(bytes) = write.key_bytes.as_deref() {
181 bytes
182 } else {
183 write_owned = write.key.clone().into_byte_exprs(cx);
184 &write_owned
185 };
186 let byte_equalities = read_bytes
187 .iter()
188 .zip(write_bytes)
189 .map(|(read, write)| {
190 let read = read.byte_term(cx, 31).unwrap_or_else(|| read.clone().low_byte(cx));
191 let write = write.byte_term(cx, 31).unwrap_or_else(|| write.clone().low_byte(cx));
192 SymBoolExpr::eq(cx, read, write)
193 })
194 .collect();
195 SymBoolExpr::and(cx, byte_equalities)
196 } else {
197 SymBoolExpr::eq(cx, read.key.clone(), write.key.clone())
198 }
199}
200
201fn masked_expr_matches(candidate: &SymExprKind, target: &SymExpr) -> Option<U256> {
202 match candidate {
203 SymExprKind::BinOp(SymBinOp::And, left, right) if left == target => right.eval(),
204 SymExprKind::BinOp(SymBinOp::And, left, right) if right == target => left.eval(),
205 _ => None,
206 }
207}
208
209fn context_forces_masked_expr(context: &[SymBoolExpr], target: &SymExpr, mask: U256) -> bool {
210 context.iter().any(|condition| match condition.kind() {
211 SymBoolExprKind::Cmp(SymCmpOp::Eq, left, right) => {
212 (left == target && masked_expr_matches(right.kind(), target) == Some(mask))
213 || (right == target && masked_expr_matches(left.kind(), target) == Some(mask))
214 }
215 SymBoolExprKind::And(values) => context_forces_masked_expr(values, target, mask),
216 _ => false,
217 })
218}
219
220pub(crate) fn concrete_expr_bytes(
221 bytes: &[SymExpr],
222 reason: &'static str,
223) -> Result<Vec<u8>, SymbolicError> {
224 bytes
225 .iter()
226 .map(|byte| match byte.as_const() {
227 Some(value) => Ok(value.to::<u8>()),
228 None => Err(SymbolicError::Unsupported(reason)),
229 })
230 .collect()
231}
232
233pub(crate) fn mask_low_bits(mask: U256) -> Option<usize> {
234 let bits = mask.bit_len();
235 (mask == mask_bits(U256::MAX, bits)).then_some(bits)
236}
237
238fn power_of_two_shift(value: U256) -> Option<usize> {
239 if value <= U256::ONE || !value.is_power_of_two() {
240 return None;
241 }
242 Some(value.bit_len() - 1)
243}
244
245pub(in crate::runtime::expr) fn low_masked_source(expr: &SymExpr, bits: usize) -> Option<&SymExpr> {
246 match expr.kind() {
247 SymExprKind::BinOp(SymBinOp::And, left, right)
249 if right.as_const().and_then(mask_low_bits) == Some(bits) =>
250 {
251 Some(left)
252 }
253 _ => None,
254 }
255}
256
257pub(in crate::runtime::expr) fn low_masked_source_any(expr: &SymExpr) -> Option<&SymExpr> {
258 match expr.kind() {
259 SymExprKind::BinOp(SymBinOp::And, left, right)
261 if right.as_const().and_then(mask_low_bits).is_some() =>
262 {
263 Some(left)
264 }
265 _ => None,
266 }
267}
268
269fn word_from_extracted_bytes(bytes: &[SymExpr]) -> Option<SymExpr> {
270 if bytes.len() < 32 {
271 return None;
272 }
273
274 let source = bytes
275 .iter()
276 .take(32)
277 .enumerate()
278 .find_map(|(idx, byte)| byte.extracted_byte_source(idx))?;
279
280 for (idx, byte) in bytes.iter().take(32).enumerate() {
281 if let Some(byte_source) = byte.extracted_byte_source(idx) {
282 if byte_source != source {
283 return None;
284 }
285 continue;
286 }
287
288 let byte = byte.as_const()?;
289 if source.known_byte(idx) != Some(byte.to::<u8>()) {
290 return None;
291 }
292 }
293 Some(source)
294}
295
296#[derive(Clone, PartialEq, Eq, Hash)]
297pub(crate) struct SymExpr {
298 pub(in crate::runtime::expr) kind: HashConsed<SymExprKind>,
299}
300
301impl fmt::Debug for SymExpr {
302 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
303 self.kind().fmt(f)
304 }
305}
306
307#[derive(Clone, PartialEq, Eq, Hash)]
308pub(in crate::runtime) enum SymExprKind {
309 Const(U256),
310 Var(Symbol),
311 GasLeft(Symbol),
312 Keccak { name: Symbol, len: SymExpr, bytes: Arc<[SymExpr]> },
313 Hash { name: Symbol, algorithm: &'static str, bytes: Arc<[SymExpr]> },
314 Not(SymExpr),
315 BinOp(SymBinOp, SymExpr, SymExpr),
316 TernOp(SymTernOp, SymExpr, SymExpr, SymExpr),
317 Ite(SymBoolExpr, SymExpr, SymExpr),
318}
319
320impl fmt::Debug for SymExprKind {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 match self {
328 Self::Const(value) => f.debug_tuple("Const").field(value).finish(),
329 Self::Var(symbol) => f.debug_tuple("Var").field(symbol).finish(),
330 Self::GasLeft(symbol) => f.debug_tuple("GasLeft").field(symbol).finish(),
331 Self::Keccak { name, len, .. } => f
332 .debug_struct("Keccak")
333 .field("name", name)
334 .field("len", len)
335 .finish_non_exhaustive(),
336 Self::Hash { name, algorithm, .. } => f
337 .debug_struct("Hash")
338 .field("name", name)
339 .field("algorithm", algorithm)
340 .finish_non_exhaustive(),
341 Self::Not(expr) => f.debug_tuple("Not").field(expr).finish(),
342 Self::BinOp(op, left, right) => {
343 f.debug_tuple("BinOp").field(op).field(left).field(right).finish()
344 }
345 Self::TernOp(op, first, second, third) => {
346 f.debug_tuple("TernOp").field(op).field(first).field(second).field(third).finish()
347 }
348 Self::Ite(condition, then, otherwise) => {
349 f.debug_tuple("Ite").field(condition).field(then).field(otherwise).finish()
350 }
351 }
352 }
353}
354
355impl SymExprKind {
356 pub(in crate::runtime) const fn get_var(&self) -> Option<Symbol> {
357 match self {
358 Self::Var(symbol)
359 | Self::GasLeft(symbol)
360 | Self::Keccak { name: symbol, .. }
361 | Self::Hash { name: symbol, .. } => Some(*symbol),
362 _ => None,
363 }
364 }
365
366 pub(in crate::runtime) const fn get_eval_var(&self) -> Option<Symbol> {
367 match self {
368 Self::Var(symbol) | Self::GasLeft(symbol) | Self::Hash { name: symbol, .. } => {
369 Some(*symbol)
370 }
371 _ => None,
372 }
373 }
374}
375
376impl SymExpr {
377 pub(in crate::runtime) fn kind(&self) -> &SymExprKind {
378 self.kind.value()
379 }
380
381 pub(in crate::runtime) fn from_kind(cx: &mut SymCx, kind: SymExprKind) -> Self {
382 cx.mk_expr_kind(kind)
383 }
384
385 pub(crate) fn zero(cx: &mut SymCx) -> Self {
386 Self::constant(cx, U256::ZERO)
387 }
388
389 pub(crate) fn one(cx: &mut SymCx) -> Self {
390 Self::constant(cx, U256::ONE)
391 }
392
393 pub(crate) fn constant(cx: &mut SymCx, value: U256) -> Self {
394 if value.is_zero() {
395 return cx.cached_zero();
396 }
397 if value == U256::ONE {
398 return cx.cached_one();
399 }
400 Self::from_kind(cx, SymExprKind::Const(value))
401 }
402
403 pub(crate) fn var(cx: &mut SymCx, name: &str) -> Self {
404 let symbol = cx.intern(name);
405 Self::get_var(cx, symbol)
406 }
407
408 pub(crate) fn get_var(cx: &mut SymCx, symbol: Symbol) -> Self {
409 Self::from_kind(cx, SymExprKind::Var(symbol))
410 }
411
412 pub(crate) fn gas_left(cx: &mut SymCx, id: usize) -> Self {
413 let symbol = cx.intern(&format!("gasleft_{id}"));
414 Self::from_kind(cx, SymExprKind::GasLeft(symbol))
415 }
416
417 pub(crate) fn not(cx: &mut SymCx, value: Self) -> Self {
418 match value.kind() {
419 SymExprKind::Const(value) => Self::constant(cx, !*value),
420 SymExprKind::Not(value) => value.clone(),
421 _ => Self::from_kind(cx, SymExprKind::Not(value)),
422 }
423 }
424
425 pub(crate) fn binop(cx: &mut SymCx, binop: SymBinOp, left: Self, right: Self) -> Self {
426 match binop {
427 SymBinOp::Add => match (left.kind(), right.kind()) {
428 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
429 Self::constant(cx, binop.eval(*left_value, *right_value))
431 }
432 (SymExprKind::Const(value), _) if value.is_zero() => right,
434 (_, SymExprKind::Const(value)) if value.is_zero() => left,
436 (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
438 if *value == U256::MAX
439 && let Some(condition) = if left.as_const() == Some(U256::MAX) {
440 right.bitwise_bool_word_condition(cx)
441 } else {
442 left.bitwise_bool_word_condition(cx)
443 } =>
444 {
445 let zero = Self::zero(cx);
446 let max = Self::constant(cx, U256::MAX);
447 Self::ite(cx, condition, zero, max)
448 }
449 (SymExprKind::Const(value), _)
451 if let Some(condition) = right.bitwise_bool_word_condition(cx) =>
452 {
453 let incremented = Self::constant(cx, value.wrapping_add(U256::ONE));
454 let value = Self::constant(cx, *value);
455 Self::ite(cx, condition, incremented, value)
456 }
457 (_, SymExprKind::Const(value))
458 if let Some(condition) = left.bitwise_bool_word_condition(cx) =>
459 {
460 let incremented = Self::constant(cx, value.wrapping_add(U256::ONE));
461 let value = Self::constant(cx, *value);
462 Self::ite(cx, condition, incremented, value)
463 }
464 _ => {
465 let (left, right) = Self::ordered_commutative_operands(left, right);
466 if let Some(value) = Self::add_with_const_ite(cx, &left, &right) {
467 value
468 } else if let Some(value) = Self::add_with_const_ite(cx, &right, &left) {
469 value
470 } else {
471 Self::from_kind(cx, SymExprKind::BinOp(binop, left, right))
472 }
473 }
474 },
475 SymBinOp::Sub => match (left.kind(), right.kind()) {
476 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
477 Self::constant(cx, binop.eval(*left_value, *right_value))
479 }
480 (_, SymExprKind::Const(value)) if value.is_zero() => left,
482 _ if left == right => Self::zero(cx),
484 (_, SymExprKind::Const(value))
486 if *value == U256::ONE
487 && let Some(condition) = left.bitwise_bool_word_condition(cx) =>
488 {
489 let zero = Self::zero(cx);
490 let max = Self::constant(cx, U256::MAX);
491 Self::ite(cx, condition, zero, max)
492 }
493 (SymExprKind::Const(value), _)
495 if let Some(condition) = right.bitwise_bool_word_condition(cx) =>
496 {
497 let decremented = Self::constant(cx, value.wrapping_sub(U256::ONE));
498 let value = Self::constant(cx, *value);
499 Self::ite(cx, condition, decremented, value)
500 }
501 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
502 },
503 SymBinOp::Mul => match (left.kind(), right.kind()) {
504 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
505 Self::constant(cx, binop.eval(*left_value, *right_value))
507 }
508 (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
510 if value.is_zero() =>
511 {
512 Self::zero(cx)
513 }
514 (SymExprKind::Const(value), _) if *value == U256::ONE => right,
516 (_, SymExprKind::Const(value)) if *value == U256::ONE => left,
518 _ => {
519 let (left, right) = Self::ordered_commutative_operands(left, right);
520 if let Some(condition) = left.direct_bool_word_condition(cx) {
521 let zero = Self::zero(cx);
523 Self::ite(cx, condition, right, zero)
524 } else if let Some(condition) = right.direct_bool_word_condition(cx) {
525 let zero = Self::zero(cx);
527 Self::ite(cx, condition, left, zero)
528 } else if let Some(shift) = right.as_const().and_then(power_of_two_shift) {
529 let shift = Self::constant(cx, U256::from(shift));
531 Self::binop(cx, SymBinOp::Shl, left, shift)
532 } else {
533 Self::from_kind(cx, SymExprKind::BinOp(binop, left, right))
534 }
535 }
536 },
537 SymBinOp::UDiv | SymBinOp::SDiv => match (left.kind(), right.kind()) {
538 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
539 Self::constant(cx, binop.eval(*left_value, *right_value))
541 }
542 (_, SymExprKind::Const(value)) if value.is_zero() => Self::zero(cx),
544 (_, SymExprKind::Const(value)) if *value == U256::ONE => left,
546 (
548 SymExprKind::BinOp(SymBinOp::Sub, value, low_bits),
549 SymExprKind::Const(divisor),
550 ) if binop == SymBinOp::UDiv
551 && let Some(shift) = power_of_two_shift(*divisor)
552 && low_masked_source(low_bits, shift) == Some(value) =>
553 {
554 let shift = Self::constant(cx, U256::from(shift));
555 Self::binop(cx, SymBinOp::Shr, value.clone(), shift)
556 }
557 (_, SymExprKind::Const(divisor))
559 if binop == SymBinOp::UDiv
560 && let Some(shift) = power_of_two_shift(*divisor) =>
561 {
562 let shift = Self::constant(cx, U256::from(shift));
563 Self::binop(cx, SymBinOp::Shr, left, shift)
564 }
565 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
566 },
567 SymBinOp::URem | SymBinOp::SRem => match (left.kind(), right.kind()) {
568 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
569 Self::constant(cx, binop.eval(*left_value, *right_value))
571 }
572 (_, SymExprKind::Const(value)) if value.is_zero() => Self::zero(cx),
574 (_, SymExprKind::Const(value)) if *value == U256::ONE => Self::zero(cx),
576 (_, SymExprKind::Const(divisor))
578 if binop == SymBinOp::URem
579 && let Some(bits) = power_of_two_shift(*divisor) =>
580 {
581 Self::and_const(cx, left, mask_bits(U256::MAX, bits))
582 }
583 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
584 },
585 SymBinOp::And => match (left.kind(), right.kind()) {
586 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
587 Self::constant(cx, binop.eval(*left_value, *right_value))
589 }
590 (SymExprKind::Const(value), _) | (_, SymExprKind::Const(value))
592 if value.is_zero() =>
593 {
594 Self::zero(cx)
595 }
596 (SymExprKind::Const(value), _) if *value == U256::MAX => right,
598 (_, SymExprKind::Const(value)) if *value == U256::MAX => left,
600 _ if left == right => left,
602 (SymExprKind::Const(mask), _) => Self::and_const(cx, right, *mask),
603 (_, SymExprKind::Const(mask)) => Self::and_const(cx, left, *mask),
604 _ => Self::commutative_binop(cx, binop, left, right),
605 },
606 SymBinOp::Or => match (left.kind(), right.kind()) {
607 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
608 Self::constant(cx, binop.eval(*left_value, *right_value))
610 }
611 (SymExprKind::Const(value), _) if value.is_zero() => right,
613 (_, SymExprKind::Const(value)) if value.is_zero() => left,
615 _ if left == right => left,
617 _ if let Some(value) = Self::or_with_absorbing_ite(cx, &left, right.clone()) => {
618 value
619 }
620 _ if let Some(value) = Self::or_with_absorbing_ite(cx, &right, left.clone()) => {
621 value
622 }
623 _ => Self::or(cx, left, right),
624 },
625 SymBinOp::Xor => match (left.kind(), right.kind()) {
626 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
627 Self::constant(cx, binop.eval(*left_value, *right_value))
629 }
630 (SymExprKind::Const(value), _) if value.is_zero() => right,
632 (_, SymExprKind::Const(value)) if value.is_zero() => left,
634 _ if left == right => Self::zero(cx),
636 _ => {
637 let (left, right) = Self::ordered_commutative_operands(left, right);
638 if let Some(value) = Self::xor_with_shared_operand(&left, &right)
640 .or_else(|| Self::xor_with_shared_operand(&right, &left))
641 {
642 value
643 } else if let Some(value) = Self::xor_with_bool_select(cx, &left, &right) {
645 value
646 } else if let Some(value) = Self::xor_with_bool_select(cx, &right, &left) {
647 value
648 } else if let Some(value) = Self::xor_with_zero_ite(cx, &left, &right) {
650 value
651 } else if let Some(value) = Self::xor_with_zero_ite(cx, &right, &left) {
652 value
653 } else {
654 Self::from_kind(cx, SymExprKind::BinOp(binop, left, right))
655 }
656 }
657 },
658 SymBinOp::Shl => match (left.kind(), right.kind()) {
659 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
660 Self::constant(cx, binop.eval(*left_value, *right_value))
662 }
663 (_, SymExprKind::Const(value)) if value.is_zero() => left,
665 (SymExprKind::Const(value), _) if value.is_zero() => Self::zero(cx),
667 (_, SymExprKind::Const(value)) if *value >= U256::from(256) => Self::zero(cx),
669 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
670 },
671 SymBinOp::Shr => match (left.kind(), right.kind()) {
672 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
673 Self::constant(cx, binop.eval(*left_value, *right_value))
675 }
676 (_, SymExprKind::Const(value)) if value.is_zero() => left,
678 (SymExprKind::Const(value), _) if value.is_zero() => Self::zero(cx),
680 (_, SymExprKind::Const(value)) => Self::shr_const(cx, left, *value),
681 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
682 },
683 SymBinOp::Sar => match (left.kind(), right.kind()) {
684 (SymExprKind::Const(left_value), SymExprKind::Const(right_value)) => {
685 Self::constant(cx, binop.eval(*left_value, *right_value))
687 }
688 (_, SymExprKind::Const(value)) if value.is_zero() => left,
690 _ => Self::from_kind(cx, SymExprKind::BinOp(binop, left, right)),
691 },
692 }
693 }
694
695 pub(crate) fn ternop(
696 cx: &mut SymCx,
697 ternop: SymTernOp,
698 left: Self,
699 right: Self,
700 modulus: Self,
701 ) -> Self {
702 match (left.kind(), right.kind(), modulus.kind()) {
703 (_, _, SymExprKind::Const(modulus)) if modulus.is_zero() || *modulus == U256::ONE => {
704 Self::zero(cx)
706 }
707 (SymExprKind::Const(left), SymExprKind::Const(right), SymExprKind::Const(modulus)) => {
708 Self::constant(cx, ternop.eval(*left, *right, *modulus))
710 }
711 (_, _, SymExprKind::Const(modulus))
713 if let Some(bits) = power_of_two_shift(*modulus) =>
714 {
715 let binop = match ternop {
716 SymTernOp::AddMod => SymBinOp::Add,
717 SymTernOp::MulMod => SymBinOp::Mul,
718 };
719 let value = Self::binop(cx, binop, left, right);
720 Self::and_const(cx, value, mask_bits(U256::MAX, bits))
721 }
722 _ => {
723 let (left, right) = Self::ordered_commutative_operands(left, right);
725 Self::from_kind(cx, SymExprKind::TernOp(ternop, left, right, modulus))
726 }
727 }
728 }
729
730 pub(crate) fn ite(
731 cx: &mut SymCx,
732 condition: SymBoolExpr,
733 then_expr: Self,
734 else_expr: Self,
735 ) -> Self {
736 match condition.as_const() {
737 Some(true) => then_expr,
739 Some(false) => else_expr,
741 None if then_expr == else_expr => then_expr,
743 None if then_expr.as_const().is_some_and(|value| value.is_zero())
745 && Self::self_div_expr_matches_zero_check(&condition, &else_expr) =>
746 {
747 let condition = condition.not(cx);
748 Self::bool_word(cx, condition)
749 }
750 None if then_expr.as_const() == Some(U256::ONE)
752 && else_expr.bool_word_condition().as_ref() == Some(&condition) =>
753 {
754 else_expr
755 }
756 None if else_expr.as_const().is_some_and(|value| value.is_zero())
758 && then_expr.bool_word_condition().as_ref() == Some(&condition) =>
759 {
760 then_expr
761 }
762 None => Self::from_kind(cx, SymExprKind::Ite(condition, then_expr, else_expr)),
763 }
764 }
765
766 pub(crate) fn bool_word(cx: &mut SymCx, value: SymBoolExpr) -> Self {
767 let one = Self::one(cx);
768 let zero = Self::zero(cx);
769 Self::ite(cx, value, one, zero)
770 }
771
772 fn self_div_expr_matches_zero_check(cond: &SymBoolExpr, expr: &Self) -> bool {
773 let Some(zero_operand) = cond.zero_check_operand() else { return false };
774 let Some((numerator, denominator)) = expr.udiv_operands() else { return false };
775 numerator == zero_operand && denominator == zero_operand
776 }
777
778 pub(crate) fn keccak_symbol(cx: &mut SymCx, name: Symbol, len: Self, bytes: Vec<Self>) -> Self {
779 Self::from_kind(cx, SymExprKind::Keccak { name, len, bytes: bytes.into() })
780 }
781
782 pub(crate) fn hash_symbol(
783 cx: &mut SymCx,
784 name: Symbol,
785 algorithm: &'static str,
786 bytes: Vec<Self>,
787 ) -> Self {
788 Self::from_kind(cx, SymExprKind::Hash { name, algorithm, bytes: bytes.into() })
789 }
790
791 fn or(cx: &mut SymCx, left: Self, right: Self) -> Self {
792 if let Some(rebuilt) = Self::rebuild_from_or_terms(&left, &right) {
793 return rebuilt;
795 }
796 Self::commutative_binop(cx, SymBinOp::Or, left, right)
797 }
798
799 fn or_with_absorbing_ite(cx: &mut SymCx, conditional: &Self, other: Self) -> Option<Self> {
800 let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
801 return None;
802 };
803 if then_expr.as_const().is_some_and(|value| value.is_zero())
804 && else_expr.as_const() == Some(U256::MAX)
805 {
806 return Some(Self::ite(cx, condition.clone(), other, else_expr.clone()));
807 }
808 if then_expr.as_const() == Some(U256::MAX)
809 && else_expr.as_const().is_some_and(|value| value.is_zero())
810 {
811 return Some(Self::ite(cx, condition.clone(), then_expr.clone(), other));
812 }
813 None
814 }
815
816 fn add_with_const_ite(cx: &mut SymCx, other: &Self, conditional: &Self) -> Option<Self> {
817 if other.as_const().is_some() {
818 return None;
819 }
820 let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
821 return None;
822 };
823 if then_expr.as_const().is_none() || else_expr.as_const().is_none() {
824 return None;
825 }
826 if !Self::duplicating_branchless_rewrite_fits(other, conditional) {
827 return None;
828 }
829 let then_expr = Self::binop(cx, SymBinOp::Add, other.clone(), then_expr.clone());
830 let else_expr = Self::binop(cx, SymBinOp::Add, other.clone(), else_expr.clone());
831 Some(Self::ite(cx, condition.clone(), then_expr, else_expr))
832 }
833
834 fn xor_with_bool_select(cx: &mut SymCx, base: &Self, selector: &Self) -> Option<Self> {
835 let SymExprKind::BinOp(SymBinOp::Mul, left, right) = selector.kind() else { return None };
836 let (condition_word, selected) = match left.kind() {
837 SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right) if delta_left == base => {
838 (right, delta_right.clone())
839 }
840 SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right) if delta_right == base => {
841 (right, delta_left.clone())
842 }
843 _ => match right.kind() {
844 SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right)
845 if delta_left == base =>
846 {
847 (left, delta_right.clone())
848 }
849 SymExprKind::BinOp(SymBinOp::Xor, delta_left, delta_right)
850 if delta_right == base =>
851 {
852 (left, delta_left.clone())
853 }
854 _ => return None,
855 },
856 };
857 let condition = condition_word.bitwise_bool_word_condition(cx)?;
858 Some(Self::ite(cx, condition, selected, base.clone()))
859 }
860
861 fn xor_with_shared_operand(base: &Self, nested: &Self) -> Option<Self> {
862 let SymExprKind::BinOp(SymBinOp::Xor, left, right) = nested.kind() else { return None };
863 if left == base {
864 Some(right.clone())
865 } else if right == base {
866 Some(left.clone())
867 } else {
868 None
869 }
870 }
871
872 fn xor_with_zero_ite(cx: &mut SymCx, base: &Self, conditional: &Self) -> Option<Self> {
873 let SymExprKind::Ite(condition, then_expr, else_expr) = conditional.kind() else {
874 return None;
875 };
876 if then_expr.as_const().is_some_and(|value| value.is_zero()) {
877 if !Self::duplicating_branchless_rewrite_fits(base, conditional) {
878 return None;
879 }
880 let selected = Self::binop(cx, SymBinOp::Xor, base.clone(), else_expr.clone());
881 return Some(Self::ite(cx, condition.clone(), base.clone(), selected));
882 }
883 if else_expr.as_const().is_some_and(|value| value.is_zero()) {
884 if !Self::duplicating_branchless_rewrite_fits(base, conditional) {
885 return None;
886 }
887 let selected = Self::binop(cx, SymBinOp::Xor, base.clone(), then_expr.clone());
888 return Some(Self::ite(cx, condition.clone(), selected, base.clone()));
889 }
890 None
891 }
892
893 fn duplicating_branchless_rewrite_fits(operand: &Self, conditional: &Self) -> bool {
899 let mut counter = UnfoldedNodeCounter::new();
900 let Some(operand_nodes) = counter.expr_nodes(operand) else {
901 return false;
902 };
903 let Some(conditional_nodes) = counter.expr_nodes(conditional) else {
904 return false;
905 };
906
907 operand_nodes
908 .checked_mul(2)
909 .and_then(|duplicated| conditional_nodes.checked_add(duplicated))
910 .and_then(|nodes| nodes.checked_add(2))
912 .is_some_and(|nodes| nodes <= MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES)
913 }
914
915 fn commutative_binop(cx: &mut SymCx, op: SymBinOp, left: Self, right: Self) -> Self {
916 let (left, right) = Self::ordered_commutative_operands(left, right);
918 Self::from_kind(cx, SymExprKind::BinOp(op, left, right))
919 }
920
921 pub(in crate::runtime::expr) fn ordered_commutative_operands(
922 left: Self,
923 right: Self,
924 ) -> (Self, Self) {
925 match left.complexity().cmp(&right.complexity()) {
926 std::cmp::Ordering::Less => (right, left),
928 std::cmp::Ordering::Greater => (left, right),
929 std::cmp::Ordering::Equal if right.kind.stable_hash_cmp(&left.kind).is_lt() => {
930 (right, left)
931 }
932 std::cmp::Ordering::Equal => (left, right),
933 }
934 }
935
936 pub(in crate::runtime) fn sort_interned_factors(factors: &mut [Self]) {
941 factors.sort_unstable_by(|left, right| left.kind.identity_cmp(&right.kind));
942 }
943
944 fn complexity(&self) -> usize {
945 match self.kind() {
946 SymExprKind::Const(_) => 0,
947 SymExprKind::Not(_) => 1,
948 SymExprKind::BinOp(..) => 2,
949 SymExprKind::TernOp(..) => 3,
950 _ => 4,
951 }
952 }
953
954 fn and_const(cx: &mut SymCx, expr: Self, mask: U256) -> Self {
955 if mask.is_zero() {
956 return Self::zero(cx);
958 }
959 if mask == U256::MAX {
960 return expr;
962 }
963
964 match expr.kind() {
965 SymExprKind::Const(value) => Self::constant(cx, *value & mask),
967 SymExprKind::BinOp(SymBinOp::Or, left, right) => {
968 let left = Self::and_const(cx, left.clone(), mask);
970 let right = Self::and_const(cx, right.clone(), mask);
971 Self::binop(cx, SymBinOp::Or, left, right)
972 }
973 SymExprKind::BinOp(SymBinOp::Shl, _, shift)
974 if mask_low_bits(mask).is_some_and(|bits| {
975 shift
976 .as_const()
977 .and_then(|shift| usize::try_from(shift).ok())
978 .is_some_and(|shift| bits <= shift)
979 }) =>
980 {
981 Self::zero(cx)
983 }
984 SymExprKind::BinOp(SymBinOp::And, left, right) => {
985 if right.as_const() == Some(mask) {
986 Self::and_const(cx, left.clone(), mask)
988 } else if left == right {
989 Self::and_const(cx, left.clone(), mask)
991 } else {
992 let mask = Self::constant(cx, mask);
993 Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::And, expr, mask))
994 }
995 }
996 _ => {
997 let mask = Self::constant(cx, mask);
998 Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::And, expr, mask))
999 }
1000 }
1001 }
1002
1003 fn shr_const(cx: &mut SymCx, expr: Self, shift: U256) -> Self {
1004 if shift.is_zero() {
1005 return expr;
1007 }
1008 if shift >= U256::from(256) {
1009 return Self::zero(cx);
1011 }
1012
1013 let shift = usize::try_from(shift).expect("shift is less than 256");
1014 if expr.unsigned_bits() <= shift {
1015 return Self::zero(cx);
1017 }
1018
1019 if let SymExprKind::BinOp(SymBinOp::Shl, inner, left_shift) = expr.kind()
1020 && left_shift.as_const() == Some(U256::from(shift))
1021 && inner.unsigned_bits() <= 256 - shift
1022 {
1023 return inner.clone();
1025 }
1026
1027 if let SymExprKind::BinOp(SymBinOp::Or, left, right) = expr.kind() {
1028 let left = Self::shr_const(cx, left.clone(), U256::from(shift));
1031 let right = Self::shr_const(cx, right.clone(), U256::from(shift));
1032 if left.as_const().is_some_and(|value| value.is_zero()) {
1033 return right;
1034 }
1035 if right.as_const().is_some_and(|value| value.is_zero()) {
1036 return left;
1037 }
1038 }
1039
1040 let shift = Self::constant(cx, U256::from(shift));
1041 Self::from_kind(cx, SymExprKind::BinOp(SymBinOp::Shr, expr, shift))
1042 }
1043
1044 fn rebuild_from_or_terms(left: &Self, right: &Self) -> Option<Self> {
1045 let mut terms = Vec::new();
1046 left.push_or_terms(&mut terms);
1047 right.push_or_terms(&mut terms);
1048 Self::rebuild_from_extracted_byte_terms(&terms)
1049 .or_else(|| Self::rebuild_from_shifted_word_fragments(&terms))
1050 }
1051
1052 pub(in crate::runtime) fn push_or_terms<'a>(&'a self, terms: &mut Vec<&'a Self>) {
1053 match self.kind() {
1054 SymExprKind::BinOp(SymBinOp::Or, left, right) => {
1055 left.push_or_terms(terms);
1056 right.push_or_terms(terms);
1057 }
1058 _ => terms.push(self),
1059 }
1060 }
1061
1062 fn rebuild_from_extracted_byte_terms(terms: &[&Self]) -> Option<Self> {
1063 if terms.len() <= 1 {
1064 return None;
1065 }
1066
1067 let mut source = None;
1068 let mut seen = [false; 32];
1069 for term in terms {
1070 if term.as_const().is_some_and(|value| value.is_zero()) {
1071 continue;
1072 }
1073 let (term_source, index) = term.extracted_shifted_byte_term()?;
1074 match &source {
1075 Some(source) if source != &term_source => return None,
1076 Some(_) => {}
1077 None => source = Some(term_source),
1078 }
1079 seen[index] = true;
1080 }
1081
1082 let source = source?;
1083 for (index, seen) in seen.into_iter().enumerate() {
1084 if !seen && source.known_byte(index) != Some(0) {
1085 return None;
1086 }
1087 }
1088 Some(source)
1089 }
1090
1091 fn extracted_shifted_byte_term(&self) -> Option<(Self, usize)> {
1092 match self.kind() {
1093 SymExprKind::BinOp(SymBinOp::Shl, byte, shift) => {
1094 let shift = shift.as_const()?;
1095 let Ok(shift) = usize::try_from(shift) else { return None };
1096 if shift % 8 != 0 || shift > 248 {
1097 return None;
1098 }
1099 let index = 31 - shift / 8;
1100 let source = byte.extracted_unshifted_byte_source(index)?;
1101 Some((source, index))
1102 }
1103 _ => self.extracted_unshifted_byte_source(31).map(|source| (source, 31)),
1104 }
1105 }
1106
1107 fn extracted_unshifted_byte_source(&self, index: usize) -> Option<Self> {
1108 let expr = self.strip_low_byte_mask();
1109 if index == 31 {
1110 return Some(expr.clone());
1111 }
1112 let SymExprKind::BinOp(SymBinOp::Shr, source, shift) = expr.kind() else { return None };
1113 let shift = shift.as_const()?;
1114 (shift == U256::from((31 - index) * 8)).then(|| source.clone())
1115 }
1116
1117 fn rebuild_from_shifted_word_fragments(terms: &[&Self]) -> Option<Self> {
1118 if terms.len() != 2 {
1119 return None;
1120 }
1121
1122 let left_low = terms[0].low_word_fragment();
1123 let right_low = terms[1].low_word_fragment();
1124 let left_high = terms[0].shifted_high_word_fragment();
1125 let right_high = terms[1].shifted_high_word_fragment();
1126 match (left_low, right_low, left_high, right_high) {
1127 (Some((low_source, low_bits)), None, None, Some((high_source, high_bits)))
1128 | (None, Some((low_source, low_bits)), Some((high_source, high_bits)), None)
1129 if low_source == high_source && low_bits == high_bits =>
1130 {
1131 Some(low_source)
1132 }
1133 _ => None,
1134 }
1135 }
1136
1137 fn low_word_fragment(&self) -> Option<(Self, usize)> {
1138 let SymExprKind::BinOp(SymBinOp::And, left, right) = self.kind() else { return None };
1139 let mask = right.as_const()?;
1140 mask_low_bits(mask).map(|bits| (left.clone(), bits))
1141 }
1142
1143 fn shifted_high_word_fragment(&self) -> Option<(Self, usize)> {
1144 let SymExprKind::BinOp(SymBinOp::Shl, value, shift) = self.kind() else { return None };
1145 let bits = shift.as_const().and_then(|shift| usize::try_from(shift).ok())?;
1146 if bits == 0 || bits >= 256 {
1147 return None;
1148 }
1149
1150 let (source, source_shift, width) = value.shifted_low_fragment_source()?;
1151 (source_shift == bits && width == 256 - bits).then_some((source, bits))
1152 }
1153
1154 fn shifted_low_fragment_source(&self) -> Option<(Self, usize, usize)> {
1155 let SymExprKind::BinOp(SymBinOp::And, left, right) = self.kind() else { return None };
1156 let mask = right.as_const()?;
1157 Self::shifted_low_fragment_source_with_mask(left, mask)
1158 }
1159
1160 fn shifted_low_fragment_source_with_mask(
1161 value: &Self,
1162 mask: U256,
1163 ) -> Option<(Self, usize, usize)> {
1164 let width = mask_low_bits(mask)?;
1165 match value.kind() {
1166 SymExprKind::BinOp(SymBinOp::Shr, source, shift) => {
1167 let shift = shift.as_const().and_then(|shift| usize::try_from(shift).ok())?;
1168 Some((source.clone(), shift, width))
1169 }
1170 _ => Some((value.clone(), 0, width)),
1171 }
1172 }
1173
1174 pub(crate) fn low_byte(self, cx: &mut SymCx) -> Self {
1175 if let Some(word) = self.as_const() {
1176 return Self::constant(cx, U256::from(word.to::<u8>()));
1177 }
1178 let mask = Self::constant(cx, U256::from(0xff));
1179 Self::binop(cx, SymBinOp::And, self, mask)
1180 }
1181
1182 pub(crate) fn into_byte_exprs(self, cx: &mut SymCx) -> Vec<Self> {
1183 SymBytes::word(cx, self).materialize(cx)
1184 }
1185
1186 pub(crate) fn into_bytes(self, cx: &mut SymCx) -> SymBytes {
1187 SymBytes::word(cx, self)
1188 }
1189
1190 pub(crate) fn from_bytes(cx: &mut SymCx, bytes: impl IntoIterator<Item = Self>) -> Self {
1191 let bytes = bytes.into_iter().collect::<Vec<_>>();
1192 if let Ok(concrete) = concrete_expr_bytes(&bytes, "symbolic word bytes") {
1193 let mut word = [0u8; 32];
1194 for (idx, byte) in concrete.into_iter().take(32).enumerate() {
1195 word[idx] = byte;
1196 }
1197 return Self::constant(cx, U256::from_be_bytes(word));
1198 }
1199
1200 if let Some(expr) = word_from_extracted_bytes(&bytes) {
1201 return expr;
1202 }
1203
1204 let mut expr = Self::zero(cx);
1205 for (idx, byte) in bytes.into_iter().take(32).enumerate() {
1206 let shift = (31 - idx) * 8;
1207 let byte = byte.low_byte(cx);
1208 let byte = if shift == 0 {
1209 byte
1210 } else {
1211 let shift = Self::constant(cx, U256::from(shift));
1212 Self::binop(cx, SymBinOp::Shl, byte, shift)
1213 };
1214 expr = Self::binop(cx, SymBinOp::Or, expr, byte);
1215 }
1216 expr
1217 }
1218
1219 pub(crate) fn as_const(&self) -> Option<U256> {
1220 match self.kind() {
1221 SymExprKind::Const(value) => Some(*value),
1222 _ => None,
1223 }
1224 }
1225
1226 pub(crate) fn eval(&self) -> Option<U256> {
1227 self.eval_model_if_complete(&NoopModel).ok().flatten()
1228 }
1229
1230 pub(crate) fn eval_model<M: SymbolicModelLookup + ?Sized>(
1231 &self,
1232 model: &M,
1233 ) -> Result<U256, SymbolicError> {
1234 ModelEvaluator::new(model).eval_word(self)
1235 }
1236
1237 pub(crate) fn eval_model_if_complete<M: SymbolicModelLookup + ?Sized>(
1238 &self,
1239 model: &M,
1240 ) -> Result<Option<U256>, SymbolicError> {
1241 let mut vars = SymbolicVars::default();
1242 self.collect_eval_vars(&mut vars);
1243 if vars.iter().copied().all(|var| model.contains_name(var)) {
1244 self.eval_model(model).map(Some)
1245 } else {
1246 Ok(None)
1247 }
1248 }
1249
1250 pub(crate) fn assign_model_value(&self, model: &mut SymbolicModel, value: U256) -> bool {
1251 match self.kind() {
1252 SymExprKind::Const(existing) => *existing == value,
1253 SymExprKind::Var(var) => {
1254 if let Some(existing) = model.get(var) {
1255 *existing == value
1256 } else {
1257 model.insert(*var, value);
1258 true
1259 }
1260 }
1261 SymExprKind::GasLeft(symbol) => {
1262 if let Some(existing) = model.get(symbol) {
1263 *existing == value
1264 } else {
1265 model.insert(*symbol, value);
1266 true
1267 }
1268 }
1269 _ => false,
1270 }
1271 }
1272
1273 pub(crate) fn bool_word_condition(&self) -> Option<SymBoolExpr> {
1274 let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() else {
1275 return None;
1276 };
1277 Self::bool_word_condition_from_parts(condition, then_expr, else_expr)
1278 }
1279
1280 pub(in crate::runtime) fn bitwise_bool_word_condition(
1281 &self,
1282 cx: &mut SymCx,
1283 ) -> Option<SymBoolExpr> {
1284 let mut pending = vec![self.clone()];
1285 let mut seen_words = HashSet::<Self>::default();
1286 let mut leaf_conditions = IndexSet::<SymBoolExpr>::default();
1287 let mut bit_widths = HashMap::default();
1288 let mut remaining = MAX_BITWISE_BOOL_WORD_VISITS;
1289 while let Some(word) = pending.pop() {
1290 if !seen_words.insert(word.clone()) {
1291 continue;
1292 }
1293 if remaining == 0 {
1294 return None;
1295 }
1296 remaining -= 1;
1297
1298 if let Some(condition) = word.direct_bool_word_condition(cx) {
1299 leaf_conditions.insert(condition);
1300 continue;
1301 }
1302 if let SymExprKind::BinOp(SymBinOp::Or, left, right) = word.kind() {
1303 pending.push(right.clone());
1304 pending.push(left.clone());
1305 continue;
1306 }
1307
1308 if word.unsigned_bits_cached(&mut bit_widths, &mut remaining) == Some(1) {
1309 let zero = Self::zero(cx);
1310 let (word, zero) = Self::ordered_commutative_operands(word, zero);
1311 let zero_check =
1312 SymBoolExpr::from_kind(cx, SymBoolExprKind::Cmp(SymCmpOp::Eq, word, zero));
1313 leaf_conditions.insert(zero_check.not(cx));
1314 continue;
1315 }
1316 return None;
1317 }
1318
1319 Some(SymBoolExpr::or(cx, leaf_conditions.into_iter().collect()))
1320 }
1321
1322 fn direct_bool_word_condition(&self, cx: &mut SymCx) -> Option<SymBoolExpr> {
1328 let SymExprKind::Ite(condition, then_expr, else_expr) = self.kind() else {
1329 return None;
1330 };
1331 match (then_expr.as_const(), else_expr.as_const()) {
1332 (Some(then_value), Some(else_value))
1333 if then_value == U256::ONE && else_value.is_zero() =>
1334 {
1335 Some(condition.clone())
1336 }
1337 (Some(then_value), Some(else_value))
1338 if then_value.is_zero() && else_value == U256::ONE =>
1339 {
1340 Some(condition.clone().not(cx))
1341 }
1342 _ => None,
1343 }
1344 }
1345
1346 fn bool_word_condition_from_parts(
1347 condition: &SymBoolExpr,
1348 then_expr: &Self,
1349 else_expr: &Self,
1350 ) -> Option<SymBoolExpr> {
1351 match (then_expr.as_const(), else_expr.as_const()) {
1352 (Some(then_value), Some(else_value))
1353 if then_value == U256::ONE && else_value.is_zero() =>
1354 {
1355 Some(condition.clone())
1356 }
1357 (Some(then_value), Some(else_value))
1358 if then_value.is_zero() && else_value == U256::ONE =>
1359 {
1360 None
1361 }
1362 _ => None,
1363 }
1364 }
1365
1366 pub(crate) fn into_zero_bool(self, cx: &mut SymCx) -> SymBoolExpr {
1367 match self.kind() {
1368 SymExprKind::Const(value) => SymBoolExpr::constant(cx, value.is_zero()),
1369 SymExprKind::Ite(condition, then_expr, else_expr) => {
1370 match Self::bool_word_condition_from_parts(condition, then_expr, else_expr) {
1371 Some(condition) => SymBoolExpr::not_bool(cx, condition),
1372 None => {
1373 let zero = Self::zero(cx);
1374 SymBoolExpr::eq(cx, self, zero)
1375 }
1376 }
1377 }
1378 _ => {
1379 let zero = Self::zero(cx);
1380 SymBoolExpr::eq(cx, self, zero)
1381 }
1382 }
1383 }
1384
1385 pub(crate) fn nonzero_bool(self, cx: &mut SymCx) -> SymBoolExpr {
1386 let zero = self.into_zero_bool(cx);
1387 SymBoolExpr::not_bool(cx, zero)
1388 }
1389
1390 pub(crate) fn as_const_or(&self, reason: &'static str) -> Result<U256, SymbolicError> {
1391 self.as_const().ok_or(SymbolicError::Unsupported(reason))
1392 }
1393
1394 pub(crate) fn as_usize_or(&self, reason: &'static str) -> Result<usize, SymbolicError> {
1395 let value = self.as_const_or(reason)?;
1396 usize::try_from(value).map_err(|_| SymbolicError::Unsupported(reason))
1397 }
1398
1399 pub(crate) fn contains_keccak(&self) -> bool {
1400 self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::Keccak { .. }))
1401 }
1402
1403 pub(crate) fn contains_gasleft(&self) -> bool {
1404 self.visit_bool(|expr| expr.is_raw_gasleft())
1405 }
1406
1407 pub(crate) fn contains_udiv(&self) -> bool {
1408 self.visit_bool(|expr| matches!(expr.kind(), SymExprKind::BinOp(SymBinOp::UDiv, _, _)))
1409 }
1410
1411 pub(crate) fn contains_ite(&self) -> bool {
1412 let mut visited = HashSet::<&Self>::default();
1413 self.contains_ite_cached(&mut visited, false)
1414 }
1415
1416 fn contains_ite_cached<'a>(&'a self, visited: &mut HashSet<&'a Self>, memoize: bool) -> bool {
1417 match self.kind() {
1418 SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => return false,
1419 SymExprKind::Ite(_, _, _) => return true,
1420 _ => {}
1421 }
1422 if memoize && !visited.insert(self) {
1423 return false;
1424 }
1425
1426 match self.kind() {
1427 SymExprKind::Keccak { len, bytes, .. } => {
1428 len.contains_ite_cached(visited, true)
1429 || bytes.iter().any(|byte| byte.contains_ite_cached(visited, true))
1430 }
1431 SymExprKind::Hash { bytes, .. } => {
1432 bytes.iter().any(|byte| byte.contains_ite_cached(visited, true))
1433 }
1434 SymExprKind::Not(value) => value.contains_ite_cached(visited, true),
1435 SymExprKind::BinOp(_, left, right) => {
1436 left.contains_ite_cached(visited, true) || right.contains_ite_cached(visited, true)
1437 }
1438 SymExprKind::TernOp(_, left, right, modulus) => {
1439 left.contains_ite_cached(visited, true)
1440 || right.contains_ite_cached(visited, true)
1441 || modulus.contains_ite_cached(visited, true)
1442 }
1443 SymExprKind::Const(_)
1444 | SymExprKind::Var(_)
1445 | SymExprKind::GasLeft(_)
1446 | SymExprKind::Ite(_, _, _) => unreachable!("leaf expression handled before descent"),
1447 }
1448 }
1449
1450 pub(in crate::runtime) fn udiv_operands(&self) -> Option<(&Self, &Self)> {
1451 match self.kind() {
1452 SymExprKind::BinOp(SymBinOp::UDiv, numerator, denominator) => {
1453 Some((numerator, denominator))
1454 }
1455 _ => None,
1456 }
1457 }
1458
1459 pub(crate) fn collect_eval_vars(&self, vars: &mut SymbolicVars) {
1460 let _ = self.visit::<()>(&mut |expr| {
1461 vars.extend(expr.kind().get_eval_var());
1462 ControlFlow::Continue(())
1463 });
1464 }
1465
1466 pub(crate) fn known_byte(&self, index: usize) -> Option<u8> {
1467 debug_assert!(index < 32);
1468 match self.kind() {
1469 SymExprKind::Const(value) => Some(value.to_be_bytes::<32>()[index]),
1470 SymExprKind::Var(_)
1471 | SymExprKind::GasLeft(_)
1472 | SymExprKind::Keccak { .. }
1473 | SymExprKind::Hash { .. } => None,
1474 SymExprKind::Not(value) => value.known_byte(index).map(|byte| !byte),
1475 SymExprKind::Ite(_, then_expr, else_expr) => {
1476 let then_byte = then_expr.known_byte(index)?;
1477 let else_byte = else_expr.known_byte(index)?;
1478 (then_byte == else_byte).then_some(then_byte)
1479 }
1480 SymExprKind::BinOp(op, left, right) => match op {
1481 SymBinOp::And => match (left.known_byte(index), right.known_byte(index)) {
1482 (Some(left), Some(right)) => Some(left & right),
1483 (Some(0), _) | (_, Some(0)) => Some(0),
1484 _ => None,
1485 },
1486 SymBinOp::Or => Some(left.known_byte(index)? | right.known_byte(index)?),
1487 SymBinOp::Xor => Some(left.known_byte(index)? ^ right.known_byte(index)?),
1488 SymBinOp::Shl => {
1489 let shift = right.as_const()?;
1490 if shift >= U256::from(256) {
1491 return Some(0);
1492 }
1493 let shift = usize::try_from(shift).expect("checked byte shift");
1494 if shift % 8 != 0 {
1495 return None;
1496 }
1497 let source_index = index + shift / 8;
1498 if source_index >= 32 { Some(0) } else { left.known_byte(source_index) }
1499 }
1500 SymBinOp::Shr => {
1501 let shift = right.as_const()?;
1502 if shift >= U256::from(256) {
1503 return Some(0);
1504 }
1505 let shift = usize::try_from(shift).expect("checked byte shift");
1506 if shift % 8 != 0 {
1507 return None;
1508 }
1509 let byte_shift = shift / 8;
1510 if index < byte_shift { Some(0) } else { left.known_byte(index - byte_shift) }
1511 }
1512 SymBinOp::Add
1513 | SymBinOp::Sub
1514 | SymBinOp::Mul
1515 | SymBinOp::UDiv
1516 | SymBinOp::URem
1517 | SymBinOp::SDiv
1518 | SymBinOp::SRem
1519 | SymBinOp::Sar => None,
1520 },
1521 SymExprKind::TernOp(_, _, _, _) => None,
1522 }
1523 }
1524
1525 pub(crate) fn known_word(&self) -> Option<U256> {
1526 let mut word = [0u8; 32];
1527 for (idx, byte) in word.iter_mut().enumerate() {
1528 *byte = self.known_byte(idx)?;
1529 }
1530 Some(U256::from_be_bytes(word))
1531 }
1532
1533 pub(crate) fn unsigned_bits(&self) -> usize {
1534 let mut bit_widths = HashMap::default();
1535 let mut remaining = usize::MAX;
1536 self.unsigned_bits_cached(&mut bit_widths, &mut remaining).unwrap_or(256)
1537 }
1538
1539 fn unsigned_bits_cached(
1540 &self,
1541 bit_widths: &mut HashMap<Self, usize>,
1542 remaining: &mut usize,
1543 ) -> Option<usize> {
1544 if let Some(bits) = bit_widths.get(self) {
1545 return Some(*bits);
1546 }
1547
1548 let mut pending = vec![(self.clone(), false)];
1549 while let Some((expr, children_visited)) = pending.pop() {
1550 if bit_widths.contains_key(&expr) {
1551 continue;
1552 }
1553 if !children_visited {
1554 if *remaining == 0 {
1555 return None;
1556 }
1557 *remaining -= 1;
1558 pending.push((expr.clone(), true));
1559 match expr.kind() {
1560 SymExprKind::BinOp(SymBinOp::And, left, right)
1561 if right.as_const().is_some() =>
1562 {
1563 pending.push((left.clone(), false));
1564 }
1565 SymExprKind::BinOp(SymBinOp::Add | SymBinOp::Mul, left, right)
1566 | SymExprKind::Ite(_, left, right) => {
1567 pending.push((right.clone(), false));
1568 pending.push((left.clone(), false));
1569 }
1570 SymExprKind::BinOp(SymBinOp::Shl | SymBinOp::Shr, left, right)
1571 if right
1572 .as_const()
1573 .and_then(|shift| usize::try_from(shift).ok())
1574 .is_some() =>
1575 {
1576 pending.push((left.clone(), false));
1577 }
1578 SymExprKind::BinOp(SymBinOp::UDiv, left, _) => {
1579 pending.push((left.clone(), false));
1580 }
1581 SymExprKind::TernOp(_, _, _, modulus) => {
1582 pending.push((modulus.clone(), false));
1583 }
1584 _ => {}
1585 }
1586 continue;
1587 }
1588
1589 let bits = match expr.kind() {
1590 SymExprKind::Const(value) => value.bit_len().max(1),
1591 SymExprKind::BinOp(SymBinOp::And, left, right) => {
1592 if let Some(mask) = right.as_const() {
1593 bit_widths[left].min(mask.bit_len())
1594 } else {
1595 256
1596 }
1597 }
1598 SymExprKind::BinOp(SymBinOp::Add, left, right) => {
1599 bit_widths[left].max(bit_widths[right]).saturating_add(1).min(256)
1600 }
1601 SymExprKind::BinOp(SymBinOp::Mul, left, right) => {
1602 bit_widths[left].saturating_add(bit_widths[right]).min(256)
1603 }
1604 SymExprKind::BinOp(SymBinOp::Shl, left, right) => right
1605 .as_const()
1606 .and_then(|shift| usize::try_from(shift).ok())
1607 .map_or(256, |shift| bit_widths[left].saturating_add(shift).min(256)),
1608 SymExprKind::BinOp(SymBinOp::Shr, left, right) => right
1609 .as_const()
1610 .and_then(|shift| usize::try_from(shift).ok())
1611 .map_or(256, |shift| bit_widths[left].saturating_sub(shift).max(1)),
1612 SymExprKind::BinOp(SymBinOp::UDiv, left, _) => bit_widths[left],
1613 SymExprKind::TernOp(_, _, _, modulus) => bit_widths[modulus],
1614 SymExprKind::Ite(_, left, right) => bit_widths[left].max(bit_widths[right]),
1615 _ => 256,
1616 };
1617 bit_widths.insert(expr, bits);
1618 }
1619 bit_widths.get(self).copied()
1620 }
1621
1622 pub(crate) fn extracted_byte(&self, cx: &mut SymCx, index: usize) -> Self {
1623 debug_assert!(index < 32);
1624 let shift = Self::constant(cx, U256::from((31 - index) * 8));
1625 let shifted = Self::binop(cx, SymBinOp::Shr, self.clone(), shift);
1626 let mask = Self::constant(cx, U256::from(0xff));
1627 Self::binop(cx, SymBinOp::And, shifted, mask)
1628 }
1629
1630 pub(crate) fn extracted_byte_source(&self, index: usize) -> Option<Self> {
1631 let expr = self.strip_low_byte_mask();
1632 if index == 31 {
1633 return Some(expr.clone());
1634 }
1635 let SymExprKind::BinOp(SymBinOp::Shr, source, shift) = expr.kind() else { return None };
1636 let shift = shift.as_const()?;
1637 (shift == U256::from((31 - index) * 8)).then(|| source.clone())
1638 }
1639
1640 pub(crate) fn strip_low_byte_mask(&self) -> &Self {
1641 match self.kind() {
1642 SymExprKind::BinOp(SymBinOp::And, left, right)
1643 if right.as_const() == Some(U256::from(0xff)) =>
1644 {
1645 left.strip_low_byte_mask()
1646 }
1647 _ => self,
1648 }
1649 }
1650
1651 pub(crate) fn byte_term(&self, cx: &mut SymCx, index: usize) -> Option<Self> {
1652 debug_assert!(index < 32);
1653
1654 match self.kind() {
1655 SymExprKind::Const(value) => {
1656 Some(Self::constant(cx, U256::from(value.to_be_bytes::<32>()[index])))
1657 }
1658 SymExprKind::Var(_)
1659 | SymExprKind::GasLeft(_)
1660 | SymExprKind::Keccak { .. }
1661 | SymExprKind::Hash { .. } => Some(self.extracted_byte(cx, index)),
1662 SymExprKind::Not(value) => {
1663 let value = value.byte_term(cx, index)?;
1664 Some(Self::not(cx, value))
1665 }
1666 SymExprKind::Ite(cond, then_expr, else_expr) => {
1667 let then_expr = then_expr.byte_term(cx, index)?;
1668 let else_expr = else_expr.byte_term(cx, index)?;
1669 Some(Self::ite(cx, cond.clone(), then_expr, else_expr))
1670 }
1671 SymExprKind::BinOp(op, left, right) => match op {
1672 SymBinOp::And => Self::binary_byte_term(
1673 cx,
1674 left,
1675 right,
1676 index,
1677 SymBinOp::And,
1678 |byte| byte == 0xff,
1679 |byte| byte == 0,
1680 ),
1681 SymBinOp::Or => Self::binary_byte_term(
1682 cx,
1683 left,
1684 right,
1685 index,
1686 SymBinOp::Or,
1687 |byte| byte == 0,
1688 |_| false,
1689 ),
1690 SymBinOp::Xor => Self::binary_byte_term(
1691 cx,
1692 left,
1693 right,
1694 index,
1695 SymBinOp::Xor,
1696 |byte| byte == 0,
1697 |_| false,
1698 ),
1699 SymBinOp::Shl => {
1700 let shift = right.eval()?;
1701 if shift >= U256::from(256) {
1702 return Some(Self::zero(cx));
1703 }
1704 let shift = usize::try_from(shift).expect("checked byte shift");
1705 if shift % 8 != 0 {
1706 return None;
1707 }
1708 let source_index = index + shift / 8;
1709 if source_index >= 32 {
1710 Some(Self::zero(cx))
1711 } else {
1712 left.byte_term(cx, source_index)
1713 }
1714 }
1715 SymBinOp::Shr => {
1716 let shift = right.eval()?;
1717 if shift >= U256::from(256) {
1718 return Some(Self::zero(cx));
1719 }
1720 let shift = usize::try_from(shift).expect("checked byte shift");
1721 if shift % 8 != 0 {
1722 return None;
1723 }
1724 let byte_shift = shift / 8;
1725 if index < byte_shift {
1726 Some(Self::zero(cx))
1727 } else {
1728 left.byte_term(cx, index - byte_shift)
1729 }
1730 }
1731 SymBinOp::Add
1732 | SymBinOp::Sub
1733 | SymBinOp::Mul
1734 | SymBinOp::UDiv
1735 | SymBinOp::URem
1736 | SymBinOp::SDiv
1737 | SymBinOp::SRem
1738 | SymBinOp::Sar => None,
1739 },
1740 SymExprKind::TernOp(_, _, _, _) => None,
1741 }
1742 }
1743
1744 fn binary_byte_term(
1745 cx: &mut SymCx,
1746 left: &Self,
1747 right: &Self,
1748 index: usize,
1749 op: SymBinOp,
1750 identity: impl Fn(u8) -> bool,
1751 absorbing: impl Fn(u8) -> bool,
1752 ) -> Option<Self> {
1753 let left = left.byte_term(cx, index)?;
1754 let right = right.byte_term(cx, index)?;
1755 match (
1756 left.as_const().map(|value| value.to::<u8>()),
1757 right.as_const().map(|value| value.to::<u8>()),
1758 ) {
1759 (Some(left), _) if absorbing(left) => Some(Self::constant(cx, U256::from(left))),
1760 (_, Some(right)) if absorbing(right) => Some(Self::constant(cx, U256::from(right))),
1761 (Some(left), _) if identity(left) => Some(right),
1762 (_, Some(right)) if identity(right) => Some(left),
1763 _ => Some(Self::binop(cx, op, left, right)),
1764 }
1765 }
1766
1767 pub(crate) fn equality_forces_const(
1768 &self,
1769 value: U256,
1770 expr: &Self,
1771 context: &[SymBoolExpr],
1772 ) -> Option<U256> {
1773 if self == expr {
1774 return Some(value);
1775 }
1776 self.equality_forces_const_inner(value, expr, context)
1777 }
1778
1779 fn equality_forces_const_inner(
1780 &self,
1781 value: U256,
1782 expr: &Self,
1783 context: &[SymBoolExpr],
1784 ) -> Option<U256> {
1785 let mask = masked_expr_matches(self.kind(), expr)?;
1786 if !(value & !mask).is_zero() || !context_forces_masked_expr(context, expr, mask) {
1787 return None;
1788 }
1789 Some(value)
1790 }
1791
1792 pub(crate) fn nonzero_forces_const(
1793 &self,
1794 target: &Self,
1795 context: &[SymBoolExpr],
1796 ) -> Option<U256> {
1797 match self.kind() {
1798 SymExprKind::Const(_)
1799 | SymExprKind::Var(_)
1800 | SymExprKind::GasLeft(_)
1801 | SymExprKind::Keccak { .. }
1802 | SymExprKind::Hash { .. }
1803 | SymExprKind::Not(_) => None,
1804 SymExprKind::Ite(cond, then_expr, else_expr) => {
1805 if then_expr.eval().is_some_and(|value| !value.is_zero())
1806 && else_expr.eval().is_some_and(|value| value.is_zero())
1807 {
1808 cond.forces_expr_const_with_context(target, context)
1809 } else {
1810 None
1811 }
1812 }
1813 SymExprKind::BinOp(SymBinOp::Or, left, right) => {
1814 if left.eval().is_some_and(|value| value.is_zero()) {
1815 return right.nonzero_forces_const(target, context);
1816 }
1817 if right.eval().is_some_and(|value| value.is_zero()) {
1818 return left.nonzero_forces_const(target, context);
1819 }
1820 None
1821 }
1822 SymExprKind::BinOp(SymBinOp::And, left, right) => {
1823 if left.eval().is_some_and(|value| !value.is_zero()) {
1824 return right.nonzero_forces_const(target, context);
1825 }
1826 if right.eval().is_some_and(|value| !value.is_zero()) {
1827 return left.nonzero_forces_const(target, context);
1828 }
1829 None
1830 }
1831 SymExprKind::BinOp(SymBinOp::Shl | SymBinOp::Shr, value, shift)
1832 if shift.eval().is_some_and(|shift| shift.is_zero()) =>
1833 {
1834 value.nonzero_forces_const(target, context)
1835 }
1836 SymExprKind::TernOp(_, _, _, _) => None,
1837 SymExprKind::BinOp(_, _, _) => None,
1838 }
1839 }
1840
1841 pub(crate) fn is_raw_gasleft(&self) -> bool {
1842 matches!(self.kind(), SymExprKind::GasLeft(_))
1843 }
1844
1845 pub(crate) fn add_const(cx: &mut SymCx, expr: Self, value: U256) -> Self {
1846 if value.is_zero() {
1847 return expr;
1848 }
1849 match expr.kind() {
1850 SymExprKind::Const(expr) => Self::constant(cx, expr.wrapping_add(value)),
1851 _ => {
1852 let value = Self::constant(cx, value);
1853 Self::binop(cx, SymBinOp::Add, expr, value)
1854 }
1855 }
1856 }
1857
1858 pub(crate) fn constant_difference(&self, other: &Self) -> Option<U256> {
1861 let mut differences = HashMap::default();
1862 let mut remaining = MAX_CONSTANT_DIFFERENCE_VISITS;
1863 self.constant_difference_cached(other, &mut differences, &mut remaining)
1864 }
1865
1866 fn constant_difference_cached(
1867 &self,
1868 other: &Self,
1869 differences: &mut HashMap<(Self, Self), Option<U256>>,
1870 remaining: &mut usize,
1871 ) -> Option<U256> {
1872 if self == other {
1873 return Some(U256::ZERO);
1874 }
1875 let key = (self.clone(), other.clone());
1876 if let Some(difference) = differences.get(&key) {
1877 return *difference;
1878 }
1879 *remaining = remaining.checked_sub(1)?;
1880
1881 let difference = match (self.kind(), other.kind()) {
1882 (SymExprKind::Const(left), SymExprKind::Const(right)) => {
1883 Some(left.wrapping_sub(*right))
1884 }
1885 (
1886 SymExprKind::Ite(left_condition, left_then, left_else),
1887 SymExprKind::Ite(right_condition, right_then, right_else),
1888 ) if left_condition == right_condition => {
1889 let then_difference =
1890 left_then.constant_difference_cached(right_then, differences, remaining)?;
1891 let else_difference =
1892 left_else.constant_difference_cached(right_else, differences, remaining)?;
1893 (then_difference == else_difference).then_some(then_difference)
1894 }
1895 (SymExprKind::BinOp(SymBinOp::Add, value, constant), _)
1896 if let Some(constant) = constant.as_const() =>
1897 {
1898 value
1899 .constant_difference_cached(other, differences, remaining)
1900 .map(|difference| difference.wrapping_add(constant))
1901 }
1902 (SymExprKind::BinOp(SymBinOp::Sub, value, constant), _)
1903 if let Some(constant) = constant.as_const() =>
1904 {
1905 value
1906 .constant_difference_cached(other, differences, remaining)
1907 .map(|difference| difference.wrapping_sub(constant))
1908 }
1909 (_, SymExprKind::BinOp(SymBinOp::Add, value, constant))
1910 if let Some(constant) = constant.as_const() =>
1911 {
1912 self.constant_difference_cached(value, differences, remaining)
1913 .map(|difference| difference.wrapping_sub(constant))
1914 }
1915 (_, SymExprKind::BinOp(SymBinOp::Sub, value, constant))
1916 if let Some(constant) = constant.as_const() =>
1917 {
1918 self.constant_difference_cached(value, differences, remaining)
1919 .map(|difference| difference.wrapping_add(constant))
1920 }
1921 _ => None,
1922 };
1923 differences.insert(key, difference);
1924 difference
1925 }
1926
1927 pub(crate) fn visit<B>(
1929 &self,
1930 visitor: &mut impl FnMut(&Self) -> ControlFlow<B>,
1931 ) -> ControlFlow<B> {
1932 visit_unique(Some(self), None, visitor)
1933 }
1934
1935 pub(crate) fn visit_bool(&self, mut visitor: impl FnMut(&Self) -> bool) -> bool {
1937 self.visit(&mut |expr| {
1938 if visitor(expr) { ControlFlow::Break(()) } else { ControlFlow::Continue(()) }
1939 })
1940 .is_break()
1941 }
1942
1943 pub(crate) fn fold(
1947 &self,
1948 cx: &mut SymCx,
1949 folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
1950 ) -> Self {
1951 let mut folded = ExpressionFoldCache::default();
1952 self.fold_cached(cx, folder, &mut folded)
1953 }
1954
1955 pub(in crate::runtime::expr) fn fold_cached<'a>(
1956 &'a self,
1957 cx: &mut SymCx,
1958 folder: &mut impl FnMut(&mut SymCx, Self) -> Self,
1959 folded: &mut ExpressionFoldCache<'a>,
1960 ) -> Self {
1961 if let Some(expr) = folded.words.get(self) {
1962 return expr.clone();
1963 }
1964
1965 let expr = match self.kind() {
1966 SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => self.clone(),
1967 SymExprKind::Keccak { name, len, bytes } => {
1968 let len = len.fold_cached(cx, folder, folded);
1969 let bytes = bytes.iter().map(|byte| byte.fold_cached(cx, folder, folded)).collect();
1970 Self::keccak_symbol(cx, *name, len, bytes)
1971 }
1972 SymExprKind::Hash { name, algorithm, bytes } => {
1973 let bytes = bytes.iter().map(|byte| byte.fold_cached(cx, folder, folded)).collect();
1974 Self::hash_symbol(cx, *name, algorithm, bytes)
1975 }
1976 SymExprKind::Not(value) => {
1977 let value = value.fold_cached(cx, folder, folded);
1978 Self::not(cx, value)
1979 }
1980 SymExprKind::BinOp(op, left, right) => {
1981 let left = left.fold_cached(cx, folder, folded);
1982 let right = right.fold_cached(cx, folder, folded);
1983 Self::binop(cx, *op, left, right)
1984 }
1985 SymExprKind::TernOp(op, left, right, modulus) => {
1986 let left = left.fold_cached(cx, folder, folded);
1987 let right = right.fold_cached(cx, folder, folded);
1988 let modulus = modulus.fold_cached(cx, folder, folded);
1989 Self::ternop(cx, *op, left, right, modulus)
1990 }
1991 SymExprKind::Ite(condition, then_expr, else_expr) => {
1992 let condition = condition.fold_exprs_cached(cx, folder, folded);
1993 let then_expr = then_expr.fold_cached(cx, folder, folded);
1994 let else_expr = else_expr.fold_cached(cx, folder, folded);
1995 Self::ite(cx, condition, then_expr, else_expr)
1996 }
1997 };
1998 let expr = folder(cx, expr);
1999 folded.words.insert(self, expr.clone());
2000 expr
2001 }
2002
2003 pub(in crate::runtime::expr) fn write_smt(&self, cx: &SymCx, out: &mut String) {
2004 match self.kind() {
2005 SymExprKind::Const(value) => {
2006 let _ = write!(out, "(_ bv{value} 256)");
2007 }
2008 SymExprKind::Var(symbol)
2009 | SymExprKind::GasLeft(symbol)
2010 | SymExprKind::Keccak { name: symbol, .. }
2011 | SymExprKind::Hash { name: symbol, .. } => out.push_str(cx.symbol_name(*symbol)),
2012 SymExprKind::Not(value) => {
2013 out.push_str("(bvnot ");
2014 value.write_smt(cx, out);
2015 out.push(')');
2016 }
2017 SymExprKind::BinOp(op, left, right) => {
2018 let _ = write!(out, "({} ", op.smt());
2019 left.write_smt(cx, out);
2020 out.push(' ');
2021 right.write_smt(cx, out);
2022 out.push(')');
2023 }
2024 SymExprKind::TernOp(op, left, right, modulus) => {
2025 write_smt_wide_modular_arithmetic(cx, out, op.smt(), left, right, modulus);
2026 }
2027 SymExprKind::Ite(cond, left, right) => {
2028 out.push_str("(ite ");
2029 cond.write_smt(cx, out);
2030 out.push(' ');
2031 left.write_smt(cx, out);
2032 out.push(' ');
2033 right.write_smt(cx, out);
2034 out.push(')');
2035 }
2036 }
2037 }
2038}
2039
2040const MAX_BRANCHLESS_REWRITE_NODES: usize = 256;
2043const MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES: usize = 8 * 1024;
2044
2045struct UnfoldedNodeCounter {
2046 expr_nodes: HashMap<SymExpr, usize>,
2047 bool_nodes: HashMap<SymBoolExpr, usize>,
2048 remaining_unique_nodes: usize,
2049}
2050
2051impl UnfoldedNodeCounter {
2052 fn new() -> Self {
2053 Self {
2054 expr_nodes: HashMap::default(),
2055 bool_nodes: HashMap::default(),
2056 remaining_unique_nodes: MAX_BRANCHLESS_REWRITE_NODES,
2057 }
2058 }
2059
2060 fn expr_nodes(&mut self, expr: &SymExpr) -> Option<usize> {
2063 if let Some(nodes) = self.expr_nodes.get(expr) {
2064 return Some(*nodes);
2065 }
2066 if self.remaining_unique_nodes == 0 {
2067 return None;
2068 }
2069 self.remaining_unique_nodes -= 1;
2070
2071 let nodes = match expr.kind() {
2072 SymExprKind::Const(_) | SymExprKind::Var(_) | SymExprKind::GasLeft(_) => 1,
2073 SymExprKind::Keccak { len, bytes, .. } => {
2074 let mut nodes = 1usize.checked_add(self.expr_nodes(len)?)?;
2075 for byte in bytes.iter() {
2076 nodes = nodes.checked_add(self.expr_nodes(byte)?)?;
2077 }
2078 nodes
2079 }
2080 SymExprKind::Hash { bytes, .. } => {
2081 let mut nodes = 1usize;
2082 for byte in bytes.iter() {
2083 nodes = nodes.checked_add(self.expr_nodes(byte)?)?;
2084 }
2085 nodes
2086 }
2087 SymExprKind::Not(value) => 1usize.checked_add(self.expr_nodes(value)?)?,
2088 SymExprKind::BinOp(_, left, right) => {
2089 1usize.checked_add(self.expr_nodes(left)?)?.checked_add(self.expr_nodes(right)?)?
2090 }
2091 SymExprKind::TernOp(_, left, right, modulus) => 1usize
2092 .checked_add(self.expr_nodes(left)?)?
2093 .checked_add(self.expr_nodes(right)?)?
2094 .checked_add(self.expr_nodes(modulus)?)?,
2095 SymExprKind::Ite(condition, then_expr, else_expr) => 1usize
2096 .checked_add(self.bool_nodes(condition)?)?
2097 .checked_add(self.expr_nodes(then_expr)?)?
2098 .checked_add(self.expr_nodes(else_expr)?)?,
2099 };
2100 if nodes > MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES {
2101 return None;
2102 }
2103 self.expr_nodes.insert(expr.clone(), nodes);
2104 Some(nodes)
2105 }
2106
2107 fn bool_nodes(&mut self, expr: &SymBoolExpr) -> Option<usize> {
2108 if let Some(nodes) = self.bool_nodes.get(expr) {
2109 return Some(*nodes);
2110 }
2111 if self.remaining_unique_nodes == 0 {
2112 return None;
2113 }
2114 self.remaining_unique_nodes -= 1;
2115
2116 let nodes = match expr.kind() {
2117 SymBoolExprKind::Const(_) => 1,
2118 SymBoolExprKind::Not(value) => 1usize.checked_add(self.bool_nodes(value)?)?,
2119 SymBoolExprKind::And(values) => {
2120 let mut nodes = 1usize;
2121 for value in values.iter() {
2122 nodes = nodes.checked_add(self.bool_nodes(value)?)?;
2123 }
2124 nodes
2125 }
2126 SymBoolExprKind::Cmp(_, left, right) => {
2127 1usize.checked_add(self.expr_nodes(left)?)?.checked_add(self.expr_nodes(right)?)?
2128 }
2129 };
2130 if nodes > MAX_BRANCHLESS_REWRITE_UNFOLDED_NODES {
2131 return None;
2132 }
2133 self.bool_nodes.insert(expr.clone(), nodes);
2134 Some(nodes)
2135 }
2136}
2137
2138fn write_smt_wide_modular_arithmetic(
2139 cx: &SymCx,
2140 out: &mut String,
2141 op: &'static str,
2142 left: &SymExpr,
2143 right: &SymExpr,
2144 modulus: &SymExpr,
2145) {
2146 out.push_str("(ite (= ");
2151 modulus.write_smt(cx, out);
2152 out.push_str(" (_ bv0 256)) (_ bv0 256) ((_ extract 255 0) (bvurem (");
2153 out.push_str(op);
2154 out.push_str(" ((_ zero_extend 256) ");
2155 left.write_smt(cx, out);
2156 out.push_str(") ((_ zero_extend 256) ");
2157 right.write_smt(cx, out);
2158 out.push_str(")) ((_ zero_extend 256) ");
2159 modulus.write_smt(cx, out);
2160 out.push_str("))))");
2161}
2162
2163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2164pub(crate) enum SymTernOp {
2165 AddMod,
2166 MulMod,
2167}
2168
2169impl SymTernOp {
2170 pub(crate) const fn smt(self) -> &'static str {
2171 match self {
2172 Self::AddMod => "bvadd",
2173 Self::MulMod => "bvmul",
2174 }
2175 }
2176
2177 pub(crate) fn eval(self, left: U256, right: U256, modulus: U256) -> U256 {
2178 if modulus.is_zero() {
2179 return U256::ZERO;
2180 }
2181 match self {
2182 Self::AddMod => left.add_mod(right, modulus),
2183 Self::MulMod => left.mul_mod(right, modulus),
2184 }
2185 }
2186}
2187
2188#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
2189pub(crate) enum SymBinOp {
2190 Add,
2191 Sub,
2192 Mul,
2193 UDiv,
2194 URem,
2195 SDiv,
2196 SRem,
2197 And,
2198 Or,
2199 Xor,
2200 Shl,
2201 Shr,
2202 Sar,
2203}
2204
2205impl SymBinOp {
2206 pub(crate) const fn smt(self) -> &'static str {
2207 match self {
2208 Self::Add => "bvadd",
2209 Self::Sub => "bvsub",
2210 Self::Mul => "bvmul",
2211 Self::UDiv => "bvudiv",
2212 Self::URem => "bvurem",
2213 Self::SDiv => "bvsdiv",
2214 Self::SRem => "bvsrem",
2215 Self::And => "bvand",
2216 Self::Or => "bvor",
2217 Self::Xor => "bvxor",
2218 Self::Shl => "bvshl",
2219 Self::Shr => "bvlshr",
2220 Self::Sar => "bvashr",
2221 }
2222 }
2223
2224 pub(crate) fn eval(self, left: U256, right: U256) -> U256 {
2225 match self {
2226 Self::Add => left.wrapping_add(right),
2227 Self::Sub => left.wrapping_sub(right),
2228 Self::Mul => left.wrapping_mul(right),
2229 Self::UDiv => {
2230 if right.is_zero() {
2231 U256::ZERO
2232 } else {
2233 left / right
2234 }
2235 }
2236 Self::URem => {
2237 if right.is_zero() {
2238 U256::ZERO
2239 } else {
2240 left % right
2241 }
2242 }
2243 Self::SDiv => i256_div(left, right),
2244 Self::SRem => i256_mod(left, right),
2245 Self::And => left & right,
2246 Self::Or => left | right,
2247 Self::Xor => left ^ right,
2248 Self::Shl => {
2249 if right >= U256::from(256) {
2250 U256::ZERO
2251 } else {
2252 left << usize::try_from(right).expect("checked word shift")
2253 }
2254 }
2255 Self::Shr => {
2256 if right >= U256::from(256) {
2257 U256::ZERO
2258 } else {
2259 left >> usize::try_from(right).expect("checked word shift")
2260 }
2261 }
2262 Self::Sar => {
2263 if right >= U256::from(256) {
2264 left.arithmetic_shr(256)
2265 } else {
2266 left.arithmetic_shr(usize::try_from(right).expect("checked word shift"))
2267 }
2268 }
2269 }
2270 }
2271}
2272
2273pub(crate) fn keccak_word(cx: &mut SymCx, bytes: Vec<SymExpr>) -> SymExpr {
2274 let len = bytes.len();
2275 let len = SymExpr::constant(cx, U256::from(len));
2276 keccak_word_with_len(cx, bytes, len)
2277}
2278
2279pub(crate) fn keccak_word_with_len(cx: &mut SymCx, bytes: Vec<SymExpr>, len: SymExpr) -> SymExpr {
2280 if let Some(len) = len.as_const()
2281 && let Ok(len) = usize::try_from(len)
2282 && len <= bytes.len()
2283 && let Ok(concrete) = concrete_expr_bytes(&bytes[..len], "symbolic keccak input")
2284 {
2285 let hash = Into::<U256>::into(keccak256(concrete));
2286 if len == 64 {
2287 cx.record_concrete_keccak_preimage(hash, bytes[..len].to_vec().into());
2288 }
2289 return SymExpr::constant(cx, hash);
2290 }
2291
2292 let exprs = bytes;
2293 let identity = ExpressionDigests::identity(std::iter::once(&len).chain(&exprs));
2294 let name = stable_symbol(cx, "keccak", identity.as_slice());
2295 SymExpr::keccak_symbol(cx, name, len, exprs)
2296}
2297
2298pub(crate) fn symbolic_hash_word_with_len(
2299 cx: &mut SymCx,
2300 algorithm: &'static str,
2301 bytes: Vec<SymExpr>,
2302 len: SymExpr,
2303) -> SymExpr {
2304 let exprs = bytes;
2305 let identity = ExpressionDigests::identity(std::iter::once(&len).chain(&exprs));
2306 let name = stable_symbol(cx, algorithm, identity.as_slice());
2307 let mut identity = Vec::with_capacity(exprs.len() + 1);
2308 identity.push(len);
2309 identity.extend(exprs);
2310 SymExpr::hash_symbol(cx, name, algorithm, identity)
2311}
2312
2313pub(crate) fn create2_address_word(
2314 cx: &mut SymCx,
2315 state: &mut PathState,
2316 creator: Address,
2317 salt: SymExpr,
2318 initcode: &SymCode,
2319) -> Result<(SymExpr, Address), SymbolicError> {
2320 match (salt.as_const(), initcode.concrete_bytes(cx, "symbolic CREATE2 initcode")) {
2321 (Some(salt), Ok(initcode)) => {
2322 let address = creator.create2_from_code(salt.to_be_bytes::<32>(), &initcode);
2323 Ok((SymExpr::constant(cx, address_word(address)), address))
2324 }
2325 (None, Ok(initcode)) => {
2326 let initcode_hash = keccak256(&initcode);
2327 let word = symbolic_create2_address_word(
2328 cx,
2329 state,
2330 format!("address:{creator:?}"),
2331 salt,
2332 format!("hash:{initcode_hash:?}"),
2333 );
2334 let address = state.world.symbolic_address_slot(word.clone());
2335 Ok((word, address))
2336 }
2337 (_, Err(SymbolicError::Unsupported("symbolic CREATE2 initcode"))) => {
2338 let initcode_bytes = initcode.read_byte_exprs(cx, 0, initcode.len());
2339 let word = symbolic_create2_address_word(
2340 cx,
2341 state,
2342 format!("address:{creator:?}"),
2343 salt,
2344 format!("initcode:{:?}", ExpressionDigests::identity(&initcode_bytes)),
2345 );
2346 let address = state.world.symbolic_address_slot(word.clone());
2347 Ok((word, address))
2348 }
2349 (_, Err(err)) => Err(err),
2350 }
2351}
2352
2353pub(crate) fn compute_create2_address_word(
2354 cx: &mut SymCx,
2355 state: &mut PathState,
2356 deployer: SymExpr,
2357 salt: SymExpr,
2358 init_code_hash: SymExpr,
2359) -> Result<SymExpr, SymbolicError> {
2360 let deployer_concrete = state.constrained_word(cx, &deployer).map(word_to_address);
2361 let salt_concrete = state.constrained_word(cx, &salt);
2362 let init_code_hash_concrete = state.constrained_word(cx, &init_code_hash);
2363
2364 if let (Some(deployer), Some(salt), Some(init_code_hash)) =
2365 (deployer_concrete, salt_concrete, init_code_hash_concrete)
2366 {
2367 let init_code_hash = B256::from(init_code_hash);
2368 let address = deployer.create2(B256::from(salt), init_code_hash);
2369 return Ok(SymExpr::constant(cx, address_word(address)));
2370 }
2371
2372 let deployer_identity = deployer_identity(deployer_concrete, &deployer);
2373 let init_code_hash_identity = init_code_hash_concrete
2374 .map(|init_code_hash| {
2375 let init_code_hash = B256::from(init_code_hash);
2376 format!("hash:{init_code_hash:?}")
2377 })
2378 .unwrap_or_else(|| {
2379 format!("hash_expr:{:?}", ExpressionDigests::identity([&init_code_hash]))
2380 });
2381
2382 Ok(symbolic_create2_address_word(cx, state, deployer_identity, salt, init_code_hash_identity))
2383}
2384
2385pub(crate) fn compute_create_address_word(
2386 cx: &mut SymCx,
2387 state: &mut PathState,
2388 deployer: SymExpr,
2389 nonce: SymExpr,
2390) -> Result<SymExpr, SymbolicError> {
2391 let deployer_concrete = state.constrained_word(cx, &deployer).map(word_to_address);
2392 let nonce_concrete = state.constrained_word(cx, &nonce);
2393
2394 if let (Some(deployer), Some(nonce)) = (deployer_concrete, nonce_concrete) {
2395 let Ok(nonce) = u64::try_from(nonce) else {
2396 return Err(SymbolicError::Unsupported("symbolic vm.computeCreateAddress nonce"));
2397 };
2398 return Ok(SymExpr::constant(cx, address_word(deployer.create(nonce))));
2399 }
2400
2401 let deployer_identity = deployer_identity(deployer_concrete, &deployer);
2402 Ok(symbolic_create_address_word(cx, state, deployer_identity, nonce))
2403}
2404
2405pub(crate) fn symbolic_create_address_word(
2406 cx: &mut SymCx,
2407 state: &mut PathState,
2408 creator_identity: String,
2409 nonce: SymExpr,
2410) -> SymExpr {
2411 let nonce = ExpressionDigests::identity([&nonce]);
2412 let name =
2413 stable_symbol(cx, "create_address", format!("{creator_identity}:{nonce:?}").as_bytes());
2414 let word = SymExpr::get_var(cx, name);
2415 state.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &word, U256::ONE << 160));
2416 word
2417}
2418
2419pub(crate) fn symbolic_create2_address_word(
2420 cx: &mut SymCx,
2421 state: &mut PathState,
2422 creator_identity: String,
2423 salt: SymExpr,
2424 initcode_identity: String,
2425) -> SymExpr {
2426 let name = stable_symbol(
2427 cx,
2428 "create2_address",
2429 format!(
2430 "{creator_identity}:{:?}:{initcode_identity}",
2431 ExpressionDigests::identity([&salt])
2432 )
2433 .as_bytes(),
2434 );
2435 let word = SymExpr::get_var(cx, name);
2436 state.constraints.push(SymBoolExpr::cmp_word_const(cx, SymCmpOp::Ult, &word, U256::ONE << 160));
2437 word
2438}
2439
2440fn deployer_identity(concrete: Option<Address>, deployer: &SymExpr) -> String {
2444 concrete
2445 .map(|deployer| format!("address:{deployer:?}"))
2446 .unwrap_or_else(|| format!("address_expr:{:?}", ExpressionDigests::identity([deployer])))
2447}
2448
2449#[cfg(test)]
2450mod tests {
2451 use super::*;
2452
2453 #[test]
2455 fn collect_vars_visits_shared_nodes_once() {
2456 let mut cx = SymCx::new();
2457 let x = SymExpr::var(&mut cx, "x");
2458 let y = SymExpr::var(&mut cx, "y");
2459 let mut expr = x.clone();
2460 for _ in 0..64 {
2461 let product = SymExpr::from_kind(
2462 &mut cx,
2463 SymExprKind::BinOp(SymBinOp::Mul, expr.clone(), y.clone()),
2464 );
2465 expr = SymExpr::from_kind(&mut cx, SymExprKind::BinOp(SymBinOp::Add, expr, product));
2466 }
2467 let condition = SymBoolExpr::eq_word_const(&mut cx, &expr, U256::from(7));
2468
2469 let mut vars = SymbolicVars::default();
2470 expr.collect_eval_vars(&mut vars);
2471 condition.collect_vars(&mut vars);
2472 assert_eq!(
2473 vars.iter().copied().collect::<Vec<_>>(),
2474 [x.kind().get_var().unwrap(), y.kind().get_var().unwrap(),]
2475 );
2476
2477 let mut visited = HashSet::<SymExpr>::default();
2478 let result = condition.visit_exprs::<()>(&mut |node| {
2479 assert!(visited.insert(node.clone()), "shared node visited twice");
2480 ControlFlow::Continue(())
2481 });
2482 assert!(result.is_continue());
2483 let mut visits = 0;
2484 let result = expr.visit(&mut |_| {
2485 visits += 1;
2486 if visits == 2 { ControlFlow::Break(42) } else { ControlFlow::Continue(()) }
2487 });
2488 assert_eq!(result, ControlFlow::Break(42));
2489 assert_eq!(visits, 2);
2490 }
2491}