1use super::*;
2
3pub(crate) fn keccak_word(bytes: Vec<SymWord>) -> SymWord {
5 keccak_word_with_len(bytes.clone(), SymWord::Concrete(U256::from(bytes.len())))
6}
7
8pub(crate) fn keccak_word_with_len(bytes: Vec<SymWord>, len: SymWord) -> SymWord {
10 if bytes.iter().all(|byte| matches!(byte, SymWord::Concrete(_)))
11 && let SymWord::Concrete(len) = len
12 && len <= U256::from(bytes.len())
13 {
14 let len = len.to::<usize>();
15 let bytes = bytes
16 .into_iter()
17 .take(len)
18 .map(|byte| {
19 let SymWord::Concrete(byte) = byte else { unreachable!() };
20 byte.to::<u8>()
21 })
22 .collect::<Vec<_>>();
23 return SymWord::Concrete(U256::from_be_bytes(keccak256(bytes).0));
24 }
25
26 let len = len.into_expr();
27 let exprs = bytes.into_iter().map(SymWord::into_expr).collect::<Vec<_>>();
28 SymWord::Expr(Expr::Keccak {
29 name: stable_symbol("keccak", format!("{len:?}:{exprs:?}")),
30 len: Box::new(len),
31 bytes: exprs,
32 })
33}
34
35pub(crate) fn symbolic_hash_word_with_len(
37 algorithm: &'static str,
38 bytes: Vec<SymWord>,
39 len: SymWord,
40) -> SymWord {
41 let len = len.into_expr();
42 let exprs = bytes.into_iter().map(SymWord::into_expr).collect::<Vec<_>>();
43 let identity = std::iter::once(len.clone()).chain(exprs.clone()).collect::<Vec<_>>();
44 SymWord::Expr(Expr::Hash {
45 name: stable_symbol(algorithm, format!("{len:?}:{exprs:?}")),
46 algorithm,
47 bytes: identity,
48 })
49}
50
51pub(crate) fn create2_address_word(
53 state: &mut PathState,
54 creator: Address,
55 salt: SymWord,
56 initcode: &SymCode,
57) -> Result<(SymWord, Address), SymbolicError> {
58 match (salt, initcode.concrete_bytes("symbolic CREATE2 initcode")) {
59 (SymWord::Concrete(salt), Ok(initcode)) => {
60 let address = creator.create2_from_code(salt.to_be_bytes::<32>(), &initcode);
61 Ok((SymWord::Concrete(address_word(address)), address))
62 }
63 (salt, Ok(initcode)) => {
64 let initcode_hash = keccak256(&initcode);
65 let word = symbolic_create2_address_word(
66 state,
67 format!("{creator:?}"),
68 salt.into_expr(),
69 format!("{initcode_hash:?}"),
70 );
71 let address = state.world.symbolic_address_slot(word.clone());
72 Ok((word, address))
73 }
74 (salt, Err(SymbolicError::Unsupported("symbolic CREATE2 initcode"))) => {
75 let initcode_bytes =
76 initcode.bytes.iter().cloned().map(SymWord::into_expr).collect::<Vec<_>>();
77 let word = symbolic_create2_address_word(
78 state,
79 format!("{creator:?}"),
80 salt.into_expr(),
81 format!("{initcode_bytes:?}"),
82 );
83 let address = state.world.symbolic_address_slot(word.clone());
84 Ok((word, address))
85 }
86 (_, Err(err)) => Err(err),
87 }
88}
89
90pub(crate) fn compute_create2_address_word(
92 state: &mut PathState,
93 deployer: SymWord,
94 salt: SymWord,
95 init_code_hash: SymWord,
96) -> Result<SymWord, SymbolicError> {
97 let deployer_concrete = state.constrained_word(&deployer).map(word_to_address);
98 let salt_concrete = state.constrained_word(&salt);
99 let init_code_hash_concrete = state.constrained_word(&init_code_hash);
100
101 if let (Some(deployer), Some(salt), Some(init_code_hash)) =
102 (deployer_concrete, salt_concrete, init_code_hash_concrete)
103 {
104 let init_code_hash = B256::from(init_code_hash.to_be_bytes::<32>());
105 let address = deployer.create2(B256::from(salt.to_be_bytes::<32>()), init_code_hash);
106 return Ok(SymWord::Concrete(address_word(address)));
107 }
108
109 let deployer_identity = deployer_concrete
110 .map(|deployer| format!("{deployer:?}"))
111 .unwrap_or_else(|| format!("{:?}", deployer.into_expr()));
112 let init_code_hash_identity = init_code_hash_concrete
113 .map(|init_code_hash| {
114 let init_code_hash = B256::from(init_code_hash.to_be_bytes::<32>());
115 format!("{init_code_hash:?}")
116 })
117 .unwrap_or_else(|| format!("{:?}", init_code_hash.into_expr()));
118
119 Ok(symbolic_create2_address_word(
120 state,
121 deployer_identity,
122 salt.into_expr(),
123 init_code_hash_identity,
124 ))
125}
126
127pub(crate) fn compute_create_address_word(
129 state: &mut PathState,
130 deployer: SymWord,
131 nonce: SymWord,
132) -> Result<SymWord, SymbolicError> {
133 let deployer_concrete = state.constrained_word(&deployer).map(word_to_address);
134 let nonce_concrete = state.constrained_word(&nonce);
135
136 if let (Some(deployer), Some(nonce)) = (deployer_concrete, nonce_concrete) {
137 if nonce > U256::from(u64::MAX) {
138 return Err(SymbolicError::Unsupported("symbolic vm.computeCreateAddress nonce"));
139 }
140 return Ok(SymWord::Concrete(address_word(deployer.create(nonce.to()))));
141 }
142
143 let deployer_identity = deployer_concrete
144 .map(|deployer| format!("{deployer:?}"))
145 .unwrap_or_else(|| format!("{:?}", deployer.into_expr()));
146 Ok(symbolic_create_address_word(state, deployer_identity, nonce.into_expr()))
147}
148
149pub(crate) fn symbolic_create_address_word(
151 state: &mut PathState,
152 creator_identity: String,
153 nonce: Expr,
154) -> SymWord {
155 let word = SymWord::Expr(Expr::Var(stable_symbol(
156 "create_address",
157 format!("{creator_identity}:{nonce:?}"),
158 )));
159 state.constraints.push(BoolExpr::cmp(
160 BoolExprOp::Ult,
161 word.clone().into_expr(),
162 Expr::Const(U256::from(1) << 160),
163 ));
164 word
165}
166
167pub(crate) fn symbolic_create2_address_word(
169 state: &mut PathState,
170 creator_identity: String,
171 salt: Expr,
172 initcode_identity: String,
173) -> SymWord {
174 let word = SymWord::Expr(Expr::Var(stable_symbol(
175 "create2_address",
176 format!("{creator_identity}:{salt:?}:{initcode_identity}"),
177 )));
178 state.constraints.push(BoolExpr::cmp(
179 BoolExprOp::Ult,
180 word.clone().into_expr(),
181 Expr::Const(U256::from(1) << 160),
182 ));
183 word
184}
185
186pub(crate) fn read_storage_writes(
188 writes: &[StorageWrite],
189 address: Address,
190 key: SymWord,
191 base: SymWord,
192) -> SymWord {
193 let mut value = base;
194 for write in writes.iter().filter(|write| write.address == address) {
195 value = storage_select(key.clone(), write.key.clone(), write.value.clone(), value);
196 }
197 value
198}
199
200pub(crate) fn storage_select(
202 read_key: SymWord,
203 write_key: SymWord,
204 write_value: SymWord,
205 base: SymWord,
206) -> SymWord {
207 if write_value == base {
208 return base;
209 }
210 let condition = storage_key_eq(read_key, write_key);
211 match condition {
212 BoolExpr::Const(true) => write_value,
213 BoolExpr::Const(false) => base,
214 condition => SymWord::Expr(Expr::Ite(
215 Box::new(condition),
216 Box::new(write_value.into_expr()),
217 Box::new(base.into_expr()),
218 )),
219 }
220}
221
222pub(crate) fn storage_key_eq(read_key: SymWord, write_key: SymWord) -> BoolExpr {
224 let read_key = read_key.into_expr();
225 let write_key = write_key.into_expr();
226 if let (Some(read_root), Some(write_root)) =
227 (storage_mapping_root_slot(&read_key), storage_mapping_root_slot(&write_key))
228 && read_root != write_root
229 {
230 return BoolExpr::Const(false);
231 }
232 match (storage_layout_key(&read_key), storage_layout_key(&write_key)) {
233 (Some((read_base, read_offset)), Some((write_base, write_offset))) => BoolExpr::and(vec![
234 BoolExpr::eq(read_base, write_base),
235 BoolExpr::eq(read_offset, write_offset),
236 ]),
237 (Some(_), None) if matches!(write_key, Expr::Const(_)) => BoolExpr::Const(false),
238 (None, Some(_)) if matches!(read_key, Expr::Const(_)) => BoolExpr::Const(false),
239 _ => BoolExpr::eq(read_key, write_key),
240 }
241}
242
243pub(crate) fn storage_mapping_root_slot(key: &Expr) -> Option<U256> {
245 let Expr::Keccak { len, bytes, .. } = key else { return None };
246 if !matches!(len.as_ref(), Expr::Const(value) if *value == U256::from(64)) || bytes.len() < 64 {
247 return None;
248 }
249
250 let slot = word_from_bytes(bytes[32..64].iter().cloned().map(|expr| match expr {
251 Expr::Const(value) => SymWord::Concrete(value),
252 expr => SymWord::Expr(expr),
253 }))
254 .into_expr();
255 match slot {
256 Expr::Const(slot) => Some(slot),
257 Expr::Keccak { .. } => storage_mapping_root_slot(&slot),
258 _ => None,
259 }
260}
261
262pub(crate) fn storage_layout_key(key: &Expr) -> Option<(Expr, Expr)> {
264 match key {
265 Expr::Keccak { .. } => Some((key.clone(), Expr::Const(U256::ZERO))),
266 Expr::Op(ExprOp::Add, left, right) => {
267 if let Some((base, offset)) = storage_layout_key(left)
268 && !expr_contains_keccak(right)
269 {
270 return Some((base, expr_add(offset, (**right).clone())));
271 }
272 if let Some((base, offset)) = storage_layout_key(right)
273 && !expr_contains_keccak(left)
274 {
275 return Some((base, expr_add(offset, (**left).clone())));
276 }
277 None
278 }
279 _ => None,
280 }
281}
282
283pub(crate) fn expr_add(left: Expr, right: Expr) -> Expr {
285 match (left, right) {
286 (Expr::Const(left), Expr::Const(right)) => Expr::Const(left.wrapping_add(right)),
287 (Expr::Const(value), expr) | (expr, Expr::Const(value)) if value.is_zero() => expr,
288 (left, right) => Expr::op(ExprOp::Add, left, right),
289 }
290}
291
292pub(crate) fn sym_add(left: SymWord, right: SymWord) -> SymWord {
294 match (left, right) {
295 (SymWord::Concrete(left), SymWord::Concrete(right)) => {
296 SymWord::Concrete(left.wrapping_add(right))
297 }
298 (left, right) => SymWord::Expr(expr_add(left.into_expr(), right.into_expr())),
299 }
300}
301
302pub(crate) fn sym_sub(left: SymWord, right: SymWord) -> SymWord {
304 match (left, right) {
305 (SymWord::Concrete(left), SymWord::Concrete(right)) => {
306 SymWord::Concrete(left.wrapping_sub(right))
307 }
308 (left, right) => SymWord::Expr(Expr::op(ExprOp::Sub, left.into_expr(), right.into_expr())),
309 }
310}
311
312pub(crate) fn expr_contains_keccak(expr: &Expr) -> bool {
314 match expr {
315 Expr::Keccak { .. } => true,
316 Expr::Const(_) | Expr::Var(_) | Expr::GasLeft(_) | Expr::Hash { .. } => false,
317 Expr::Not(value) => expr_contains_keccak(value),
318 Expr::Op(_, left, right) => expr_contains_keccak(left) || expr_contains_keccak(right),
319 Expr::Ite(cond, left, right) => {
320 bool_contains_keccak(cond) || expr_contains_keccak(left) || expr_contains_keccak(right)
321 }
322 }
323}
324
325pub(crate) fn expr_contains_gasleft(expr: &Expr) -> bool {
327 match expr {
328 Expr::Const(_) => false,
329 Expr::Var(_) => false,
330 Expr::GasLeft(_) => true,
331 Expr::Keccak { len, bytes, .. } => {
332 expr_contains_gasleft(len) || bytes.iter().any(expr_contains_gasleft)
333 }
334 Expr::Hash { bytes, .. } => bytes.iter().any(expr_contains_gasleft),
335 Expr::Not(value) => expr_contains_gasleft(value),
336 Expr::Op(_, left, right) => expr_contains_gasleft(left) || expr_contains_gasleft(right),
337 Expr::Ite(cond, left, right) => {
338 bool_contains_gasleft(cond)
339 || expr_contains_gasleft(left)
340 || expr_contains_gasleft(right)
341 }
342 }
343}
344
345pub(crate) fn bool_forces_expr_const_with_context(
347 condition: &BoolExpr,
348 expr: &Expr,
349 context: &[BoolExpr],
350) -> Option<U256> {
351 match condition {
352 BoolExpr::Eq(left, Expr::Const(value)) => {
353 expr_equality_forces_const(left, *value, expr, context)
354 }
355 BoolExpr::Eq(Expr::Const(value), right) => {
356 expr_equality_forces_const(right, *value, expr, context)
357 }
358 BoolExpr::Not(value) => match value.as_ref() {
359 BoolExpr::Eq(left, Expr::Const(value)) if value.is_zero() => {
360 expr_nonzero_forces_const(left, expr, context)
361 }
362 BoolExpr::Eq(Expr::Const(value), right) if value.is_zero() => {
363 expr_nonzero_forces_const(right, expr, context)
364 }
365 BoolExpr::Not(value) => bool_forces_expr_const_with_context(value, expr, context),
366 _ => None,
367 },
368 BoolExpr::And(values) => values
369 .iter()
370 .find_map(|value| bool_forces_expr_const_with_context(value, expr, context)),
371 _ => None,
372 }
373}
374
375pub(crate) fn expr_equality_forces_const(
377 candidate: &Expr,
378 value: U256,
379 expr: &Expr,
380 context: &[BoolExpr],
381) -> Option<U256> {
382 if candidate == expr {
383 return Some(value);
384 }
385 let mask = masked_expr_matches(candidate, expr)?;
386 if value & !mask != U256::ZERO || !context_forces_masked_expr(context, expr, mask) {
387 return None;
388 }
389 Some(value)
390}
391
392pub(crate) fn expr_nonzero_forces_const(
394 expr: &Expr,
395 target: &Expr,
396 context: &[BoolExpr],
397) -> Option<U256> {
398 match expr {
399 Expr::Const(_)
400 | Expr::Var(_)
401 | Expr::GasLeft(_)
402 | Expr::Keccak { .. }
403 | Expr::Hash { .. }
404 | Expr::Not(_) => None,
405 Expr::Ite(cond, then_expr, else_expr) => {
406 if expr_const_value(then_expr).is_some_and(|value| !value.is_zero())
407 && expr_const_value(else_expr).is_some_and(|value| value.is_zero())
408 {
409 bool_forces_expr_const_with_context(cond, target, context)
410 } else {
411 None
412 }
413 }
414 Expr::Op(ExprOp::Or, left, right) => {
415 if expr_const_value(left).is_some_and(|value| value.is_zero()) {
416 return expr_nonzero_forces_const(right, target, context);
417 }
418 if expr_const_value(right).is_some_and(|value| value.is_zero()) {
419 return expr_nonzero_forces_const(left, target, context);
420 }
421 None
422 }
423 Expr::Op(ExprOp::And, left, right) => {
424 if expr_const_value(left).is_some_and(|value| !value.is_zero()) {
425 return expr_nonzero_forces_const(right, target, context);
426 }
427 if expr_const_value(right).is_some_and(|value| !value.is_zero()) {
428 return expr_nonzero_forces_const(left, target, context);
429 }
430 None
431 }
432 Expr::Op(ExprOp::Shl | ExprOp::Shr, value, shift)
433 if expr_const_value(shift).is_some_and(|shift| shift.is_zero()) =>
434 {
435 expr_nonzero_forces_const(value, target, context)
436 }
437 Expr::Op(_, _, _) => None,
438 }
439}
440
441pub(crate) fn masked_expr_matches(candidate: &Expr, target: &Expr) -> Option<U256> {
443 match candidate {
444 Expr::Op(ExprOp::And, left, right) if left.as_ref() == target => expr_const_value(right),
445 Expr::Op(ExprOp::And, left, right) if right.as_ref() == target => expr_const_value(left),
446 _ => None,
447 }
448}
449
450pub(crate) fn context_forces_masked_expr(context: &[BoolExpr], target: &Expr, mask: U256) -> bool {
452 context.iter().any(|condition| match condition {
453 BoolExpr::Eq(left, right) => {
454 (left == target && masked_expr_matches(right, target) == Some(mask))
455 || (right == target && masked_expr_matches(left, target) == Some(mask))
456 }
457 BoolExpr::And(values) => context_forces_masked_expr(values, target, mask),
458 _ => false,
459 })
460}
461
462pub(crate) fn expr_const_value(expr: &Expr) -> Option<U256> {
464 match expr {
465 Expr::Const(value) => Some(*value),
466 Expr::Var(_) | Expr::GasLeft(_) | Expr::Keccak { .. } | Expr::Hash { .. } => None,
467 Expr::Not(value) => Some(!expr_const_value(value)?),
468 Expr::Op(op, left, right) => {
469 Some(eval_expr_op(*op, expr_const_value(left)?, expr_const_value(right)?))
470 }
471 Expr::Ite(cond, then_expr, else_expr) => {
472 if bool_const_value(cond)? {
473 expr_const_value(then_expr)
474 } else {
475 expr_const_value(else_expr)
476 }
477 }
478 }
479}
480
481pub(crate) fn bool_const_value(expr: &BoolExpr) -> Option<bool> {
483 match expr {
484 BoolExpr::Const(value) => Some(*value),
485 BoolExpr::Not(value) => Some(!bool_const_value(value)?),
486 BoolExpr::And(values) => {
487 let mut all_true = true;
488 for value in values {
489 all_true &= bool_const_value(value)?;
490 }
491 Some(all_true)
492 }
493 BoolExpr::Eq(left, right) => Some(expr_const_value(left)? == expr_const_value(right)?),
494 BoolExpr::Cmp(op, left, right) => {
495 let left = expr_const_value(left)?;
496 let right = expr_const_value(right)?;
497 Some(match op {
498 BoolExprOp::Ult => left < right,
499 BoolExprOp::Ugt => left > right,
500 BoolExprOp::Ule => left <= right,
501 BoolExprOp::Uge => left >= right,
502 BoolExprOp::Slt => slt(left, right),
503 BoolExprOp::Sgt => slt(right, left),
504 })
505 }
506 }
507}
508
509pub(crate) fn bool_contains_keccak(expr: &BoolExpr) -> bool {
511 match expr {
512 BoolExpr::Const(_) => false,
513 BoolExpr::Not(value) => bool_contains_keccak(value),
514 BoolExpr::And(values) => values.iter().any(bool_contains_keccak),
515 BoolExpr::Eq(left, right) | BoolExpr::Cmp(_, left, right) => {
516 expr_contains_keccak(left) || expr_contains_keccak(right)
517 }
518 }
519}
520
521pub(crate) fn bool_contains_gasleft(expr: &BoolExpr) -> bool {
523 match expr {
524 BoolExpr::Const(_) => false,
525 BoolExpr::Not(value) => bool_contains_gasleft(value),
526 BoolExpr::And(values) => values.iter().any(bool_contains_gasleft),
527 BoolExpr::Eq(left, right) | BoolExpr::Cmp(_, left, right) => {
528 expr_contains_gasleft(left) || expr_contains_gasleft(right)
529 }
530 }
531}
532
533pub(crate) fn word_bytes(word: SymWord) -> Vec<SymWord> {
535 match word {
536 SymWord::Concrete(word) => word
537 .to_be_bytes::<32>()
538 .into_iter()
539 .map(|byte| SymWord::Concrete(U256::from(byte)))
540 .collect(),
541 word => (0..32).map(|idx| byte_word(U256::from(idx), word.clone())).collect(),
542 }
543}
544
545pub(crate) fn word_from_bytes(bytes: impl IntoIterator<Item = SymWord>) -> SymWord {
547 let bytes = bytes.into_iter().collect::<Vec<_>>();
548 if bytes.iter().all(|byte| matches!(byte, SymWord::Concrete(_))) {
549 let mut word = [0u8; 32];
550 for (idx, byte) in bytes.into_iter().take(32).enumerate() {
551 let SymWord::Concrete(byte) = byte else { unreachable!() };
552 word[idx] = byte.to::<u8>();
553 }
554 return SymWord::Concrete(U256::from_be_bytes(word));
555 }
556
557 if let Some(expr) = word_from_extracted_bytes(&bytes) {
558 return SymWord::Expr(expr);
559 }
560
561 let mut expr = Expr::Const(U256::ZERO);
562 for (idx, byte) in bytes.into_iter().take(32).enumerate() {
563 let shift = (31 - idx) * 8;
564 let byte = low_byte(byte).into_expr();
565 let byte = if shift == 0 {
566 byte
567 } else {
568 Expr::op(ExprOp::Shl, byte, Expr::Const(U256::from(shift)))
569 };
570 expr = Expr::op(ExprOp::Or, expr, byte);
571 }
572 SymWord::Expr(expr)
573}
574
575pub(crate) fn word_from_extracted_bytes(bytes: &[SymWord]) -> Option<Expr> {
577 if bytes.len() < 32 {
578 return None;
579 }
580
581 let source = bytes
582 .iter()
583 .take(32)
584 .enumerate()
585 .find_map(|(idx, byte)| extracted_byte_source(byte, idx))?;
586
587 for (idx, byte) in bytes.iter().take(32).enumerate() {
588 if let Some(byte_source) = extracted_byte_source(byte, idx) {
589 if byte_source != source {
590 return None;
591 }
592 continue;
593 }
594
595 let SymWord::Concrete(byte) = byte else { return None };
596 if expr_known_byte(&source, idx) != Some(byte.to::<u8>()) {
597 return None;
598 }
599 }
600 Some(source)
601}
602
603pub(crate) fn extracted_byte_source(byte: &SymWord, index: usize) -> Option<Expr> {
605 let SymWord::Expr(expr) = byte else { return None };
606 let expr = strip_low_byte_mask(expr)?;
607 if index == 31 {
608 return Some(expr.clone());
609 }
610 let Expr::Op(ExprOp::Shr, source, shift) = expr else { return None };
611 let Expr::Const(shift) = shift.as_ref() else { return None };
612 (*shift == U256::from((31 - index) * 8)).then(|| *source.clone())
613}
614
615pub(crate) fn strip_low_byte_mask(expr: &Expr) -> Option<&Expr> {
617 match expr {
618 Expr::Op(ExprOp::And, left, right) if matches!(right.as_ref(), Expr::Const(mask) if *mask == U256::from(0xff)) => {
619 Some(strip_low_byte_mask(left).unwrap_or(left))
620 }
621 Expr::Op(ExprOp::And, left, right) if matches!(left.as_ref(), Expr::Const(mask) if *mask == U256::from(0xff)) => {
622 Some(strip_low_byte_mask(right).unwrap_or(right))
623 }
624 _ => Some(expr),
625 }
626}
627
628pub(crate) fn low_byte(word: SymWord) -> SymWord {
630 match word {
631 SymWord::Concrete(word) => SymWord::Concrete(U256::from(word.to::<u8>())),
632 word => {
633 SymWord::Expr(Expr::op(ExprOp::And, word.into_expr(), Expr::Const(U256::from(0xff))))
634 }
635 }
636}
637
638pub(crate) fn model_word(
640 word: &SymWord,
641 model: &BTreeMap<String, U256>,
642) -> Result<U256, SymbolicError> {
643 eval_expr(&word.clone().into_expr(), model)
644}
645
646pub(crate) fn model_bytes(
648 bytes: &[SymWord],
649 model: &BTreeMap<String, U256>,
650) -> Result<Vec<u8>, SymbolicError> {
651 bytes.iter().map(|byte| Ok(model_word(byte, model)?.to::<u8>())).collect()
652}
653
654pub(crate) fn concrete_bytes(
656 bytes: &[SymWord],
657 reason: &'static str,
658) -> Result<Vec<u8>, SymbolicError> {
659 bytes
660 .iter()
661 .map(|byte| match byte {
662 SymWord::Concrete(value) => Ok(value.to::<u8>()),
663 SymWord::Expr(_) => Err(SymbolicError::Unsupported(reason)),
664 })
665 .collect()
666}
667
668pub(crate) fn calldata_prefix_condition(
670 calldata: &[SymWord],
671 prefix: &[SymWord],
672 _reason: &'static str,
673) -> Result<Option<BoolExpr>, SymbolicError> {
674 if prefix.len() > calldata.len() {
675 return Ok(None);
676 }
677 let mut conditions = Vec::new();
678 for (actual, expected) in calldata.iter().zip(prefix) {
679 if actual == expected {
680 continue;
681 }
682 match (actual, expected) {
683 (SymWord::Concrete(actual), SymWord::Concrete(expected))
684 if actual.to::<u8>() == expected.to::<u8>() => {}
685 (SymWord::Concrete(_), SymWord::Concrete(_)) => return Ok(None),
686 _ => conditions
687 .push(BoolExpr::eq(actual.clone().into_expr(), expected.clone().into_expr())),
688 }
689 }
690 Ok(Some(BoolExpr::and(conditions)))
691}
692
693pub(crate) fn function_mock_match_condition(
695 mock: &FunctionMock,
696 callee: Address,
697 calldata: &[SymWord],
698 reason: &'static str,
699) -> Result<Option<BoolExpr>, SymbolicError> {
700 let Some(data_condition) = calldata_prefix_condition(calldata, &mock.data, reason)? else {
701 return Ok(None);
702 };
703 Ok(Some(BoolExpr::and(vec![address_match_condition(&mock.callee, callee), data_condition])))
704}
705
706pub(crate) fn eval_expr(
708 expr: &Expr,
709 model: &BTreeMap<String, U256>,
710) -> Result<U256, SymbolicError> {
711 Ok(match expr {
712 Expr::Const(value) => *value,
713 Expr::Var(var) => model.get(var).copied().unwrap_or_default(),
714 Expr::GasLeft(_) => return Err(SymbolicError::Unsupported("GAS/gasleft() not modeled")),
715 Expr::Keccak { len, bytes, .. } => eval_keccak_expr(len, bytes, model)?,
716 Expr::Hash { name, .. } => model.get(name).copied().unwrap_or_default(),
717 Expr::Not(value) => !eval_expr(value, model)?,
718 Expr::Op(op, left, right) => {
719 let left = eval_expr(left, model)?;
720 let right = eval_expr(right, model)?;
721 eval_expr_op(*op, left, right)
722 }
723 Expr::Ite(cond, then_expr, else_expr) => {
724 if eval_bool_expr(cond, model)? {
725 eval_expr(then_expr, model)?
726 } else {
727 eval_expr(else_expr, model)?
728 }
729 }
730 })
731}
732
733pub(crate) fn eval_keccak_expr(
735 len: &Expr,
736 bytes: &[Expr],
737 model: &BTreeMap<String, U256>,
738) -> Result<U256, SymbolicError> {
739 let len = eval_expr(len, model)?;
740 if len > U256::from(bytes.len()) {
741 return Err(SymbolicError::Solver(
742 "solver model uses an invalid keccak length".to_string(),
743 ));
744 }
745
746 let mut input = Vec::with_capacity(len.to::<usize>());
747 for byte in bytes.iter().take(len.to::<usize>()) {
748 input.push((eval_expr(byte, model)? & U256::from(0xff)).to::<u8>());
749 }
750
751 Ok(U256::from_be_bytes(keccak256(input).0))
752}
753
754pub(crate) fn eval_expr_op(op: ExprOp, left: U256, right: U256) -> U256 {
756 match op {
757 ExprOp::Add => left.wrapping_add(right),
758 ExprOp::Sub => left.wrapping_sub(right),
759 ExprOp::Mul => left.wrapping_mul(right),
760 ExprOp::UDiv => {
761 if right.is_zero() {
762 U256::ZERO
763 } else {
764 left / right
765 }
766 }
767 ExprOp::URem => {
768 if right.is_zero() {
769 U256::ZERO
770 } else {
771 left % right
772 }
773 }
774 ExprOp::SDiv => sdiv(left, right),
775 ExprOp::SRem => smod(left, right),
776 ExprOp::And => left & right,
777 ExprOp::Or => left | right,
778 ExprOp::Xor => left ^ right,
779 ExprOp::Shl => {
780 if right >= U256::from(256) {
781 U256::ZERO
782 } else {
783 left << right.to::<usize>()
784 }
785 }
786 ExprOp::Shr => {
787 if right >= U256::from(256) {
788 U256::ZERO
789 } else {
790 left >> right.to::<usize>()
791 }
792 }
793 ExprOp::Sar => {
794 if right >= U256::from(256) {
795 sar(left, 256)
796 } else {
797 sar(left, right.to::<usize>())
798 }
799 }
800 }
801}
802
803pub(crate) fn eval_bool_expr(
805 expr: &BoolExpr,
806 model: &BTreeMap<String, U256>,
807) -> Result<bool, SymbolicError> {
808 Ok(match expr {
809 BoolExpr::Const(value) => *value,
810 BoolExpr::Not(value) => !eval_bool_expr(value, model)?,
811 BoolExpr::And(values) => {
812 for value in values {
813 if !eval_bool_expr(value, model)? {
814 return Ok(false);
815 }
816 }
817 true
818 }
819 BoolExpr::Eq(left, right) => eval_expr(left, model)? == eval_expr(right, model)?,
820 BoolExpr::Cmp(op, left, right) => {
821 let left = eval_expr(left, model)?;
822 let right = eval_expr(right, model)?;
823 match op {
824 BoolExprOp::Ult => left < right,
825 BoolExprOp::Ugt => left > right,
826 BoolExprOp::Ule => left <= right,
827 BoolExprOp::Uge => left >= right,
828 BoolExprOp::Slt => slt(left, right),
829 BoolExprOp::Sgt => slt(right, left),
830 }
831 }
832 })
833}
834
835#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
836pub(crate) enum SymWord {
837 Concrete(U256),
838 Expr(Expr),
839}
840
841impl SymWord {
842 pub(crate) const fn zero() -> Self {
844 Self::Concrete(U256::ZERO)
845 }
846
847 pub(crate) fn contains_gasleft(&self) -> bool {
849 match self {
850 Self::Concrete(_) => false,
851 Self::Expr(expr) => expr_contains_gasleft(expr),
852 }
853 }
854
855 pub(crate) const fn is_raw_gasleft(&self) -> bool {
857 matches!(self, Self::Expr(Expr::GasLeft(_)))
858 }
859
860 pub(crate) fn into_expr(self) -> Expr {
862 match self {
863 Self::Concrete(value) => Expr::Const(value),
864 Self::Expr(expr) => expr,
865 }
866 }
867
868 pub(crate) fn from_bool(value: BoolExpr) -> Self {
870 match value {
871 BoolExpr::Const(value) => Self::Concrete(U256::from(value)),
872 value => Self::Expr(Expr::Ite(
873 Box::new(value),
874 Box::new(Expr::Const(U256::from(1))),
875 Box::new(Expr::Const(U256::ZERO)),
876 )),
877 }
878 }
879
880 pub(crate) fn truth(&self) -> Option<bool> {
882 match self {
883 Self::Concrete(value) => Some(!value.is_zero()),
884 _ => None,
885 }
886 }
887
888 pub(crate) fn into_zero_bool(self) -> BoolExpr {
890 match self {
891 Self::Concrete(value) => BoolExpr::Const(value.is_zero()),
892 Self::Expr(Expr::Ite(cond, then_expr, else_expr))
893 if then_expr.as_ref() == &Expr::Const(U256::from(1))
894 && else_expr.as_ref() == &Expr::Const(U256::ZERO) =>
895 {
896 cond.not()
897 }
898 Self::Expr(Expr::Ite(cond, then_expr, else_expr))
899 if then_expr.as_ref() == &Expr::Const(U256::ZERO)
900 && else_expr.as_ref() == &Expr::Const(U256::from(1)) =>
901 {
902 *cond
903 }
904 value => BoolExpr::eq(value.into_expr(), Expr::Const(U256::ZERO)),
905 }
906 }
907
908 pub(crate) fn nonzero_bool(self) -> BoolExpr {
910 self.into_zero_bool().not()
911 }
912
913 pub(crate) fn into_concrete(self, reason: &'static str) -> Result<U256, SymbolicError> {
915 match self {
916 Self::Concrete(value) => Ok(value),
917 Self::Expr(_) => Err(SymbolicError::Unsupported(reason)),
918 }
919 }
920
921 pub(crate) fn into_usize(self, reason: &'static str) -> Result<usize, SymbolicError> {
923 let value = self.into_concrete(reason)?;
924 if value > U256::from(usize::MAX) {
925 return Err(SymbolicError::Unsupported(reason));
926 }
927 Ok(value.to::<usize>())
928 }
929}
930
931#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
932pub(crate) enum Expr {
933 Const(U256),
934 Var(String),
935 GasLeft(usize),
936 Keccak { name: String, len: Box<Self>, bytes: Vec<Self> },
937 Hash { name: String, algorithm: &'static str, bytes: Vec<Self> },
938 Not(Box<Self>),
939 Op(ExprOp, Box<Self>, Box<Self>),
940 Ite(Box<BoolExpr>, Box<Self>, Box<Self>),
941}
942
943impl Expr {
944 pub(crate) fn op(op: ExprOp, left: Self, right: Self) -> Self {
946 if let (Self::Const(left), Self::Const(right)) = (&left, &right) {
947 return Self::Const(eval_expr_op(op, *left, *right));
948 }
949
950 match (op, left, right) {
951 (ExprOp::Add, Self::Const(value), expr) | (ExprOp::Add, expr, Self::Const(value))
952 if value.is_zero() =>
953 {
954 expr
955 }
956 (ExprOp::Sub, expr, Self::Const(value)) if value.is_zero() => expr,
957 (ExprOp::Sub, left, right) if left == right => Self::Const(U256::ZERO),
958 (ExprOp::Mul, Self::Const(value), _) | (ExprOp::Mul, _, Self::Const(value))
959 if value.is_zero() =>
960 {
961 Self::Const(U256::ZERO)
962 }
963 (ExprOp::Mul, Self::Const(value), expr) | (ExprOp::Mul, expr, Self::Const(value))
964 if value == U256::from(1) =>
965 {
966 expr
967 }
968 (ExprOp::UDiv | ExprOp::URem | ExprOp::SDiv | ExprOp::SRem, _, Self::Const(value))
969 if value.is_zero() =>
970 {
971 Self::Const(U256::ZERO)
972 }
973 (ExprOp::UDiv | ExprOp::SDiv, expr, Self::Const(value)) if value == U256::from(1) => {
974 expr
975 }
976 (ExprOp::URem | ExprOp::SRem, _, Self::Const(value)) if value == U256::from(1) => {
977 Self::Const(U256::ZERO)
978 }
979 (ExprOp::And, Self::Const(value), _) | (ExprOp::And, _, Self::Const(value))
980 if value.is_zero() =>
981 {
982 Self::Const(U256::ZERO)
983 }
984 (ExprOp::And, Self::Const(value), expr) | (ExprOp::And, expr, Self::Const(value))
985 if value == U256::MAX =>
986 {
987 expr
988 }
989 (ExprOp::And, left, right) if left == right => left,
990 (ExprOp::And, Self::Const(mask), expr) | (ExprOp::And, expr, Self::Const(mask)) => {
991 Self::and_const(expr, mask)
992 }
993 (ExprOp::Or | ExprOp::Xor, Self::Const(value), expr)
994 | (ExprOp::Or | ExprOp::Xor, expr, Self::Const(value))
995 if value.is_zero() =>
996 {
997 expr
998 }
999 (ExprOp::Shl | ExprOp::Shr | ExprOp::Sar, expr, Self::Const(value))
1000 if value.is_zero() =>
1001 {
1002 expr
1003 }
1004 (ExprOp::Shl | ExprOp::Shr, Self::Const(value), _) if value.is_zero() => {
1005 Self::Const(U256::ZERO)
1006 }
1007 (op, left, right) => Self::Op(op, Box::new(left), Box::new(right)),
1008 }
1009 }
1010
1011 fn and_const(expr: Self, mask: U256) -> Self {
1012 if mask.is_zero() {
1013 return Self::Const(U256::ZERO);
1014 }
1015 if mask == U256::MAX {
1016 return expr;
1017 }
1018
1019 match expr {
1020 Self::Op(ExprOp::And, left, right) => match (*left, *right) {
1021 (Self::Const(existing), inner) | (inner, Self::Const(existing))
1022 if existing == mask =>
1023 {
1024 Self::and_const(inner, mask)
1025 }
1026 (left, right) if left == right => Self::and_const(left, mask),
1027 (left, right) => Self::Op(
1028 ExprOp::And,
1029 Box::new(Self::Op(ExprOp::And, Box::new(left), Box::new(right))),
1030 Box::new(Self::Const(mask)),
1031 ),
1032 },
1033 expr => Self::Op(ExprOp::And, Box::new(expr), Box::new(Self::Const(mask))),
1034 }
1035 }
1036
1037 pub(crate) fn collect_vars(&self, vars: &mut BTreeSet<String>) {
1039 match self {
1040 Self::Const(_) => {}
1041 Self::GasLeft(_) => {}
1042 Self::Var(var) => {
1043 vars.insert(var.clone());
1044 }
1045 Self::Keccak { name, .. } | Self::Hash { name, .. } => {
1046 vars.insert(name.clone());
1047 }
1048 Self::Not(value) => value.collect_vars(vars),
1049 Self::Op(_, left, right) => {
1050 left.collect_vars(vars);
1051 right.collect_vars(vars);
1052 }
1053 Self::Ite(cond, left, right) => {
1054 cond.collect_vars(vars);
1055 left.collect_vars(vars);
1056 right.collect_vars(vars);
1057 }
1058 }
1059 }
1060
1061 pub(crate) fn smt(&self) -> String {
1063 match self {
1064 Self::Const(value) => format!("(_ bv{value} 256)"),
1065 Self::Var(var) => var.clone(),
1066 Self::GasLeft(id) => format!("gasleft_{id}"),
1067 Self::Keccak { name, .. } | Self::Hash { name, .. } => name.clone(),
1068 Self::Not(value) => format!("(bvnot {})", value.smt()),
1069 Self::Op(op, left, right) => format!("({} {} {})", op.smt(), left.smt(), right.smt()),
1070 Self::Ite(cond, left, right) => {
1071 format!("(ite {} {} {})", cond.smt(), left.smt(), right.smt())
1072 }
1073 }
1074 }
1075}
1076
1077#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1078pub(crate) enum ExprOp {
1079 Add,
1080 Sub,
1081 Mul,
1082 UDiv,
1083 URem,
1084 SDiv,
1085 SRem,
1086 And,
1087 Or,
1088 Xor,
1089 Shl,
1090 Shr,
1091 Sar,
1092}
1093
1094impl ExprOp {
1095 pub(crate) const fn smt(self) -> &'static str {
1097 match self {
1098 Self::Add => "bvadd",
1099 Self::Sub => "bvsub",
1100 Self::Mul => "bvmul",
1101 Self::UDiv => "bvudiv",
1102 Self::URem => "bvurem",
1103 Self::SDiv => "bvsdiv",
1104 Self::SRem => "bvsrem",
1105 Self::And => "bvand",
1106 Self::Or => "bvor",
1107 Self::Xor => "bvxor",
1108 Self::Shl => "bvshl",
1109 Self::Shr => "bvlshr",
1110 Self::Sar => "bvashr",
1111 }
1112 }
1113}
1114
1115#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1116pub(crate) enum BoolExpr {
1117 Const(bool),
1118 Not(Box<Self>),
1119 And(Vec<Self>),
1120 Eq(Expr, Expr),
1121 Cmp(BoolExprOp, Expr, Expr),
1122}
1123
1124impl BoolExpr {
1125 pub(crate) fn eq(left: Expr, right: Expr) -> Self {
1127 if left == right {
1128 return Self::Const(true);
1129 }
1130 match (&left, &right) {
1131 (Expr::Const(left), Expr::Const(right)) => Self::Const(left == right),
1132 (
1133 Expr::Keccak { len: left_len, bytes: left_bytes, .. },
1134 Expr::Keccak { len: right_len, bytes: right_bytes, .. },
1135 ) if left_bytes.len() == right_bytes.len() => {
1136 let mut conditions = vec![Self::eq((**left_len).clone(), (**right_len).clone())];
1137 conditions.extend(
1138 left_bytes
1139 .iter()
1140 .cloned()
1141 .zip(right_bytes.iter().cloned())
1142 .map(|(left, right)| Self::eq(left, right)),
1143 );
1144 Self::and(conditions)
1145 }
1146 (
1147 Expr::Hash { algorithm: left_algorithm, bytes: left_bytes, .. },
1148 Expr::Hash { algorithm: right_algorithm, bytes: right_bytes, .. },
1149 ) if left_algorithm == right_algorithm && left_bytes.len() == right_bytes.len() => {
1150 Self::and(
1151 left_bytes
1152 .iter()
1153 .cloned()
1154 .zip(right_bytes.iter().cloned())
1155 .map(|(left, right)| Self::eq(left, right))
1156 .collect(),
1157 )
1158 }
1159 _ => Self::Eq(left, right),
1160 }
1161 }
1162
1163 pub(crate) fn and(values: Vec<Self>) -> Self {
1165 let mut out = Vec::new();
1166 for value in values {
1167 match value {
1168 Self::Const(true) => {}
1169 Self::Const(false) => return Self::Const(false),
1170 Self::And(values) => out.extend(values),
1171 value => out.push(value),
1172 }
1173 }
1174 if out.is_empty() {
1175 Self::Const(true)
1176 } else if out.len() == 1 {
1177 out.pop().expect("single item exists")
1178 } else {
1179 Self::And(out)
1180 }
1181 }
1182
1183 pub(crate) fn or(values: Vec<Self>) -> Self {
1185 let mut out = Vec::new();
1186 for value in values {
1187 match value {
1188 Self::Const(false) => {}
1189 Self::Const(true) => return Self::Const(true),
1190 value => out.push(value),
1191 }
1192 }
1193 if out.is_empty() {
1194 Self::Const(false)
1195 } else if out.len() == 1 {
1196 out.pop().expect("single item exists")
1197 } else {
1198 Self::and(out.into_iter().map(Self::not).collect()).not()
1199 }
1200 }
1201
1202 pub(crate) fn cmp(op: BoolExprOp, left: Expr, right: Expr) -> Self {
1204 if left == right {
1205 return Self::Const(matches!(op, BoolExprOp::Ule | BoolExprOp::Uge));
1206 }
1207 if let (Expr::Const(left), Expr::Const(right)) = (&left, &right) {
1208 return Self::Const(match op {
1209 BoolExprOp::Ult => left < right,
1210 BoolExprOp::Ugt => left > right,
1211 BoolExprOp::Ule => left <= right,
1212 BoolExprOp::Uge => left >= right,
1213 BoolExprOp::Slt => slt(*left, *right),
1214 BoolExprOp::Sgt => slt(*right, *left),
1215 });
1216 }
1217 match (op, &left, &right) {
1218 (BoolExprOp::Ugt, Expr::Const(value), _) if value.is_zero() => {
1219 return Self::Const(false);
1220 }
1221 (BoolExprOp::Ule, Expr::Const(value), _) if value.is_zero() => {
1222 return Self::Const(true);
1223 }
1224 (BoolExprOp::Ult, _, Expr::Const(value)) if value.is_zero() => {
1225 return Self::Const(false);
1226 }
1227 (BoolExprOp::Uge, _, Expr::Const(value)) if value.is_zero() => {
1228 return Self::Const(true);
1229 }
1230 (BoolExprOp::Ult, Expr::Const(value), _) if *value == U256::MAX => {
1231 return Self::Const(false);
1232 }
1233 (BoolExprOp::Uge, Expr::Const(value), _) if *value == U256::MAX => {
1234 return Self::Const(true);
1235 }
1236 (BoolExprOp::Ugt, _, Expr::Const(value)) if *value == U256::MAX => {
1237 return Self::Const(false);
1238 }
1239 (BoolExprOp::Ule, _, Expr::Const(value)) if *value == U256::MAX => {
1240 return Self::Const(true);
1241 }
1242 _ => {}
1243 }
1244 Self::Cmp(op, left, right)
1245 }
1246
1247 pub(crate) fn not(self) -> Self {
1249 match self {
1250 Self::Const(value) => Self::Const(!value),
1251 Self::Not(value) => *value,
1252 Self::And(values) => Self::Not(Box::new(Self::And(values))),
1253 value => Self::Not(Box::new(value)),
1254 }
1255 }
1256
1257 pub(crate) fn collect_vars(&self, vars: &mut BTreeSet<String>) {
1259 match self {
1260 Self::Const(_) => {}
1261 Self::Not(value) => value.collect_vars(vars),
1262 Self::And(values) => {
1263 for value in values {
1264 value.collect_vars(vars);
1265 }
1266 }
1267 Self::Eq(left, right) | Self::Cmp(_, left, right) => {
1268 left.collect_vars(vars);
1269 right.collect_vars(vars);
1270 }
1271 }
1272 }
1273
1274 pub(crate) fn smt(&self) -> String {
1276 match self {
1277 Self::Const(value) => value.to_string(),
1278 Self::Not(value) => format!("(not {})", value.smt()),
1279 Self::And(values) => {
1280 format!("(and {})", values.iter().map(Self::smt).collect::<Vec<_>>().join(" "))
1281 }
1282 Self::Eq(left, right) => format!("(= {} {})", left.smt(), right.smt()),
1283 Self::Cmp(op, left, right) => format!("({} {} {})", op.smt(), left.smt(), right.smt()),
1284 }
1285 }
1286}
1287
1288#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
1289pub(crate) enum BoolExprOp {
1290 Ult,
1291 Ugt,
1292 Ule,
1293 Uge,
1294 Slt,
1295 Sgt,
1296}
1297
1298impl BoolExprOp {
1299 pub(crate) const fn smt(self) -> &'static str {
1301 match self {
1302 Self::Ult => "bvult",
1303 Self::Ugt => "bvugt",
1304 Self::Ule => "bvule",
1305 Self::Uge => "bvuge",
1306 Self::Slt => "bvslt",
1307 Self::Sgt => "bvsgt",
1308 }
1309 }
1310}
1311
1312pub(crate) fn u256_to_usize(value: U256) -> Option<usize> {
1314 if value > U256::from(usize::MAX) { None } else { Some(value.to::<usize>()) }
1315}
1316
1317pub(crate) fn bool_upper_bound_usize(condition: &BoolExpr, expr: &Expr) -> Option<usize> {
1319 match condition {
1320 BoolExpr::Const(_) | BoolExpr::Not(_) => None,
1321 BoolExpr::And(values) => {
1322 let mut bound: Option<usize> = None;
1323 for value in values {
1324 if let Some(candidate) = bool_upper_bound_usize(value, expr) {
1325 bound = Some(bound.map_or(candidate, |bound| bound.min(candidate)));
1326 }
1327 }
1328 bound
1329 }
1330 BoolExpr::Eq(left, right) => match (left == expr, right == expr) {
1331 (true, _) => expr_const_value(right).and_then(u256_to_usize),
1332 (_, true) => expr_const_value(left).and_then(u256_to_usize),
1333 _ => None,
1334 },
1335 BoolExpr::Cmp(op, left, right) => {
1336 if left == expr {
1337 match op {
1338 BoolExprOp::Ult => expr_const_value(right)
1339 .and_then(|bound| (!bound.is_zero()).then(|| bound - U256::from(1)))
1340 .and_then(u256_to_usize),
1341 BoolExprOp::Ule => expr_const_value(right).and_then(u256_to_usize),
1342 _ => None,
1343 }
1344 } else if right == expr {
1345 match op {
1346 BoolExprOp::Ugt => expr_const_value(left)
1347 .and_then(|bound| (!bound.is_zero()).then(|| bound - U256::from(1)))
1348 .and_then(u256_to_usize),
1349 BoolExprOp::Uge => expr_const_value(left).and_then(u256_to_usize),
1350 _ => None,
1351 }
1352 } else {
1353 None
1354 }
1355 }
1356 }
1357}