Skip to main content

soldb_core/
table.rs

1//! The steps of a trace, held in columns.
2//!
3//! A trace runs to millions of steps, and a step holding its own stack, maps and options
4//! made building, walking and dropping one a matter of allocation and copying. A
5//! [`StepTable`] keeps each field of every step in a column of plain values instead: the
6//! program counter, the opcode byte, the depth, the gas, and a tape of stack words in
7//! which a step stores only the words above the ones it kept from the step before, with
8//! the whole stack every [`STACK_CHECKPOINT`] steps. What only some steps have, an error,
9//! a storage diff, transient storage, is kept for those steps alone, and what changes
10//! rarely, storage and return data, once per change. Memory is kept by
11//! [`crate::memory`].
12//!
13//! Readers get a [`StepView`] by index; values are formatted only when shown. A trace
14//! file is unchanged: a table writes each step in the shape [`TraceStep`] always had.
15
16use std::collections::BTreeMap;
17use std::fmt;
18use std::sync::Arc;
19
20use bytes::Bytes;
21use serde::de::{self, SeqAccess, Visitor};
22use serde::ser::{SerializeSeq, SerializeStruct};
23use serde::{Deserialize, Deserializer, Serialize, Serializer};
24use thiserror::Error;
25
26use crate::memory::{MemoryRef, MemoryStore};
27use crate::{
28    encode_hex, opcode, EvmWord, Hex, MemoryWriteError, StepSnapshot, StorageChange, TraceStep,
29    Word, WordError,
30};
31
32/// Every this many steps, a step stores its whole stack, so that reading a word never
33/// walks back further.
34pub const STACK_CHECKPOINT: usize = 32;
35
36const CHUNK_BITS: u32 = 14;
37const CHUNK: usize = 1 << CHUNK_BITS;
38
39/// A column of values, grown a fixed-size chunk at a time so that growing never moves
40/// what is already in it. The chunk being filled is held apart from the full ones, so
41/// appending is one vector push; it grows like any vector until it is full, which keeps
42/// a short trace small.
43#[derive(Debug, Clone)]
44struct Column<T> {
45    /// The full chunks, of `CHUNK` values each.
46    full: Vec<Box<[T]>>,
47    /// The values after the full chunks, at most `CHUNK` of them.
48    tail: Vec<T>,
49}
50
51impl<T> Default for Column<T> {
52    fn default() -> Self {
53        Self {
54            full: Vec::new(),
55            tail: Vec::new(),
56        }
57    }
58}
59
60impl<T: Copy> Column<T> {
61    fn len(&self) -> usize {
62        self.full.len() * CHUNK + self.tail.len()
63    }
64
65    fn push(&mut self, value: T) {
66        if self.tail.len() == CHUNK {
67            let full = std::mem::replace(&mut self.tail, Vec::with_capacity(CHUNK));
68            self.full.push(full.into_boxed_slice());
69        }
70        self.tail.push(value);
71    }
72
73    fn get(&self, index: usize) -> &T {
74        let chunk = index >> CHUNK_BITS;
75        match self.full.get(chunk) {
76            Some(full) => &full[index & (CHUNK - 1)],
77            None => &self.tail[index - self.full.len() * CHUNK],
78        }
79    }
80
81    fn get_mut(&mut self, index: usize) -> &mut T {
82        let chunk = index >> CHUNK_BITS;
83        let full = self.full.len();
84        match self.full.get_mut(chunk) {
85            Some(full) => &mut full[index & (CHUNK - 1)],
86            None => &mut self.tail[index - full * CHUNK],
87        }
88    }
89
90    fn last(&self) -> Option<&T> {
91        self.tail.last().or_else(|| self.full.last()?.last())
92    }
93
94    /// Makes room for `chunks` chunks, the first of `first` values.
95    fn reserve(&mut self, chunks: usize, first: usize) {
96        self.full.reserve(chunks);
97        if self.full.is_empty() && self.tail.is_empty() {
98            self.tail.reserve(first.min(CHUNK));
99        }
100    }
101
102    fn truncate(&mut self, len: usize) {
103        if len >= self.len() {
104            return;
105        }
106        let whole = len / CHUNK;
107        if whole < self.full.len() {
108            // The chunk `len` falls in is the one filled from now on.
109            self.full.truncate(whole + 1);
110            self.tail = self.full.pop().map(Vec::from).unwrap_or_default();
111        }
112        self.tail.truncate(len - whole * CHUNK);
113    }
114}
115
116/// The fixed-size fields of one step, kept together: a reader of a step reads several of
117/// them, and appending a step is then one push.
118#[derive(Debug, Clone, Copy)]
119struct Row {
120    gas: u64,
121    gas_cost: u64,
122    /// Where the step's words above the kept ones start on the tape.
123    stack_start: u64,
124    memory: MemoryRef,
125    /// The program counter, or `u32::MAX` for one in `wide_pcs`.
126    pc: u32,
127    stack_len: u32,
128    /// How many bottom words of the step's stack are the previous step's; the rest are on
129    /// the tape from `stack_start`.
130    stack_kept: u32,
131    /// The depth, or `u16::MAX` for one in `wide_depths`.
132    depth: u16,
133    op: u8,
134    /// Whether the step is in `errors`, so a step without one does not look.
135    failed: bool,
136}
137
138/// One step as a producer appends it with [`StepTable::append`]: its plain fields, and
139/// the state that changed since the step before.
140#[derive(Debug, Clone, Default, PartialEq, Eq)]
141pub struct StepRecord {
142    pub pc: u64,
143    pub op: u8,
144    pub gas: u64,
145    /// The cost as the tracer reports it; see [`TraceStep::gas_cost`].
146    pub gas_cost: u64,
147    pub depth: u64,
148    pub error: Option<String>,
149    /// The memory the step starts with, in full. `None` leaves it to the frame being
150    /// recorded with [`StepTable::write_memory`], or keeps the previous step's when none
151    /// is.
152    pub memory: Option<Bytes>,
153    /// The storage the backend reports for the step; `None` keeps the previous step's.
154    pub storage: Option<Arc<BTreeMap<String, String>>>,
155    /// The slots the step changed.
156    pub storage_diff: Option<BTreeMap<String, StorageChange>>,
157    /// The transient slots the step read or wrote, with their value after it.
158    pub transient_storage: Option<BTreeMap<String, String>>,
159    /// The return data buffer the step starts with, as unprefixed hex; empty for an empty
160    /// buffer, and `None` keeps the previous step's.
161    pub return_data: Option<Arc<str>>,
162}
163
164/// Why a step cannot be added to a [`StepTable`].
165#[derive(Debug, Clone, PartialEq, Eq, Error)]
166pub enum StepError {
167    #[error("step {step}: stack word {position}: {error}")]
168    StackWord {
169        step: usize,
170        position: usize,
171        error: WordError,
172    },
173}
174
175/// The steps of a trace, in columns. See the module docs.
176#[derive(Clone, Default)]
177pub struct StepTable {
178    rows: Column<Row>,
179    words: Column<EvmWord>,
180    /// The steps whose backend spelled their mnemonic other than [`opcode::mnemonic`]
181    /// does, ascending, with that spelling.
182    mnemonics: Vec<(usize, Word)>,
183    errors: Vec<(usize, Box<str>)>,
184    /// The program counters and depths too wide for their columns, which no EVM trace has
185    /// but nothing forbids.
186    wide_pcs: Vec<(usize, u64)>,
187    wide_depths: Vec<(usize, u64)>,
188    /// Storage as of each step that changed it, ascending; empty before the first.
189    storage: Vec<(usize, Arc<BTreeMap<String, String>>)>,
190    storage_diffs: Vec<(usize, BTreeMap<String, StorageChange>)>,
191    transient_storage: Vec<(usize, BTreeMap<String, String>)>,
192    /// Return data as of each step that changed it, ascending; none before the first.
193    return_data: Vec<(usize, Option<Arc<str>>)>,
194    memories: MemoryStore,
195    /// The stack of the step appended last, which the next one is stored against.
196    last_stack: Vec<EvmWord>,
197    /// That stack as the backend spelled it, when the step was pushed as a [`TraceStep`]:
198    /// a word the next step shares by pointer, as an interning backend shares them, is the
199    /// same word and is not parsed again.
200    last_spelled: Vec<Word>,
201    /// The opcode byte of each mnemonic pushed recently, by the backend's shared string,
202    /// and whether the backend spells it as [`opcode::mnemonic`] does.
203    spellings: Vec<(Word, Option<u8>, bool)>,
204    /// A buffer for the stack of the step being pushed.
205    scratch: Vec<EvmWord>,
206}
207
208/// How many mnemonics [`StepTable`] remembers by pointer. Every opcode a trace runs fits;
209/// a backend that does not share its mnemonics only pays for the misses.
210const SPELLINGS: usize = 64;
211
212static EMPTY_STORAGE: BTreeMap<String, String> = BTreeMap::new();
213static EMPTY_STORAGE_DIFF: BTreeMap<String, StorageChange> = BTreeMap::new();
214
215impl StepTable {
216    #[must_use]
217    pub fn new() -> Self {
218        Self::default()
219    }
220
221    /// An empty table that holds `steps` steps without allocating its columns again.
222    #[must_use]
223    pub fn with_capacity(steps: usize) -> Self {
224        let mut table = Self::new();
225        table.rows.reserve(steps.div_ceil(CHUNK), steps);
226        table
227    }
228
229    /// A table of `steps`, in order.
230    ///
231    /// # Errors
232    ///
233    /// As [`StepTable::try_push`].
234    pub fn try_from_steps(steps: impl IntoIterator<Item = TraceStep>) -> Result<Self, StepError> {
235        let mut table = Self::new();
236        for step in steps {
237            table.try_push(step)?;
238        }
239        Ok(table)
240    }
241
242    #[must_use]
243    pub fn len(&self) -> usize {
244        self.rows.len()
245    }
246
247    #[must_use]
248    pub fn is_empty(&self) -> bool {
249        self.len() == 0
250    }
251
252    #[must_use]
253    pub fn get(&self, index: usize) -> Option<StepView<'_>> {
254        (index < self.len()).then_some(StepView { table: self, index })
255    }
256
257    /// The step at `index`.
258    ///
259    /// # Panics
260    ///
261    /// When `index` is not below [`StepTable::len`], as indexing a slice does.
262    #[must_use]
263    pub fn step(&self, index: usize) -> StepView<'_> {
264        assert!(
265            index < self.len(),
266            "step {index} of a trace of {} steps",
267            self.len()
268        );
269        StepView { table: self, index }
270    }
271
272    #[must_use]
273    pub fn first(&self) -> Option<StepView<'_>> {
274        self.get(0)
275    }
276
277    #[must_use]
278    pub fn last(&self) -> Option<StepView<'_>> {
279        self.len().checked_sub(1).and_then(|index| self.get(index))
280    }
281
282    #[must_use]
283    pub fn iter(&self) -> Steps<'_> {
284        Steps {
285            table: self,
286            front: 0,
287            back: self.len(),
288        }
289    }
290
291    /// Appends a step built the way every backend builds one, sharing what did not change
292    /// since the step before: its stack words, memory, storage and return data.
293    ///
294    /// # Panics
295    ///
296    /// When a stack word is not hex. A producer reading words from untrusted input, a node
297    /// or a file, appends with [`StepTable::try_push`] instead.
298    pub fn push(&mut self, step: TraceStep) {
299        if let Err(error) = self.try_push(step) {
300            panic!("a step built in this process holds a stack word that is not hex: {error}");
301        }
302    }
303
304    /// [`StepTable::push`] for a step whose stack words may not be hex.
305    ///
306    /// # Errors
307    ///
308    /// Returns a [`StepError`] when a stack word is not hex. Nothing is appended then.
309    pub fn try_push(&mut self, step: TraceStep) -> Result<(), StepError> {
310        let index = self.len();
311        let step = step.normalized();
312        let snapshot = step.snapshot;
313        let mut stack = std::mem::take(&mut self.scratch);
314        stack.clear();
315        let mut kept = 0;
316        for (position, word) in snapshot.stack.iter().enumerate() {
317            let shared = self
318                .last_spelled
319                .get(position)
320                .is_some_and(|before| Arc::ptr_eq(before, word));
321            let parsed = match self.last_stack.get(position) {
322                Some(before) if shared => *before,
323                _ => match EvmWord::parse(word) {
324                    Ok(parsed) => parsed,
325                    Err(error) => {
326                        self.scratch = stack;
327                        return Err(StepError::StackWord {
328                            step: index,
329                            position,
330                            error,
331                        });
332                    }
333                },
334            };
335            if kept == position && self.last_stack.get(position) == Some(&parsed) {
336                kept += 1;
337            }
338            stack.push(parsed);
339        }
340        let (op, canonical) = self.spelling(&step.op);
341        self.append_inner(
342            StepRecord {
343                pc: step.pc,
344                op: op.unwrap_or(opcode::INVALID),
345                gas: step.gas,
346                gas_cost: step.gas_cost,
347                depth: step.depth,
348                error: step.error,
349                memory: None,
350                storage: Some(snapshot.storage),
351                storage_diff: Some(snapshot.storage_diff),
352                transient_storage: Some(snapshot.transient_storage),
353                return_data: None,
354            },
355            kept,
356            stack[kept..].iter().copied(),
357        );
358        self.scratch = stack;
359        self.last_spelled = snapshot.stack;
360        self.change_return_data(index, snapshot.return_data);
361        if !canonical {
362            self.mnemonics.push((index, step.op));
363        }
364        // A step's memory is the one it carries, in full, or none.
365        let memory = match snapshot.memory {
366            Some(memory) => self.memories.snapshot(memory),
367            None => MemoryRef::NONE,
368        };
369        self.rows.get_mut(index).memory = memory;
370        Ok(())
371    }
372
373    /// The opcode byte a backend means by `spelled`, and whether it spells it as
374    /// [`opcode::mnemonic`] does.
375    fn spelling(&mut self, spelled: &Word) -> (Option<u8>, bool) {
376        if let Some((_, op, canonical)) = self
377            .spellings
378            .iter()
379            .find(|(known, _, _)| Arc::ptr_eq(known, spelled))
380        {
381            return (*op, *canonical);
382        }
383        let op = opcode::from_spelling(spelled);
384        let canonical = op.is_some_and(|op| opcode::mnemonic(op) == &**spelled);
385        if self.spellings.len() < SPELLINGS {
386            self.spellings.push((Arc::clone(spelled), op, canonical));
387        }
388        (op, canonical)
389    }
390
391    /// Appends a step whose whole stack is `stack`, bottom first. The words the previous
392    /// step's stack starts with are kept rather than stored again.
393    pub fn append(&mut self, record: StepRecord, stack: impl IntoIterator<Item = EvmWord>) {
394        // Compared word by word as they come, so the stack is never collected.
395        let mut words = stack.into_iter();
396        let mut kept = 0;
397        let mut first_new = None;
398        for word in words.by_ref() {
399            if self.last_stack.get(kept) == Some(&word) {
400                kept += 1;
401            } else {
402                first_new = Some(word);
403                break;
404            }
405        }
406        self.last_spelled.clear();
407        self.append_inner(record, kept, first_new.into_iter().chain(words));
408    }
409
410    /// Appends a step whose stack is the bottom `kept` words of the previous step's with
411    /// `words` on top, for a producer that knows how much of the stack an instruction left
412    /// alone and so never copies the rest.
413    ///
414    /// # Panics
415    ///
416    /// When `kept` is more than the previous step's stack holds.
417    pub fn append_kept(
418        &mut self,
419        record: StepRecord,
420        kept: usize,
421        words: impl IntoIterator<Item = EvmWord>,
422    ) {
423        self.last_spelled.clear();
424        self.append_inner(record, kept, words);
425    }
426
427    fn append_inner(
428        &mut self,
429        record: StepRecord,
430        kept: usize,
431        words: impl IntoIterator<Item = EvmWord>,
432    ) {
433        let index = self.len();
434        assert!(
435            kept <= self.last_stack.len(),
436            "step {index} keeps {kept} stack words, but the step before has {}",
437            self.last_stack.len()
438        );
439
440        self.last_stack.truncate(kept);
441        self.last_stack.extend(words);
442        let stored_from = if index.is_multiple_of(STACK_CHECKPOINT) {
443            0
444        } else {
445            kept
446        };
447        let stack_start = self.words.len() as u64;
448        for word in &self.last_stack[stored_from..] {
449            self.words.push(*word);
450        }
451        let pc = match u32::try_from(record.pc) {
452            Ok(pc) if pc != u32::MAX => pc,
453            _ => {
454                self.wide_pcs.push((index, record.pc));
455                u32::MAX
456            }
457        };
458        let depth = match u16::try_from(record.depth) {
459            Ok(depth) if depth != u16::MAX => depth,
460            _ => {
461                self.wide_depths.push((index, record.depth));
462                u16::MAX
463            }
464        };
465        let memory = match record.memory {
466            Some(memory) => self.memories.snapshot(memory),
467            None if self.memories.recording() => self.memories.current(),
468            None => self.rows.last().map_or(MemoryRef::NONE, |row| row.memory),
469        };
470        self.rows.push(Row {
471            gas: record.gas,
472            gas_cost: record.gas_cost,
473            stack_start,
474            memory,
475            pc,
476            // A stack holds at most 1024 words; a count past `u32::MAX` is not a stack.
477            stack_len: u32::try_from(self.last_stack.len()).unwrap_or(u32::MAX),
478            stack_kept: u32::try_from(stored_from).unwrap_or(u32::MAX),
479            depth,
480            op: record.op,
481            failed: record.error.is_some(),
482        });
483        if let Some(error) = record.error {
484            self.errors.push((index, error.into_boxed_str()));
485        }
486        if let Some(storage) = record.storage {
487            self.change_storage(index, storage);
488        }
489        if let Some(diff) = record.storage_diff.filter(|diff| !diff.is_empty()) {
490            self.storage_diffs.push((index, diff));
491        }
492        if let Some(slots) = record.transient_storage.filter(|slots| !slots.is_empty()) {
493            self.transient_storage.push((index, slots));
494        }
495        if let Some(return_data) = record.return_data {
496            self.change_return_data(index, Some(return_data));
497        }
498    }
499
500    /// Starts recording the memory of a new frame, empty, as a call or a creation starts
501    /// one: the steps appended from now on have it, as written with
502    /// [`StepTable::write_memory`].
503    pub fn start_memory_frame(&mut self) {
504        self.memories.start_frame();
505    }
506
507    /// Stops recording the innermost frame's memory, as its call returns; the steps
508    /// appended from now on have its caller's again.
509    pub fn end_memory_frame(&mut self) {
510        self.memories.end_frame();
511    }
512
513    /// Records that the step appended last wrote `data` at `offset` of its frame's
514    /// memory, which is `size` bytes long afterwards; a write without data records memory
515    /// growing. The steps appended after it see the write: a step's memory is the memory
516    /// it starts with.
517    ///
518    /// # Errors
519    ///
520    /// Returns a [`MemoryWriteError`] when the write ends past `size` or `size` is smaller
521    /// than the memory was. Nothing is recorded then.
522    pub fn write_memory(
523        &mut self,
524        offset: u64,
525        data: &[u8],
526        size: u64,
527    ) -> Result<(), MemoryWriteError> {
528        self.memories.write(offset, data, size)
529    }
530
531    /// Starts a run of `storage` at `index`, the step appended last, unless it is the
532    /// storage the step already has.
533    fn change_storage(&mut self, index: usize, storage: Arc<BTreeMap<String, String>>) {
534        let current = self.storage.last().map(|(_, storage)| storage);
535        let unchanged = match current {
536            Some(current) => Arc::ptr_eq(current, &storage) || **current == *storage,
537            None => storage.is_empty(),
538        };
539        if !unchanged {
540            set_sparse(&mut self.storage, index, storage);
541        }
542    }
543
544    /// Starts a run of `return_data` at `index`, the step appended last, unless it is the
545    /// return data the step already has.
546    fn change_return_data(&mut self, index: usize, return_data: Option<Arc<str>>) {
547        let return_data = return_data.filter(|data| !data.is_empty());
548        let current = self.return_data.last().and_then(|(_, data)| data.as_ref());
549        // Producers share one buffer between the steps that did not change it, so the
550        // pointers decide most comparisons without reading the text.
551        let unchanged = match (current, &return_data) {
552            (Some(current), Some(data)) => Arc::ptr_eq(current, data) || current == data,
553            (current, data) => current.is_none() && data.is_none(),
554        };
555        if !unchanged {
556            set_sparse(&mut self.return_data, index, return_data);
557        }
558    }
559
560    /// The steps that changed storage, with the slots they changed, in order.
561    pub fn storage_diffs(
562        &self,
563    ) -> impl DoubleEndedIterator<Item = (usize, &BTreeMap<String, StorageChange>)> {
564        self.storage_diffs
565            .iter()
566            .map(|(index, diff)| (*index, diff))
567    }
568
569    /// The steps that read or wrote transient storage, with the slots, in order.
570    pub fn transient_storage(
571        &self,
572    ) -> impl DoubleEndedIterator<Item = (usize, &BTreeMap<String, String>)> {
573        self.transient_storage
574            .iter()
575            .map(|(index, slots)| (*index, slots))
576    }
577
578    /// Drops the steps from `len` on. What was recorded of their memory stays, unused.
579    pub fn truncate(&mut self, len: usize) {
580        if len >= self.len() {
581            return;
582        }
583        let last_stack = len
584            .checked_sub(1)
585            .map(|last| self.step(last).stack().to_vec())
586            .unwrap_or_default();
587        let words = len.checked_sub(1).map_or(0, |last| {
588            let row = self.rows.get(last);
589            row.stack_start as usize + row.stack_len as usize - row.stack_kept as usize
590        });
591        self.rows.truncate(len);
592        self.words.truncate(words);
593        self.mnemonics.retain(|(index, _)| *index < len);
594        self.errors.retain(|(index, _)| *index < len);
595        self.wide_pcs.retain(|(index, _)| *index < len);
596        self.wide_depths.retain(|(index, _)| *index < len);
597        self.storage.retain(|(index, _)| *index < len);
598        self.storage_diffs.retain(|(index, _)| *index < len);
599        self.transient_storage.retain(|(index, _)| *index < len);
600        self.return_data.retain(|(index, _)| *index < len);
601        self.last_stack = last_stack;
602        self.last_spelled.clear();
603    }
604
605    /// Every step as a [`TraceStep`] of its own.
606    #[must_use]
607    pub fn to_steps(&self) -> Vec<TraceStep> {
608        self.iter().map(|step| step.to_trace_step()).collect()
609    }
610
611    /// The word at `position` from the bottom of the stack of step `index`, which must
612    /// be below the stack's length.
613    fn stack_word(&self, index: usize, position: usize) -> EvmWord {
614        let mut step = index;
615        loop {
616            let row = self.rows.get(step);
617            let kept = row.stack_kept as usize;
618            if position >= kept {
619                return *self.words.get(row.stack_start as usize + position - kept);
620            }
621            // A step that kept words is not a checkpoint, so it has a step before it, and
622            // that step's stack holds every word it kept.
623            step -= 1;
624        }
625    }
626}
627
628/// The value of a run list as of `index`.
629fn run_value<T>(runs: &[(usize, T)], index: usize) -> Option<&T> {
630    let after = runs.partition_point(|(start, _)| *start <= index);
631    after.checked_sub(1).map(|run| &runs[run].1)
632}
633
634/// The entry of a sparse list for exactly `index`.
635fn sparse_value<T>(entries: &[(usize, T)], index: usize) -> Option<&T> {
636    entries
637        .binary_search_by_key(&index, |(step, _)| *step)
638        .ok()
639        .map(|position| &entries[position].1)
640}
641
642/// Sets the entry for `index`, the last step, replacing one already there.
643fn set_sparse<T>(entries: &mut Vec<(usize, T)>, index: usize, value: T) {
644    match entries.last_mut() {
645        Some((last, current)) if *last == index => *current = value,
646        _ => entries.push((index, value)),
647    }
648}
649
650impl From<Vec<TraceStep>> for StepTable {
651    /// # Panics
652    ///
653    /// As [`StepTable::push`].
654    fn from(steps: Vec<TraceStep>) -> Self {
655        steps.into_iter().collect()
656    }
657}
658
659impl FromIterator<TraceStep> for StepTable {
660    /// # Panics
661    ///
662    /// As [`StepTable::push`].
663    fn from_iter<I: IntoIterator<Item = TraceStep>>(steps: I) -> Self {
664        let mut table = Self::new();
665        table.extend(steps);
666        table
667    }
668}
669
670impl Extend<TraceStep> for StepTable {
671    /// # Panics
672    ///
673    /// As [`StepTable::push`].
674    fn extend<I: IntoIterator<Item = TraceStep>>(&mut self, steps: I) {
675        for step in steps {
676            self.push(step);
677        }
678    }
679}
680
681impl PartialEq for StepTable {
682    fn eq(&self, other: &Self) -> bool {
683        self.len() == other.len() && self.iter().zip(other.iter()).all(|(a, b)| a == b)
684    }
685}
686
687impl Eq for StepTable {}
688
689impl fmt::Debug for StepTable {
690    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
691        formatter.debug_list().entries(self.iter()).finish()
692    }
693}
694
695impl<'a> IntoIterator for &'a StepTable {
696    type Item = StepView<'a>;
697    type IntoIter = Steps<'a>;
698
699    fn into_iter(self) -> Self::IntoIter {
700        self.iter()
701    }
702}
703
704/// The steps of a [`StepTable`], in order.
705#[derive(Debug, Clone)]
706pub struct Steps<'a> {
707    table: &'a StepTable,
708    front: usize,
709    back: usize,
710}
711
712impl<'a> Iterator for Steps<'a> {
713    type Item = StepView<'a>;
714
715    fn next(&mut self) -> Option<Self::Item> {
716        (self.front < self.back).then(|| {
717            self.front += 1;
718            StepView {
719                table: self.table,
720                index: self.front - 1,
721            }
722        })
723    }
724
725    fn size_hint(&self) -> (usize, Option<usize>) {
726        let remaining = self.back - self.front;
727        (remaining, Some(remaining))
728    }
729
730    fn nth(&mut self, n: usize) -> Option<Self::Item> {
731        self.front = self.front.saturating_add(n).min(self.back);
732        self.next()
733    }
734}
735
736impl DoubleEndedIterator for Steps<'_> {
737    fn next_back(&mut self) -> Option<Self::Item> {
738        (self.front < self.back).then(|| {
739            self.back -= 1;
740            StepView {
741                table: self.table,
742                index: self.back,
743            }
744        })
745    }
746}
747
748impl ExactSizeIterator for Steps<'_> {}
749
750/// One step of a [`StepTable`].
751#[derive(Clone, Copy)]
752pub struct StepView<'a> {
753    table: &'a StepTable,
754    index: usize,
755}
756
757impl<'a> StepView<'a> {
758    /// The step's position in the trace.
759    #[must_use]
760    pub fn index(&self) -> usize {
761        self.index
762    }
763
764    fn row(&self) -> &'a Row {
765        self.table.rows.get(self.index)
766    }
767
768    #[must_use]
769    pub fn pc(&self) -> u64 {
770        match self.row().pc {
771            u32::MAX => sparse_value(&self.table.wide_pcs, self.index)
772                .copied()
773                .unwrap_or(u64::from(u32::MAX)),
774            pc => u64::from(pc),
775        }
776    }
777
778    /// The opcode byte. A step whose backend spelled its mnemonic in a way no opcode is
779    /// spelled has [`opcode::INVALID`]; [`StepView::mnemonic`] still has the spelling.
780    #[must_use]
781    pub fn op(&self) -> u8 {
782        self.row().op
783    }
784
785    /// The mnemonic, as the backend spelled it.
786    #[must_use]
787    pub fn mnemonic(&self) -> &'a str {
788        if !self.table.mnemonics.is_empty() {
789            if let Some(spelled) = sparse_value(&self.table.mnemonics, self.index) {
790                return spelled;
791            }
792        }
793        opcode::mnemonic(self.op())
794    }
795
796    #[must_use]
797    pub fn gas(&self) -> u64 {
798        self.row().gas
799    }
800
801    /// The cost as the tracer reports it; see [`TraceStep::gas_cost`].
802    #[must_use]
803    pub fn gas_cost(&self) -> u64 {
804        self.row().gas_cost
805    }
806
807    #[must_use]
808    pub fn depth(&self) -> u64 {
809        match self.row().depth {
810            u16::MAX => sparse_value(&self.table.wide_depths, self.index)
811                .copied()
812                .unwrap_or(u64::from(u16::MAX)),
813            depth => u64::from(depth),
814        }
815    }
816
817    #[must_use]
818    pub fn error(&self) -> Option<&'a str> {
819        if !self.row().failed {
820            return None;
821        }
822        sparse_value(&self.table.errors, self.index).map(|error| &**error)
823    }
824
825    #[must_use]
826    pub fn stack(&self) -> Stack<'a> {
827        Stack {
828            table: self.table,
829            step: self.index,
830            len: self.row().stack_len as usize,
831        }
832    }
833
834    /// The memory the step starts with; `None` when the backend did not record memory.
835    /// The bytes are shared with the trace when it holds them in full, and rebuilt from
836    /// the writes before the step otherwise.
837    #[must_use]
838    pub fn memory(&self) -> Option<Bytes> {
839        self.table.memories.read(self.row().memory)
840    }
841
842    /// The storage the backend reported for the step.
843    #[must_use]
844    pub fn storage(&self) -> &'a BTreeMap<String, String> {
845        run_value(&self.table.storage, self.index).map_or(&EMPTY_STORAGE, |storage| storage)
846    }
847
848    /// The slots the step changed.
849    #[must_use]
850    pub fn storage_diff(&self) -> &'a BTreeMap<String, StorageChange> {
851        sparse_value(&self.table.storage_diffs, self.index).unwrap_or(&EMPTY_STORAGE_DIFF)
852    }
853
854    /// The transient slots the step read or wrote, with their value after it.
855    #[must_use]
856    pub fn transient_storage(&self) -> &'a BTreeMap<String, String> {
857        sparse_value(&self.table.transient_storage, self.index).unwrap_or(&EMPTY_STORAGE)
858    }
859
860    /// The return data buffer before the step, as unprefixed hex; `None` when empty.
861    #[must_use]
862    pub fn return_data(&self) -> Option<&'a str> {
863        run_value(&self.table.return_data, self.index).and_then(|data| data.as_deref())
864    }
865
866    /// The step as a [`TraceStep`] of its own.
867    #[must_use]
868    pub fn to_trace_step(&self) -> TraceStep {
869        TraceStep::new(
870            self.pc(),
871            Arc::from(self.mnemonic()),
872            self.gas(),
873            self.gas_cost(),
874            self.depth(),
875            self.error().map(str::to_owned),
876            StepSnapshot {
877                stack: self
878                    .stack()
879                    .iter()
880                    .map(|word| Arc::from(word.to_string()))
881                    .collect(),
882                memory: self.memory(),
883                storage: run_value(&self.table.storage, self.index)
884                    .cloned()
885                    .unwrap_or_default(),
886                storage_diff: self.storage_diff().clone(),
887                transient_storage: self.transient_storage().clone(),
888                return_data: run_value(&self.table.return_data, self.index)
889                    .cloned()
890                    .flatten(),
891            },
892        )
893    }
894}
895
896impl PartialEq for StepView<'_> {
897    /// Two steps are equal when they record the same thing.
898    fn eq(&self, other: &Self) -> bool {
899        self.pc() == other.pc()
900            && self.mnemonic() == other.mnemonic()
901            && self.gas() == other.gas()
902            && self.gas_cost() == other.gas_cost()
903            && self.depth() == other.depth()
904            && self.error() == other.error()
905            && self.stack() == other.stack()
906            && self.memory() == other.memory()
907            && self.storage() == other.storage()
908            && self.storage_diff() == other.storage_diff()
909            && self.transient_storage() == other.transient_storage()
910            && self.return_data() == other.return_data()
911    }
912}
913
914impl Eq for StepView<'_> {}
915
916impl fmt::Debug for StepView<'_> {
917    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
918        formatter
919            .debug_struct("StepView")
920            .field("index", &self.index)
921            .field("pc", &self.pc())
922            .field("op", &self.mnemonic())
923            .field("gas", &self.gas())
924            .field("gas_cost", &self.gas_cost())
925            .field("depth", &self.depth())
926            .field("error", &self.error())
927            .field("stack", &self.stack().to_vec())
928            .field("memory", &self.memory().map(|memory| encode_hex(&memory)))
929            .field("storage", self.storage())
930            .field("storage_diff", self.storage_diff())
931            .field("transient_storage", self.transient_storage())
932            .field("return_data", &self.return_data())
933            .finish()
934    }
935}
936
937impl Serialize for StepView<'_> {
938    /// Writes the step as a [`TraceStep`] is written: the flat fields and the snapshot.
939    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
940        let stack = self.stack().to_vec();
941        let memory = self.memory();
942        let memory = memory.as_deref().map(Hex);
943        let mut state = serializer.serialize_struct("TraceStep", 10)?;
944        state.serialize_field("pc", &self.pc())?;
945        state.serialize_field("op", self.mnemonic())?;
946        state.serialize_field("gas", &self.gas())?;
947        state.serialize_field("gas_cost", &self.gas_cost())?;
948        state.serialize_field("depth", &self.depth())?;
949        state.serialize_field("stack", &stack)?;
950        state.serialize_field("memory", &memory)?;
951        state.serialize_field("storage", self.storage())?;
952        state.serialize_field("error", &self.error())?;
953        state.serialize_field(
954            "snapshot",
955            &SnapshotOut {
956                stack: &stack,
957                memory,
958                storage: self.storage(),
959                storage_diff: self.storage_diff(),
960                transient_storage: self.transient_storage(),
961                return_data: self.return_data(),
962            },
963        )?;
964        state.end()
965    }
966}
967
968/// A step's snapshot in the shape [`StepSnapshot`] is written in.
969struct SnapshotOut<'a> {
970    stack: &'a [EvmWord],
971    memory: Option<Hex<'a>>,
972    storage: &'a BTreeMap<String, String>,
973    storage_diff: &'a BTreeMap<String, StorageChange>,
974    transient_storage: &'a BTreeMap<String, String>,
975    return_data: Option<&'a str>,
976}
977
978impl Serialize for SnapshotOut<'_> {
979    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
980        let mut state = serializer.serialize_struct("StepSnapshot", 6)?;
981        state.serialize_field("stack", self.stack)?;
982        state.serialize_field("memory", &self.memory)?;
983        state.serialize_field("storage", self.storage)?;
984        state.serialize_field("storage_diff", self.storage_diff)?;
985        // Only where a step touched it, so files without it are unchanged.
986        if self.transient_storage.is_empty() {
987            state.skip_field("transient_storage")?;
988        } else {
989            state.serialize_field("transient_storage", self.transient_storage)?;
990        }
991        if self.return_data.is_none() {
992            state.skip_field("return_data")?;
993        } else {
994            state.serialize_field("return_data", &self.return_data)?;
995        }
996        state.end()
997    }
998}
999
1000impl Serialize for StepTable {
1001    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
1002        let mut sequence = serializer.serialize_seq(Some(self.len()))?;
1003        for step in self {
1004            sequence.serialize_element(&step)?;
1005        }
1006        sequence.end()
1007    }
1008}
1009
1010impl<'de> Deserialize<'de> for StepTable {
1011    /// Reads steps as written by any version, appending each as it is read so that a
1012    /// file's steps are never all resident as [`TraceStep`]s.
1013    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1014        struct Steps;
1015
1016        impl<'de> Visitor<'de> for Steps {
1017            type Value = StepTable;
1018
1019            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
1020                formatter.write_str("an array of trace steps")
1021            }
1022
1023            fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
1024                let mut table = StepTable::new();
1025                while let Some(step) = seq.next_element::<TraceStep>()? {
1026                    table.try_push(step).map_err(de::Error::custom)?;
1027                }
1028                Ok(table)
1029            }
1030        }
1031
1032        deserializer.deserialize_seq(Steps)
1033    }
1034}
1035
1036/// The stack of one step, bottom first.
1037#[derive(Clone, Copy)]
1038pub struct Stack<'a> {
1039    table: &'a StepTable,
1040    step: usize,
1041    len: usize,
1042}
1043
1044impl<'a> Stack<'a> {
1045    #[must_use]
1046    pub fn len(&self) -> usize {
1047        self.len
1048    }
1049
1050    #[must_use]
1051    pub fn is_empty(&self) -> bool {
1052        self.len == 0
1053    }
1054
1055    /// The word at `position` from the bottom.
1056    #[must_use]
1057    pub fn get(&self, position: usize) -> Option<EvmWord> {
1058        (position < self.len).then(|| self.table.stack_word(self.step, position))
1059    }
1060
1061    /// The word `depth` words below the top; `0` is the top.
1062    #[must_use]
1063    pub fn peek(&self, depth: usize) -> Option<EvmWord> {
1064        self.len
1065            .checked_sub(depth.checked_add(1)?)
1066            .and_then(|position| self.get(position))
1067    }
1068
1069    /// The top word.
1070    #[must_use]
1071    pub fn top(&self) -> Option<EvmWord> {
1072        self.peek(0)
1073    }
1074
1075    /// The words, bottom first.
1076    #[must_use]
1077    pub fn to_vec(&self) -> Vec<EvmWord> {
1078        let mut words = vec![EvmWord::ZERO; self.len];
1079        // Fill from the top down, one step's stored words at a time: the words a step
1080        // kept are the previous step's, at the same positions.
1081        let mut step = self.step;
1082        let mut end = self.len;
1083        loop {
1084            let row = self.table.rows.get(step);
1085            let stored_from = row.stack_kept as usize;
1086            let kept = stored_from.min(end);
1087            let start = row.stack_start as usize;
1088            for (position, slot) in words.iter_mut().enumerate().take(end).skip(kept) {
1089                *slot = *self.table.words.get(start + position - stored_from);
1090            }
1091            if kept == 0 {
1092                return words;
1093            }
1094            end = kept;
1095            step -= 1;
1096        }
1097    }
1098
1099    /// The words, bottom first.
1100    pub fn iter(&self) -> impl DoubleEndedIterator<Item = EvmWord> + ExactSizeIterator {
1101        self.to_vec().into_iter()
1102    }
1103}
1104
1105impl PartialEq for Stack<'_> {
1106    fn eq(&self, other: &Self) -> bool {
1107        self.len == other.len && self.to_vec() == other.to_vec()
1108    }
1109}
1110
1111impl PartialEq<[EvmWord]> for Stack<'_> {
1112    fn eq(&self, other: &[EvmWord]) -> bool {
1113        self.len == other.len() && self.to_vec() == other
1114    }
1115}
1116
1117impl fmt::Debug for Stack<'_> {
1118    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1119        formatter.debug_list().entries(self.to_vec()).finish()
1120    }
1121}
1122
1123#[cfg(test)]
1124mod tests {
1125    use std::collections::BTreeMap;
1126    use std::sync::Arc;
1127
1128    use bytes::Bytes;
1129
1130    use super::{StepRecord, StepTable, STACK_CHECKPOINT};
1131    use crate::{opcode, EvmWord, StepSnapshot, StorageChange, TraceStep};
1132
1133    /// A small generator, so that the randomized tests are the same on every run.
1134    struct Random(u64);
1135
1136    impl Random {
1137        fn next(&mut self) -> u64 {
1138            self.0 ^= self.0 << 13;
1139            self.0 ^= self.0 >> 7;
1140            self.0 ^= self.0 << 17;
1141            self.0
1142        }
1143
1144        fn below(&mut self, bound: u64) -> u64 {
1145            self.next() % bound
1146        }
1147    }
1148
1149    fn word(value: u64) -> Arc<str> {
1150        Arc::from(EvmWord::from(value).to_string())
1151    }
1152
1153    /// Steps of every shape a backend builds: stacks growing and shrinking, memory and
1154    /// storage changing now and then, errors, transient storage and return data.
1155    fn random_steps(count: usize, seed: u64) -> Vec<TraceStep> {
1156        let mut random = Random(seed);
1157        let mut stack = Vec::<Arc<str>>::new();
1158        let mut memory = Bytes::from(vec![0_u8; 64]);
1159        let mut storage = Arc::new(BTreeMap::new());
1160        let mut return_data: Option<String> = None;
1161        (0..count)
1162            .map(|index| {
1163                match random.below(4) {
1164                    0 => {
1165                        stack.pop();
1166                    }
1167                    1 if !stack.is_empty() => {
1168                        let top = stack.len() - 1;
1169                        stack[top] = word(random.below(1 << 20));
1170                    }
1171                    _ => stack.push(word(random.next())),
1172                }
1173                if random.below(10) == 0 {
1174                    let mut changed = memory.to_vec();
1175                    let at = random.below(changed.len() as u64) as usize;
1176                    changed[at] = random.next() as u8;
1177                    if random.below(3) == 0 {
1178                        changed.extend_from_slice(&[0; 32]);
1179                    }
1180                    memory = Bytes::from(changed);
1181                }
1182                let mut storage_diff = BTreeMap::new();
1183                if random.below(20) == 0 {
1184                    let slot = format!("0x{:x}", random.below(5));
1185                    let value = format!("0x{:x}", random.next());
1186                    storage_diff.insert(
1187                        slot.clone(),
1188                        StorageChange {
1189                            before: storage.get(&slot).cloned(),
1190                            after: Some(value.clone()),
1191                        },
1192                    );
1193                    Arc::make_mut(&mut storage).insert(slot, value);
1194                }
1195                if random.below(25) == 0 {
1196                    return_data =
1197                        (random.below(2) == 0).then(|| format!("{:02x}", random.next() as u8));
1198                }
1199                let transient = if random.below(30) == 0 {
1200                    BTreeMap::from([("0x1".to_owned(), format!("0x{:x}", random.next()))])
1201                } else {
1202                    BTreeMap::new()
1203                };
1204                let op = match random.below(6) {
1205                    0 => "SSTORE".to_owned(),
1206                    1 => "SHA3".to_owned(),
1207                    2 => "UNKNOWN(0x0c)".to_owned(),
1208                    _ => opcode::mnemonic(random.next() as u8).to_owned(),
1209                };
1210                TraceStep::new(
1211                    if random.below(100) == 0 {
1212                        u64::MAX - index as u64
1213                    } else {
1214                        random.below(30_000)
1215                    },
1216                    Arc::from(op),
1217                    random.next(),
1218                    random.below(100),
1219                    if random.below(100) == 0 {
1220                        1 << 40
1221                    } else {
1222                        random.below(4) + 1
1223                    },
1224                    (random.below(50) == 0).then(|| "revert".to_owned()),
1225                    StepSnapshot {
1226                        stack: stack.clone(),
1227                        memory: (random.below(40) != 0).then(|| memory.clone()),
1228                        storage: Arc::clone(&storage),
1229                        storage_diff,
1230                        transient_storage: transient,
1231                        return_data: return_data.clone().map(Arc::from),
1232                    },
1233                )
1234            })
1235            .collect()
1236    }
1237
1238    #[test]
1239    fn columns_span_chunks() {
1240        let mut column = super::Column::<u32>::default();
1241        let total = 2 * super::CHUNK + 5;
1242        for value in 0..total {
1243            column.push(value as u32);
1244        }
1245        assert_eq!(column.len(), total);
1246        assert_eq!(column.full.len(), 2);
1247        for index in [
1248            0,
1249            super::CHUNK - 1,
1250            super::CHUNK,
1251            2 * super::CHUNK,
1252            total - 1,
1253        ] {
1254            assert_eq!(*column.get(index), index as u32);
1255        }
1256        assert_eq!(column.last(), Some(&(total as u32 - 1)));
1257        *column.get_mut(super::CHUNK) = 7;
1258        assert_eq!(*column.get(super::CHUNK), 7);
1259
1260        // Back into a full chunk, which is filled again from where it was cut.
1261        column.truncate(super::CHUNK + 3);
1262        assert_eq!((column.len(), column.full.len()), (super::CHUNK + 3, 1));
1263        column.push(99);
1264        assert_eq!(*column.get(super::CHUNK + 3), 99);
1265        column.truncate(super::CHUNK);
1266        assert_eq!(column.last(), Some(&(super::CHUNK as u32 - 1)));
1267        column.truncate(0);
1268        assert_eq!((column.len(), column.last()), (0, None));
1269    }
1270
1271    #[test]
1272    fn writes_exactly_what_the_steps_wrote() {
1273        for seed in [1, 2, 3] {
1274            let steps = random_steps(500, seed);
1275            let table = StepTable::from(steps.clone());
1276            assert_eq!(table.len(), steps.len());
1277            assert_eq!(
1278                serde_json::to_string(&table).expect("table"),
1279                serde_json::to_string(&steps).expect("steps"),
1280                "seed {seed}"
1281            );
1282            for (index, step) in steps.iter().enumerate() {
1283                assert_eq!(table.step(index).to_trace_step(), *step, "step {index}");
1284            }
1285            let read: StepTable =
1286                serde_json::from_str(&serde_json::to_string(&steps).expect("steps")).expect("read");
1287            assert_eq!(read, table);
1288        }
1289    }
1290
1291    #[test]
1292    fn stacks_read_back_across_checkpoints() {
1293        let steps = random_steps(5 * STACK_CHECKPOINT + 7, 9);
1294        let table = StepTable::from(steps.clone());
1295        let mut stored = 0;
1296        for (index, step) in steps.iter().enumerate() {
1297            let expected = step
1298                .snapshot
1299                .stack
1300                .iter()
1301                .map(|word| EvmWord::parse(word).expect("word"))
1302                .collect::<Vec<_>>();
1303            let stack = table.step(index).stack();
1304            assert_eq!(stack.to_vec(), expected, "step {index}");
1305            assert_eq!(stack.len(), expected.len());
1306            for (position, word) in expected.iter().enumerate() {
1307                assert_eq!(stack.get(position), Some(*word));
1308            }
1309            assert_eq!(stack.top(), expected.last().copied());
1310            assert_eq!(stack.peek(expected.len()), None);
1311            stored += expected.len();
1312        }
1313        // Most of every stack is the previous step's and is not stored again.
1314        assert!(
1315            table.words.len() < stored / 2,
1316            "{} of {stored}",
1317            table.words.len()
1318        );
1319    }
1320
1321    #[test]
1322    fn appending_records_what_pushing_records() {
1323        let steps = random_steps(300, 4);
1324        let pushed = StepTable::from(steps.clone());
1325        let mut appended = StepTable::with_capacity(steps.len());
1326        let mut previous: Option<&TraceStep> = None;
1327        for step in &steps {
1328            let snapshot = &step.snapshot;
1329            appended.append(
1330                StepRecord {
1331                    pc: step.pc,
1332                    op: opcode::from_spelling(&step.op).expect("known"),
1333                    gas: step.gas,
1334                    gas_cost: step.gas_cost,
1335                    depth: step.depth,
1336                    error: step.error.clone(),
1337                    memory: snapshot.memory.clone(),
1338                    storage: Some(Arc::clone(&snapshot.storage)),
1339                    storage_diff: Some(snapshot.storage_diff.clone()),
1340                    transient_storage: Some(snapshot.transient_storage.clone()),
1341                    return_data: Some(
1342                        snapshot
1343                            .return_data
1344                            .clone()
1345                            .unwrap_or_else(|| Arc::from("")),
1346                    ),
1347                },
1348                snapshot
1349                    .stack
1350                    .iter()
1351                    .map(|word| EvmWord::parse(word).expect("word")),
1352            );
1353            // A step without memory has none, rather than the one before it.
1354            if snapshot.memory.is_none()
1355                && previous.is_some_and(|step| step.snapshot.memory.is_some())
1356            {
1357                let last = appended.len() - 1;
1358                appended.rows.get_mut(last).memory = super::MemoryRef::NONE;
1359            }
1360            previous = Some(step);
1361        }
1362        // Only the spellings differ: the table keeps a backend's own.
1363        for index in 0..steps.len() {
1364            let (a, b) = (pushed.step(index), appended.step(index));
1365            assert_eq!(a.op(), b.op());
1366            assert_eq!(a.stack(), b.stack());
1367            assert_eq!(a.memory(), b.memory());
1368            assert_eq!(a.storage(), b.storage());
1369            assert_eq!(a.storage_diff(), b.storage_diff());
1370            assert_eq!(a.transient_storage(), b.transient_storage());
1371            assert_eq!(a.return_data(), b.return_data());
1372            assert_eq!(
1373                (a.pc(), a.depth(), a.gas(), a.error()),
1374                (b.pc(), b.depth(), b.gas(), b.error())
1375            );
1376        }
1377    }
1378
1379    #[test]
1380    fn keeps_a_backends_spelling_with_the_byte_it_means() {
1381        let step =
1382            |op: &str| TraceStep::new(0, Arc::from(op), 0, 0, 1, None, StepSnapshot::default());
1383        let table = StepTable::from(vec![
1384            step("SHA3"),
1385            step("KECCAK256"),
1386            step("opcode 0xef not defined"),
1387            step("NOT AN OPCODE"),
1388        ]);
1389        let ops = table
1390            .iter()
1391            .map(|step| (step.op(), step.mnemonic()))
1392            .collect::<Vec<_>>();
1393        assert_eq!(
1394            ops,
1395            [
1396                (opcode::KECCAK256, "SHA3"),
1397                (opcode::KECCAK256, "KECCAK256"),
1398                (0xef, "opcode 0xef not defined"),
1399                (opcode::INVALID, "NOT AN OPCODE"),
1400            ]
1401        );
1402    }
1403
1404    #[test]
1405    fn a_word_that_is_not_hex_is_refused() {
1406        let mut table = StepTable::new();
1407        let mut step = TraceStep::new(0, Arc::from("ADD"), 0, 0, 1, None, StepSnapshot::default());
1408        step.snapshot.stack = vec![Arc::from("0x1"), Arc::from("zz")];
1409        let error = table.try_push(step).expect_err("not hex");
1410        assert_eq!(
1411            error.to_string(),
1412            "step 0: stack word 1: `z` at offset 0 is not a hex digit"
1413        );
1414        assert!(table.is_empty());
1415    }
1416
1417    #[test]
1418    fn truncating_keeps_what_comes_before() {
1419        let steps = random_steps(3 * STACK_CHECKPOINT, 5);
1420        let mut table = StepTable::from(steps.clone());
1421        table.truncate(STACK_CHECKPOINT + 3);
1422        // Steps appended after a truncation are stored against the step now last.
1423        table.extend(steps[STACK_CHECKPOINT + 3..].iter().cloned());
1424        assert_eq!(table, StepTable::from(steps));
1425        table.truncate(0);
1426        assert!(table.is_empty() && table.first().is_none());
1427    }
1428
1429    #[test]
1430    fn memory_recorded_write_by_write() {
1431        let record = |pc| StepRecord {
1432            pc,
1433            op: opcode::MSTORE,
1434            depth: 1,
1435            ..StepRecord::default()
1436        };
1437        let mut table = StepTable::new();
1438        table.append(record(0), []);
1439        assert_eq!(table.step(0).memory(), None);
1440        table.start_memory_frame();
1441        table.append(record(1), []);
1442        table.write_memory(0, &[7; 32], 32).expect("write");
1443        table.append(record(2), []);
1444        table.start_memory_frame();
1445        table.append(record(3), []);
1446        table.end_memory_frame();
1447        table.write_memory(32, &[], 64).expect("grow");
1448        table.append(record(4), []);
1449        let memories = table
1450            .iter()
1451            .map(|step| step.memory().map(|memory| memory.to_vec()))
1452            .collect::<Vec<_>>();
1453        let mut grown = vec![7; 32];
1454        grown.resize(64, 0);
1455        assert_eq!(
1456            memories,
1457            [
1458                None,
1459                Some(Vec::new()),
1460                Some(vec![7; 32]),
1461                Some(Vec::new()),
1462                Some(grown)
1463            ]
1464        );
1465    }
1466}