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