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    /// The keccak-256 hash of the code the frame runs, when the backend knows it. It tells
634    /// apart programs that run at one address, such as code replaced by a test cheatcode.
635    #[serde(default, skip_serializing_if = "Option::is_none")]
636    pub code_hash: Option<String>,
637}
638
639#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
640pub struct ContractCreation {
641    pub id: usize,
642    pub parent_id: Option<usize>,
643    pub depth: u64,
644    #[serde(default, skip_serializing_if = "Option::is_none")]
645    pub entry_step: Option<usize>,
646    #[serde(default, skip_serializing_if = "Option::is_none")]
647    pub exit_step: Option<usize>,
648    pub create_type: String,
649    pub caller: String,
650    pub address: Option<String>,
651    pub value: String,
652    pub init_code: String,
653    pub gas_limit: u64,
654    pub gas_used: Option<u64>,
655    pub output: Option<String>,
656    pub success: Option<bool>,
657    pub error: Option<String>,
658    /// The keccak-256 hash of the code the frame runs, when the backend knows it. It tells
659    /// apart programs that run at one address, such as code replaced by a test cheatcode.
660    #[serde(default, skip_serializing_if = "Option::is_none")]
661    pub code_hash: Option<String>,
662}
663
664#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
665pub struct ExecutionLog {
666    pub index: usize,
667    pub depth: u64,
668    pub address: String,
669    pub topics: Vec<String>,
670    pub data: String,
671}
672
673#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
674pub struct AccountChange {
675    pub depth: u64,
676    pub kind: String,
677    pub address: Option<String>,
678    pub from: Option<String>,
679    pub to: Option<String>,
680    pub value: Option<String>,
681    pub key: Option<String>,
682    pub previous_value: Option<String>,
683    pub previous_nonce: Option<u64>,
684}
685
686#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
687pub struct GasSummary {
688    pub used: u64,
689    pub spent: Option<u64>,
690    pub refunded: Option<u64>,
691    pub remaining: Option<u64>,
692    pub limit: Option<u64>,
693}
694
695#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
696pub struct FunctionCall {
697    pub name: String,
698    pub selector: String,
699    pub entry_step: usize,
700    pub exit_step: Option<usize>,
701    pub gas_used: u64,
702    pub depth: u64,
703    pub call_type: String,
704    pub contract_address: Option<String>,
705}
706
707/// Whether two byte views are the same bytes in the same place, which is how a shared
708/// [`Bytes`] is recognized without comparing its contents.
709#[must_use]
710pub fn same_buffer(a: &[u8], b: &[u8]) -> bool {
711    a.as_ptr() == b.as_ptr() && a.len() == b.len()
712}
713
714/// Why a string is not hex.
715#[derive(Debug, Clone, PartialEq, Eq, Error)]
716pub enum HexError {
717    /// Hex spells a byte with two digits, so a byte string has an even number of them.
718    #[error("{0} hex digits is not a whole number of bytes")]
719    OddLength(usize),
720    /// A character that is not a hex digit, at its byte offset after any `0x` prefix.
721    #[error("`{character}` at offset {offset} is not a hex digit")]
722    InvalidDigit { offset: usize, character: char },
723}
724
725/// Bytes as lowercase hex digits without a `0x` prefix, the spelling trace files use for
726/// memory.
727#[must_use]
728pub fn encode_hex(bytes: &[u8]) -> String {
729    let mut hex = String::with_capacity(bytes.len() * 2);
730    for byte in bytes {
731        hex.push(char::from(HEX_DIGITS[usize::from(byte >> 4)]));
732        hex.push(char::from(HEX_DIGITS[usize::from(byte & 0x0f)]));
733    }
734    hex
735}
736
737/// Hex digits, with or without a `0x` prefix and in either case, as bytes.
738///
739/// # Errors
740///
741/// Returns a [`HexError`] when the digits are not a whole number of bytes or a character
742/// is not a hex digit.
743pub fn decode_hex(hex: &str) -> Result<Vec<u8>, HexError> {
744    let mut bytes = Vec::new();
745    decode_hex_into(hex, &mut bytes)?;
746    Ok(bytes)
747}
748
749/// [`decode_hex`] appending to `bytes`, for a caller joining many pieces into one buffer.
750///
751/// # Errors
752///
753/// As [`decode_hex`]. On an error `bytes` may hold some of the digits decoded before it.
754pub fn decode_hex_into(hex: &str, bytes: &mut Vec<u8>) -> Result<(), HexError> {
755    let digits = hex
756        .strip_prefix("0x")
757        .or_else(|| hex.strip_prefix("0X"))
758        .unwrap_or(hex);
759    // Decoded over bytes, never by slicing the string: input from a node or a file is not
760    // guaranteed to be ASCII.
761    let raw = digits.as_bytes();
762    let invalid = |offset: usize| HexError::InvalidDigit {
763        offset,
764        // Every byte before `offset` is an ASCII digit, so `offset` is a character boundary.
765        character: digits
766            .get(offset..)
767            .and_then(|rest| rest.chars().next())
768            .unwrap_or(char::REPLACEMENT_CHARACTER),
769    };
770    bytes.reserve(raw.len() / 2);
771    let (pairs, rest) = raw.as_chunks::<2>();
772    for (index, [high, low]) in pairs.iter().enumerate() {
773        let high = hex_value(*high).ok_or_else(|| invalid(index * 2))?;
774        let low = hex_value(*low).ok_or_else(|| invalid(index * 2 + 1))?;
775        bytes.push(high << 4 | low);
776    }
777    if let [last] = rest {
778        return Err(match hex_value(*last) {
779            Some(_) => HexError::OddLength(raw.len()),
780            None => invalid(raw.len() - 1),
781        });
782    }
783    Ok(())
784}
785
786const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
787
788fn hex_value(digit: u8) -> Option<u8> {
789    match digit {
790        b'0'..=b'9' => Some(digit - b'0'),
791        b'a'..=b'f' => Some(digit - b'a' + 10),
792        b'A'..=b'F' => Some(digit - b'A' + 10),
793        _ => None,
794    }
795}
796
797/// Writes bytes as one unprefixed lowercase hex string without building the string
798/// first: a serializer that streams, as `serde_json` does, writes the digits straight
799/// into the output.
800#[derive(Clone, Copy)]
801pub(crate) struct Hex<'a>(pub(crate) &'a [u8]);
802
803impl fmt::Display for Hex<'_> {
804    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
805        let mut buffer = [0_u8; 128];
806        for chunk in self.0.chunks(buffer.len() / 2) {
807            for (index, byte) in chunk.iter().enumerate() {
808                buffer[index * 2] = HEX_DIGITS[usize::from(byte >> 4)];
809                buffer[index * 2 + 1] = HEX_DIGITS[usize::from(byte & 0x0f)];
810            }
811            let digits = std::str::from_utf8(&buffer[..chunk.len() * 2]).map_err(|_| fmt::Error)?;
812            formatter.write_str(digits)?;
813        }
814        Ok(())
815    }
816}
817
818impl Serialize for Hex<'_> {
819    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
820        serializer.collect_str(self)
821    }
822}
823
824/// Reads memory spelled as hex, the way every trace file writes it, into bytes.
825fn deserialize_memory<'de, D: Deserializer<'de>>(
826    deserializer: D,
827) -> Result<Option<Bytes>, D::Error> {
828    struct MemoryHex;
829
830    impl<'de> Visitor<'de> for MemoryHex {
831        type Value = Option<Bytes>;
832
833        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
834            formatter.write_str("memory as a hex string")
835        }
836
837        fn visit_none<E: de::Error>(self) -> Result<Self::Value, E> {
838            Ok(None)
839        }
840
841        fn visit_unit<E: de::Error>(self) -> Result<Self::Value, E> {
842            Ok(None)
843        }
844
845        fn visit_some<D: Deserializer<'de>>(
846            self,
847            deserializer: D,
848        ) -> Result<Self::Value, D::Error> {
849            deserializer.deserialize_str(self)
850        }
851
852        fn visit_str<E: de::Error>(self, value: &str) -> Result<Self::Value, E> {
853            decode_hex(value)
854                .map(|bytes| Some(Bytes::from(bytes)))
855                .map_err(|error| E::custom(format!("memory is not hex: {error}")))
856        }
857    }
858
859    deserializer.deserialize_option(MemoryHex)
860}
861
862#[cfg(test)]
863mod tests {
864    /// Borrows a stack as plain strings, for comparing in assertions.
865
866    #[test]
867    fn repeated_stack_words_are_stored_once() {
868        let mut words = super::WordInterner::new();
869        assert!(words.is_empty());
870        let first = words.intern("0x2a");
871        let again = words.intern("0x2a");
872        let other = words.intern("0x1");
873        assert_eq!(words.len(), 2);
874        assert_eq!(&*first, "0x2a");
875        // The same value is the same allocation, which is the whole point: a trace of a
876        // few hundred thousand steps repeats a few thousand distinct words millions of
877        // times.
878        assert!(Arc::ptr_eq(&first, &again));
879        assert!(!Arc::ptr_eq(&first, &other));
880
881        let mut stack = vec![Arc::from("0x2a"), Arc::from("0x2a"), Arc::from("0x1")];
882        words.intern_stack(&mut stack);
883        assert!(Arc::ptr_eq(&stack[0], &stack[1]));
884        assert!(Arc::ptr_eq(&stack[0], &first));
885        assert_eq!(words.len(), 2, "nothing new was seen");
886    }
887
888    #[test]
889    fn a_parsed_trace_reads_its_stack_words() {
890        // Two steps holding the same words, the second in another spelling.
891        let document = serde_json::json!({
892            "tx_hash": "0xabc",
893            "from_addr": "0x1",
894            "to_addr": "0x2",
895            "value": "0x0",
896            "input_data": "0x",
897            "gas_used": 0,
898            "output": "0x",
899            "success": true,
900            "debug_trace_available": true,
901            "steps": [
902                {"pc": 0, "op": "PUSH1", "gas": 1, "gas_cost": 1, "depth": 1,
903                 "stack": ["0x2a", "0x1"]},
904                {"pc": 2, "op": "ADD", "gas": 0, "gas_cost": 1, "depth": 1,
905                 "stack": ["0x002A", "0x1"]}
906            ]
907        })
908        .to_string();
909        let trace: TransactionTrace = serde_json::from_str(&document).expect("trace");
910        let stack = |index: usize| trace.steps.step(index).stack().to_vec();
911        assert_eq!(stack(0), [EvmWord::from(0x2a), EvmWord::from(1)]);
912        assert_eq!(stack(1), stack(0));
913        // Written back in the one spelling stack words have.
914        let written = serde_json::to_value(&trace).expect("json");
915        assert_eq!(
916            written["steps"][1]["stack"],
917            serde_json::json!(["0x2a", "0x1"])
918        );
919    }
920    fn words(stack: &[super::Word]) -> Vec<&str> {
921        stack.iter().map(|word| &**word).collect()
922    }
923
924    use std::collections::BTreeMap;
925    use std::sync::Arc;
926
927    use super::{
928        decode_hex, encode_hex, same_buffer, Bytes, EvmWord, FunctionCall, HexError, StepSnapshot,
929        StorageChange, TraceArtifacts, TraceCapabilities, TraceStep, TransactionTrace,
930    };
931
932    /// Memory spelled as the hex a test reads easiest.
933    fn memory(hex: &str) -> Bytes {
934        Bytes::from(decode_hex(hex).expect("test memory is hex"))
935    }
936
937    fn step_with(snapshot: StepSnapshot) -> TraceStep {
938        TraceStep {
939            pc: 3,
940            op: Arc::from("SSTORE"),
941            gas: 100,
942            gas_cost: 5,
943            depth: 1,
944            stack: vec![Arc::from("0x01"), Arc::from("0x02")],
945            memory: Some(memory("aabb")),
946            storage: Some(BTreeMap::from([("0x00".to_owned(), "0x2a".to_owned())])),
947            error: None,
948            snapshot,
949        }
950    }
951
952    #[test]
953    fn borrowed_snapshot_matches_the_owned_one() {
954        // `snapshot_ref` is what every trace walk uses, so it has to be indistinguishable
955        // from `normalized_snapshot` in both shapes a step can take: snapshot populated,
956        // and only the flat fields populated. The serialized forms must match too, because
957        // a saved trace (`--save-trace`) writes a snapshot per step.
958        let flat = step_with(StepSnapshot::default());
959        let populated = step_with(StepSnapshot::new(
960            vec![Arc::from("0x09")],
961            Some(memory("ccdd")),
962            BTreeMap::from([("0x01".to_owned(), "0x63".to_owned())]),
963            BTreeMap::from([(
964                "0x01".to_owned(),
965                StorageChange {
966                    before: None,
967                    after: Some("0x63".to_owned()),
968                },
969            )]),
970        ));
971
972        for step in [&flat, &populated] {
973            assert_eq!(
974                step.snapshot_ref().to_owned_snapshot(),
975                step.normalized_snapshot()
976            );
977            assert_eq!(
978                serde_json::to_string(&step.snapshot_ref()).expect("borrowed snapshot"),
979                serde_json::to_string(&step.normalized_snapshot()).expect("owned snapshot"),
980            );
981        }
982
983        // The flat shape falls back to the top-level fields and reports no storage diff.
984        assert_eq!(words(flat.snapshot_ref().stack), ["0x01", "0x02"]);
985        assert_eq!(flat.snapshot_ref().memory, Some(&[0xaa, 0xbb][..]));
986        assert!(flat.snapshot_ref().storage_diff.is_empty());
987
988        // A populated snapshot wins over the flat fields.
989        assert_eq!(words(populated.snapshot_ref().stack), ["0x09"]);
990        assert_eq!(populated.snapshot_ref().memory, Some(&[0xcc, 0xdd][..]));
991        assert_eq!(populated.snapshot_ref().storage_diff.len(), 1);
992    }
993
994    #[test]
995    fn steps_serialize_in_the_wire_format_and_read_back_stored_once() {
996        // A step holds its state once, in the snapshot; the document still carries the
997        // flat fields older readers expect, filled from that one copy.
998        let step = TraceStep::new(
999            3,
1000            Arc::from("SSTORE"),
1001            100,
1002            5,
1003            1,
1004            None,
1005            StepSnapshot::new(
1006                vec![Arc::from("0x01")],
1007                Some(memory("aabb")),
1008                BTreeMap::from([("0x00".to_owned(), "0x2a".to_owned())]),
1009                BTreeMap::new(),
1010            ),
1011        );
1012        let json: serde_json::Value = serde_json::to_value(&step).expect("json");
1013        assert_eq!(json["stack"], serde_json::json!(["0x01"]));
1014        assert_eq!(json["memory"], "aabb");
1015        assert_eq!(json["storage"]["0x00"], "0x2a");
1016        assert_eq!(json["snapshot"]["memory"], "aabb");
1017        assert_eq!(json["snapshot"]["storage"]["0x00"], "0x2a");
1018        assert_eq!(json["gas_cost"], 5);
1019
1020        // A step written by hand in the flat shape serializes the same way.
1021        let flat = step_with(StepSnapshot::default());
1022        let flat_json: serde_json::Value = serde_json::to_value(&flat).expect("json");
1023        assert_eq!(
1024            flat_json["snapshot"]["stack"],
1025            serde_json::json!(["0x01", "0x02"])
1026        );
1027        assert_eq!(flat_json["stack"], serde_json::json!(["0x01", "0x02"]));
1028
1029        // Reading back either shape stores the state in the snapshot only, and the
1030        // result equals the original because equality compares what was recorded.
1031        let restored: TraceStep = serde_json::from_value(json).expect("step");
1032        assert_eq!(restored, step);
1033        assert!(
1034            restored.stack.is_empty() && restored.memory.is_none() && restored.storage.is_none()
1035        );
1036        let legacy: TraceStep = serde_json::from_str(
1037            r#"{"pc":1,"op":"ADD","gas":9,"gas_cost":3,"depth":1,"stack":["0x1"],"memory":"cc","storage":{"0x0":"0x1"},"error":null}"#,
1038        )
1039        .expect("legacy step");
1040        assert_eq!(words(legacy.snapshot_ref().stack), ["0x1"]);
1041        assert_eq!(legacy.snapshot_ref().memory, Some(&[0xcc][..]));
1042        assert_eq!(legacy.snapshot_ref().storage["0x0"], "0x1");
1043        assert!(legacy.stack.is_empty());
1044        assert_eq!(legacy, step_with_legacy_fields());
1045
1046        // Steps share memory and storage by reference when a backend hands them on.
1047        let shared = TraceStep::new(
1048            4,
1049            Arc::from("POP"),
1050            95,
1051            2,
1052            1,
1053            None,
1054            StepSnapshot {
1055                stack: Vec::new(),
1056                memory: step.snapshot.memory.clone(),
1057                storage: Arc::clone(&step.snapshot.storage),
1058                storage_diff: BTreeMap::new(),
1059                transient_storage: BTreeMap::new(),
1060                return_data: None,
1061            },
1062        );
1063        assert!(Arc::ptr_eq(
1064            &shared.snapshot.storage,
1065            &step.snapshot.storage
1066        ));
1067        assert_eq!(shared.snapshot_ref().memory, Some(&[0xaa, 0xbb][..]));
1068        assert!(same_buffer(
1069            shared.snapshot_ref().memory.expect("memory"),
1070            step.snapshot_ref().memory.expect("memory")
1071        ));
1072    }
1073
1074    #[test]
1075    fn transient_storage_is_written_only_where_a_step_touched_it() {
1076        let plain = TraceStep::new(
1077            0,
1078            Arc::from("ADD"),
1079            1,
1080            1,
1081            1,
1082            None,
1083            StepSnapshot::new(
1084                vec![Arc::from("0x1")],
1085                None,
1086                BTreeMap::new(),
1087                BTreeMap::new(),
1088            ),
1089        );
1090        let json = serde_json::to_value(&plain).expect("json");
1091        assert!(json["snapshot"].get("transient_storage").is_none());
1092
1093        let touched = TraceStep::new(
1094            1,
1095            Arc::from("TSTORE"),
1096            1,
1097            100,
1098            1,
1099            None,
1100            StepSnapshot::new(
1101                vec![Arc::from("0x1")],
1102                None,
1103                BTreeMap::new(),
1104                BTreeMap::new(),
1105            )
1106            .with_transient_storage(BTreeMap::from([("0x0".to_owned(), "0x2".to_owned())])),
1107        );
1108        let json = serde_json::to_value(&touched).expect("json");
1109        assert_eq!(json["snapshot"]["transient_storage"]["0x0"], "0x2");
1110        assert_eq!(
1111            serde_json::to_value(touched.snapshot_ref()).expect("borrowed"),
1112            json["snapshot"]
1113        );
1114        let restored: TraceStep = serde_json::from_value(json).expect("step");
1115        assert_eq!(restored, touched);
1116        assert_eq!(restored.snapshot_ref().transient_storage["0x0"], "0x2");
1117
1118        // A file written before the field existed reads with none, and so does its
1119        // capability.
1120        let capabilities: TraceCapabilities =
1121            serde_json::from_str(r#"{"stack":true}"#).expect("capabilities");
1122        assert!(!capabilities.transient_storage);
1123    }
1124
1125    #[test]
1126    fn return_data_is_written_only_where_the_buffer_holds_some() {
1127        let snapshot = || {
1128            StepSnapshot::new(
1129                vec![Arc::from("0x1")],
1130                None,
1131                BTreeMap::new(),
1132                BTreeMap::new(),
1133            )
1134        };
1135        let empty = TraceStep::new(
1136            0,
1137            Arc::from("CALL"),
1138            1,
1139            1,
1140            1,
1141            None,
1142            snapshot().with_return_data(Some(String::new())),
1143        );
1144        assert!(empty.snapshot.return_data.is_none());
1145        let json = serde_json::to_value(&empty).expect("json");
1146        assert!(json["snapshot"].get("return_data").is_none());
1147
1148        let filled = TraceStep::new(
1149            1,
1150            Arc::from("RETURNDATASIZE"),
1151            1,
1152            2,
1153            1,
1154            None,
1155            snapshot().with_return_data(Some("2a".to_owned())),
1156        );
1157        let json = serde_json::to_value(&filled).expect("json");
1158        assert_eq!(json["snapshot"]["return_data"], "2a");
1159        assert_eq!(
1160            serde_json::to_value(filled.snapshot_ref()).expect("borrowed"),
1161            json["snapshot"]
1162        );
1163        let restored: TraceStep = serde_json::from_value(json).expect("step");
1164        assert_eq!(restored, filled);
1165        assert_eq!(restored.snapshot_ref().return_data, Some("2a"));
1166
1167        // Consecutive steps read back from a file share one copy of the buffer.
1168        let mut trace = TransactionTrace {
1169            tx_hash: None,
1170            from_addr: "0x1".to_owned(),
1171            to_addr: None,
1172            value: "0x0".to_owned(),
1173            input_data: "0x".to_owned(),
1174            gas_used: 0,
1175            output: "0x".to_owned(),
1176            success: true,
1177            error: None,
1178            debug_trace_available: true,
1179            contract_address: None,
1180            backend: None,
1181            capabilities: TraceCapabilities::default(),
1182            artifacts: TraceArtifacts::default(),
1183            steps: vec![filled.clone(), filled].into(),
1184        };
1185        trace = serde_json::from_str(&serde_json::to_string(&trace).expect("json")).expect("trace");
1186        let data = |index: usize| trace.steps.step(index).return_data().expect("data");
1187        assert!(std::ptr::eq(data(0), data(1)));
1188        assert!(!trace.capabilities.return_data);
1189    }
1190
1191    fn step_with_legacy_fields() -> TraceStep {
1192        TraceStep {
1193            pc: 1,
1194            op: Arc::from("ADD"),
1195            gas: 9,
1196            gas_cost: 3,
1197            depth: 1,
1198            stack: vec![Arc::from("0x1")],
1199            memory: Some(memory("cc")),
1200            storage: Some(BTreeMap::from([("0x0".to_owned(), "0x1".to_owned())])),
1201            error: None,
1202            snapshot: StepSnapshot::default(),
1203        }
1204    }
1205
1206    #[test]
1207    fn reading_a_trace_back_shares_unchanged_state_between_steps() {
1208        let snapshot = |hex: &str, slot: &str| {
1209            StepSnapshot::new(
1210                Vec::new(),
1211                Some(memory(hex)),
1212                BTreeMap::from([("0x0".to_owned(), slot.to_owned())]),
1213                BTreeMap::new(),
1214            )
1215        };
1216        let step = |pc: u64, snapshot: StepSnapshot| {
1217            TraceStep::new(pc, Arc::from("PUSH1"), 1, 1, 1, None, snapshot)
1218        };
1219        let trace = TransactionTrace {
1220            tx_hash: None,
1221            from_addr: "0x1".to_owned(),
1222            to_addr: None,
1223            value: "0x0".to_owned(),
1224            input_data: "0x".to_owned(),
1225            gas_used: 0,
1226            output: "0x".to_owned(),
1227            success: true,
1228            error: None,
1229            debug_trace_available: true,
1230            contract_address: None,
1231            backend: None,
1232            capabilities: TraceCapabilities::default(),
1233            artifacts: TraceArtifacts::default(),
1234            steps: vec![
1235                step(0, snapshot("aa", "0x1")),
1236                step(1, snapshot("aa", "0x1")),
1237                step(2, snapshot("bb", "0x1")),
1238                step(3, snapshot("bb", "0x2")),
1239            ]
1240            .into(),
1241        };
1242        let json = serde_json::to_string(&trace).expect("json");
1243        let restored: TransactionTrace = serde_json::from_str(&json).expect("trace");
1244        assert_eq!(restored, trace);
1245        // Steps share what did not change however the trace was put together: read back
1246        // from a file, or built from steps that each held their own copy.
1247        for trace in [&restored, &trace] {
1248            let memory = |index: usize| trace.steps.step(index).memory().expect("memory");
1249            assert!(same_buffer(&memory(0), &memory(1)));
1250            assert!(!same_buffer(&memory(1), &memory(2)));
1251            assert!(same_buffer(&memory(2), &memory(3)));
1252            let storage = |index: usize| trace.steps.step(index).storage();
1253            assert!(std::ptr::eq(storage(0), storage(2)));
1254            assert!(!std::ptr::eq(storage(2), storage(3)));
1255        }
1256    }
1257
1258    #[test]
1259    fn core_models_are_serializable() {
1260        let trace = TransactionTrace {
1261            tx_hash: Some("0xabc".to_owned()),
1262            from_addr: "0x1".to_owned(),
1263            to_addr: Some("0x2".to_owned()),
1264            value: "0".to_owned(),
1265            input_data: "0x".to_owned(),
1266            gas_used: 1,
1267            output: "0x".to_owned(),
1268            success: true,
1269            error: None,
1270            steps: vec![TraceStep {
1271                pc: 0,
1272                op: Arc::from("STOP"),
1273                gas: 1,
1274                gas_cost: 0,
1275                depth: 0,
1276                stack: Vec::new(),
1277                memory: None,
1278                storage: None,
1279                error: None,
1280                snapshot: StepSnapshot::default(),
1281            }]
1282            .into(),
1283            debug_trace_available: true,
1284            contract_address: None,
1285            backend: Some("debug-rpc".to_owned()),
1286            capabilities: TraceCapabilities::default(),
1287            artifacts: TraceArtifacts::default(),
1288        };
1289
1290        let encoded = serde_json::to_string(&trace).expect("trace serializes");
1291        assert!(encoded.contains("\"success\":true"));
1292
1293        let call = FunctionCall {
1294            name: "runtime_dispatcher".to_owned(),
1295            selector: String::new(),
1296            entry_step: 0,
1297            exit_step: Some(0),
1298            gas_used: 1,
1299            depth: 0,
1300            call_type: "entry".to_owned(),
1301            contract_address: Some("0x2".to_owned()),
1302        };
1303        assert_eq!(call.call_type, "entry");
1304
1305        let change = StorageChange {
1306            before: None,
1307            after: Some("0x2a".to_owned()),
1308        };
1309        assert_eq!(change.after.as_deref(), Some("0x2a"));
1310    }
1311
1312    #[test]
1313    fn memory_reads_and_writes_as_the_same_hex_string() {
1314        // A trace file spells memory as one unprefixed lowercase hex string, in the flat
1315        // field and in the snapshot. Holding it as bytes must not change a byte of the
1316        // file: what is read back writes out exactly as it was read.
1317        let words = format!("{}{}", "00".repeat(31), "2a").repeat(3);
1318        let document = serde_json::json!({
1319            "tx_hash": "0xabc",
1320            "from_addr": "0x1",
1321            "to_addr": "0x2",
1322            "value": "0x0",
1323            "input_data": "0x",
1324            "gas_used": 0,
1325            "output": "0x",
1326            "success": true,
1327            "error": null,
1328            "debug_trace_available": true,
1329            "contract_address": null,
1330            "backend": "replay",
1331            "capabilities": TraceCapabilities::default(),
1332            "artifacts": TraceArtifacts::default(),
1333            "steps": [
1334                {"pc": 0, "op": "MSTORE", "gas": 1, "gas_cost": 1, "depth": 1,
1335                 "stack": [], "memory": "", "storage": {}, "error": null,
1336                 "snapshot": {"stack": [], "memory": "", "storage": {}, "storage_diff": {}}},
1337                {"pc": 1, "op": "MLOAD", "gas": 1, "gas_cost": 1, "depth": 1,
1338                 "stack": [], "memory": words, "storage": {}, "error": null,
1339                 "snapshot": {"stack": [], "memory": words, "storage": {}, "storage_diff": {}}},
1340                {"pc": 2, "op": "POP", "gas": 1, "gas_cost": 1, "depth": 1,
1341                 "stack": [], "memory": words, "storage": {}, "error": null,
1342                 "snapshot": {"stack": [], "memory": words, "storage": {}, "storage_diff": {}}},
1343                {"pc": 3, "op": "STOP", "gas": 1, "gas_cost": 0, "depth": 1,
1344                 "stack": [], "memory": null, "storage": {}, "error": null,
1345                 "snapshot": {"stack": [], "memory": null, "storage": {}, "storage_diff": {}}}
1346            ]
1347        })
1348        .to_string();
1349        let trace: TransactionTrace = serde_json::from_str(&document).expect("trace");
1350        let written = serde_json::to_string(&trace).expect("json");
1351        assert_eq!(
1352            serde_json::from_str::<serde_json::Value>(&written).expect("written json"),
1353            serde_json::from_str::<serde_json::Value>(&document).expect("document json"),
1354        );
1355        // Byte for byte, once the keys are in the order the writer puts them.
1356        let again: TransactionTrace = serde_json::from_str(&written).expect("trace");
1357        assert_eq!(serde_json::to_string(&again).expect("json"), written);
1358
1359        let memory = |index: usize| trace.steps.step(index).memory();
1360        assert_eq!(memory(0).as_deref(), Some(&[][..]));
1361        assert_eq!(memory(1).map(|memory| memory.len()), Some(96));
1362        assert_eq!(
1363            memory(1).and_then(|memory| memory.get(31).copied()),
1364            Some(0x2a)
1365        );
1366        assert_eq!(memory(3), None);
1367        // The unchanged memory of the third step is the second step's buffer.
1368        assert!(same_buffer(
1369            &memory(1).expect("memory"),
1370            &memory(2).expect("memory")
1371        ));
1372    }
1373
1374    #[test]
1375    fn memory_from_a_file_may_be_prefixed_but_must_be_hex() {
1376        let step = |memory: &str| {
1377            serde_json::from_str::<TraceStep>(&format!(
1378                r#"{{"pc":0,"op":"STOP","gas":0,"gas_cost":0,"depth":1,"memory":{memory}}}"#
1379            ))
1380        };
1381        let prefixed = step(r#""0xAABB""#).expect("prefixed memory");
1382        assert_eq!(prefixed.snapshot_ref().memory, Some(&[0xaa, 0xbb][..]));
1383        // Written back in the one canonical spelling.
1384        assert_eq!(
1385            serde_json::to_value(&prefixed).expect("json")["memory"],
1386            "aabb"
1387        );
1388
1389        let odd = step(r#""abc""#).expect_err("odd length");
1390        assert!(odd
1391            .to_string()
1392            .starts_with("memory is not hex: 3 hex digits"));
1393        let invalid = step(r#""aazz""#).expect_err("not hex");
1394        assert!(invalid
1395            .to_string()
1396            .starts_with("memory is not hex: `z` at offset 2"));
1397    }
1398
1399    #[test]
1400    fn hex_round_trips_and_names_what_is_wrong() {
1401        assert_eq!(encode_hex(&[0x00, 0x2a, 0xff]), "002aff");
1402        assert_eq!(decode_hex("002AfF"), Ok(vec![0x00, 0x2a, 0xff]));
1403        assert_eq!(decode_hex("0x"), Ok(Vec::new()));
1404        assert_eq!(decode_hex(""), Ok(Vec::new()));
1405        assert_eq!(decode_hex("0x2"), Err(HexError::OddLength(1)));
1406        assert_eq!(
1407            decode_hex("2x"),
1408            Err(HexError::InvalidDigit {
1409                offset: 1,
1410                character: 'x'
1411            })
1412        );
1413        // A multi-byte character is reported whole rather than split.
1414        assert_eq!(
1415            decode_hex("aé"),
1416            Err(HexError::InvalidDigit {
1417                offset: 1,
1418                character: 'é'
1419            })
1420        );
1421    }
1422}