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