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