Skip to main content

foundry_evm_symbolic/runtime/
memory.rs

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    /// Proven lower bound for the start of a guarded, non-wrapping write.
56    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            // Share the byte-read path so symbolic writes are included.
257            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    /// Reads the byte at `offset + delta`.
491    ///
492    /// Symbolic stores need not extend `materialized_size`, even when their offsets
493    /// are const-evaluable. Enumerate that region only if it contains every write;
494    /// otherwise fold all writes in insertion order so later writes win.
495    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}