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