Skip to main content

soldb_core/
lib.rs

1//! Shared vocabulary types for the SolDB workspace.
2//!
3//! Every other crate agrees on the types defined here: a [`TransactionTrace`] and the
4//! [`StepTable`] of its steps are what an execution backend produces and what the
5//! debugger and the frontends consume. A backend builds steps as [`TraceStep`]s or
6//! appends them field by field; readers get a [`StepView`] per step. This crate
7//! deliberately has no soldb dependencies so that it can stay the single type boundary in
8//! the graph.
9//!
10//! These types are also a serialization contract. They round-trip through `serde` into
11//! trace files and into the web-facing JSON document, so adding a field is additive and
12//! needs `#[serde(default)]`, while renaming or removing one is a breaking change.
13//!
14//! [`TraceCapabilities`] describes what the selected backend was actually able to
15//! record. Frontends should branch on those flags rather than on the backend name, so a
16//! new backend does not require changes further up the stack.
17
18use std::collections::BTreeMap;
19use std::fmt;
20use std::sync::Arc;
21
22use serde::de::{self, Visitor};
23use serde::ser::SerializeStruct;
24use serde::{Deserialize, Deserializer, Serialize, Serializer};
25use thiserror::Error;
26
27pub use bytes::Bytes;
28
29mod memory;
30pub mod opcode;
31mod table;
32mod word;
33
34pub use memory::MemoryWriteError;
35pub use table::{Stack, StepError, StepRecord, StepTable, StepView, Steps, STACK_CHECKPOINT};
36pub use word::{EvmWord, WordError};
37
38#[derive(Debug, Error)]
39pub enum SoldbError {
40    /// A failure described by a human-readable message.
41    #[error("{0}")]
42    Message(String),
43    /// The failure was already rendered for the user, typically as a JSON error document
44    /// on a `--json` code path. Callers must exit non-zero without printing anything else.
45    ///
46    /// This exists so the exit path never has to inspect error *text* to decide whether a
47    /// message was already shown.
48    #[error("")]
49    AlreadyReported,
50}
51
52pub type SoldbResult<T> = Result<T, SoldbError>;
53
54impl From<StepError> for SoldbError {
55    fn from(error: StepError) -> Self {
56        Self::Message(format!("invalid trace step: {error}"))
57    }
58}
59
60/// One recorded EVM step, as a backend builds it.
61///
62/// A [`TransactionTrace`] holds its steps in a [`StepTable`], which takes them with
63/// [`StepTable::push`] and gives each back as a [`StepView`] or, when ownership is needed,
64/// with [`StepView::to_trace_step`].
65///
66/// The machine state lives in `snapshot`, once. The flat `stack`, `memory`, and
67/// `storage` fields are the layout older trace files and web clients read: every
68/// constructor in this workspace leaves them empty, serialization fills them from the
69/// snapshot so the wire format is unchanged, and deserialization moves whatever they
70/// hold into the snapshot. Read a step through [`TraceStep::snapshot_ref`], which
71/// handles both shapes; never through the flat fields.
72///
73/// Consecutive steps share their memory and storage when nothing changed between them
74/// (see [`StepSnapshot`]), which is what keeps a trace of hundreds of thousands of steps
75/// resident: memory changes on a handful of opcodes and storage on `SSTORE` alone.
76#[derive(Debug, Clone, Eq)]
77pub struct TraceStep {
78    pub pc: u64,
79    /// The mnemonic, shared with every other step running the same opcode: there are a
80    /// couple of hundred of them and hundreds of thousands of steps.
81    pub op: Word,
82    pub gas: u64,
83    /// The cost as the tracer reports it: for a call, its own cost plus the gas it forwards to
84    /// the callee; for a creation, the same under REVM but without the forwarded gas under geth.
85    pub gas_cost: u64,
86    pub depth: u64,
87    /// Legacy layout; empty unless a caller filled it by hand. See the type docs.
88    pub stack: Vec<Word>,
89    /// Legacy layout; `None` unless a caller filled it by hand. See the type docs.
90    pub memory: Option<Bytes>,
91    /// Legacy layout; `None` unless a caller filled it by hand. See the type docs.
92    pub storage: Option<BTreeMap<String, String>>,
93    pub error: Option<String>,
94    pub snapshot: StepSnapshot,
95}
96
97impl TraceStep {
98    /// A step whose state lives in `snapshot`, the way every backend builds one.
99    #[must_use]
100    pub fn new(
101        pc: u64,
102        op: Word,
103        gas: u64,
104        gas_cost: u64,
105        depth: u64,
106        error: Option<String>,
107        snapshot: StepSnapshot,
108    ) -> Self {
109        Self {
110            pc,
111            op,
112            gas,
113            gas_cost,
114            depth,
115            stack: Vec::new(),
116            memory: None,
117            storage: None,
118            error,
119            snapshot,
120        }
121    }
122
123    /// Borrows the machine state this step captured, without copying it.
124    ///
125    /// Whichever shape the step is in, its snapshot or the legacy flat fields, this
126    /// returns a view of it.
127    ///
128    /// Prefer this over [`TraceStep::normalized_snapshot`] anywhere that walks a trace.
129    /// Traces routinely run to hundreds of thousands of steps and each one's `memory` can
130    /// be tens of kilobytes, so copying per step — or, as variable decoding does, per
131    /// variable per step — dominates the cost of reading a trace.
132    #[must_use]
133    pub fn snapshot_ref(&self) -> StepSnapshotRef<'_> {
134        static EMPTY_STORAGE: BTreeMap<String, String> = BTreeMap::new();
135        static EMPTY_STORAGE_DIFF: BTreeMap<String, StorageChange> = BTreeMap::new();
136
137        if !self.snapshot.is_empty() {
138            return StepSnapshotRef {
139                stack: &self.snapshot.stack,
140                memory: self.snapshot.memory.as_deref(),
141                storage: &self.snapshot.storage,
142                storage_diff: &self.snapshot.storage_diff,
143                transient_storage: &self.snapshot.transient_storage,
144                return_data: self.snapshot.return_data.as_deref(),
145            };
146        }
147        StepSnapshotRef {
148            stack: &self.stack,
149            memory: self.memory.as_deref(),
150            storage: self.storage.as_ref().unwrap_or(&EMPTY_STORAGE),
151            storage_diff: &EMPTY_STORAGE_DIFF,
152            transient_storage: &EMPTY_STORAGE,
153            return_data: None,
154        }
155    }
156
157    /// Copies the machine state this step captured into an owned [`StepSnapshot`].
158    ///
159    /// Use this only when ownership is genuinely required; [`TraceStep::snapshot_ref`] is
160    /// the same view without the copy.
161    #[must_use]
162    pub fn normalized_snapshot(&self) -> StepSnapshot {
163        self.snapshot_ref().to_owned_snapshot()
164    }
165
166    /// Moves legacy flat fields into the snapshot, so a step read from an older file is
167    /// stored once like every other.
168    pub(crate) fn normalized(mut self) -> Self {
169        if self.snapshot.is_empty() {
170            self.snapshot = StepSnapshot {
171                stack: std::mem::take(&mut self.stack),
172                memory: self.memory.take(),
173                storage: Arc::new(self.storage.take().unwrap_or_default()),
174                storage_diff: BTreeMap::new(),
175                transient_storage: BTreeMap::new(),
176                return_data: None,
177            };
178        } else {
179            self.stack = Vec::new();
180            self.memory = None;
181            self.storage = None;
182        }
183        self
184    }
185}
186
187impl PartialEq for TraceStep {
188    /// Two steps are equal when they record the same thing, whichever shape holds it.
189    fn eq(&self, other: &Self) -> bool {
190        self.pc == other.pc
191            && self.op == other.op
192            && self.gas == other.gas
193            && self.gas_cost == other.gas_cost
194            && self.depth == other.depth
195            && self.error == other.error
196            && self.snapshot_ref() == other.snapshot_ref()
197    }
198}
199
200impl Serialize for TraceStep {
201    /// Writes the step in the wire format every client reads: the flat fields and the
202    /// snapshot, both from the one copy of the state.
203    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
204        let snapshot = self.snapshot_ref();
205        let mut state = serializer.serialize_struct("TraceStep", 10)?;
206        state.serialize_field("pc", &self.pc)?;
207        state.serialize_field("op", &self.op)?;
208        state.serialize_field("gas", &self.gas)?;
209        state.serialize_field("gas_cost", &self.gas_cost)?;
210        state.serialize_field("depth", &self.depth)?;
211        state.serialize_field("stack", snapshot.stack)?;
212        state.serialize_field("memory", &snapshot.memory.map(Hex))?;
213        state.serialize_field("storage", snapshot.storage)?;
214        state.serialize_field("error", &self.error)?;
215        state.serialize_field("snapshot", &snapshot)?;
216        state.end()
217    }
218}
219
220/// The wire shape of a step, read as written by any version and then normalized.
221#[derive(Deserialize)]
222struct TraceStepRepr {
223    pc: u64,
224    op: String,
225    gas: u64,
226    gas_cost: u64,
227    depth: u64,
228    #[serde(default)]
229    stack: Vec<String>,
230    #[serde(default, deserialize_with = "deserialize_memory")]
231    memory: Option<Bytes>,
232    #[serde(default)]
233    storage: Option<BTreeMap<String, String>>,
234    #[serde(default)]
235    error: Option<String>,
236    #[serde(default)]
237    snapshot: StepSnapshot,
238}
239
240impl<'de> Deserialize<'de> for TraceStep {
241    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
242        let repr = TraceStepRepr::deserialize(deserializer)?;
243        Ok(TraceStep {
244            pc: repr.pc,
245            op: Arc::from(repr.op),
246            gas: repr.gas,
247            gas_cost: repr.gas_cost,
248            depth: repr.depth,
249            stack: repr.stack.into_iter().map(Arc::from).collect(),
250            memory: repr.memory,
251            storage: repr.storage,
252            error: repr.error,
253            snapshot: repr.snapshot,
254        }
255        .normalized())
256    }
257}
258
259/// One machine word as a backend spelled it, shared between the steps that hold it.
260///
261/// A trace of hundreds of thousands of steps carries a stack of a dozen words at every
262/// one of them, and the same handful of values — a jump target, a length, a small
263/// constant — appear over and over: a 600,000-step trace of one loop holds 1.7 million
264/// stack words drawn from about 1,200 distinct ones. Holding each as its own `String`
265/// costs a heap allocation per occurrence; holding a shared pointer costs one per
266/// distinct value. [`WordInterner`] is what produces the sharing.
267pub type Word = Arc<str>;
268
269/// Hands out one [`Word`] per distinct value, so repeated stack words are stored once.
270///
271/// One interner covers one trace being built or parsed; it is dropped with the loop that
272/// filled it, and the words it handed out live as long as the steps holding them.
273#[derive(Debug, Default)]
274pub struct WordInterner {
275    words: std::collections::HashMap<Word, Word>,
276}
277
278impl WordInterner {
279    #[must_use]
280    pub fn new() -> Self {
281        Self::default()
282    }
283
284    /// The shared word equal to `word`, interning it the first time it is seen.
285    #[must_use]
286    pub fn intern(&mut self, word: &str) -> Word {
287        if let Some(shared) = self.words.get(word) {
288            return Arc::clone(shared);
289        }
290        let shared: Word = Arc::from(word);
291        self.words.insert(Arc::clone(&shared), Arc::clone(&shared));
292        shared
293    }
294
295    /// Interns a whole stack, in place.
296    pub fn intern_stack(&mut self, stack: &mut [Word]) {
297        for word in stack {
298            *word = self.intern(word);
299        }
300    }
301
302    /// How many distinct words have been interned.
303    #[must_use]
304    pub fn len(&self) -> usize {
305        self.words.len()
306    }
307
308    #[must_use]
309    pub fn is_empty(&self) -> bool {
310        self.words.is_empty()
311    }
312}
313
314/// A borrowed view of one step's captured machine state.
315///
316/// Serializes identically to [`StepSnapshot`], so it can stand in wherever a snapshot is
317/// written out. Produced by [`TraceStep::snapshot_ref`].
318#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
319pub struct StepSnapshotRef<'a> {
320    pub stack: &'a [Word],
321    /// Memory as bytes; written out as one unprefixed lowercase hex string.
322    #[serde(serialize_with = "serialize_memory")]
323    pub memory: Option<&'a [u8]>,
324    pub storage: &'a BTreeMap<String, String>,
325    pub storage_diff: &'a BTreeMap<String, StorageChange>,
326    #[serde(skip_serializing_if = "is_empty_ref")]
327    pub transient_storage: &'a BTreeMap<String, String>,
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub return_data: Option<&'a str>,
330}
331
332fn is_empty_ref(map: &&BTreeMap<String, String>) -> bool {
333    map.is_empty()
334}
335
336fn serialize_memory<S: Serializer>(
337    memory: &Option<&[u8]>,
338    serializer: S,
339) -> Result<S::Ok, S::Error> {
340    memory.map(Hex).serialize(serializer)
341}
342
343impl StepSnapshotRef<'_> {
344    /// Copies this view into an owned [`StepSnapshot`].
345    #[must_use]
346    pub fn to_owned_snapshot(&self) -> StepSnapshot {
347        StepSnapshot {
348            stack: self.stack.to_vec(),
349            memory: self.memory.map(Bytes::copy_from_slice),
350            storage: Arc::new(self.storage.clone()),
351            storage_diff: self.storage_diff.clone(),
352            transient_storage: self.transient_storage.clone(),
353            return_data: self.return_data.map(Arc::from),
354        }
355    }
356}
357
358/// The machine state one step captured.
359///
360/// `memory` and `storage` are shared: a backend building steps hands the previous step's
361/// value on unchanged, so a trace holds one copy of memory per change rather than one
362/// per step. Reads see plain values through `Deref`; a step that changes them gets its
363/// own.
364///
365/// Memory is held as bytes. Trace files spell it as one unprefixed lowercase hex string,
366/// and serialization writes and reads that spelling, so the bytes are only a change in
367/// how a step holds memory, not in what a file says.
368#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
369#[serde(from = "StepSnapshotRepr")]
370pub struct StepSnapshot {
371    pub stack: Vec<Word>,
372    /// Memory as bytes. A [`Bytes`] clone shares the buffer, which is how consecutive
373    /// steps hold one copy of unchanged memory.
374    pub memory: Option<Bytes>,
375    pub storage: Arc<BTreeMap<String, String>>,
376    pub storage_diff: BTreeMap<String, StorageChange>,
377    /// The transient slots this step read or wrote, with their value after it.
378    pub transient_storage: BTreeMap<String, String>,
379    /// The return data buffer before this step, as unprefixed hex; `None` when empty.
380    pub return_data: Option<Arc<str>>,
381}
382
383impl StepSnapshot {
384    /// A snapshot that owns its values, for a backend or a test building one from parts.
385    #[must_use]
386    pub fn new(
387        stack: Vec<Word>,
388        memory: Option<Bytes>,
389        storage: BTreeMap<String, String>,
390        storage_diff: BTreeMap<String, StorageChange>,
391    ) -> Self {
392        Self {
393            stack,
394            memory,
395            storage: Arc::new(storage),
396            storage_diff,
397            transient_storage: BTreeMap::new(),
398            return_data: None,
399        }
400    }
401
402    /// The same snapshot with `transient_storage` set.
403    #[must_use]
404    pub fn with_transient_storage(mut self, transient_storage: BTreeMap<String, String>) -> Self {
405        self.transient_storage = transient_storage;
406        self
407    }
408
409    /// The same snapshot with `return_data` set; empty data is stored as `None`.
410    #[must_use]
411    pub fn with_return_data(mut self, return_data: Option<String>) -> Self {
412        self.return_data = return_data.filter(|data| !data.is_empty()).map(Arc::from);
413        self
414    }
415
416    #[must_use]
417    pub fn is_empty(&self) -> bool {
418        self.stack.is_empty()
419            && self.memory.is_none()
420            && self.storage.is_empty()
421            && self.storage_diff.is_empty()
422            && self.transient_storage.is_empty()
423            && self.return_data.is_none()
424    }
425}
426
427impl Serialize for StepSnapshot {
428    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
429        let mut state = serializer.serialize_struct("StepSnapshot", 6)?;
430        state.serialize_field("stack", &self.stack)?;
431        state.serialize_field("memory", &self.memory.as_deref().map(Hex))?;
432        state.serialize_field("storage", &*self.storage)?;
433        state.serialize_field("storage_diff", &self.storage_diff)?;
434        // Only where a step touched it, so files without it are unchanged.
435        if self.transient_storage.is_empty() {
436            state.skip_field("transient_storage")?;
437        } else {
438            state.serialize_field("transient_storage", &self.transient_storage)?;
439        }
440        if self.return_data.is_none() {
441            state.skip_field("return_data")?;
442        } else {
443            state.serialize_field("return_data", &self.return_data.as_deref())?;
444        }
445        state.end()
446    }
447}
448
449#[derive(Deserialize, Default)]
450struct StepSnapshotRepr {
451    #[serde(default)]
452    stack: Vec<Word>,
453    #[serde(default, deserialize_with = "deserialize_memory")]
454    memory: Option<Bytes>,
455    #[serde(default)]
456    storage: BTreeMap<String, String>,
457    #[serde(default)]
458    storage_diff: BTreeMap<String, StorageChange>,
459    #[serde(default)]
460    transient_storage: BTreeMap<String, String>,
461    #[serde(default)]
462    return_data: Option<String>,
463}
464
465impl From<StepSnapshotRepr> for StepSnapshot {
466    fn from(repr: StepSnapshotRepr) -> Self {
467        Self::new(repr.stack, repr.memory, repr.storage, repr.storage_diff)
468            .with_transient_storage(repr.transient_storage)
469            .with_return_data(repr.return_data)
470    }
471}
472
473#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
474pub struct StorageChange {
475    pub before: Option<String>,
476    pub after: Option<String>,
477}
478
479/// A complete recording of one execution.
480///
481/// Its steps are a [`StepTable`], which shares what did not change between consecutive
482/// steps however they were added, so a trace read from a file costs what one built by a
483/// backend costs, at its peak as well as afterwards.
484#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
485#[serde(from = "TransactionTraceRepr")]
486pub struct TransactionTrace {
487    pub tx_hash: Option<String>,
488    pub from_addr: String,
489    pub to_addr: Option<String>,
490    pub value: String,
491    pub input_data: String,
492    pub gas_used: u64,
493    pub output: String,
494    pub success: bool,
495    pub error: Option<String>,
496    pub debug_trace_available: bool,
497    pub contract_address: Option<String>,
498    #[serde(default)]
499    pub backend: Option<String>,
500    #[serde(default)]
501    pub capabilities: TraceCapabilities,
502    #[serde(default)]
503    pub artifacts: TraceArtifacts,
504    pub steps: StepTable,
505}
506
507/// The wire shape of a trace.
508#[derive(Deserialize)]
509struct TransactionTraceRepr {
510    tx_hash: Option<String>,
511    from_addr: String,
512    to_addr: Option<String>,
513    value: String,
514    input_data: String,
515    gas_used: u64,
516    output: String,
517    success: bool,
518    error: Option<String>,
519    debug_trace_available: bool,
520    contract_address: Option<String>,
521    #[serde(default)]
522    backend: Option<String>,
523    #[serde(default)]
524    capabilities: TraceCapabilities,
525    #[serde(default)]
526    artifacts: TraceArtifacts,
527    steps: StepTable,
528}
529
530impl From<TransactionTraceRepr> for TransactionTrace {
531    fn from(repr: TransactionTraceRepr) -> Self {
532        Self {
533            tx_hash: repr.tx_hash,
534            from_addr: repr.from_addr,
535            to_addr: repr.to_addr,
536            value: repr.value,
537            input_data: repr.input_data,
538            gas_used: repr.gas_used,
539            output: repr.output,
540            success: repr.success,
541            error: repr.error,
542            debug_trace_available: repr.debug_trace_available,
543            contract_address: repr.contract_address,
544            backend: repr.backend,
545            capabilities: repr.capabilities,
546            artifacts: repr.artifacts,
547            steps: repr.steps,
548        }
549    }
550}
551
552#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
553pub struct TraceCapabilities {
554    #[serde(default)]
555    pub opcode_steps: bool,
556    #[serde(default)]
557    pub stack: bool,
558    #[serde(default)]
559    pub memory: bool,
560    #[serde(default)]
561    pub storage: bool,
562    #[serde(default)]
563    pub storage_diff: bool,
564    #[serde(default)]
565    pub call_trace: bool,
566    #[serde(default)]
567    pub contract_creation: bool,
568    #[serde(default)]
569    pub logs: bool,
570    #[serde(default)]
571    pub revert_data: bool,
572    #[serde(default)]
573    pub gas_details: bool,
574    #[serde(default)]
575    pub account_changes: bool,
576    /// Every `TLOAD`/`TSTORE` since the transaction began carries `transient_storage`.
577    #[serde(default)]
578    pub transient_storage: bool,
579    /// Every step carries the return data buffer it starts with.
580    #[serde(default)]
581    pub return_data: bool,
582    #[serde(default)]
583    pub notes: Vec<String>,
584}
585
586#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
587pub struct TraceArtifacts {
588    #[serde(default)]
589    pub calls: Vec<ExecutionCall>,
590    #[serde(default)]
591    pub creations: Vec<ContractCreation>,
592    #[serde(default)]
593    pub logs: Vec<ExecutionLog>,
594    #[serde(default)]
595    pub account_changes: Vec<AccountChange>,
596    #[serde(default)]
597    pub gas: Option<GasSummary>,
598    #[serde(default)]
599    pub revert_data: Option<String>,
600    /// What each top-level call is, for a trace with several, such as Foundry's
601    /// `deployment`, `setup` and `execution`. Display only.
602    #[serde(default, skip_serializing_if = "Vec::is_empty")]
603    pub root_kinds: Vec<RootKind>,
604}
605
606/// The kind of the top-level call that starts at `entry_step`.
607#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
608pub struct RootKind {
609    pub entry_step: usize,
610    pub kind: String,
611}
612
613#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
614pub struct ExecutionCall {
615    pub id: usize,
616    pub parent_id: Option<usize>,
617    pub depth: u64,
618    #[serde(default, skip_serializing_if = "Option::is_none")]
619    pub entry_step: Option<usize>,
620    #[serde(default, skip_serializing_if = "Option::is_none")]
621    pub exit_step: Option<usize>,
622    pub call_type: String,
623    pub from: String,
624    pub to: String,
625    pub bytecode_address: String,
626    pub value: String,
627    pub input: String,
628    pub gas_limit: u64,
629    pub gas_used: Option<u64>,
630    pub output: Option<String>,
631    pub success: Option<bool>,
632    pub error: Option<String>,
633}
634
635#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
636pub struct ContractCreation {
637    pub id: usize,
638    pub parent_id: Option<usize>,
639    pub depth: u64,
640    #[serde(default, skip_serializing_if = "Option::is_none")]
641    pub entry_step: Option<usize>,
642    #[serde(default, skip_serializing_if = "Option::is_none")]
643    pub exit_step: Option<usize>,
644    pub create_type: String,
645    pub caller: String,
646    pub address: Option<String>,
647    pub value: String,
648    pub init_code: String,
649    pub gas_limit: u64,
650    pub gas_used: Option<u64>,
651    pub output: Option<String>,
652    pub success: Option<bool>,
653    pub error: Option<String>,
654}
655
656#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
657pub struct ExecutionLog {
658    pub index: usize,
659    pub depth: u64,
660    pub address: String,
661    pub topics: Vec<String>,
662    pub data: String,
663}
664
665#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
666pub struct AccountChange {
667    pub depth: u64,
668    pub kind: String,
669    pub address: Option<String>,
670    pub from: Option<String>,
671    pub to: Option<String>,
672    pub value: Option<String>,
673    pub key: Option<String>,
674    pub previous_value: Option<String>,
675    pub previous_nonce: Option<u64>,
676}
677
678#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
679pub struct GasSummary {
680    pub used: u64,
681    pub spent: Option<u64>,
682    pub refunded: Option<u64>,
683    pub remaining: Option<u64>,
684    pub limit: Option<u64>,
685}
686
687#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
688pub struct FunctionCall {
689    pub name: String,
690    pub selector: String,
691    pub entry_step: usize,
692    pub exit_step: Option<usize>,
693    pub gas_used: u64,
694    pub depth: u64,
695    pub call_type: String,
696    pub contract_address: Option<String>,
697}
698
699/// Whether two byte views are the same bytes in the same place, which is how a shared
700/// [`Bytes`] is recognized without comparing its contents.
701#[must_use]
702pub fn same_buffer(a: &[u8], b: &[u8]) -> bool {
703    a.as_ptr() == b.as_ptr() && a.len() == b.len()
704}
705
706/// Why a string is not hex.
707#[derive(Debug, Clone, PartialEq, Eq, Error)]
708pub enum HexError {
709    /// Hex spells a byte with two digits, so a byte string has an even number of them.
710    #[error("{0} hex digits is not a whole number of bytes")]
711    OddLength(usize),
712    /// A character that is not a hex digit, at its byte offset after any `0x` prefix.
713    #[error("`{character}` at offset {offset} is not a hex digit")]
714    InvalidDigit { offset: usize, character: char },
715}
716
717/// Bytes as lowercase hex digits without a `0x` prefix, the spelling trace files use for
718/// memory.
719#[must_use]
720pub fn encode_hex(bytes: &[u8]) -> String {
721    let mut hex = String::with_capacity(bytes.len() * 2);
722    for byte in bytes {
723        hex.push(char::from(HEX_DIGITS[usize::from(byte >> 4)]));
724        hex.push(char::from(HEX_DIGITS[usize::from(byte & 0x0f)]));
725    }
726    hex
727}
728
729/// Hex digits, with or without a `0x` prefix and in either case, as bytes.
730///
731/// # Errors
732///
733/// Returns a [`HexError`] when the digits are not a whole number of bytes or a character
734/// is not a hex digit.
735pub fn decode_hex(hex: &str) -> Result<Vec<u8>, HexError> {
736    let mut bytes = Vec::new();
737    decode_hex_into(hex, &mut bytes)?;
738    Ok(bytes)
739}
740
741/// [`decode_hex`] appending to `bytes`, for a caller joining many pieces into one buffer.
742///
743/// # Errors
744///
745/// As [`decode_hex`]. On an error `bytes` may hold some of the digits decoded before it.
746pub fn decode_hex_into(hex: &str, bytes: &mut Vec<u8>) -> Result<(), HexError> {
747    let digits = hex
748        .strip_prefix("0x")
749        .or_else(|| hex.strip_prefix("0X"))
750        .unwrap_or(hex);
751    // Decoded over bytes, never by slicing the string: input from a node or a file is not
752    // guaranteed to be ASCII.
753    let raw = digits.as_bytes();
754    let invalid = |offset: usize| HexError::InvalidDigit {
755        offset,
756        // Every byte before `offset` is an ASCII digit, so `offset` is a character boundary.
757        character: digits
758            .get(offset..)
759            .and_then(|rest| rest.chars().next())
760            .unwrap_or(char::REPLACEMENT_CHARACTER),
761    };
762    bytes.reserve(raw.len() / 2);
763    let (pairs, rest) = raw.as_chunks::<2>();
764    for (index, [high, low]) in pairs.iter().enumerate() {
765        let high = hex_value(*high).ok_or_else(|| invalid(index * 2))?;
766        let low = hex_value(*low).ok_or_else(|| invalid(index * 2 + 1))?;
767        bytes.push(high << 4 | low);
768    }
769    if let [last] = rest {
770        return Err(match hex_value(*last) {
771            Some(_) => HexError::OddLength(raw.len()),
772            None => invalid(raw.len() - 1),
773        });
774    }
775    Ok(())
776}
777
778const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
779
780fn hex_value(digit: u8) -> Option<u8> {
781    match digit {
782        b'0'..=b'9' => Some(digit - b'0'),
783        b'a'..=b'f' => Some(digit - b'a' + 10),
784        b'A'..=b'F' => Some(digit - b'A' + 10),
785        _ => None,
786    }
787}
788
789/// Writes bytes as one unprefixed lowercase hex string without building the string
790/// first: a serializer that streams, as `serde_json` does, writes the digits straight
791/// into the output.
792#[derive(Clone, Copy)]
793pub(crate) struct Hex<'a>(pub(crate) &'a [u8]);
794
795impl fmt::Display for Hex<'_> {
796    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
797        let mut buffer = [0_u8; 128];
798        for chunk in self.0.chunks(buffer.len() / 2) {
799            for (index, byte) in chunk.iter().enumerate() {
800                buffer[index * 2] = HEX_DIGITS[usize::from(byte >> 4)];
801                buffer[index * 2 + 1] = HEX_DIGITS[usize::from(byte & 0x0f)];
802            }
803            let digits = std::str::from_utf8(&buffer[..chunk.len() * 2]).map_err(|_| fmt::Error)?;
804            formatter.write_str(digits)?;
805        }
806        Ok(())
807    }
808}
809
810impl Serialize for Hex<'_> {
811    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
812        serializer.collect_str(self)
813    }
814}
815
816/// Reads memory spelled as hex, the way every trace file writes it, into bytes.
817fn deserialize_memory<'de, D: Deserializer<'de>>(
818    deserializer: D,
819) -> Result<Option<Bytes>, D::Error> {
820    struct MemoryHex;
821
822    impl<'de> Visitor<'de> for MemoryHex {
823        type Value = Option<Bytes>;
824
825        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
826            formatter.write_str("memory as a hex string")
827        }
828
829        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
830            Ok(None)
831        }
832
833        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
834            Ok(None)
835        }
836
837        fn visit_some<D: Deserializer<'de>>(
838            self,
839            deserializer: D,
840        ) -> Result<Self::Value, D::Error> {
841            deserializer.deserialize_str(self)
842        }
843
844        fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
845            decode_hex(value)
846                .map(|bytes| Some(Bytes::from(bytes)))
847                .map_err(|error| E::custom(format!("memory is not hex: {error}")))
848        }
849    }
850
851    deserializer.deserialize_option(MemoryHex)
852}
853
854#[cfg(test)]
855mod tests {
856    /// Borrows a stack as plain strings, for comparing in assertions.
857
858    #[test]
859    fn repeated_stack_words_are_stored_once() {
860        let mut words = super::WordInterner::new();
861        assert!(words.is_empty());
862        let first = words.intern("0x2a");
863        let again = words.intern("0x2a");
864        let other = words.intern("0x1");
865        assert_eq!(words.len(), 2);
866        assert_eq!(&*first, "0x2a");
867        // The same value is the same allocation, which is the whole point: a trace of a
868        // few hundred thousand steps repeats a few thousand distinct words millions of
869        // times.
870        assert!(Arc::ptr_eq(&first, &again));
871        assert!(!Arc::ptr_eq(&first, &other));
872
873        let mut stack = vec![Arc::from("0x2a"), Arc::from("0x2a"), Arc::from("0x1")];
874        words.intern_stack(&mut stack);
875        assert!(Arc::ptr_eq(&stack[0], &stack[1]));
876        assert!(Arc::ptr_eq(&stack[0], &first));
877        assert_eq!(words.len(), 2, "nothing new was seen");
878    }
879
880    #[test]
881    fn a_parsed_trace_reads_its_stack_words() {
882        // Two steps holding the same words, the second in another spelling.
883        let document = serde_json::json!({
884            "tx_hash": "0xabc",
885            "from_addr": "0x1",
886            "to_addr": "0x2",
887            "value": "0x0",
888            "input_data": "0x",
889            "gas_used": 0,
890            "output": "0x",
891            "success": true,
892            "debug_trace_available": true,
893            "steps": [
894                {"pc": 0, "op": "PUSH1", "gas": 1, "gas_cost": 1, "depth": 1,
895                 "stack": ["0x2a", "0x1"]},
896                {"pc": 2, "op": "ADD", "gas": 0, "gas_cost": 1, "depth": 1,
897                 "stack": ["0x002A", "0x1"]}
898            ]
899        })
900        .to_string();
901        let trace: TransactionTrace = serde_json::from_str(&document).expect("trace");
902        let stack = |index: usize| trace.steps.step(index).stack().to_vec();
903        assert_eq!(stack(0), [EvmWord::from(0x2a), EvmWord::from(1)]);
904        assert_eq!(stack(1), stack(0));
905        // Written back in the one spelling stack words have.
906        let written = serde_json::to_value(&trace).expect("json");
907        assert_eq!(
908            written["steps"][1]["stack"],
909            serde_json::json!(["0x2a", "0x1"])
910        );
911    }
912    fn words(stack: &[super::Word]) -> Vec<&str> {
913        stack.iter().map(|word| &**word).collect()
914    }
915
916    use std::collections::BTreeMap;
917    use std::sync::Arc;
918
919    use super::{
920        decode_hex, encode_hex, same_buffer, Bytes, EvmWord, FunctionCall, HexError, StepSnapshot,
921        StorageChange, TraceArtifacts, TraceCapabilities, TraceStep, TransactionTrace,
922    };
923
924    /// Memory spelled as the hex a test reads easiest.
925    fn memory(hex: &str) -> Bytes {
926        Bytes::from(decode_hex(hex).expect("test memory is hex"))
927    }
928
929    fn step_with(snapshot: StepSnapshot) -> TraceStep {
930        TraceStep {
931            pc: 3,
932            op: Arc::from("SSTORE"),
933            gas: 100,
934            gas_cost: 5,
935            depth: 1,
936            stack: vec![Arc::from("0x01"), Arc::from("0x02")],
937            memory: Some(memory("aabb")),
938            storage: Some(BTreeMap::from([("0x00".to_owned(), "0x2a".to_owned())])),
939            error: None,
940            snapshot,
941        }
942    }
943
944    #[test]
945    fn borrowed_snapshot_matches_the_owned_one() {
946        // `snapshot_ref` is what every trace walk uses, so it has to be indistinguishable
947        // from `normalized_snapshot` in both shapes a step can take: snapshot populated,
948        // and only the flat fields populated. The serialized forms must match too, because
949        // a saved trace (`--save-trace`) writes a snapshot per step.
950        let flat = step_with(StepSnapshot::default());
951        let populated = step_with(StepSnapshot::new(
952            vec![Arc::from("0x09")],
953            Some(memory("ccdd")),
954            BTreeMap::from([("0x01".to_owned(), "0x63".to_owned())]),
955            BTreeMap::from([(
956                "0x01".to_owned(),
957                StorageChange {
958                    before: None,
959                    after: Some("0x63".to_owned()),
960                },
961            )]),
962        ));
963
964        for step in [&flat, &populated] {
965            assert_eq!(
966                step.snapshot_ref().to_owned_snapshot(),
967                step.normalized_snapshot()
968            );
969            assert_eq!(
970                serde_json::to_string(&step.snapshot_ref()).expect("borrowed snapshot"),
971                serde_json::to_string(&step.normalized_snapshot()).expect("owned snapshot"),
972            );
973        }
974
975        // The flat shape falls back to the top-level fields and reports no storage diff.
976        assert_eq!(words(flat.snapshot_ref().stack), ["0x01", "0x02"]);
977        assert_eq!(flat.snapshot_ref().memory, Some(&[0xaa, 0xbb][..]));
978        assert!(flat.snapshot_ref().storage_diff.is_empty());
979
980        // A populated snapshot wins over the flat fields.
981        assert_eq!(words(populated.snapshot_ref().stack), ["0x09"]);
982        assert_eq!(populated.snapshot_ref().memory, Some(&[0xcc, 0xdd][..]));
983        assert_eq!(populated.snapshot_ref().storage_diff.len(), 1);
984    }
985
986    #[test]
987    fn steps_serialize_in_the_wire_format_and_read_back_stored_once() {
988        // A step holds its state once, in the snapshot; the document still carries the
989        // flat fields older readers expect, filled from that one copy.
990        let step = TraceStep::new(
991            3,
992            Arc::from("SSTORE"),
993            100,
994            5,
995            1,
996            None,
997            StepSnapshot::new(
998                vec![Arc::from("0x01")],
999                Some(memory("aabb")),
1000                BTreeMap::from([("0x00".to_owned(), "0x2a".to_owned())]),
1001                BTreeMap::new(),
1002            ),
1003        );
1004        let json: serde_json::Value = serde_json::to_value(&step).expect("json");
1005        assert_eq!(json["stack"], serde_json::json!(["0x01"]));
1006        assert_eq!(json["memory"], "aabb");
1007        assert_eq!(json["storage"]["0x00"], "0x2a");
1008        assert_eq!(json["snapshot"]["memory"], "aabb");
1009        assert_eq!(json["snapshot"]["storage"]["0x00"], "0x2a");
1010        assert_eq!(json["gas_cost"], 5);
1011
1012        // A step written by hand in the flat shape serializes the same way.
1013        let flat = step_with(StepSnapshot::default());
1014        let flat_json: serde_json::Value = serde_json::to_value(&flat).expect("json");
1015        assert_eq!(
1016            flat_json["snapshot"]["stack"],
1017            serde_json::json!(["0x01", "0x02"])
1018        );
1019        assert_eq!(flat_json["stack"], serde_json::json!(["0x01", "0x02"]));
1020
1021        // Reading back either shape stores the state in the snapshot only, and the
1022        // result equals the original because equality compares what was recorded.
1023        let restored: TraceStep = serde_json::from_value(json).expect("step");
1024        assert_eq!(restored, step);
1025        assert!(
1026            restored.stack.is_empty() && restored.memory.is_none() && restored.storage.is_none()
1027        );
1028        let legacy: TraceStep = serde_json::from_str(
1029            r#"{"pc":1,"op":"ADD","gas":9,"gas_cost":3,"depth":1,"stack":["0x1"],"memory":"cc","storage":{"0x0":"0x1"},"error":null}"#,
1030        )
1031        .expect("legacy step");
1032        assert_eq!(words(legacy.snapshot_ref().stack), ["0x1"]);
1033        assert_eq!(legacy.snapshot_ref().memory, Some(&[0xcc][..]));
1034        assert_eq!(legacy.snapshot_ref().storage["0x0"], "0x1");
1035        assert!(legacy.stack.is_empty());
1036        assert_eq!(legacy, step_with_legacy_fields());
1037
1038        // Steps share memory and storage by reference when a backend hands them on.
1039        let shared = TraceStep::new(
1040            4,
1041            Arc::from("POP"),
1042            95,
1043            2,
1044            1,
1045            None,
1046            StepSnapshot {
1047                stack: Vec::new(),
1048                memory: step.snapshot.memory.clone(),
1049                storage: Arc::clone(&step.snapshot.storage),
1050                storage_diff: BTreeMap::new(),
1051                transient_storage: BTreeMap::new(),
1052                return_data: None,
1053            },
1054        );
1055        assert!(Arc::ptr_eq(
1056            &shared.snapshot.storage,
1057            &step.snapshot.storage
1058        ));
1059        assert_eq!(shared.snapshot_ref().memory, Some(&[0xaa, 0xbb][..]));
1060        assert!(same_buffer(
1061            shared.snapshot_ref().memory.expect("memory"),
1062            step.snapshot_ref().memory.expect("memory")
1063        ));
1064    }
1065
1066    #[test]
1067    fn transient_storage_is_written_only_where_a_step_touched_it() {
1068        let plain = TraceStep::new(
1069            0,
1070            Arc::from("ADD"),
1071            1,
1072            1,
1073            1,
1074            None,
1075            StepSnapshot::new(
1076                vec![Arc::from("0x1")],
1077                None,
1078                BTreeMap::new(),
1079                BTreeMap::new(),
1080            ),
1081        );
1082        let json = serde_json::to_value(&plain).expect("json");
1083        assert!(json["snapshot"].get("transient_storage").is_none());
1084
1085        let touched = TraceStep::new(
1086            1,
1087            Arc::from("TSTORE"),
1088            1,
1089            100,
1090            1,
1091            None,
1092            StepSnapshot::new(
1093                vec![Arc::from("0x1")],
1094                None,
1095                BTreeMap::new(),
1096                BTreeMap::new(),
1097            )
1098            .with_transient_storage(BTreeMap::from([("0x0".to_owned(), "0x2".to_owned())])),
1099        );
1100        let json = serde_json::to_value(&touched).expect("json");
1101        assert_eq!(json["snapshot"]["transient_storage"]["0x0"], "0x2");
1102        assert_eq!(
1103            serde_json::to_value(touched.snapshot_ref()).expect("borrowed"),
1104            json["snapshot"]
1105        );
1106        let restored: TraceStep = serde_json::from_value(json).expect("step");
1107        assert_eq!(restored, touched);
1108        assert_eq!(restored.snapshot_ref().transient_storage["0x0"], "0x2");
1109
1110        // A file written before the field existed reads with none, and so does its
1111        // capability.
1112        let capabilities: TraceCapabilities =
1113            serde_json::from_str(r#"{"stack":true}"#).expect("capabilities");
1114        assert!(!capabilities.transient_storage);
1115    }
1116
1117    #[test]
1118    fn return_data_is_written_only_where_the_buffer_holds_some() {
1119        let snapshot = || {
1120            StepSnapshot::new(
1121                vec![Arc::from("0x1")],
1122                None,
1123                BTreeMap::new(),
1124                BTreeMap::new(),
1125            )
1126        };
1127        let empty = TraceStep::new(
1128            0,
1129            Arc::from("CALL"),
1130            1,
1131            1,
1132            1,
1133            None,
1134            snapshot().with_return_data(Some(String::new())),
1135        );
1136        assert!(empty.snapshot.return_data.is_none());
1137        let json = serde_json::to_value(&empty).expect("json");
1138        assert!(json["snapshot"].get("return_data").is_none());
1139
1140        let filled = TraceStep::new(
1141            1,
1142            Arc::from("RETURNDATASIZE"),
1143            1,
1144            2,
1145            1,
1146            None,
1147            snapshot().with_return_data(Some("2a".to_owned())),
1148        );
1149        let json = serde_json::to_value(&filled).expect("json");
1150        assert_eq!(json["snapshot"]["return_data"], "2a");
1151        assert_eq!(
1152            serde_json::to_value(filled.snapshot_ref()).expect("borrowed"),
1153            json["snapshot"]
1154        );
1155        let restored: TraceStep = serde_json::from_value(json).expect("step");
1156        assert_eq!(restored, filled);
1157        assert_eq!(restored.snapshot_ref().return_data, Some("2a"));
1158
1159        // Consecutive steps read back from a file share one copy of the buffer.
1160        let mut trace = TransactionTrace {
1161            tx_hash: None,
1162            from_addr: "0x1".to_owned(),
1163            to_addr: None,
1164            value: "0x0".to_owned(),
1165            input_data: "0x".to_owned(),
1166            gas_used: 0,
1167            output: "0x".to_owned(),
1168            success: true,
1169            error: None,
1170            debug_trace_available: true,
1171            contract_address: None,
1172            backend: None,
1173            capabilities: TraceCapabilities::default(),
1174            artifacts: TraceArtifacts::default(),
1175            steps: vec![filled.clone(), filled].into(),
1176        };
1177        trace = serde_json::from_str(&serde_json::to_string(&trace).expect("json")).expect("trace");
1178        let data = |index: usize| trace.steps.step(index).return_data().expect("data");
1179        assert!(std::ptr::eq(data(0), data(1)));
1180        assert!(!trace.capabilities.return_data);
1181    }
1182
1183    fn step_with_legacy_fields() -> TraceStep {
1184        TraceStep {
1185            pc: 1,
1186            op: Arc::from("ADD"),
1187            gas: 9,
1188            gas_cost: 3,
1189            depth: 1,
1190            stack: vec![Arc::from("0x1")],
1191            memory: Some(memory("cc")),
1192            storage: Some(BTreeMap::from([("0x0".to_owned(), "0x1".to_owned())])),
1193            error: None,
1194            snapshot: StepSnapshot::default(),
1195        }
1196    }
1197
1198    #[test]
1199    fn reading_a_trace_back_shares_unchanged_state_between_steps() {
1200        let snapshot = |hex: &str, slot: &str| {
1201            StepSnapshot::new(
1202                Vec::new(),
1203                Some(memory(hex)),
1204                BTreeMap::from([("0x0".to_owned(), slot.to_owned())]),
1205                BTreeMap::new(),
1206            )
1207        };
1208        let step = |pc: u64, snapshot: StepSnapshot| {
1209            TraceStep::new(pc, Arc::from("PUSH1"), 1, 1, 1, None, snapshot)
1210        };
1211        let trace = TransactionTrace {
1212            tx_hash: None,
1213            from_addr: "0x1".to_owned(),
1214            to_addr: None,
1215            value: "0x0".to_owned(),
1216            input_data: "0x".to_owned(),
1217            gas_used: 0,
1218            output: "0x".to_owned(),
1219            success: true,
1220            error: None,
1221            debug_trace_available: true,
1222            contract_address: None,
1223            backend: None,
1224            capabilities: TraceCapabilities::default(),
1225            artifacts: TraceArtifacts::default(),
1226            steps: vec![
1227                step(0, snapshot("aa", "0x1")),
1228                step(1, snapshot("aa", "0x1")),
1229                step(2, snapshot("bb", "0x1")),
1230                step(3, snapshot("bb", "0x2")),
1231            ]
1232            .into(),
1233        };
1234        let json = serde_json::to_string(&trace).expect("json");
1235        let restored: TransactionTrace = serde_json::from_str(&json).expect("trace");
1236        assert_eq!(restored, trace);
1237        // Steps share what did not change however the trace was put together: read back
1238        // from a file, or built from steps that each held their own copy.
1239        for trace in [&restored, &trace] {
1240            let memory = |index: usize| trace.steps.step(index).memory().expect("memory");
1241            assert!(same_buffer(&memory(0), &memory(1)));
1242            assert!(!same_buffer(&memory(1), &memory(2)));
1243            assert!(same_buffer(&memory(2), &memory(3)));
1244            let storage = |index: usize| trace.steps.step(index).storage();
1245            assert!(std::ptr::eq(storage(0), storage(2)));
1246            assert!(!std::ptr::eq(storage(2), storage(3)));
1247        }
1248    }
1249
1250    #[test]
1251    fn core_models_are_serializable() {
1252        let trace = TransactionTrace {
1253            tx_hash: Some("0xabc".to_owned()),
1254            from_addr: "0x1".to_owned(),
1255            to_addr: Some("0x2".to_owned()),
1256            value: "0".to_owned(),
1257            input_data: "0x".to_owned(),
1258            gas_used: 1,
1259            output: "0x".to_owned(),
1260            success: true,
1261            error: None,
1262            steps: vec![TraceStep {
1263                pc: 0,
1264                op: Arc::from("STOP"),
1265                gas: 1,
1266                gas_cost: 0,
1267                depth: 0,
1268                stack: Vec::new(),
1269                memory: None,
1270                storage: None,
1271                error: None,
1272                snapshot: StepSnapshot::default(),
1273            }]
1274            .into(),
1275            debug_trace_available: true,
1276            contract_address: None,
1277            backend: Some("debug-rpc".to_owned()),
1278            capabilities: TraceCapabilities::default(),
1279            artifacts: TraceArtifacts::default(),
1280        };
1281
1282        let encoded = serde_json::to_string(&trace).expect("trace serializes");
1283        assert!(encoded.contains("\"success\":true"));
1284
1285        let call = FunctionCall {
1286            name: "runtime_dispatcher".to_owned(),
1287            selector: String::new(),
1288            entry_step: 0,
1289            exit_step: Some(0),
1290            gas_used: 1,
1291            depth: 0,
1292            call_type: "entry".to_owned(),
1293            contract_address: Some("0x2".to_owned()),
1294        };
1295        assert_eq!(call.call_type, "entry");
1296
1297        let change = StorageChange {
1298            before: None,
1299            after: Some("0x2a".to_owned()),
1300        };
1301        assert_eq!(change.after.as_deref(), Some("0x2a"));
1302    }
1303
1304    #[test]
1305    fn memory_reads_and_writes_as_the_same_hex_string() {
1306        // A trace file spells memory as one unprefixed lowercase hex string, in the flat
1307        // field and in the snapshot. Holding it as bytes must not change a byte of the
1308        // file: what is read back writes out exactly as it was read.
1309        let words = format!("{}{}", "00".repeat(31), "2a").repeat(3);
1310        let document = serde_json::json!({
1311            "tx_hash": "0xabc",
1312            "from_addr": "0x1",
1313            "to_addr": "0x2",
1314            "value": "0x0",
1315            "input_data": "0x",
1316            "gas_used": 0,
1317            "output": "0x",
1318            "success": true,
1319            "error": null,
1320            "debug_trace_available": true,
1321            "contract_address": null,
1322            "backend": "replay",
1323            "capabilities": TraceCapabilities::default(),
1324            "artifacts": TraceArtifacts::default(),
1325            "steps": [
1326                {"pc": 0, "op": "MSTORE", "gas": 1, "gas_cost": 1, "depth": 1,
1327                 "stack": [], "memory": "", "storage": {}, "error": null,
1328                 "snapshot": {"stack": [], "memory": "", "storage": {}, "storage_diff": {}}},
1329                {"pc": 1, "op": "MLOAD", "gas": 1, "gas_cost": 1, "depth": 1,
1330                 "stack": [], "memory": words, "storage": {}, "error": null,
1331                 "snapshot": {"stack": [], "memory": words, "storage": {}, "storage_diff": {}}},
1332                {"pc": 2, "op": "POP", "gas": 1, "gas_cost": 1, "depth": 1,
1333                 "stack": [], "memory": words, "storage": {}, "error": null,
1334                 "snapshot": {"stack": [], "memory": words, "storage": {}, "storage_diff": {}}},
1335                {"pc": 3, "op": "STOP", "gas": 1, "gas_cost": 0, "depth": 1,
1336                 "stack": [], "memory": null, "storage": {}, "error": null,
1337                 "snapshot": {"stack": [], "memory": null, "storage": {}, "storage_diff": {}}}
1338            ]
1339        })
1340        .to_string();
1341        let trace: TransactionTrace = serde_json::from_str(&document).expect("trace");
1342        let written = serde_json::to_string(&trace).expect("json");
1343        assert_eq!(
1344            serde_json::from_str::<serde_json::Value>(&written).expect("written json"),
1345            serde_json::from_str::<serde_json::Value>(&document).expect("document json"),
1346        );
1347        // Byte for byte, once the keys are in the order the writer puts them.
1348        let again: TransactionTrace = serde_json::from_str(&written).expect("trace");
1349        assert_eq!(serde_json::to_string(&again).expect("json"), written);
1350
1351        let memory = |index: usize| trace.steps.step(index).memory();
1352        assert_eq!(memory(0).as_deref(), Some(&[][..]));
1353        assert_eq!(memory(1).map(|memory| memory.len()), Some(96));
1354        assert_eq!(
1355            memory(1).and_then(|memory| memory.get(31).copied()),
1356            Some(0x2a)
1357        );
1358        assert_eq!(memory(3), None);
1359        // The unchanged memory of the third step is the second step's buffer.
1360        assert!(same_buffer(
1361            &memory(1).expect("memory"),
1362            &memory(2).expect("memory")
1363        ));
1364    }
1365
1366    #[test]
1367    fn memory_from_a_file_may_be_prefixed_but_must_be_hex() {
1368        let step = |memory: &str| {
1369            serde_json::from_str::<TraceStep>(&format!(
1370                r#"{{"pc":0,"op":"STOP","gas":0,"gas_cost":0,"depth":1,"memory":{memory}}}"#
1371            ))
1372        };
1373        let prefixed = step(r#""0xAABB""#).expect("prefixed memory");
1374        assert_eq!(prefixed.snapshot_ref().memory, Some(&[0xaa, 0xbb][..]));
1375        // Written back in the one canonical spelling.
1376        assert_eq!(
1377            serde_json::to_value(&prefixed).expect("json")["memory"],
1378            "aabb"
1379        );
1380
1381        let odd = step(r#""abc""#).expect_err("odd length");
1382        assert!(odd
1383            .to_string()
1384            .starts_with("memory is not hex: 3 hex digits"));
1385        let invalid = step(r#""aazz""#).expect_err("not hex");
1386        assert!(invalid
1387            .to_string()
1388            .starts_with("memory is not hex: `z` at offset 2"));
1389    }
1390
1391    #[test]
1392    fn hex_round_trips_and_names_what_is_wrong() {
1393        assert_eq!(encode_hex(&[0x00, 0x2a, 0xff]), "002aff");
1394        assert_eq!(decode_hex("002AfF"), Ok(vec![0x00, 0x2a, 0xff]));
1395        assert_eq!(decode_hex("0x"), Ok(Vec::new()));
1396        assert_eq!(decode_hex(""), Ok(Vec::new()));
1397        assert_eq!(decode_hex("0x2"), Err(HexError::OddLength(1)));
1398        assert_eq!(
1399            decode_hex("2x"),
1400            Err(HexError::InvalidDigit {
1401                offset: 1,
1402                character: 'x'
1403            })
1404        );
1405        // A multi-byte character is reported whole rather than split.
1406        assert_eq!(
1407            decode_hex("aé"),
1408            Err(HexError::InvalidDigit {
1409                offset: 1,
1410                character: 'é'
1411            })
1412        );
1413    }
1414}