1use super::*;
2use foundry_evm::revm::interpreter::STACK_LIMIT;
3
4#[derive(Clone, Debug, Default)]
5pub(crate) struct SymStack(Vec<SymExpr>);
6
7impl SymStack {
8 pub(crate) fn push(&mut self, value: SymExpr) -> Result<(), SymbolicError> {
9 if self.0.len() >= STACK_LIMIT {
10 return Err(SymbolicError::StackOverflow);
11 }
12 self.0.push(value);
13 Ok(())
14 }
15
16 pub(crate) fn pop(&mut self) -> Result<SymExpr, SymbolicError> {
17 self.0.pop().ok_or(SymbolicError::StackUnderflow)
18 }
19
20 pub(crate) fn peek(&self, index_from_top: usize) -> Result<&SymExpr, SymbolicError> {
21 self.0
22 .get(
23 self.0
24 .len()
25 .checked_sub(index_from_top + 1)
26 .ok_or(SymbolicError::StackUnderflow)?,
27 )
28 .ok_or(SymbolicError::StackUnderflow)
29 }
30
31 pub(crate) fn swap(&mut self, index_from_top: usize) -> Result<(), SymbolicError> {
32 let len = self.0.len();
33 let other = len.checked_sub(index_from_top + 1).ok_or(SymbolicError::StackUnderflow)?;
34 self.0.swap(len - 1, other);
35 Ok(())
36 }
37}
38
39#[derive(Clone, Debug)]
40pub(crate) enum BoundedCopySize {
41 Concrete(usize),
42 Symbolic { size: SymExpr, max_size: usize },
43}
44
45#[derive(Clone, Debug, Default)]
46pub(crate) struct SymMemory {
47 symbolic_writes: Vec<SymbolicMemoryWrite>,
48 materialized_size: usize,
49 logical_size: Option<SymExpr>,
50}
51
52#[derive(Clone, Debug)]
53struct SymbolicMemoryWrite {
54 offset: SymExpr,
55 bytes: SymBytes,
56 minimum_offset: usize,
58}
59
60impl SymbolicMemoryWrite {
61 fn concrete_offset(&self) -> Option<usize> {
62 self.offset.eval().and_then(|offset| usize::try_from(offset).ok())
63 }
64
65 fn concrete_byte_index(&self, offset: usize) -> Option<usize> {
66 let write_offset = self.concrete_offset()?;
67 let idx = offset.checked_sub(write_offset)?;
68 (idx < self.bytes.len()).then_some(idx)
69 }
70
71 fn concrete_byte(&self, cx: &mut SymCx, offset: usize) -> Option<SymExpr> {
72 self.concrete_byte_index(offset).map(|idx| self.bytes.byte(cx, idx))
73 }
74}
75
76impl SymMemory {
77 fn saturating_add_word(cx: &mut SymCx, left: SymExpr, right: SymExpr) -> SymExpr {
78 let sum = SymExpr::binop(cx, SymBinOp::Add, left.clone(), right);
79 let overflow = SymBoolExpr::cmp_word_expr(cx, SymCmpOp::Ult, &sum, left);
80 let max = SymExpr::constant(cx, U256::MAX);
81 SymExpr::ite(cx, overflow, max, sum)
82 }
83
84 pub(crate) fn size_after_access_word(cx: &mut SymCx, offset: SymExpr, len: usize) -> SymExpr {
85 let size = SymExpr::constant(cx, U256::from(len));
86 Self::size_after_range_word(cx, offset, size)
87 }
88
89 fn size_after_range_word(cx: &mut SymCx, offset: SymExpr, size: SymExpr) -> SymExpr {
90 let end = Self::saturating_add_word(cx, offset, size.clone());
91 let round = SymExpr::constant(cx, U256::from(31));
92 let rounded = Self::saturating_add_word(cx, end, round);
93 let mask = SymExpr::constant(cx, !U256::from(31));
94 let rounded = SymExpr::binop(cx, SymBinOp::And, rounded, mask);
95 let is_empty = SymBoolExpr::eq_word_const(cx, &size, U256::ZERO);
96 let zero = SymExpr::zero(cx);
97 SymExpr::ite(cx, is_empty, zero, rounded)
98 }
99
100 fn size_after_access(offset: usize, len: usize) -> usize {
101 let Some(end) = offset.checked_add(len) else {
102 return usize::MAX & !31usize;
103 };
104 end.checked_add(31).map(|size| size & !31usize).unwrap_or(usize::MAX & !31usize)
105 }
106
107 fn max_size_word(cx: &mut SymCx, left: SymExpr, right: SymExpr) -> SymExpr {
108 if let (Some(left_value), Some(right_value)) = (left.as_const(), right.as_const()) {
109 return SymExpr::constant(cx, left_value.max(right_value));
110 }
111 if left == right {
112 left
113 } else {
114 let condition = SymBoolExpr::cmp_word_expr(cx, SymCmpOp::Ult, &left, right.clone());
115 SymExpr::ite(cx, condition, right, left)
116 }
117 }
118
119 fn expand_to(&mut self, cx: &mut SymCx, size: SymExpr) {
120 self.logical_size = Some(match self.logical_size.take() {
121 Some(current) => Self::max_size_word(cx, current, size),
122 None => size,
123 });
124 }
125
126 pub(crate) fn store_word(&mut self, cx: &mut SymCx, offset: usize, value: SymExpr) {
127 let bytes = value.into_bytes(cx);
128 self.store_bytes(cx, offset, bytes);
129 }
130
131 pub(crate) fn store_word_offset(
132 &mut self,
133 cx: &mut SymCx,
134 offset: SymExpr,
135 value: SymExpr,
136 minimum_offset: usize,
137 ) {
138 if let Some(offset) = offset.as_const() {
139 if let Ok(offset) = usize::try_from(offset) {
140 self.store_word(cx, offset, value);
141 }
142 } else {
143 let bytes = value.into_bytes(cx);
144 self.store_symbolic_bytes(cx, offset, bytes, minimum_offset);
145 }
146 }
147
148 pub(crate) fn store_byte(&mut self, cx: &mut SymCx, offset: usize, value: SymExpr) {
149 let byte = value.low_byte(cx);
150 let bytes = SymBytes::exprs(cx, vec![byte]);
151 self.store_bytes(cx, offset, bytes);
152 }
153
154 pub(crate) fn store_byte_offset(
155 &mut self,
156 cx: &mut SymCx,
157 offset: SymExpr,
158 value: SymExpr,
159 minimum_offset: usize,
160 ) {
161 if let Some(offset) = offset.as_const() {
162 if let Ok(offset) = usize::try_from(offset) {
163 self.store_byte(cx, offset, value);
164 }
165 } else {
166 let byte = value.low_byte(cx);
167 let bytes = SymBytes::exprs(cx, vec![byte]);
168 self.store_symbolic_bytes(cx, offset, bytes, minimum_offset);
169 }
170 }
171
172 pub(crate) fn store_bytes(&mut self, cx: &mut SymCx, offset: usize, bytes: SymBytes) {
173 if bytes.is_empty() {
174 return;
175 }
176 let size = Self::size_after_access(offset, bytes.len());
177 self.materialized_size = self.materialized_size.max(size);
178 let size = SymExpr::constant(cx, U256::from(size));
179 self.expand_to(cx, size);
180 let minimum_offset = offset;
181 let offset = SymExpr::constant(cx, U256::from(offset));
182 self.symbolic_writes.push(SymbolicMemoryWrite { offset, bytes, minimum_offset });
183 }
184
185 fn store_symbolic_bytes(
186 &mut self,
187 cx: &mut SymCx,
188 offset: SymExpr,
189 bytes: SymBytes,
190 minimum_offset: usize,
191 ) {
192 if bytes.is_empty() {
193 return;
194 }
195 let size = Self::size_after_access_word(cx, offset.clone(), bytes.len());
196 self.expand_to(cx, size);
197 self.symbolic_writes.push(SymbolicMemoryWrite { offset, bytes, minimum_offset });
198 }
199
200 fn store_symbolic_sized_bytes(
201 &mut self,
202 cx: &mut SymCx,
203 offset: SymExpr,
204 bytes: SymBytes,
205 access_size: SymExpr,
206 ) {
207 if !bytes.is_empty()
208 && let Some(offset) = offset.eval().and_then(|offset| usize::try_from(offset).ok())
209 {
210 let size = Self::size_after_access(offset, bytes.len());
211 self.materialized_size = self.materialized_size.max(size);
212 }
213 if !bytes.is_empty() {
214 self.symbolic_writes.push(SymbolicMemoryWrite {
215 offset: offset.clone(),
216 bytes,
217 minimum_offset: 0,
218 });
219 }
220 let size = Self::size_after_range_word(cx, offset, access_size);
221 self.expand_to(cx, size);
222 }
223
224 pub(crate) fn store_bytes_offset(&mut self, cx: &mut SymCx, offset: SymExpr, bytes: SymBytes) {
225 if let Some(offset) = offset.as_const() {
226 if let Ok(offset) = usize::try_from(offset) {
227 self.store_bytes(cx, offset, bytes);
228 }
229 } else {
230 self.store_symbolic_bytes(cx, offset, bytes, 0);
231 }
232 }
233
234 pub(crate) fn load_word(
235 &self,
236 cx: &mut SymCx,
237 offset: usize,
238 ) -> Result<SymExpr, SymbolicError> {
239 let offset = SymExpr::constant(cx, U256::from(offset));
240 Ok(self.read_bytes_offset(cx, offset, 32).word_at(cx, 0))
241 }
242
243 pub(crate) fn load_word_offset(
244 &mut self,
245 cx: &mut SymCx,
246 offset: SymExpr,
247 ) -> Result<SymExpr, SymbolicError> {
248 if let Some(offset) = offset.as_const() {
249 let Ok(offset) = usize::try_from(offset) else { return Ok(SymExpr::zero(cx)) };
250 let size = Self::size_after_access(offset, 32);
251 let size = SymExpr::constant(cx, U256::from(size));
252 self.expand_to(cx, size);
253 self.load_word(cx, offset)
254 } else {
255 let size = Self::size_after_access_word(cx, offset.clone(), 32);
256 self.expand_to(cx, size);
257 Ok(self.read_bytes_offset(cx, offset, 32).word_at(cx, 0))
259 }
260 }
261
262 pub(crate) fn read_concrete(
263 &self,
264 cx: &mut SymCx,
265 offset: usize,
266 size: usize,
267 ) -> Result<Vec<u8>, SymbolicError> {
268 if let Some(bytes) = self.read_stored_bytes(cx, offset, size) {
269 return bytes.concrete_bytes(cx, "symbolic memory read");
270 }
271
272 let mut out = vec![0u8; size];
273 for (idx, byte) in out.iter_mut().enumerate() {
274 if let Some(value) = self.byte(cx, offset + idx).as_const() {
275 *byte = value.to::<u8>();
276 } else {
277 return Err(SymbolicError::Unsupported("symbolic memory read"));
278 }
279 }
280 Ok(out)
281 }
282
283 pub(crate) fn read_byte_exprs(
284 &self,
285 cx: &mut SymCx,
286 offset: usize,
287 size: usize,
288 ) -> Vec<SymExpr> {
289 self.read_bytes(cx, offset, size).materialize(cx)
290 }
291
292 pub(crate) fn read_byte_exprs_offset(
293 &self,
294 cx: &mut SymCx,
295 offset: SymExpr,
296 size: usize,
297 ) -> Vec<SymExpr> {
298 self.read_bytes_offset(cx, offset, size).materialize(cx)
299 }
300
301 pub(crate) fn read_bytes(&self, cx: &mut SymCx, offset: usize, size: usize) -> SymBytes {
302 let offset = SymExpr::constant(cx, U256::from(offset));
303 self.read_bytes_offset(cx, offset, size)
304 }
305
306 pub(crate) fn read_bytes_offset(
307 &self,
308 cx: &mut SymCx,
309 offset: SymExpr,
310 size: usize,
311 ) -> SymBytes {
312 self.read_bytes_offset_with_bounds(cx, offset, size, 0, None)
313 }
314
315 pub(crate) fn read_bytes_offset_with_bounds(
316 &self,
317 cx: &mut SymCx,
318 offset: SymExpr,
319 size: usize,
320 minimum_offset: usize,
321 maximum_offset: Option<usize>,
322 ) -> SymBytes {
323 if let Some(offset) = offset.as_const() {
324 let Ok(offset) = usize::try_from(offset) else {
325 return SymBytes::concrete(cx, vec![0; size]);
326 };
327 if let Some(bytes) = self.read_stored_bytes(cx, offset, size) {
328 return bytes;
329 }
330 let bytes = (0..size).map(|idx| self.byte(cx, offset + idx)).collect();
331 SymBytes::exprs(cx, bytes)
332 } else {
333 let bytes = (0..size)
334 .map(|idx| {
335 self.byte_dynamic_with_delta_and_bounds(
336 cx,
337 &offset,
338 idx,
339 minimum_offset,
340 maximum_offset,
341 )
342 })
343 .collect();
344 SymBytes::exprs(cx, bytes)
345 }
346 }
347
348 pub(crate) fn load_word_offset_with_bounds(
349 &self,
350 cx: &mut SymCx,
351 base: &SymExpr,
352 relative_offset: usize,
353 minimum_base: usize,
354 maximum_base: Option<usize>,
355 ) -> SymExpr {
356 let offset = SymExpr::add_const(cx, base.clone(), U256::from(relative_offset));
357 let maximum_offset = maximum_base.and_then(|offset| offset.checked_add(relative_offset));
358 let minimum_offset = if relative_offset == 0 || maximum_offset.is_some() {
359 minimum_base.checked_add(relative_offset).unwrap_or_default()
360 } else {
361 0
362 };
363 self.read_bytes_offset_with_bounds(cx, offset, 32, minimum_offset, maximum_offset)
364 .word_at(cx, 0)
365 }
366
367 fn read_stored_bytes(&self, cx: &mut SymCx, offset: usize, size: usize) -> Option<SymBytes> {
368 if size == 0 {
369 return Some(SymBytes::empty(cx));
370 }
371 let end = offset.checked_add(size)?;
372
373 let mut unresolved = vec![(offset, end)];
374 let mut pieces = Vec::new();
375
376 for write in self.symbolic_writes.iter().rev() {
377 if unresolved.is_empty() {
378 break;
379 }
380
381 let write_offset = write.concrete_offset()?;
382 let write_end = write_offset.checked_add(write.bytes.len())?;
383
384 if write_end <= offset || end <= write_offset {
385 continue;
386 }
387
388 let mut next_unresolved = Vec::new();
389 for (start, end) in unresolved {
390 let overlap_start = start.max(write_offset);
391 let overlap_end = end.min(write_end);
392
393 if overlap_start >= overlap_end {
394 next_unresolved.push((start, end));
395 continue;
396 }
397
398 if start < overlap_start {
399 next_unresolved.push((start, overlap_start));
400 }
401
402 pieces.push((
403 overlap_start - offset,
404 write.bytes.slice_concrete(
405 cx,
406 overlap_start - write_offset,
407 overlap_end - overlap_start,
408 ),
409 ));
410
411 if overlap_end < end {
412 next_unresolved.push((overlap_end, end));
413 }
414 }
415 unresolved = next_unresolved;
416 }
417
418 pieces.extend(
419 unresolved
420 .into_iter()
421 .map(|(start, end)| (start - offset, SymBytes::concrete(cx, vec![0; end - start]))),
422 );
423 pieces.sort_by_key(|(offset, _)| *offset);
424
425 Some(SymBytes::concat(cx, pieces.into_iter().map(|(_, bytes)| bytes)))
426 }
427
428 pub(crate) fn read_byte_exprs_symbolic_size(
429 &self,
430 cx: &mut SymCx,
431 offset: SymExpr,
432 size: SymExpr,
433 max_size: usize,
434 ) -> Vec<SymExpr> {
435 self.read_bytes_symbolic_size(cx, offset, size, max_size).materialize(cx)
436 }
437
438 pub(crate) fn read_bytes_symbolic_size(
439 &self,
440 cx: &mut SymCx,
441 offset: SymExpr,
442 size: SymExpr,
443 max_size: usize,
444 ) -> SymBytes {
445 if let Some(size) = size.eval() {
446 let size = usize::try_from(size).map_or(max_size, |size| size.min(max_size));
447 let bytes = self.read_bytes_offset(cx, offset, size);
448 let padding = SymBytes::concrete(cx, vec![0; max_size - size]);
449 return SymBytes::concat(cx, [bytes, padding]);
450 }
451
452 let bytes = self.read_bytes_offset(cx, offset, max_size);
453 SymBytes::sized(cx, bytes, size, max_size)
454 }
455
456 pub(crate) fn byte(&self, cx: &mut SymCx, offset: usize) -> SymExpr {
457 let mut writes = self.symbolic_writes.as_slice();
458 let mut result = if let Some(base_idx) =
459 writes.iter().rposition(|write| write.concrete_byte_index(offset).is_some())
460 {
461 let write = &writes[base_idx];
462 let byte = write.concrete_byte(cx, offset).expect("concrete byte index is present");
463 writes = &writes[base_idx + 1..];
464 byte
465 } else {
466 SymExpr::zero(cx)
467 };
468
469 for write in writes {
470 if let Some(byte) = write.concrete_byte(cx, offset) {
471 result = byte;
472 continue;
473 }
474 if write.concrete_offset().is_some() {
475 continue;
476 }
477 if write.minimum_offset > offset {
478 continue;
479 }
480 for idx in 0..write.bytes.len() {
481 let write_offset = SymExpr::add_const(cx, write.offset.clone(), U256::from(idx));
482 let offset = SymExpr::constant(cx, U256::from(offset));
483 let condition = SymBoolExpr::eq(cx, write_offset, offset);
484 let byte = write.bytes.byte(cx, idx);
485 result = SymExpr::ite(cx, condition, byte, result);
486 }
487 }
488 result
489 }
490
491 pub(crate) fn byte_dynamic_with_delta(
497 &self,
498 cx: &mut SymCx,
499 offset: &SymExpr,
500 delta: usize,
501 ) -> SymExpr {
502 self.byte_dynamic_with_delta_and_bounds(cx, offset, delta, 0, None)
503 }
504
505 fn byte_dynamic_with_delta_and_bounds(
506 &self,
507 cx: &mut SymCx,
508 offset: &SymExpr,
509 delta: usize,
510 minimum_offset: usize,
511 maximum_offset: Option<usize>,
512 ) -> SymExpr {
513 let materialized_size = self.materialized_size;
514 let all_writes_bounded = self.symbolic_writes.iter().all(|write| {
515 write
516 .concrete_offset()
517 .and_then(|write_offset| write_offset.checked_add(write.bytes.len()))
518 .is_some_and(|end| end <= materialized_size)
519 });
520 let maximum_target = maximum_offset.and_then(|offset| offset.checked_add(delta));
521 let target_non_wrapping = delta == 0 || maximum_target.is_some();
522
523 if all_writes_bounded && target_non_wrapping {
524 let mut result = SymExpr::zero(cx);
525 for candidate in (delta..self.materialized_size).rev() {
526 let candidate_expr = SymExpr::constant(cx, U256::from(candidate - delta));
527 let condition = SymBoolExpr::eq(cx, offset.clone(), candidate_expr);
528 let byte = self.byte(cx, candidate);
529 result = SymExpr::ite(cx, condition, byte, result);
530 }
531 return result;
532 }
533
534 let target = SymExpr::add_const(cx, offset.clone(), U256::from(delta));
535 let minimum_target = if target_non_wrapping {
536 minimum_offset.checked_add(delta).unwrap_or_default()
537 } else {
538 0
539 };
540 let gas_dependent_offset = offset.contains_gasleft();
541 let mut result = SymExpr::zero(cx);
542 for write in &self.symbolic_writes {
543 if write
544 .concrete_offset()
545 .and_then(|offset| offset.checked_add(write.bytes.len()))
546 .is_some_and(|end| end <= minimum_target)
547 || maximum_target.is_some_and(|target| write.minimum_offset > target)
548 {
549 continue;
550 }
551 if !gas_dependent_offset
552 && !write.offset.contains_gasleft()
553 && let Some(index) = target.constant_difference(&write.offset)
554 {
555 if let Ok(index) = usize::try_from(index)
556 && index < write.bytes.len()
557 {
558 result = write.bytes.byte(cx, index);
559 }
560 continue;
561 }
562 for idx in 0..write.bytes.len() {
563 let write_offset = SymExpr::add_const(cx, write.offset.clone(), U256::from(idx));
564 let condition = SymBoolExpr::eq(cx, write_offset, target.clone());
565 let byte = write.bytes.byte(cx, idx);
566 result = SymExpr::ite(cx, condition, byte, result);
567 }
568 }
569 result
570 }
571
572 pub(crate) fn size_word(&self, cx: &mut SymCx) -> SymExpr {
573 self.logical_size.clone().unwrap_or_else(|| SymExpr::zero(cx))
574 }
575
576 pub(crate) fn size_after_range_expansion_word(
577 &self,
578 cx: &mut SymCx,
579 offset: SymExpr,
580 size: SymExpr,
581 ) -> SymExpr {
582 let current = self.size_word(cx);
583 let expanded = Self::size_after_range_word(cx, offset, size);
584 Self::max_size_word(cx, current, expanded)
585 }
586
587 pub(crate) fn expand_range(&mut self, cx: &mut SymCx, offset: SymExpr, size: SymExpr) {
588 if let (Some(offset), Some(size)) = (offset.as_const(), size.as_const())
589 && let (Ok(offset), Ok(size)) = (usize::try_from(offset), usize::try_from(size))
590 {
591 if size != 0 {
592 let size = Self::size_after_access(offset, size);
593 let size = SymExpr::constant(cx, U256::from(size));
594 self.expand_to(cx, size);
595 }
596 return;
597 }
598 let size = Self::size_after_range_word(cx, offset, size);
599 self.expand_to(cx, size);
600 }
601
602 pub(crate) fn copy_bytes_size_offset(
603 &mut self,
604 cx: &mut SymCx,
605 dest: SymExpr,
606 size: SymExpr,
607 src: SymBytes,
608 ) -> Result<(), SymbolicError> {
609 if src.is_empty() {
610 return Ok(());
611 }
612 if let Some(size) = size.eval() {
613 let size = usize::try_from(size).map_or(src.len(), |size| size.min(src.len()));
614 if size != 0 {
615 let src = src.slice_concrete(cx, 0, size);
616 self.store_bytes_offset(cx, dest, src);
617 }
618 return Ok(());
619 }
620
621 if let Some(dest) = dest.as_const() {
622 if let Ok(dest) = usize::try_from(dest) {
623 let bytes = (0..src.len())
624 .map(|idx| {
625 let source = src.byte(cx, idx);
626 self.copy_size_byte_at(cx, dest + idx, idx, &size, source)
627 })
628 .collect::<Vec<_>>();
629 let bytes = SymBytes::exprs(cx, bytes);
630 let dest = SymExpr::constant(cx, U256::from(dest));
631 self.store_symbolic_sized_bytes(cx, dest, bytes, size);
632 }
633 } else {
634 let bytes = (0..src.len())
635 .map(|idx| {
636 let existing = self.byte_dynamic_with_delta(cx, &dest, idx);
637 let source = src.byte(cx, idx);
638 Self::copy_size_byte(cx, idx, &size, source, existing)
639 })
640 .collect();
641 let bytes = SymBytes::exprs(cx, bytes);
642 self.store_symbolic_sized_bytes(cx, dest, bytes, size);
643 }
644 Ok(())
645 }
646
647 pub(crate) fn copy_calldata_to_offset(
648 &mut self,
649 cx: &mut SymCx,
650 dest: SymExpr,
651 offset: SymExpr,
652 size: usize,
653 calldata: &SymCalldata,
654 ) {
655 let bytes = if offset.as_const().is_some_and(|offset| usize::try_from(offset).is_err()) {
656 SymBytes::concrete(cx, vec![0; size])
657 } else {
658 calldata.read_bytes_offset(cx, offset, size)
659 };
660 self.store_bytes_offset(cx, dest, bytes);
661 }
662
663 pub(crate) fn copy_calldata_symbolic_size(
664 &mut self,
665 cx: &mut SymCx,
666 dest: SymExpr,
667 offset: SymExpr,
668 size: SymExpr,
669 max_size: usize,
670 calldata: &SymCalldata,
671 ) -> Result<(), SymbolicError> {
672 let bytes = calldata.read_bytes_offset(cx, offset, max_size);
673 self.copy_bytes_size_offset(cx, dest, size, bytes)
674 }
675
676 fn copy_size_byte_at(
677 &self,
678 cx: &mut SymCx,
679 dest: usize,
680 idx: usize,
681 size: &SymExpr,
682 source: SymExpr,
683 ) -> SymExpr {
684 let existing = self.byte(cx, dest);
685 Self::copy_size_byte(cx, idx, size, source, existing)
686 }
687
688 fn copy_size_byte(
689 cx: &mut SymCx,
690 idx: usize,
691 size: &SymExpr,
692 source: SymExpr,
693 existing: SymExpr,
694 ) -> SymExpr {
695 let idx = SymExpr::constant(cx, U256::from(idx));
696 let condition = SymBoolExpr::cmp(cx, SymCmpOp::Ult, idx, size.clone());
697 SymExpr::ite(cx, condition, source, existing)
698 }
699
700 pub(crate) fn copy_return_data_to_offset(
701 &mut self,
702 cx: &mut SymCx,
703 dest: SymExpr,
704 offset: SymExpr,
705 size: usize,
706 return_data: &SymReturnData,
707 ) -> Result<(), SymbolicError> {
708 if size == 0 {
709 return Ok(());
710 }
711 if let Some(offset) = offset.as_const() {
712 let Ok(offset) = usize::try_from(offset) else {
713 return Err(SymbolicError::Unsupported("out-of-bounds symbolic RETURNDATACOPY"));
714 };
715 if offset.saturating_add(size) > return_data.len() {
716 return Err(SymbolicError::Unsupported("out-of-bounds symbolic RETURNDATACOPY"));
717 }
718 }
719 let bytes = return_data.read_bytes_offset(cx, offset, size);
720 self.store_bytes_offset(cx, dest, bytes);
721 Ok(())
722 }
723
724 pub(crate) fn copy_return_data_symbolic_size(
725 &mut self,
726 cx: &mut SymCx,
727 dest: SymExpr,
728 offset: SymExpr,
729 size: SymExpr,
730 max_size: usize,
731 return_data: &SymReturnData,
732 ) -> Result<(), SymbolicError> {
733 if max_size == 0 {
734 return Ok(());
735 }
736 if let Some(offset) = offset.as_const() {
737 let Ok(offset) = usize::try_from(offset) else {
738 return Err(SymbolicError::Unsupported("out-of-bounds symbolic RETURNDATACOPY"));
739 };
740 if offset.saturating_add(max_size) > return_data.len() {
741 return Err(SymbolicError::Unsupported("out-of-bounds symbolic RETURNDATACOPY"));
742 }
743 }
744 let bytes = return_data.read_bytes_offset(cx, offset, max_size);
745 self.copy_bytes_size_offset(cx, dest, size, bytes)
746 }
747
748 pub(crate) fn copy_call_output_offset(
749 &mut self,
750 cx: &mut SymCx,
751 dest: SymExpr,
752 size: &BoundedCopySize,
753 return_data: &SymReturnData,
754 ) -> Result<(), SymbolicError> {
755 match size {
756 BoundedCopySize::Concrete(size) => {
757 if *size != 0 {
758 let copy_size = (*size).min(return_data.len());
759 let bytes = if return_data.len_word.as_const().is_none() {
760 let bytes = (0..copy_size)
761 .map(|idx| self.call_output_byte(cx, &dest, idx, None, return_data))
762 .collect::<Vec<_>>();
763 SymBytes::exprs(cx, bytes)
764 } else {
765 let offset = SymExpr::zero(cx);
766 return_data.read_bytes_offset(cx, offset, copy_size)
767 };
768 let size = SymExpr::constant(cx, U256::from(*size));
769 self.store_symbolic_sized_bytes(cx, dest, bytes, size);
770 }
771 }
772 BoundedCopySize::Symbolic { size, max_size } => {
773 let output_size = size.clone();
774 if *max_size != 0 {
775 let copy_size = (*max_size).min(return_data.len());
776 let bytes = (0..copy_size)
777 .map(|idx| {
778 self.call_output_byte(cx, &dest, idx, Some(&output_size), return_data)
779 })
780 .collect::<Vec<_>>();
781 let bytes = SymBytes::exprs(cx, bytes);
782 self.store_symbolic_sized_bytes(cx, dest, bytes, output_size);
783 }
784 }
785 }
786 Ok(())
787 }
788
789 pub(crate) fn call_output_byte(
790 &self,
791 cx: &mut SymCx,
792 dest: &SymExpr,
793 idx: usize,
794 output_size: Option<&SymExpr>,
795 return_data: &SymReturnData,
796 ) -> SymExpr {
797 let mut guards = Vec::new();
798 if let Some(output_size) = output_size {
799 let idx_expr = SymExpr::constant(cx, U256::from(idx));
800 guards.push(SymBoolExpr::cmp(cx, SymCmpOp::Ult, idx_expr, output_size.clone()));
801 }
802 if return_data.len_word.as_const().is_none() {
803 let idx_expr = SymExpr::constant(cx, U256::from(idx));
804 guards.push(SymBoolExpr::cmp(
805 cx,
806 SymCmpOp::Ult,
807 idx_expr,
808 return_data.len_word.clone(),
809 ));
810 }
811 let guard = SymBoolExpr::and(cx, guards);
812 match guard.as_const() {
813 Some(true) => return_data.byte(cx, idx),
814 Some(false) => self.call_output_existing_byte(cx, dest, idx),
815 None => {
816 let byte = return_data.byte(cx, idx);
817 let existing = self.call_output_existing_byte(cx, dest, idx);
818 SymExpr::ite(cx, guard, byte, existing)
819 }
820 }
821 }
822
823 pub(crate) fn call_output_existing_byte(
824 &self,
825 cx: &mut SymCx,
826 dest: &SymExpr,
827 idx: usize,
828 ) -> SymExpr {
829 if let Some(dest) = dest.as_const() {
830 match usize::try_from(dest) {
831 Ok(dest) => self.byte(cx, dest + idx),
832 Err(_) => SymExpr::zero(cx),
833 }
834 } else {
835 self.byte_dynamic_with_delta(cx, dest, idx)
836 }
837 }
838
839 pub(crate) fn copy_memory_to_offset(
840 &mut self,
841 cx: &mut SymCx,
842 dest: SymExpr,
843 src: SymExpr,
844 size: usize,
845 ) -> Result<(), SymbolicError> {
846 if size == 0 {
847 return Ok(());
848 }
849 let bytes = self.read_bytes_offset(cx, src, size);
850 self.store_bytes_offset(cx, dest, bytes);
851 Ok(())
852 }
853
854 pub(crate) fn copy_memory_symbolic_size(
855 &mut self,
856 cx: &mut SymCx,
857 dest: SymExpr,
858 src: SymExpr,
859 size: SymExpr,
860 max_size: usize,
861 ) -> Result<(), SymbolicError> {
862 if max_size == 0 {
863 return Ok(());
864 }
865 let source = self.read_bytes_offset(cx, src, max_size);
866 self.copy_bytes_size_offset(cx, dest, size, source)
867 }
868
869 pub(crate) fn return_data(
870 &self,
871 cx: &mut SymCx,
872 offset: SymExpr,
873 size: usize,
874 ) -> Result<SymReturnData, SymbolicError> {
875 let bytes = self.read_bytes_offset(cx, offset, size);
876 Ok(SymReturnData::from_bytes(cx, bytes))
877 }
878
879 pub(crate) fn return_data_symbolic_size(
880 &self,
881 cx: &mut SymCx,
882 offset: SymExpr,
883 size: SymExpr,
884 max_size: usize,
885 ) -> Result<SymReturnData, SymbolicError> {
886 Ok(SymReturnData {
887 bytes: self.read_bytes_symbolic_size(cx, offset, size.clone(), max_size),
888 len_word: size,
889 })
890 }
891}
892
893#[derive(Clone, Debug, PartialEq, Eq)]
894pub(crate) struct SymCode {
895 bytes: SymBytes,
896 jump_table: JumpTable,
897}
898
899#[derive(Clone, Debug, PartialEq, Eq)]
900pub(crate) enum GuardedOpcode {
901 End,
902 Concrete(u8),
903 SymbolicSize { condition: SymBoolExpr, opcode: u8 },
904}
905
906impl SymCode {
907 pub(crate) fn empty(cx: &mut SymCx) -> Self {
908 Self { bytes: SymBytes::empty(cx), jump_table: JumpTable::default() }
909 }
910
911 pub(crate) fn from_byte_exprs(cx: &mut SymCx, bytes: Vec<SymExpr>) -> Self {
912 let bytes = SymBytes::exprs(cx, bytes);
913 Self::from_bytes(cx, bytes)
914 }
915
916 pub(crate) fn from_bytes(cx: &mut SymCx, bytes: SymBytes) -> Self {
917 let analysis = if let Some(bytes) = bytes.as_concrete_slice() {
918 bytes.to_vec()
919 } else {
920 (0..bytes.len())
921 .map(|idx| {
922 bytes.byte(cx, idx).as_const().map_or(opcode::STOP, |value| value.to::<u8>())
923 })
924 .collect::<Vec<_>>()
925 };
926 let analyzed = Bytecode::new_legacy(Bytes::from(analysis));
927 let jump_table = analyzed.legacy_jump_table().cloned().unwrap_or_default();
928 Self { bytes, jump_table }
929 }
930
931 pub(crate) fn concrete(cx: &mut SymCx, bytes: Vec<u8>) -> Self {
932 Self::from_bytecode(cx, &Bytecode::new_legacy(Bytes::from(bytes)))
933 }
934
935 pub(crate) fn from_bytecode(cx: &mut SymCx, bytecode: &Bytecode) -> Self {
936 let bytes = SymBytes::concrete(cx, bytecode.original_byte_slice().to_vec());
937 let jump_table = bytecode.legacy_jump_table().cloned().unwrap_or_default();
938 Self { bytes, jump_table }
939 }
940
941 pub(crate) fn from_memory_offset(
942 cx: &mut SymCx,
943 memory: &SymMemory,
944 offset: SymExpr,
945 size: usize,
946 ) -> Self {
947 let bytes = memory.read_bytes_offset(cx, offset, size);
948 Self::from_bytes(cx, bytes)
949 }
950
951 pub(crate) fn from_memory_symbolic_size(
952 cx: &mut SymCx,
953 memory: &SymMemory,
954 offset: SymExpr,
955 size: SymExpr,
956 max_size: usize,
957 ) -> Self {
958 let bytes = memory.read_bytes_symbolic_size(cx, offset, size, max_size);
959 Self::from_bytes(cx, bytes)
960 }
961
962 pub(crate) fn len(&self) -> usize {
963 self.bytes.len()
964 }
965
966 pub(crate) fn is_empty(&self) -> bool {
967 self.bytes.is_empty()
968 }
969
970 pub(crate) const fn jump_table(&self) -> &JumpTable {
971 &self.jump_table
972 }
973
974 pub(crate) fn opcode(&self, cx: &mut SymCx, pc: usize) -> Result<Option<u8>, SymbolicError> {
975 if pc >= self.len() {
976 return Ok(None);
977 }
978 match self.bytes.byte(cx, pc).as_const() {
979 Some(value) => Ok(Some(value.to::<u8>())),
980 None => Err(SymbolicError::Unsupported("symbolic bytecode opcode")),
981 }
982 }
983
984 pub(crate) fn guarded_opcode(
985 &self,
986 cx: &mut SymCx,
987 pc: usize,
988 ) -> Result<GuardedOpcode, SymbolicError> {
989 if pc >= self.len() {
990 return Ok(GuardedOpcode::End);
991 }
992 let byte = self.bytes.byte(cx, pc);
993 match byte.as_const() {
994 Some(value) => Ok(GuardedOpcode::Concrete(value.to::<u8>())),
995 None => {
996 if let SymExprKind::Ite(condition, then_expr, else_expr) = byte.kind()
997 && else_expr.as_const().is_some_and(|value| value.is_zero())
998 {
999 match then_expr.as_const() {
1000 Some(value) if value.is_zero() => Ok(GuardedOpcode::Concrete(0)),
1001 Some(value) => Ok(GuardedOpcode::SymbolicSize {
1002 condition: condition.clone(),
1003 opcode: value.to::<u8>(),
1004 }),
1005 None => Err(SymbolicError::Unsupported("symbolic bytecode opcode")),
1006 }
1007 } else {
1008 Err(SymbolicError::Unsupported("symbolic bytecode opcode"))
1009 }
1010 }
1011 }
1012 }
1013
1014 pub(crate) fn concrete_range(
1015 &self,
1016 cx: &mut SymCx,
1017 offset: usize,
1018 size: usize,
1019 reason: &'static str,
1020 ) -> Result<Vec<u8>, SymbolicError> {
1021 if let Some(bytes) = self.bytes.as_concrete_slice() {
1022 let mut out = Vec::with_capacity(size);
1023 let end = offset.saturating_add(size).min(bytes.len());
1024 if offset < end {
1025 out.extend_from_slice(&bytes[offset..end]);
1026 }
1027 out.resize(size, 0);
1028 return Ok(out);
1029 }
1030
1031 let mut out = Vec::with_capacity(size);
1032 for idx in 0..size {
1033 if offset + idx >= self.len() {
1034 out.push(0);
1035 continue;
1036 }
1037 match self.bytes.byte(cx, offset + idx).as_const() {
1038 Some(value) => out.push(value.to::<u8>()),
1039 None => return Err(SymbolicError::Unsupported(reason)),
1040 }
1041 }
1042 Ok(out)
1043 }
1044
1045 pub(crate) fn read_byte_exprs(
1046 &self,
1047 cx: &mut SymCx,
1048 offset: usize,
1049 size: usize,
1050 ) -> Vec<SymExpr> {
1051 self.read_bytes(cx, offset, size).materialize(cx)
1052 }
1053
1054 pub(crate) fn read_byte_exprs_offset(
1055 &self,
1056 cx: &mut SymCx,
1057 offset: SymExpr,
1058 size: usize,
1059 ) -> Vec<SymExpr> {
1060 self.read_bytes_offset(cx, offset, size).materialize(cx)
1061 }
1062
1063 pub(crate) fn read_bytes(&self, cx: &mut SymCx, offset: usize, size: usize) -> SymBytes {
1064 self.bytes.slice_concrete(cx, offset, size)
1065 }
1066
1067 pub(crate) fn read_bytes_offset(
1068 &self,
1069 cx: &mut SymCx,
1070 offset: SymExpr,
1071 size: usize,
1072 ) -> SymBytes {
1073 self.bytes.read_offset(cx, offset, size)
1074 }
1075
1076 pub(crate) fn push_data_word(&self, cx: &mut SymCx, offset: usize, len: usize) -> SymExpr {
1077 self.bytes.right_aligned_word(cx, offset, len)
1078 }
1079
1080 pub(crate) fn concrete_bytes(
1081 &self,
1082 cx: &mut SymCx,
1083 reason: &'static str,
1084 ) -> Result<Vec<u8>, SymbolicError> {
1085 self.concrete_range(cx, 0, self.len(), reason)
1086 }
1087}
1088
1089#[derive(Clone, Debug)]
1090pub(crate) struct SymReturnData {
1091 pub(crate) len_word: SymExpr,
1092 pub(crate) bytes: SymBytes,
1093}
1094
1095impl SymReturnData {
1096 pub(crate) fn empty(cx: &mut SymCx) -> Self {
1097 Self { len_word: SymExpr::zero(cx), bytes: SymBytes::empty(cx) }
1098 }
1099
1100 pub(crate) fn from_words(cx: &mut SymCx, words: Vec<SymExpr>) -> Self {
1101 let bytes = words.into_iter().map(|word| word.into_bytes(cx)).collect::<Vec<_>>();
1102 let bytes = SymBytes::concat(cx, bytes);
1103 Self::from_bytes(cx, bytes)
1104 }
1105
1106 pub(crate) fn from_concrete_bytes(cx: &mut SymCx, bytes: Vec<u8>) -> Self {
1107 let bytes = SymBytes::concrete(cx, bytes);
1108 Self::from_bytes(cx, bytes)
1109 }
1110
1111 pub(crate) fn from_byte_exprs(cx: &mut SymCx, bytes: Vec<SymExpr>) -> Self {
1112 let bytes = SymBytes::exprs(cx, bytes);
1113 Self::from_bytes(cx, bytes)
1114 }
1115
1116 pub(crate) fn from_bytes(cx: &mut SymCx, bytes: SymBytes) -> Self {
1117 let len = bytes.len();
1118 Self { len_word: SymExpr::constant(cx, U256::from(len)), bytes }
1119 }
1120
1121 pub(crate) fn len(&self) -> usize {
1122 self.bytes.len()
1123 }
1124
1125 pub(crate) fn byte(&self, cx: &mut SymCx, offset: usize) -> SymExpr {
1126 self.bytes.byte(cx, offset)
1127 }
1128
1129 pub(crate) fn read_bytes_offset(
1130 &self,
1131 cx: &mut SymCx,
1132 offset: SymExpr,
1133 size: usize,
1134 ) -> SymBytes {
1135 self.bytes.read_offset(cx, offset, size)
1136 }
1137
1138 pub(crate) fn read_concrete(
1139 &self,
1140 cx: &mut SymCx,
1141 reason: &'static str,
1142 ) -> Result<Vec<u8>, SymbolicError> {
1143 self.bytes.concrete_bytes(cx, reason)
1144 }
1145
1146 pub(crate) fn to_code(&self, cx: &mut SymCx) -> Result<SymCode, SymbolicError> {
1147 if self.len_word.as_const().is_none() {
1148 return Err(SymbolicError::Unsupported(
1149 "CREATE with symbolic runtime size not modeled",
1150 ));
1151 }
1152 Ok(SymCode::from_bytes(cx, self.bytes.clone()))
1153 }
1154}