1use super::*;
2
3#[derive(Clone, Debug, Default)]
4pub(crate) struct SymStack(Vec<SymWord>);
5
6impl SymStack {
7 pub(crate) fn push(&mut self, value: SymWord) -> Result<(), SymbolicError> {
9 if self.0.len() >= EVM_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<SymWord, SymbolicError> {
18 self.0.pop().ok_or(SymbolicError::StackUnderflow)
19 }
20
21 pub(crate) fn peek(&self, index_from_top: usize) -> Result<&SymWord, SymbolicError> {
23 self.0
24 .get(
25 self.0
26 .len()
27 .checked_sub(index_from_top + 1)
28 .ok_or(SymbolicError::StackUnderflow)?,
29 )
30 .ok_or(SymbolicError::StackUnderflow)
31 }
32
33 pub(crate) fn swap(&mut self, index_from_top: usize) -> Result<(), SymbolicError> {
35 let len = self.0.len();
36 let other = len.checked_sub(index_from_top + 1).ok_or(SymbolicError::StackUnderflow)?;
37 self.0.swap(len - 1, other);
38 Ok(())
39 }
40}
41
42#[derive(Clone, Debug)]
43pub(crate) enum BoundedCopySize {
44 Concrete(usize),
45 Symbolic { size: SymWord, max_size: usize },
46}
47
48#[derive(Clone, Debug, Default)]
49pub(crate) struct SymMemory {
50 pub(crate) bytes: BTreeMap<usize, SymWord>,
51 pub(crate) byte_epochs: BTreeMap<usize, u64>,
52 pub(crate) symbolic_writes: Vec<SymbolicMemoryWrite>,
53 pub(crate) epoch: u64,
54 pub(crate) size: usize,
55}
56
57#[derive(Clone, Debug)]
58pub(crate) struct SymbolicMemoryWrite {
59 pub(crate) epoch: u64,
60 pub(crate) offset: Expr,
61 pub(crate) bytes: Vec<SymWord>,
62}
63
64pub(crate) fn memory_size_after_access(offset: usize, len: usize) -> usize {
66 let Some(end) = offset.checked_add(len) else {
67 return usize::MAX & !31usize;
68 };
69 end.checked_add(31).map(|size| size & !31usize).unwrap_or(usize::MAX & !31usize)
70}
71
72pub(crate) fn memory_size_after_symbolic_access(offset: Expr, len: U256) -> Expr {
74 let end = Expr::op(ExprOp::Add, offset, Expr::Const(len));
75 Expr::op(
76 ExprOp::And,
77 Expr::op(ExprOp::Add, end, Expr::Const(U256::from(31))),
78 Expr::Const(!U256::from(31)),
79 )
80}
81
82pub(crate) fn max_u256_expr(left: Expr, right: Expr) -> Expr {
84 match (&left, &right) {
85 (Expr::Const(left), Expr::Const(right)) => Expr::Const((*left).max(*right)),
86 _ if left == right => left,
87 _ => Expr::Ite(
88 Box::new(BoolExpr::cmp(BoolExprOp::Ult, left.clone(), right.clone())),
89 Box::new(right),
90 Box::new(left),
91 ),
92 }
93}
94
95impl SymMemory {
96 pub(crate) fn store_word(&mut self, offset: usize, value: SymWord) {
98 self.store_bytes(offset, word_bytes(value));
99 }
100
101 pub(crate) fn store_word_offset(&mut self, offset: SymWord, value: SymWord) {
103 match offset {
104 SymWord::Concrete(offset) if offset <= U256::from(usize::MAX) => {
105 self.store_word(offset.to::<usize>(), value);
106 }
107 SymWord::Concrete(_) => {}
108 SymWord::Expr(offset) => self.store_symbolic_bytes(offset, word_bytes(value)),
109 }
110 }
111
112 pub(crate) fn store_byte(&mut self, offset: usize, value: SymWord) {
114 self.store_bytes(offset, vec![low_byte(value)]);
115 }
116
117 pub(crate) fn store_byte_offset(&mut self, offset: SymWord, value: SymWord) {
119 match offset {
120 SymWord::Concrete(offset) if offset <= U256::from(usize::MAX) => {
121 self.store_byte(offset.to::<usize>(), value);
122 }
123 SymWord::Concrete(_) => {}
124 SymWord::Expr(offset) => self.store_symbolic_bytes(offset, vec![low_byte(value)]),
125 }
126 }
127
128 pub(crate) fn store_bytes(&mut self, offset: usize, bytes: Vec<SymWord>) {
130 if bytes.is_empty() {
131 return;
132 }
133 self.epoch = self.epoch.saturating_add(1);
134 self.size = self.size.max(memory_size_after_access(offset, bytes.len()));
135 for (idx, byte) in bytes.into_iter().enumerate() {
136 let offset = offset + idx;
137 self.bytes.insert(offset, byte);
138 self.byte_epochs.insert(offset, self.epoch);
139 }
140 }
141
142 pub(crate) fn store_symbolic_bytes(&mut self, offset: Expr, bytes: Vec<SymWord>) {
144 if bytes.is_empty() {
145 return;
146 }
147 self.epoch = self.epoch.saturating_add(1);
148 self.symbolic_writes.push(SymbolicMemoryWrite { epoch: self.epoch, offset, bytes });
149 }
150
151 pub(crate) fn store_bytes_offset(&mut self, offset: SymWord, bytes: Vec<SymWord>) {
153 match offset {
154 SymWord::Concrete(offset) if offset <= U256::from(usize::MAX) => {
155 self.store_bytes(offset.to::<usize>(), bytes);
156 }
157 SymWord::Concrete(_) => {}
158 SymWord::Expr(offset) => self.store_symbolic_bytes(offset, bytes),
159 }
160 }
161
162 pub(crate) fn load_word(&self, offset: usize) -> Result<SymWord, SymbolicError> {
164 Ok(word_from_bytes((0..32).map(|idx| self.byte(offset + idx))))
165 }
166
167 pub(crate) fn load_word_offset(&self, offset: SymWord) -> Result<SymWord, SymbolicError> {
169 match offset {
170 SymWord::Concrete(offset) => {
171 if offset > U256::from(usize::MAX) {
172 return Ok(SymWord::zero());
173 }
174 self.load_word(offset.to::<usize>())
175 }
176 SymWord::Expr(offset) => {
177 Ok(word_from_bytes(self.read_bytes_offset(SymWord::Expr(offset), 32)))
178 }
179 }
180 }
181
182 pub(crate) fn read_concrete(
184 &self,
185 offset: usize,
186 size: usize,
187 ) -> Result<Vec<u8>, SymbolicError> {
188 let mut out = vec![0u8; size];
189 for (idx, byte) in out.iter_mut().enumerate() {
190 match self.byte(offset + idx) {
191 SymWord::Concrete(value) => *byte = value.to::<u8>(),
192 SymWord::Expr(_) => {
193 return Err(SymbolicError::Unsupported("symbolic memory read"));
194 }
195 }
196 }
197 Ok(out)
198 }
199
200 pub(crate) fn read_bytes(&self, offset: usize, size: usize) -> Vec<SymWord> {
202 (0..size).map(|idx| self.byte(offset + idx)).collect()
203 }
204
205 pub(crate) fn read_bytes_offset(&self, offset: SymWord, size: usize) -> Vec<SymWord> {
207 match offset {
208 SymWord::Concrete(offset) => {
209 if offset > U256::from(usize::MAX) {
210 return vec![SymWord::zero(); size];
211 }
212 self.read_bytes(offset.to::<usize>(), size)
213 }
214 SymWord::Expr(offset) => {
215 (0..size).map(|idx| self.byte_dynamic_with_delta(offset.clone(), idx)).collect()
216 }
217 }
218 }
219
220 pub(crate) fn read_bytes_symbolic_size(
222 &self,
223 offset: SymWord,
224 size: SymWord,
225 max_size: usize,
226 ) -> Vec<SymWord> {
227 let size = size.into_expr();
228 self.read_bytes_offset(offset, max_size)
229 .into_iter()
230 .enumerate()
231 .map(|(idx, source)| {
232 SymWord::Expr(Expr::Ite(
233 Box::new(BoolExpr::cmp(
234 BoolExprOp::Ult,
235 Expr::Const(U256::from(idx)),
236 size.clone(),
237 )),
238 Box::new(source.into_expr()),
239 Box::new(Expr::Const(U256::ZERO)),
240 ))
241 })
242 .collect()
243 }
244
245 pub(crate) fn byte(&self, offset: usize) -> SymWord {
247 let (base, base_epoch) = self.base_byte(offset);
248 let mut result = base.clone().into_expr();
249 let mut has_symbolic_match = false;
250 for write in self.symbolic_writes.iter().filter(|write| write.epoch > base_epoch) {
251 for (idx, byte) in write.bytes.iter().enumerate() {
252 has_symbolic_match = true;
253 result = Expr::Ite(
254 Box::new(BoolExpr::eq(
255 Expr::op(ExprOp::Add, write.offset.clone(), Expr::Const(U256::from(idx))),
256 Expr::Const(U256::from(offset)),
257 )),
258 Box::new(byte.clone().into_expr()),
259 Box::new(result),
260 );
261 }
262 }
263 if has_symbolic_match { SymWord::Expr(result) } else { base }
264 }
265
266 pub(crate) fn base_byte(&self, offset: usize) -> (SymWord, u64) {
268 (
269 self.bytes.get(&offset).cloned().unwrap_or_else(SymWord::zero),
270 self.byte_epochs.get(&offset).copied().unwrap_or_default(),
271 )
272 }
273
274 pub(crate) fn byte_dynamic_with_delta(&self, offset: Expr, delta: usize) -> SymWord {
276 let mut result = Expr::Const(U256::ZERO);
277 for candidate in (delta..self.size).rev() {
278 let (byte, base_epoch) = self.base_byte(candidate);
279 let mut candidate_result = byte.into_expr();
280 for write in self.symbolic_writes.iter().filter(|write| write.epoch > base_epoch) {
281 for (idx, byte) in write.bytes.iter().enumerate() {
282 candidate_result = Expr::Ite(
283 Box::new(BoolExpr::eq(
284 Expr::op(
285 ExprOp::Add,
286 write.offset.clone(),
287 Expr::Const(U256::from(idx)),
288 ),
289 Expr::Const(U256::from(candidate)),
290 )),
291 Box::new(byte.clone().into_expr()),
292 Box::new(candidate_result),
293 );
294 }
295 }
296 result = Expr::Ite(
297 Box::new(BoolExpr::eq(offset.clone(), Expr::Const(U256::from(candidate - delta)))),
298 Box::new(candidate_result),
299 Box::new(result),
300 );
301 }
302 SymWord::Expr(result)
303 }
304
305 pub(crate) fn size_word(&self) -> SymWord {
307 let mut size = Expr::Const(U256::from(self.size));
308 for write in &self.symbolic_writes {
309 let write_size = memory_size_after_symbolic_access(
310 write.offset.clone(),
311 U256::from(write.bytes.len()),
312 );
313 size = max_u256_expr(size, write_size);
314 }
315 match size {
316 Expr::Const(value) => SymWord::Concrete(value),
317 size => SymWord::Expr(size),
318 }
319 }
320
321 #[cfg(test)]
322 pub(crate) fn copy_symbolic(&mut self, dest: usize, src: Vec<SymWord>) {
324 self.store_bytes(dest, src);
325 }
326
327 pub(crate) fn copy_symbolic_offset(&mut self, dest: SymWord, src: Vec<SymWord>) {
329 self.store_bytes_offset(dest, src);
330 }
331
332 #[cfg(test)]
333 pub(crate) fn copy_symbolic_size(&mut self, dest: usize, size: SymWord, src: Vec<SymWord>) {
335 self.copy_symbolic_size_offset(SymWord::Concrete(U256::from(dest)), size, src)
336 .expect("concrete symbolic-size memory copy cannot fail");
337 }
338
339 pub(crate) fn copy_symbolic_size_offset(
341 &mut self,
342 dest: SymWord,
343 size: SymWord,
344 src: Vec<SymWord>,
345 ) -> Result<(), SymbolicError> {
346 if src.is_empty() {
347 return Ok(());
348 }
349 let size = size.into_expr();
350 match dest {
351 SymWord::Concrete(dest) if dest <= U256::from(usize::MAX) => {
352 let dest = dest.to::<usize>();
353 let bytes = src
354 .into_iter()
355 .enumerate()
356 .map(|(idx, source)| {
357 self.symbolic_copy_size_byte(dest + idx, idx, &size, source)
358 })
359 .collect();
360 self.store_bytes(dest, bytes);
361 }
362 SymWord::Concrete(_) => {}
363 SymWord::Expr(dest) => {
364 let bytes = src
365 .into_iter()
366 .enumerate()
367 .map(|(idx, source)| {
368 let existing = self.byte_dynamic_with_delta(dest.clone(), idx);
369 symbolic_copy_size_byte(idx, &size, source, existing)
370 })
371 .collect();
372 self.store_symbolic_bytes(dest, bytes);
373 }
374 }
375 Ok(())
376 }
377
378 #[cfg(test)]
379 pub(crate) fn copy_calldata(
381 &mut self,
382 dest: usize,
383 offset: usize,
384 size: usize,
385 calldata: &SymCalldata,
386 ) -> Result<(), SymbolicError> {
387 self.store_bytes(dest, (0..size).map(|idx| calldata.byte(offset + idx)).collect());
388 Ok(())
389 }
390
391 #[cfg(test)]
392 pub(crate) fn copy_calldata_offset(
394 &mut self,
395 dest: usize,
396 offset: SymWord,
397 size: usize,
398 calldata: &SymCalldata,
399 ) -> Result<(), SymbolicError> {
400 self.copy_calldata_to_offset(SymWord::Concrete(U256::from(dest)), offset, size, calldata)
401 }
402
403 pub(crate) fn copy_calldata_to_offset(
405 &mut self,
406 dest: SymWord,
407 offset: SymWord,
408 size: usize,
409 calldata: &SymCalldata,
410 ) -> Result<(), SymbolicError> {
411 match offset {
412 SymWord::Concrete(offset) => {
413 if offset > U256::from(usize::MAX) {
414 self.copy_symbolic_offset(dest, vec![SymWord::zero(); size]);
415 return Ok(());
416 }
417 self.store_bytes_offset(
418 dest,
419 (0..size).map(|idx| calldata.byte(offset.to::<usize>() + idx)).collect(),
420 );
421 Ok(())
422 }
423 SymWord::Expr(offset) => {
424 self.store_bytes_offset(
425 dest,
426 (0..size)
427 .map(|idx| calldata.byte_dynamic_with_delta(offset.clone(), idx))
428 .collect(),
429 );
430 Ok(())
431 }
432 }
433 }
434
435 pub(crate) fn copy_calldata_symbolic_size(
437 &mut self,
438 dest: SymWord,
439 offset: SymWord,
440 size: SymWord,
441 max_size: usize,
442 calldata: &SymCalldata,
443 ) -> Result<(), SymbolicError> {
444 let bytes = match offset {
445 SymWord::Concrete(offset) => {
446 let offset =
447 if offset > U256::from(usize::MAX) { None } else { Some(offset.to::<usize>()) };
448 (0..max_size)
449 .map(|idx| {
450 offset
451 .map(|offset| calldata.byte(offset + idx))
452 .unwrap_or_else(SymWord::zero)
453 })
454 .collect()
455 }
456 SymWord::Expr(offset) => (0..max_size)
457 .map(|idx| calldata.byte_dynamic_with_delta(offset.clone(), idx))
458 .collect(),
459 };
460 self.copy_symbolic_size_offset(dest, size, bytes)
461 }
462
463 pub(crate) fn symbolic_copy_size_byte(
465 &self,
466 dest: usize,
467 idx: usize,
468 size: &Expr,
469 source: SymWord,
470 ) -> SymWord {
471 let existing = self.byte(dest);
472 symbolic_copy_size_byte(idx, size, source, existing)
473 }
474
475 pub(crate) fn copy_return_data_to_offset(
477 &mut self,
478 dest: SymWord,
479 offset: SymWord,
480 size: usize,
481 return_data: &SymReturnData,
482 ) -> Result<(), SymbolicError> {
483 if size == 0 {
484 return Ok(());
485 }
486 if let SymWord::Concrete(offset) = &offset {
487 if *offset > U256::from(usize::MAX) {
488 return Err(SymbolicError::Unsupported("out-of-bounds symbolic RETURNDATACOPY"));
489 }
490 if offset.to::<usize>().saturating_add(size) > return_data.len {
491 return Err(SymbolicError::Unsupported("out-of-bounds symbolic RETURNDATACOPY"));
492 }
493 }
494 self.store_bytes_offset(dest, return_data.read_bytes_offset(offset, size));
495 Ok(())
496 }
497
498 pub(crate) fn copy_return_data_symbolic_size(
500 &mut self,
501 dest: SymWord,
502 offset: SymWord,
503 size: SymWord,
504 max_size: usize,
505 return_data: &SymReturnData,
506 ) -> Result<(), SymbolicError> {
507 if max_size == 0 {
508 return Ok(());
509 }
510 if let SymWord::Concrete(offset) = &offset {
511 if *offset > U256::from(usize::MAX) {
512 return Err(SymbolicError::Unsupported("out-of-bounds symbolic RETURNDATACOPY"));
513 }
514 if offset.to::<usize>().saturating_add(max_size) > return_data.len {
515 return Err(SymbolicError::Unsupported("out-of-bounds symbolic RETURNDATACOPY"));
516 }
517 }
518 let bytes = return_data.read_bytes_offset(offset, max_size);
519 self.copy_symbolic_size_offset(dest, size, bytes)
520 }
521
522 pub(crate) fn copy_call_output_offset(
524 &mut self,
525 dest: SymWord,
526 size: &BoundedCopySize,
527 return_data: &SymReturnData,
528 ) -> Result<(), SymbolicError> {
529 match size {
530 BoundedCopySize::Concrete(size) => {
531 let size = (*size).min(return_data.len);
532 if size != 0 {
533 if return_data.has_symbolic_len() {
534 let bytes = (0..size)
535 .map(|idx| self.call_output_byte(&dest, idx, None, return_data))
536 .collect();
537 self.store_bytes_offset(dest, bytes);
538 } else {
539 self.store_bytes_offset(
540 dest,
541 (0..size).map(|idx| return_data.byte(idx)).collect(),
542 );
543 }
544 }
545 }
546 BoundedCopySize::Symbolic { size, max_size } => {
547 let output_size = size.clone().into_expr();
548 let max_size = (*max_size).min(return_data.len);
549 if max_size != 0 {
550 let bytes = (0..max_size)
551 .map(|idx| {
552 self.call_output_byte(&dest, idx, Some(&output_size), return_data)
553 })
554 .collect();
555 self.store_bytes_offset(dest, bytes);
556 }
557 }
558 }
559 Ok(())
560 }
561
562 pub(crate) fn call_output_byte(
564 &self,
565 dest: &SymWord,
566 idx: usize,
567 output_size: Option<&Expr>,
568 return_data: &SymReturnData,
569 ) -> SymWord {
570 let mut guards = Vec::new();
571 if let Some(output_size) = output_size {
572 guards.push(BoolExpr::cmp(
573 BoolExprOp::Ult,
574 Expr::Const(U256::from(idx)),
575 output_size.clone(),
576 ));
577 }
578 if return_data.has_symbolic_len() {
579 guards.push(BoolExpr::cmp(
580 BoolExprOp::Ult,
581 Expr::Const(U256::from(idx)),
582 return_data.len_expr(),
583 ));
584 }
585 let guard = BoolExpr::and(guards);
586 match guard {
587 BoolExpr::Const(true) => return_data.byte(idx),
588 BoolExpr::Const(false) => self.call_output_existing_byte(dest, idx),
589 guard => SymWord::Expr(Expr::Ite(
590 Box::new(guard),
591 Box::new(return_data.byte(idx).into_expr()),
592 Box::new(self.call_output_existing_byte(dest, idx).into_expr()),
593 )),
594 }
595 }
596
597 pub(crate) fn call_output_existing_byte(&self, dest: &SymWord, idx: usize) -> SymWord {
599 match dest {
600 SymWord::Concrete(dest) if *dest <= U256::from(usize::MAX) => {
601 self.byte(dest.to::<usize>() + idx)
602 }
603 SymWord::Concrete(_) => SymWord::zero(),
604 SymWord::Expr(dest) => self.byte_dynamic_with_delta(dest.clone(), idx),
605 }
606 }
607
608 #[cfg(test)]
609 pub(crate) fn copy_memory_offset(
611 &mut self,
612 dest: usize,
613 src: SymWord,
614 size: usize,
615 ) -> Result<(), SymbolicError> {
616 self.copy_memory_to_offset(SymWord::Concrete(U256::from(dest)), src, size)
617 }
618
619 pub(crate) fn copy_memory_to_offset(
621 &mut self,
622 dest: SymWord,
623 src: SymWord,
624 size: usize,
625 ) -> Result<(), SymbolicError> {
626 if size == 0 {
627 return Ok(());
628 }
629 let bytes = self.read_bytes_offset(src, size);
630 self.store_bytes_offset(dest, bytes);
631 Ok(())
632 }
633
634 pub(crate) fn copy_memory_symbolic_size(
636 &mut self,
637 dest: SymWord,
638 src: SymWord,
639 size: SymWord,
640 max_size: usize,
641 ) -> Result<(), SymbolicError> {
642 if max_size == 0 {
643 return Ok(());
644 }
645 let source = self.read_bytes_offset(src, max_size);
646 self.copy_symbolic_size_offset(dest, size, source)
647 }
648
649 pub(crate) fn return_data(
651 &self,
652 offset: SymWord,
653 size: usize,
654 ) -> Result<SymReturnData, SymbolicError> {
655 Ok(SymReturnData::from_symbolic_bytes(self.read_bytes_offset(offset, size)))
656 }
657
658 pub(crate) fn return_data_symbolic_size(
660 &self,
661 offset: SymWord,
662 size: SymWord,
663 max_size: usize,
664 ) -> Result<SymReturnData, SymbolicError> {
665 Ok(SymReturnData::from_symbolic_bytes_with_len(
666 self.read_bytes_symbolic_size(offset, size.clone(), max_size),
667 size,
668 ))
669 }
670}
671
672pub(crate) fn symbolic_copy_size_byte(
674 idx: usize,
675 size: &Expr,
676 source: SymWord,
677 existing: SymWord,
678) -> SymWord {
679 SymWord::Expr(Expr::Ite(
680 Box::new(BoolExpr::cmp(BoolExprOp::Ult, Expr::Const(U256::from(idx)), size.clone())),
681 Box::new(source.into_expr()),
682 Box::new(existing.into_expr()),
683 ))
684}
685
686#[derive(Clone, Debug, Default, PartialEq, Eq)]
687pub(crate) struct SymCode {
688 pub(crate) bytes: Vec<SymWord>,
689}
690
691#[derive(Clone, Debug, PartialEq, Eq)]
692pub(crate) enum GuardedOpcode {
693 End,
694 Concrete(u8),
695 SymbolicSize { condition: BoolExpr, opcode: u8 },
696}
697
698impl SymCode {
699 pub(crate) fn concrete(bytes: Vec<u8>) -> Self {
701 Self { bytes: bytes.into_iter().map(|byte| SymWord::Concrete(U256::from(byte))).collect() }
702 }
703
704 pub(crate) fn from_memory_offset(memory: &SymMemory, offset: SymWord, size: usize) -> Self {
706 Self { bytes: memory.read_bytes_offset(offset, size) }
707 }
708
709 pub(crate) fn from_memory_symbolic_size(
711 memory: &SymMemory,
712 offset: SymWord,
713 size: SymWord,
714 max_size: usize,
715 ) -> Self {
716 Self { bytes: memory.read_bytes_symbolic_size(offset, size, max_size) }
717 }
718
719 pub(crate) const fn len(&self) -> usize {
721 self.bytes.len()
722 }
723
724 pub(crate) const fn is_empty(&self) -> bool {
726 self.bytes.is_empty()
727 }
728
729 pub(crate) fn opcode(&self, pc: usize) -> Result<Option<u8>, SymbolicError> {
731 self.bytes
732 .get(pc)
733 .map(|byte| match byte {
734 SymWord::Concrete(value) => Ok(value.to::<u8>()),
735 SymWord::Expr(_) => Err(SymbolicError::Unsupported("symbolic bytecode opcode")),
736 })
737 .transpose()
738 }
739
740 pub(crate) fn guarded_opcode(&self, pc: usize) -> Result<GuardedOpcode, SymbolicError> {
742 match self.bytes.get(pc) {
743 None => Ok(GuardedOpcode::End),
744 Some(SymWord::Concrete(value)) => Ok(GuardedOpcode::Concrete(value.to::<u8>())),
745 Some(SymWord::Expr(Expr::Ite(condition, then_expr, else_expr))) if matches!(else_expr.as_ref(), Expr::Const(value) if value.is_zero()) => {
746 match then_expr.as_ref() {
747 Expr::Const(value) if value.is_zero() => Ok(GuardedOpcode::Concrete(0)),
748 Expr::Const(value) => Ok(GuardedOpcode::SymbolicSize {
749 condition: (**condition).clone(),
750 opcode: value.to::<u8>(),
751 }),
752 _ => Err(SymbolicError::Unsupported("symbolic bytecode opcode")),
753 }
754 }
755 Some(SymWord::Expr(_)) => Err(SymbolicError::Unsupported("symbolic bytecode opcode")),
756 }
757 }
758
759 pub(crate) fn analysis_opcode(&self, pc: usize) -> Option<u8> {
761 self.bytes.get(pc).map(|byte| match byte {
762 SymWord::Concrete(value) => value.to::<u8>(),
763 SymWord::Expr(_) => opcode::STOP,
764 })
765 }
766
767 pub(crate) fn concrete_range(
769 &self,
770 offset: usize,
771 size: usize,
772 reason: &'static str,
773 ) -> Result<Vec<u8>, SymbolicError> {
774 let mut out = Vec::with_capacity(size);
775 for idx in 0..size {
776 match self.bytes.get(offset + idx) {
777 Some(SymWord::Concrete(value)) => out.push(value.to::<u8>()),
778 Some(SymWord::Expr(_)) => return Err(SymbolicError::Unsupported(reason)),
779 None => out.push(0),
780 }
781 }
782 Ok(out)
783 }
784
785 pub(crate) fn read_bytes(&self, offset: usize, size: usize) -> Vec<SymWord> {
787 (0..size)
788 .map(|idx| self.bytes.get(offset + idx).cloned().unwrap_or_else(SymWord::zero))
789 .collect()
790 }
791
792 pub(crate) fn read_bytes_offset(&self, offset: SymWord, size: usize) -> Vec<SymWord> {
794 match offset {
795 SymWord::Concrete(offset) => {
796 if offset > U256::from(usize::MAX) {
797 return vec![SymWord::zero(); size];
798 }
799 self.read_bytes(offset.to::<usize>(), size)
800 }
801 SymWord::Expr(offset) => {
802 (0..size).map(|idx| self.byte_dynamic_with_delta(offset.clone(), idx)).collect()
803 }
804 }
805 }
806
807 pub(crate) fn byte_dynamic_with_delta(&self, offset: Expr, delta: usize) -> SymWord {
809 let mut result = Expr::Const(U256::ZERO);
810 for candidate in (delta..self.len()).rev() {
811 result = Expr::Ite(
812 Box::new(BoolExpr::eq(offset.clone(), Expr::Const(U256::from(candidate - delta)))),
813 Box::new(self.bytes[candidate].clone().into_expr()),
814 Box::new(result),
815 );
816 }
817 SymWord::Expr(result)
818 }
819
820 pub(crate) fn concrete_bytes(&self, reason: &'static str) -> Result<Vec<u8>, SymbolicError> {
822 self.concrete_range(0, self.len(), reason)
823 }
824}
825
826#[derive(Clone, Debug)]
827pub(crate) struct SymReturnData {
828 pub(crate) len: usize,
829 pub(crate) len_word: SymWord,
830 pub(crate) bytes: BTreeMap<usize, SymWord>,
831}
832
833impl Default for SymReturnData {
834 fn default() -> Self {
836 Self { len: 0, len_word: SymWord::zero(), bytes: BTreeMap::new() }
837 }
838}
839
840impl SymReturnData {
841 pub(crate) fn from_words(words: Vec<SymWord>) -> Self {
843 let bytes = words.into_iter().flat_map(word_bytes).collect::<Vec<_>>();
844 Self::from_symbolic_bytes(bytes)
845 }
846
847 pub(crate) fn from_concrete_bytes(bytes: Vec<u8>) -> Self {
849 Self::from_symbolic_bytes(
850 bytes.into_iter().map(|byte| SymWord::Concrete(U256::from(byte))).collect(),
851 )
852 }
853
854 pub(crate) fn from_symbolic_bytes(bytes: Vec<SymWord>) -> Self {
856 let len = bytes.len();
857 Self {
858 len,
859 len_word: SymWord::Concrete(U256::from(len)),
860 bytes: bytes.into_iter().enumerate().collect(),
861 }
862 }
863
864 pub(crate) fn from_symbolic_bytes_with_len(bytes: Vec<SymWord>, len_word: SymWord) -> Self {
866 let len = bytes.len();
867 Self { len, len_word, bytes: bytes.into_iter().enumerate().collect() }
868 }
869
870 pub(crate) fn len_word(&self) -> SymWord {
872 self.len_word.clone()
873 }
874
875 pub(crate) fn len_expr(&self) -> Expr {
877 self.len_word.clone().into_expr()
878 }
879
880 pub(crate) const fn has_symbolic_len(&self) -> bool {
882 matches!(self.len_word, SymWord::Expr(_))
883 }
884
885 pub(crate) fn byte(&self, offset: usize) -> SymWord {
887 self.bytes.get(&offset).cloned().unwrap_or_else(SymWord::zero)
888 }
889
890 pub(crate) fn read_bytes_offset(&self, offset: SymWord, size: usize) -> Vec<SymWord> {
892 match offset {
893 SymWord::Concrete(offset) => {
894 if offset > U256::from(usize::MAX) {
895 return vec![SymWord::zero(); size];
896 }
897 let offset = offset.to::<usize>();
898 (0..size).map(|idx| self.byte(offset + idx)).collect()
899 }
900 SymWord::Expr(offset) => {
901 (0..size).map(|idx| self.byte_dynamic_with_delta(offset.clone(), idx)).collect()
902 }
903 }
904 }
905
906 pub(crate) fn byte_dynamic_with_delta(&self, offset: Expr, delta: usize) -> SymWord {
908 let mut result = Expr::Const(U256::ZERO);
909 for candidate in (delta..self.len).rev() {
910 result = Expr::Ite(
911 Box::new(BoolExpr::eq(offset.clone(), Expr::Const(U256::from(candidate - delta)))),
912 Box::new(self.byte(candidate).into_expr()),
913 Box::new(result),
914 );
915 }
916 SymWord::Expr(result)
917 }
918
919 pub(crate) fn load_word(&self, offset: usize) -> Result<SymWord, SymbolicError> {
921 if offset.saturating_add(32) > self.len {
922 return Err(SymbolicError::Unsupported("out-of-bounds symbolic returndata word"));
923 }
924 Ok(word_from_bytes((0..32).map(|idx| self.byte(offset + idx))))
925 }
926
927 pub(crate) fn read_concrete(&self, reason: &'static str) -> Result<Vec<u8>, SymbolicError> {
929 let mut out = Vec::with_capacity(self.len);
930 for offset in 0..self.len {
931 match self.byte(offset) {
932 SymWord::Concrete(value) => out.push(value.to::<u8>()),
933 SymWord::Expr(_) => return Err(SymbolicError::Unsupported(reason)),
934 }
935 }
936 Ok(out)
937 }
938
939 pub(crate) fn to_code(&self) -> Result<SymCode, SymbolicError> {
941 if self.has_symbolic_len() {
942 return Err(SymbolicError::Unsupported(
943 "CREATE with symbolic runtime size not modeled",
944 ));
945 }
946 Ok(SymCode { bytes: (0..self.len).map(|offset| self.byte(offset)).collect() })
947 }
948}