Skip to main content

soldb_debugger/
stepping.rs

1//! Source-level stepping over a recorded trace.
2//!
3//! A trace is a list of EVM instructions. A source-level debugger needs to know, for each
4//! of them, which source line it belongs to and how deep in the call structure it runs,
5//! and then answer questions such as "where does the next line start?" in both
6//! directions. [`StepMap`] computes that once, in one pass over the trace, and every
7//! frontend asks it the same questions, so `next` means the same thing in the terminal
8//! and in an editor.
9//!
10//! Depth has two parts. The EVM depth changes on `CALL` and friends; the code address a
11//! frame executes is read off the caller's stack at the call, which is how steps inside
12//! another contract map through that contract's debug info. Inside one contract,
13//! Solidity's internal calls are plain jumps, so the map keeps a virtual stack of the
14//! source functions a frame passes through. A jump is a call when the artifact marks it
15//! as one (legacy source maps mark calls and returns; ETHDebug's `invoke` and `return`
16//! context does the same once a compiler emits it) or when it lands on a parsed
17//! function's entry point, and that holds even when the function is already active, so
18//! recursion and mutual recursion count. A marked return pops the frame. Otherwise the
19//! function a span lands in decides: one not on the stack is entered, one on the stack
20//! is returned to.
21//!
22//! Two habits of solc's output shape the model. Generated helpers (checked arithmetic,
23//! `require` reverts, storage updates) carry the whole-contract span, the same one the
24//! dispatcher has, so a step outside every function is read as generated code belonging
25//! to the statement that was executing, unless no function follows it in its frame, in
26//! which case it is the dispatcher finishing up and the frame's functions have returned.
27//! And a lone instruction attributed to another line between two runs of one line is
28//! treated as part of that line, so `next` does not stop on it.
29//!
30//! Everything here is a search over the recording. Nothing re-executes.
31
32use std::collections::BTreeMap;
33use std::num::NonZeroU32;
34use std::sync::Arc;
35
36use rustc_hash::FxHashMap;
37use soldb_core::{opcode, EvmWord, StepView, TransactionTrace};
38use soldb_ethdebug::{
39    function_selector, parse_path, CodeGenerator, EthdebugInfo, FunctionExit, Instruction,
40    PathSegment, SourceLocation, StorageLayout,
41};
42
43use crate::condition::Value;
44use crate::decode::{Place, ValueReader};
45use crate::roots::{root_calls, root_index, RootCall};
46use crate::state::StorageWords;
47use crate::types::SourceTypes;
48use crate::{
49    decode_arguments, is_value_type, parse_source_functions, readable_parameter, ArgumentLayout,
50    ArgumentOrder, ByteRange, DebugLocation, DebugValue, DebugValueStatus, DebugVariable,
51    FrameArgument, FrameState, SourceFunction, SourceParam,
52};
53
54/// The warning a frontend shows once when it presents inferred local variables.
55pub const INFERRED_LOCALS_WARNING: &str = "local variables are inferred from the legacy source \
56map and the stack layout of solc's legacy code generator, not from compiler-reported \
57variable locations";
58
59/// The note that accompanies [`INFERRED_LOCALS_WARNING`].
60pub const INFERRED_LOCALS_NOTE: &str = "values can be wrong under the optimizer, and a \
61variable whose frame could not be placed shows as unavailable; ETHDebug variable \
62information will replace this once compilers emit it";
63
64/// One contract's debug information, prepared for stepping: the ETHDebug metadata, the
65/// source text, the functions parsed from it, and the indexes that make per-step lookups
66/// constant time.
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub struct ContractDebugInfo {
69    /// The deployed address this info describes, lowercase, when known.
70    pub address: Option<String>,
71    pub name: String,
72    pub info: EthdebugInfo,
73    pub source_contents: BTreeMap<u64, String>,
74    pub functions: Vec<SourceFunction>,
75    /// The struct, enum, and user-defined value type declarations of the sources, which
76    /// say how a variable of such a type is shown.
77    pub types: SourceTypes,
78    /// Where the contract's state variables live, when it was compiled with
79    /// `--storage-layout`.
80    pub storage_layout: Option<StorageLayout>,
81    /// Where its `transient` state variables live, from `--transient-storage-layout`.
82    pub transient_storage_layout: Option<StorageLayout>,
83    /// Which code generator produced the program, when the artifact or the host says.
84    /// Decides whether local variables can be inferred from the stack; see
85    /// [`StepMap::locals_at`].
86    pub code_generator: Option<CodeGenerator>,
87    /// Byte offsets at which each line of each source starts.
88    line_starts: BTreeMap<u64, Vec<usize>>,
89    /// Instruction index by program counter.
90    pc_index: PcIndex,
91    /// What each instruction says, parallel to `info.instructions` as it was given. Like
92    /// the line and PC indexes, these are derived once in [`ContractDebugInfo::new`], so
93    /// mapping a step reads them rather than walking the instruction's JSON again.
94    facts: Vec<InstructionFacts>,
95}
96
97/// What one instruction's debug info says, as stepping reads it.
98#[derive(Debug, Clone, PartialEq, Eq)]
99struct InstructionFacts {
100    /// The source span the compiler attached, when it attached exactly one.
101    location: Option<SourceLocation>,
102    /// The line `location` starts on, when the source text is loaded.
103    line: Option<u64>,
104    /// The narrowest parsed function whose declaration contains `location`.
105    function: Option<usize>,
106    /// The function whose entry point this instruction's program counter is: the first
107    /// `JUMPDEST` carrying the declaration's span.
108    entry: Option<usize>,
109    /// What the artifact says about the jump the instruction makes.
110    marker: JumpMarker,
111    jumpdest: bool,
112}
113
114/// Instruction index by program counter: a table over the program's code, which every
115/// step of a trace looks up, and a map for offsets beyond any real code.
116#[derive(Debug, Clone, PartialEq, Eq)]
117struct PcIndex {
118    /// Indexed by program counter: the instruction's index plus one, or `None` where no
119    /// instruction starts.
120    dense: Vec<Option<NonZeroU32>>,
121    /// Offsets past `dense`.
122    sparse: FxHashMap<u64, usize>,
123}
124
125impl PcIndex {
126    /// Indexes the instructions by offset. A later instruction at an offset replaces an
127    /// earlier one, as collecting them into a map does.
128    fn new(instructions: &[Instruction]) -> Self {
129        // No instruction is longer than 33 bytes (`PUSH32` and its operand), so real code
130        // has every offset below this bound. Offsets past it come from an artifact that
131        // does not describe real code, and go to the map so they cannot size the table.
132        let bound = instructions.len().saturating_mul(33);
133        let dense_len = instructions
134            .iter()
135            .filter_map(|instruction| usize::try_from(instruction.offset).ok())
136            .filter(|offset| *offset < bound)
137            .max()
138            .map_or(0, |offset| offset + 1);
139        let mut dense = vec![None; dense_len];
140        let mut sparse = FxHashMap::default();
141        for (index, instruction) in instructions.iter().enumerate() {
142            let slot = usize::try_from(instruction.offset)
143                .ok()
144                .and_then(|offset| dense.get_mut(offset));
145            let entry = u32::try_from(index + 1).ok().and_then(NonZeroU32::new);
146            match (slot, entry) {
147                (Some(slot), Some(entry)) => *slot = Some(entry),
148                (slot, _) => {
149                    if let Some(slot) = slot {
150                        *slot = None;
151                    }
152                    sparse.insert(instruction.offset, index);
153                }
154            }
155        }
156        Self { dense, sparse }
157    }
158
159    fn get(&self, pc: u64) -> Option<usize> {
160        let dense = usize::try_from(pc)
161            .ok()
162            .and_then(|pc| self.dense.get(pc).copied().flatten());
163        match dense {
164            Some(entry) => Some(entry.get() as usize - 1),
165            None => self.sparse.get(&pc).copied(),
166        }
167    }
168}
169
170/// Which of a contract's two programs a frame executes: the creation code, run once by a
171/// `CREATE`, or the deployed code, run by every call.
172#[derive(Debug, Clone, Copy, PartialEq, Eq)]
173enum CodeEnvironment {
174    Create,
175    Call,
176}
177
178/// What an artifact says about the jump an instruction makes.
179#[derive(Debug, Clone, Copy, PartialEq, Eq)]
180pub enum JumpMarker {
181    None,
182    /// The jump enters a function.
183    Call,
184    /// The jump returns from one.
185    Return,
186}
187
188/// What stepping reads from the source text: its functions, type declarations, and
189/// line starts. Contracts compiled together share most of their sources, so a host that
190/// prepares several of them passes one cache to
191/// [`ContractDebugInfo::with_source_cache`] and each source is read once.
192#[derive(Debug, Default)]
193pub struct SourceCache {
194    /// The sources read so far, by id; the same id can name different text in
195    /// different compilations.
196    sources: FxHashMap<u64, Vec<Arc<ReadSource>>>,
197}
198
199#[derive(Debug)]
200struct ReadSource {
201    text: String,
202    functions: Vec<SourceFunction>,
203    types: SourceTypes,
204    line_starts: Vec<usize>,
205}
206
207impl SourceCache {
208    /// Source `source_id` with `text`, read now unless it was before.
209    fn read(&mut self, source_id: u64, text: &str) -> Arc<ReadSource> {
210        let read = self.sources.entry(source_id).or_default();
211        if let Some(source) = read.iter().find(|source| source.text == text) {
212            return Arc::clone(source);
213        }
214        let mut types = SourceTypes::default();
215        types.add_source(source_id, text);
216        let source = Arc::new(ReadSource {
217            text: text.to_owned(),
218            functions: parse_source_functions(source_id, text),
219            types,
220            line_starts: line_starts(text),
221        });
222        read.push(Arc::clone(&source));
223        source
224    }
225}
226
227impl ContractDebugInfo {
228    /// Prepares one contract's debug info. Functions are parsed from the source text,
229    /// which serves ETHDebug and legacy source maps alike: both attach byte ranges of the
230    /// same sources to instructions.
231    #[must_use]
232    pub fn new(
233        address: Option<&str>,
234        name: &str,
235        info: EthdebugInfo,
236        source_contents: BTreeMap<u64, String>,
237    ) -> Self {
238        Self::with_source_cache(
239            address,
240            name,
241            info,
242            source_contents,
243            &mut SourceCache::default(),
244        )
245    }
246
247    /// Prepares one contract's debug info as [`ContractDebugInfo::new`] does, reading
248    /// each source through `cache` so that the contracts sharing it read it once.
249    #[must_use]
250    pub fn with_source_cache(
251        address: Option<&str>,
252        name: &str,
253        info: EthdebugInfo,
254        source_contents: BTreeMap<u64, String>,
255        cache: &mut SourceCache,
256    ) -> Self {
257        let read = source_contents
258            .iter()
259            .map(|(source_id, text)| (*source_id, cache.read(*source_id, text)))
260            .collect::<Vec<_>>();
261        let functions = read
262            .iter()
263            .flat_map(|(_, source)| source.functions.iter().cloned())
264            .collect::<Vec<SourceFunction>>();
265        let mut types = SourceTypes::default();
266        for (_, source) in &read {
267            types.extend_from(&source.types);
268        }
269        let line_starts = read
270            .iter()
271            .map(|(source_id, source)| (*source_id, source.line_starts.clone()))
272            .collect();
273        let pc_index = PcIndex::new(&info.instructions);
274        let locations = info
275            .instructions
276            .iter()
277            .map(Instruction::source_location)
278            .collect::<Vec<_>>();
279        let jumpdests = info
280            .instructions
281            .iter()
282            .map(|instruction| instruction.mnemonic() == Some("JUMPDEST"))
283            .collect::<Vec<_>>();
284        // The earliest `JUMPDEST` at each span start, in one pass, so that finding a
285        // function's entry point is one lookup and not a scan of every instruction.
286        let mut earliest_jumpdests = FxHashMap::<(u64, u64), u64>::default();
287        for (index, instruction) in info.instructions.iter().enumerate() {
288            if !jumpdests[index] {
289                continue;
290            }
291            let Some(location) = &locations[index] else {
292                continue;
293            };
294            let earliest = earliest_jumpdests
295                .entry((location.source_id, location.offset))
296                .or_insert(instruction.offset);
297            *earliest = (*earliest).min(instruction.offset);
298        }
299        // A later function with the same entry point replaces an earlier one.
300        let function_entries = functions
301            .iter()
302            .enumerate()
303            .filter_map(|(function_index, function)| {
304                earliest_jumpdests
305                    .get(&(function.source_id, function.declaration_start))
306                    .map(|pc| (*pc, function_index))
307            })
308            .collect::<FxHashMap<_, _>>();
309        let mut contract = Self {
310            address: address.map(normalize_address),
311            name: name.to_owned(),
312            info,
313            source_contents,
314            functions,
315            types,
316            storage_layout: None,
317            transient_storage_layout: None,
318            code_generator: None,
319            line_starts,
320            pc_index,
321            facts: Vec::new(),
322        };
323        contract.facts = contract.instruction_facts(locations, &jumpdests, &function_entries);
324        contract
325    }
326
327    /// Derives what each instruction says, from its parsed span, whether it is a
328    /// `JUMPDEST`, and the prepared line index and functions.
329    fn instruction_facts(
330        &self,
331        locations: Vec<Option<SourceLocation>>,
332        jumpdests: &[bool],
333        function_entries: &FxHashMap<u64, usize>,
334    ) -> Vec<InstructionFacts> {
335        // The function a span is in is one of its own source's.
336        let mut by_source = FxHashMap::<u64, Vec<usize>>::default();
337        for (index, function) in self.functions.iter().enumerate() {
338            by_source.entry(function.source_id).or_default().push(index);
339        }
340        // The instructions of a statement share its span, so each span start's line and
341        // function are found once.
342        let mut found = FxHashMap::<(u64, u64), (Option<u64>, Option<usize>)>::default();
343        let mut line_and_function = |location: &SourceLocation| {
344            *found
345                .entry((location.source_id, location.offset))
346                .or_insert_with(|| {
347                    let function = by_source.get(&location.source_id).and_then(|candidates| {
348                        narrowest_function(&self.functions, candidates, location.offset)
349                    });
350                    (self.line_of(location.source_id, location.offset), function)
351                })
352        };
353        self.info
354            .instructions
355            .iter()
356            .zip(locations)
357            .zip(jumpdests)
358            .map(|((instruction, location), jumpdest)| {
359                let (line, function) = location
360                    .as_ref()
361                    .map_or((None, None), &mut line_and_function);
362                InstructionFacts {
363                    line,
364                    function,
365                    entry: function_entries.get(&instruction.offset).copied(),
366                    marker: jump_marker(instruction),
367                    jumpdest: *jumpdest,
368                    location,
369                }
370            })
371            .collect()
372    }
373
374    /// What the instruction at `pc` says, using the prepared index.
375    fn facts_at_pc(&self, pc: u64) -> Option<&InstructionFacts> {
376        self.facts.get(self.pc_index.get(pc)?)
377    }
378
379    /// Records which code generator produced the program.
380    #[must_use]
381    pub const fn with_code_generator(mut self, code_generator: Option<CodeGenerator>) -> Self {
382        self.code_generator = code_generator;
383        self
384    }
385
386    /// Attaches the contract's storage layout, so state variables can be read by name.
387    #[must_use]
388    pub fn with_storage_layout(mut self, storage_layout: Option<StorageLayout>) -> Self {
389        self.storage_layout = self.with_enum_variants(storage_layout);
390        self
391    }
392
393    /// Attaches the layout of the contract's `transient` state variables.
394    #[must_use]
395    pub fn with_transient_storage_layout(mut self, layout: Option<StorageLayout>) -> Self {
396        self.transient_storage_layout = self.with_enum_variants(layout);
397        self
398    }
399
400    /// Hands the sources' enum declarations to a layout, so an enum shows by variant name.
401    fn with_enum_variants(&self, layout: Option<StorageLayout>) -> Option<StorageLayout> {
402        layout.map(|mut layout| {
403            layout.enum_variants = self.types.enum_variants_by_name();
404            layout
405        })
406    }
407
408    /// The program this info describes, when the artifact says: ETHDebug programs and legacy
409    /// source maps name their environment `create` or `call`. `None` describes either.
410    fn code_environment(&self) -> Option<CodeEnvironment> {
411        match self.info.environment.as_str() {
412            "create" => Some(CodeEnvironment::Create),
413            "call" => Some(CodeEnvironment::Call),
414            _ => None,
415        }
416    }
417
418    /// The function whose entry point `pc` is, when it is one.
419    #[must_use]
420    pub fn function_entry_at_pc(&self, pc: u64) -> Option<usize> {
421        self.facts_at_pc(pc).and_then(|facts| facts.entry)
422    }
423
424    /// Whether the instruction at `pc` is a `JUMPDEST`.
425    #[must_use]
426    pub fn is_jumpdest(&self, pc: u64) -> bool {
427        self.facts_at_pc(pc).is_some_and(|facts| facts.jumpdest)
428    }
429
430    /// What the artifact says about the jump the instruction at `pc` makes.
431    #[must_use]
432    pub fn jump_marker_at_pc(&self, pc: u64) -> JumpMarker {
433        self.facts_at_pc(pc)
434            .map_or(JumpMarker::None, |facts| facts.marker)
435    }
436
437    #[must_use]
438    pub fn source_path(&self, source_id: u64) -> Option<&str> {
439        self.info.sources.get(&source_id).map(String::as_str)
440    }
441
442    /// The one-based line a byte offset falls on, when the source text is available.
443    #[must_use]
444    pub fn line_of(&self, source_id: u64, offset: u64) -> Option<u64> {
445        let starts = self.line_starts.get(&source_id)?;
446        let offset = usize::try_from(offset).ok()?;
447        Some(starts.partition_point(|start| *start <= offset) as u64)
448    }
449
450    #[must_use]
451    pub fn column_of(&self, source_id: u64, offset: u64) -> Option<u64> {
452        let starts = self.line_starts.get(&source_id)?;
453        let line = self.line_of(source_id, offset)?;
454        let line_start = *starts.get(usize::try_from(line).ok()?.checked_sub(1)?)?;
455        Some(offset.saturating_sub(line_start as u64) + 1)
456    }
457
458    #[must_use]
459    pub fn line_count(&self, source_id: u64) -> Option<u64> {
460        self.line_starts
461            .get(&source_id)
462            .map(|starts| starts.len() as u64)
463    }
464
465    /// The text of one line, without its line ending.
466    #[must_use]
467    pub fn line_text(&self, source_id: u64, line: u64) -> Option<&str> {
468        let source = self.source_contents.get(&source_id)?;
469        let index = usize::try_from(line).ok()?.checked_sub(1)?;
470        source.lines().nth(index)
471    }
472
473    /// The source span the compiler attached to the instruction at `pc`.
474    #[must_use]
475    pub fn location_at_pc(&self, pc: u64) -> Option<SourceLocation> {
476        self.facts_at_pc(pc)?.location.clone()
477    }
478
479    /// The compiler's instruction at `pc`, using the prepared index.
480    #[must_use]
481    pub fn instruction_at_pc(&self, pc: u64) -> Option<&Instruction> {
482        self.info.instructions.get(self.pc_index.get(pc)?)
483    }
484
485    /// Legacy source-map modifier depth at `pc`, when the artifact carries it.
486    #[must_use]
487    pub fn modifier_depth_at_pc(&self, pc: u64) -> Option<i64> {
488        self.instruction_at_pc(pc)?.modifier_depth()
489    }
490
491    /// The narrowest parsed function whose declaration contains the span.
492    #[must_use]
493    pub fn function_for_location(&self, location: &SourceLocation) -> Option<usize> {
494        let candidates = (0..self.functions.len())
495            .filter(|index| self.functions[*index].source_id == location.source_id)
496            .collect::<Vec<_>>();
497        narrowest_function(&self.functions, &candidates, location.offset)
498    }
499
500    #[must_use]
501    pub fn function_at_pc(&self, pc: u64) -> Option<&SourceFunction> {
502        self.functions.get(self.facts_at_pc(pc)?.function?)
503    }
504
505    /// The line a breakpoint on `line` of `source_id` resolves to: the line itself when
506    /// the compiler generated code that begins there, otherwise the first line of the
507    /// narrowest statement that contains it, or nothing when no code maps to the line.
508    ///
509    /// Ranking matters because solc attaches the whole-contract span to the dispatcher,
510    /// and that span intersects every line in the file.
511    #[must_use]
512    pub fn effective_line(&self, source_id: u64, line: u64) -> Option<u64> {
513        if line == 0 || line > self.line_count(source_id)? {
514            return None;
515        }
516        let mut narrowest = None::<(u64, u64)>;
517        for instruction in &self.info.instructions {
518            let Some(location) = instruction.source_location() else {
519                continue;
520            };
521            if location.source_id != source_id {
522                continue;
523            }
524            let start = self.line_of(source_id, location.offset)?;
525            let end = self.line_of(
526                source_id,
527                location
528                    .offset
529                    .saturating_add(location.length.saturating_sub(1)),
530            )?;
531            if start > line || line > end {
532                continue;
533            }
534            if start == line {
535                return Some(line);
536            }
537            if narrowest.is_none_or(|(length, _)| location.length < length) {
538                narrowest = Some((location.length, start));
539            }
540        }
541        narrowest.map(|(_, start)| start)
542    }
543}
544
545/// Lowercases an address and gives it a `0x` prefix, so addresses from the command line,
546/// from a manifest, and from the stack compare equal.
547#[must_use]
548pub fn normalize_address(value: &str) -> String {
549    let hex = value.trim_start_matches("0x").trim_start_matches("0X");
550    format!("0x{}", hex.to_ascii_lowercase())
551}
552
553/// Whether a source path from the compiler matches what a user or an editor asked for:
554/// the whole path, a trailing part of either (an editor sends absolute paths where the
555/// compiler recorded relative ones), or just the file name.
556#[must_use]
557pub fn source_path_matches(source_path: &str, requested: &str) -> bool {
558    fn ends_with_path(longer: &str, shorter: &str) -> bool {
559        longer.ends_with(shorter)
560            && longer
561                .as_bytes()
562                .get(longer.len() - shorter.len() - 1)
563                .is_none_or(|byte| *byte == b'/')
564    }
565    fn file_name(path: &str) -> &str {
566        path.rsplit('/').next().unwrap_or(path)
567    }
568    source_path == requested
569        || ends_with_path(source_path, requested)
570        || ends_with_path(requested, source_path)
571        || (!requested.is_empty() && file_name(source_path) == file_name(requested))
572}
573
574/// The narrowest of the `candidates` among `functions` whose declaration contains
575/// `offset`; the first of them when several are as narrow.
576fn narrowest_function(
577    functions: &[SourceFunction],
578    candidates: &[usize],
579    offset: u64,
580) -> Option<usize> {
581    candidates
582        .iter()
583        .copied()
584        .filter(|index| {
585            let function = &functions[*index];
586            function.declaration_start <= offset && offset <= function.body_end
587        })
588        .min_by_key(|index| {
589            let function = &functions[*index];
590            function.body_end.saturating_sub(function.declaration_start)
591        })
592}
593
594fn line_starts(source: &str) -> Vec<usize> {
595    let mut starts = vec![0];
596    starts.extend(
597        source
598            .bytes()
599            .enumerate()
600            .filter(|(_, byte)| *byte == b'\n')
601            .map(|(index, _)| index + 1),
602    );
603    starts
604}
605
606/// What an instruction's context says about the jump it makes.
607fn jump_marker(instruction: &Instruction) -> JumpMarker {
608    if !instruction.function_invocations().is_empty() {
609        JumpMarker::Call
610    } else if instruction.function_exit() == Some(FunctionExit::Return) {
611        JumpMarker::Return
612    } else {
613        JumpMarker::None
614    }
615}
616
617/// Identifies a source line across every contract in a [`StepMap`].
618#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
619pub struct LineKey {
620    pub contract: usize,
621    pub source_id: u64,
622    pub line: u64,
623}
624
625/// Identifies a parsed source function across every contract in a [`StepMap`].
626#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
627pub struct FunctionId {
628    pub contract: usize,
629    pub function: usize,
630}
631
632/// Where one step is in the source, as a frontend shows it.
633#[derive(Debug, Clone, PartialEq, Eq)]
634pub struct StepLocation {
635    pub key: LineKey,
636    /// True when the instruction itself is compiler-generated helper code and the
637    /// location shown is the statement it was generated for.
638    pub generated: bool,
639    pub contract_name: String,
640    pub path: String,
641    pub offset: u64,
642    pub length: u64,
643    pub line: u64,
644    pub column: u64,
645    pub function: Option<FunctionId>,
646    pub function_name: Option<String>,
647}
648
649/// A line breakpoint target resolved against the loaded sources.
650#[derive(Debug, Clone, PartialEq, Eq)]
651pub struct ResolvedLine {
652    pub key: LineKey,
653    pub path: String,
654    /// The line the user asked for, which may sit inside the statement that `key` names.
655    pub requested_line: u64,
656}
657
658/// A function breakpoint target resolved against the parsed sources.
659#[derive(Debug, Clone, PartialEq, Eq)]
660pub struct ResolvedFunction {
661    pub id: FunctionId,
662    pub name: String,
663    pub contract_name: String,
664    pub path: String,
665    pub line: u64,
666}
667
668/// One frame of the call structure at a step, innermost first in [`StepMap::frames`].
669#[derive(Debug, Clone, PartialEq, Eq)]
670pub struct Frame {
671    /// Zero for the transaction's root frame.
672    pub depth: u32,
673    /// True for a frame the EVM entered with a call or a creation; false for a Solidity
674    /// internal function inferred from the source spans.
675    pub external: bool,
676    pub address: Option<String>,
677    pub contract_name: Option<String>,
678    pub function_name: Option<String>,
679    /// The step the frame was entered at.
680    pub entry_step: usize,
681    /// The step the frame is at: the current step for the innermost frame, the call site
682    /// for every frame above it.
683    pub step: usize,
684    pub pc: u64,
685    pub location: Option<StepLocation>,
686    /// The arguments the function was entered with, when the frame was entered at its
687    /// entry point and the contract's argument order is known. Filled in by
688    /// [`StepMap::frame_arguments`]; empty otherwise.
689    pub arguments: Vec<FrameArgument>,
690    /// For a root frame, what its top-level call is, when the trace says.
691    pub kind: Option<String>,
692}
693
694/// Lines of source around a step.
695#[derive(Debug, Clone, PartialEq, Eq)]
696pub struct SourceListing {
697    pub path: String,
698    pub current_line: u64,
699    /// One-based line numbers with their text.
700    pub lines: Vec<(u64, String)>,
701}
702
703#[derive(Debug, Clone, Copy, PartialEq, Eq)]
704struct LocationRef {
705    key: LineKey,
706    offset: u64,
707    length: u64,
708    function: Option<usize>,
709}
710
711#[derive(Debug, Clone, Copy, PartialEq, Eq)]
712struct StepInfo {
713    /// The location shown for the step: its own span, or the statement generated code
714    /// belongs to.
715    location: Option<LocationRef>,
716    /// True when `location` is inherited from the executing statement.
717    generated: bool,
718    /// The line the step counts as for stepping and breakpoints. `None` for steps without
719    /// source and for the dispatcher, which belongs to no line a user steps through.
720    key: Option<LineKey>,
721    /// Which contract's code this step executes, when known.
722    contract: Option<usize>,
723    /// Index into the address table, when the executing address is known.
724    address: Option<usize>,
725    /// Index into the address table of the account whose storage this step reads and
726    /// writes: the executing address, or the caller's for a `DELEGATECALL`.
727    storage: Option<usize>,
728    /// EVM depth, zero at the root.
729    evm_depth: u32,
730    /// True when this step starts a top-level call after the first.
731    starts_root: bool,
732    /// EVM depth plus the inferred internal-function depth, zero at the root.
733    frame_depth: u32,
734    /// True when this step enters a frame: an external call, or an internal function.
735    frame_entry: bool,
736    /// True when this step begins a run of one source line at its depth, skipping over
737    /// deeper frames it calls into.
738    line_start: bool,
739}
740
741/// The line of the nearest step at each frame depth, for a walk over the steps in one
742/// direction. A step hides every deeper depth from the steps after it; a step without a
743/// line does not hide the line at its own depth. A line is anything that stands for one:
744/// its key, or the index of the step it is the line of.
745#[derive(Debug)]
746struct NearestLines<T> {
747    /// Depths with a line, shallowest first. A depth that is missing has none.
748    lines: Vec<(u32, T)>,
749}
750
751impl<T> Default for NearestLines<T> {
752    fn default() -> Self {
753        Self { lines: Vec::new() }
754    }
755}
756
757impl<T: Copy> NearestLines<T> {
758    /// Walks past a step and returns the line of the nearest step before it at the same
759    /// depth, or `None` when a shallower step or no step at that depth comes first.
760    fn visit(&mut self, depth: u32, key: Option<T>) -> Option<T> {
761        while self.lines.last().is_some_and(|(level, _)| *level > depth) {
762            self.lines.pop();
763        }
764        let nearest = match self.lines.last() {
765            Some((level, line)) if *level == depth => Some(*line),
766            _ => None,
767        };
768        if let Some(key) = key {
769            if nearest.is_some() {
770                self.lines.pop();
771            }
772            self.lines.push((depth, key));
773        }
774        nearest
775    }
776
777    /// Forgets every step walked past so far.
778    fn clear(&mut self) {
779        self.lines.clear();
780    }
781}
782
783/// Per-step source locations and frame depths over one trace.
784#[derive(Debug, Clone, PartialEq, Eq)]
785pub struct StepMap {
786    contracts: Vec<ContractDebugInfo>,
787    steps: Vec<StepInfo>,
788    addresses: Vec<String>,
789    pcs: Vec<u64>,
790    /// Every EVM frame that reverted, as the step it was entered at and the step it
791    /// reverted on, innermost first.
792    reverted: Vec<(usize, usize)>,
793    /// What this trace proved about where each contract's compiler leaves function
794    /// parameters on the stack.
795    argument_layouts: Vec<Evidence>,
796    /// Per contract, whether local variables can be inferred, or why not.
797    locals_support: Vec<Result<(), &'static str>>,
798    /// Every distinct variable layout some step has, so a run of steps sharing one holds
799    /// an index rather than a copy.
800    variable_layouts: Vec<Vec<InferredVariable>>,
801    /// Per step, the index into `variable_layouts`, or `NO_LAYOUT`.
802    step_variables: Vec<u32>,
803    /// The top-level calls, in execution order. Never empty.
804    roots: Vec<RootCall>,
805}
806
807const NO_LAYOUT: u32 = u32::MAX;
808/// The layout of a frame the optimizer inlined: its variables have no slots of their own.
809const INLINED_LAYOUT: u32 = u32::MAX - 1;
810
811/// What a variable is to the function whose frame holds it.
812#[derive(Debug, Clone, Copy, PartialEq, Eq)]
813pub enum VariableKind {
814    Parameter,
815    Return,
816    Local,
817}
818
819/// A variable of the executing function, with the stack slot inferred for it.
820#[derive(Debug, Clone, PartialEq, Eq)]
821pub struct InferredVariable {
822    pub name: String,
823    /// The declared type, with its data location.
824    pub ty: String,
825    pub kind: VariableKind,
826    /// The slot, counted from the bottom of the stack, of the variable's first word;
827    /// `None` when the variable is in scope but its frame could not be placed.
828    pub slot: Option<usize>,
829    /// How many stack words the variable takes: two for a `calldata` slice, whose words
830    /// are its offset and its length, one for everything else.
831    pub words: usize,
832}
833
834/// Whether local variables are known at a step.
835#[derive(Debug, Clone, Copy, PartialEq, Eq)]
836pub enum LocalsStatus<'a> {
837    /// The executing function's variables, inferred from the legacy stack layout.
838    Inferred(&'a [InferredVariable]),
839    /// Nothing can be inferred here, and why.
840    Unavailable(&'static str),
841}
842
843/// What a trace has shown about one contract's argument passing. Only a proof is used;
844/// a contradiction disables arguments for the contract entirely, because it means the
845/// entry point we detect is not where the parameters are.
846#[derive(Debug, Clone, Copy, PartialEq, Eq)]
847enum Evidence {
848    /// Nothing in the trace said either way.
849    Unknown,
850    /// A frame whose arguments we knew did not have them on top of its entry stack.
851    Contradicted,
852    /// The parameters are the top words; the order was not established.
853    TopWords,
854    /// The parameters are the top words, in this order.
855    Ordered(ArgumentOrder),
856}
857
858impl Evidence {
859    /// Keeps the stronger of two observations, and a contradiction over everything: one
860    /// frame that disagrees is enough to stop guessing for the whole contract.
861    fn merge(self, other: Self) -> Self {
862        match (self, other) {
863            (Self::Contradicted, _) | (_, Self::Contradicted) => Self::Contradicted,
864            (Self::Ordered(order), _) | (_, Self::Ordered(order)) => Self::Ordered(order),
865            (Self::TopWords, _) | (_, Self::TopWords) => Self::TopWords,
866            _ => Self::Unknown,
867        }
868    }
869
870    fn layout(self) -> Option<ArgumentLayout> {
871        match self {
872            Self::Ordered(order) => Some(ArgumentLayout::Ordered(order)),
873            Self::TopWords => Some(ArgumentLayout::TopWords),
874            Self::Unknown | Self::Contradicted => None,
875        }
876    }
877}
878
879/// What the first pass records about a step: its EVM frame and its own span.
880#[derive(Debug, Clone, Copy)]
881struct FramedStep {
882    evm_depth: u32,
883    frame_id: u32,
884    /// Index into the top-level calls of the one this step belongs to.
885    root: usize,
886    contract: Option<usize>,
887    address: Option<usize>,
888    storage: Option<usize>,
889    location: Option<LocationRef>,
890    /// The function this step's program counter is the entry point of.
891    entry: Option<usize>,
892    /// What the artifact says about the jump this step makes.
893    marker: JumpMarker,
894}
895
896struct EvmFrame {
897    id: u32,
898    contract: Option<usize>,
899    address: Option<usize>,
900    /// The account whose storage the frame reads and writes.
901    storage: Option<usize>,
902    /// The step the frame was entered at.
903    entry_step: usize,
904}
905
906/// What is known about an EVM frame as it is entered.
907#[derive(Clone)]
908struct FrameEntered {
909    /// The address whose code the frame runs; `None` when nothing recorded it, or for a
910    /// creation whose address the backend did not report.
911    address: Option<String>,
912    environment: CodeEnvironment,
913    /// Whether the frame runs against its caller's storage.
914    delegated: bool,
915}
916
917impl FrameEntered {
918    /// The calls and creations the backend recorded, by the step each was entered at,
919    /// so that entering a frame finds its record without searching them all. A call that
920    /// ran no steps, such as one to a precompile, ends at the step it started and is not a
921    /// frame in the trace; of two records entered at one step, a call is taken before a
922    /// creation and an earlier record before a later one.
923    fn recorded(trace: &TransactionTrace) -> FxHashMap<usize, Self> {
924        let entered = |entry: Option<usize>, exit: Option<usize>| {
925            entry.filter(|entry| exit.is_none_or(|exit| exit > *entry))
926        };
927        let artifacts = &trace.artifacts;
928        let mut recorded = FxHashMap::default();
929        for call in &artifacts.calls {
930            if let Some(step) = entered(call.entry_step, call.exit_step) {
931                recorded.entry(step).or_insert_with(|| Self {
932                    address: Some(call.bytecode_address.clone()),
933                    environment: CodeEnvironment::Call,
934                    delegated: matches!(call.call_type.as_str(), "DELEGATECALL" | "CALLCODE"),
935                });
936            }
937        }
938        for creation in &artifacts.creations {
939            if let Some(step) = entered(creation.entry_step, creation.exit_step) {
940                recorded.entry(step).or_insert_with(|| Self {
941                    address: creation.address.clone(),
942                    environment: CodeEnvironment::Create,
943                    delegated: false,
944                });
945            }
946        }
947        recorded
948    }
949
950    /// What the call instruction at `caller` says about the frame it entered.
951    fn from_call(trace: &TransactionTrace, caller: Option<usize>) -> Self {
952        let op = caller.map(|caller| trace.steps.step(caller).op());
953        Self {
954            address: caller.and_then(|caller| call_target(trace.steps.step(caller))),
955            environment: if matches!(op, Some(opcode::CREATE | opcode::CREATE2)) {
956                CodeEnvironment::Create
957            } else {
958                CodeEnvironment::Call
959            },
960            delegated: matches!(op, Some(opcode::DELEGATECALL | opcode::CALLCODE)),
961        }
962    }
963}
964
965/// One internal frame: a function, or a placeholder for a compiler-generated helper
966/// entered through a marked call, which absorbs the matching marked return and counts as
967/// no frame of its own.
968#[derive(Debug, Clone, PartialEq, Eq)]
969struct FrameEntry {
970    function: Option<usize>,
971    /// Where the frame returns to: the tag the caller pushed before the arguments, read
972    /// off the stack at the call. A jump landing there is the return, whatever function
973    /// it lands in, which is what tells a return from a recursive call apart from
974    /// staying in the function.
975    return_pc: Option<u64>,
976    /// The frame's variables, tracked when the contract's code generator keeps them at
977    /// fixed stack slots.
978    variables: Option<FrameVariables>,
979}
980
981/// The stack slots of one frame's variables, following solc's legacy code generator.
982///
983/// That generator keeps every variable at a fixed slot: the parameters are the words
984/// below the height the function was entered at, the return parameters are reserved right
985/// above them at entry, and each local takes the next free slot when its declaration
986/// executes and gives it back at the end of its block. So once the frame's base is known,
987/// a live local's slot is the locals base plus the number of locals declared before it
988/// that are still in scope, which the source alone decides.
989///
990/// The base is the calling convention's: the parameters are the top words at the entry
991/// of the function's body, whether an internal call or the dispatcher jumped there, and
992/// the optimizer keeps that whatever it does inside a block. When the entry was not seen
993/// the frame is placed by whichever comes first: the first return parameter's
994/// reservation, or the first local's, or the first instruction of the body; those read
995/// single instructions, which the optimizer may reorder.
996#[derive(Debug, Clone, PartialEq, Eq)]
997struct FrameVariables {
998    /// The slot of the first parameter, once established.
999    params_base: Option<usize>,
1000    /// The slot of each return parameter, once known.
1001    returns: Vec<Option<usize>>,
1002    /// The slot the first live local occupies, once established.
1003    locals_base: Option<usize>,
1004    /// Whether a step of the function body has been seen.
1005    body_seen: bool,
1006    /// The layout last computed for the frame, which steps without a location of their
1007    /// own in the function inherit.
1008    layout: Option<u32>,
1009    /// The stack slots the function's modifiers hold below its body's locals: their
1010    /// parameters, and the locals each has declared by its `_`. `None` when a modifier's
1011    /// declaration was not found, so the body's first instruction places the locals.
1012    modifier_slots: Option<usize>,
1013    /// Whether the frame was reached without a jump onto the function's entry: the
1014    /// optimizer inlined the call, and the function's variables have no slots of their
1015    /// own — its parameters are the caller's expressions, its results the caller's
1016    /// temporaries.
1017    inlined: bool,
1018    /// What `layout` was computed from, so a step that changes none of it reuses the
1019    /// layout instead of building it again.
1020    layout_inputs: LayoutInputs,
1021    /// The stack slots the function's parameters take, counted once from their types
1022    /// rather than at every step.
1023    param_slots: usize,
1024    /// The span and stack height of the step last followed.
1025    last_span: Option<ByteRange>,
1026    last_height: usize,
1027}
1028
1029/// Everything a frame's layout depends on besides its function: the slots placed so far
1030/// and which locals are in scope. Two steps with the same inputs have the same layout.
1031#[derive(Debug, Clone, Default, PartialEq, Eq)]
1032struct LayoutInputs {
1033    params_base: Option<usize>,
1034    returns: Vec<Option<usize>>,
1035    locals_base: Option<usize>,
1036    /// Per local of the function, whether it was in scope.
1037    in_scope: Vec<bool>,
1038}
1039
1040impl FrameVariables {
1041    fn new(
1042        function: &SourceFunction,
1043        params_base: Option<usize>,
1044        modifier_slots: Option<usize>,
1045    ) -> Self {
1046        Self {
1047            params_base,
1048            returns: vec![None; function.returns.len()],
1049            locals_base: None,
1050            body_seen: false,
1051            layout: None,
1052            modifier_slots,
1053            inlined: false,
1054            layout_inputs: LayoutInputs::default(),
1055            param_slots: parameter_slots(&function.params),
1056            last_span: None,
1057            last_height: 0,
1058        }
1059    }
1060
1061    /// Places the return parameters and the locals from known parameters: the returns
1062    /// sit right above the parameters, the locals above those and the modifiers' slots.
1063    fn place_from_parameters(&mut self, function: &SourceFunction) {
1064        let Some(base) = self.params_base else {
1065            return;
1066        };
1067        let returns_base = base + self.param_slots;
1068        for (index, slot) in self.returns.iter_mut().enumerate() {
1069            slot.get_or_insert(returns_base + index);
1070        }
1071        if self.locals_base.is_none() {
1072            if let Some(modifier_slots) = self.modifier_slots {
1073                self.locals_base = Some(returns_base + function.returns.len() + modifier_slots);
1074            }
1075        }
1076    }
1077
1078    /// Follows the frame's variables through one step of its function's own code, at
1079    /// `span` with the stack `height` high, and returns the layout after it.
1080    fn track(
1081        &mut self,
1082        function: &SourceFunction,
1083        span: ByteRange,
1084        height: usize,
1085        layouts: &mut Vec<Vec<InferredVariable>>,
1086    ) -> u32 {
1087        // Following a step only places what is not placed yet, from its span, so a step
1088        // of the span the last one had places nothing more and has its layout, unless
1089        // something else placed a slot since. The height only matters while the locals
1090        // are not placed: a local the span declares is placed below it.
1091        if !self.inlined
1092            && self.last_span == Some(span)
1093            && (self.locals_base.is_some() || self.last_height == height)
1094        {
1095            if let Some(id) = self.layout {
1096                let inputs = &self.layout_inputs;
1097                if inputs.params_base == self.params_base
1098                    && inputs.locals_base == self.locals_base
1099                    && inputs.returns == self.returns
1100                {
1101                    self.last_height = height;
1102                    return id;
1103                }
1104            }
1105        }
1106        let id = self.track_span(function, span, height, layouts);
1107        self.last_span = Some(span);
1108        self.last_height = height;
1109        id
1110    }
1111
1112    /// [`FrameVariables::track`] for a step whose layout is worked out again.
1113    fn track_span(
1114        &mut self,
1115        function: &SourceFunction,
1116        span: ByteRange,
1117        height: usize,
1118        layouts: &mut Vec<Vec<InferredVariable>>,
1119    ) -> u32 {
1120        if self.inlined {
1121            self.layout = Some(INLINED_LAYOUT);
1122            return INLINED_LAYOUT;
1123        }
1124        let length = span.end - span.start;
1125        let param_slots = self.param_slots;
1126        // Known parameters place everything else before any instruction is read.
1127        self.place_from_parameters(function);
1128        // The body's first instruction runs right above whatever was reserved before it:
1129        // the first local goes there, and without modifiers in between that is right
1130        // above the parameters and the return parameters. A modifier resumed after its
1131        // `_` is not at its beginning.
1132        let at_beginning = function
1133            .placeholder
1134            .is_none_or(|placeholder| span.start < placeholder);
1135        if !self.body_seen
1136            && at_beginning
1137            && span.start >= function.body_start
1138            && span.start < function.body_end
1139        {
1140            self.body_seen = true;
1141            if self.locals_base.is_none() {
1142                self.locals_base = Some(height);
1143            }
1144            if self.params_base.is_none() && !function.has_modifiers {
1145                self.params_base = height.checked_sub(param_slots + function.returns.len());
1146            }
1147        }
1148        // A return parameter's reservation runs at entry, before any modifier, so the
1149        // first one's slot also says where the parameters end.
1150        for (index, parameter) in function.returns.iter().enumerate() {
1151            if self.returns[index].is_none() && parameter.declaration.covers(span.start, length) {
1152                self.returns[index] = Some(height);
1153                if index == 0 && self.params_base.is_none() {
1154                    self.params_base = height.checked_sub(param_slots);
1155                }
1156            }
1157        }
1158        // The first local reservation seen places every local, which is what places them
1159        // when a modifier's slots sit between the parameters and the body.
1160        if self.locals_base.is_none() {
1161            if let Some((index, _)) = function
1162                .locals
1163                .iter()
1164                .enumerate()
1165                .find(|(_, local)| local.statement.covers(span.start, length))
1166            {
1167                self.locals_base = height.checked_sub(live_index(function, index, span.start));
1168            }
1169        }
1170        // Parameters a reservation just placed place the rest.
1171        self.place_from_parameters(function);
1172
1173        // The layout is a function of its inputs, so unchanged inputs give the layout
1174        // computed last. In a loop that is almost every step.
1175        let inputs = &self.layout_inputs;
1176        if let Some(id) = self.layout {
1177            if inputs.params_base == self.params_base
1178                && inputs.locals_base == self.locals_base
1179                && inputs.returns == self.returns
1180                && inputs.in_scope.len() == function.locals.len()
1181                && function
1182                    .locals
1183                    .iter()
1184                    .zip(&inputs.in_scope)
1185                    .all(|(local, in_scope)| local.scope.contains(span.start) == *in_scope)
1186            {
1187                debug_assert_eq!(
1188                    layouts[id as usize],
1189                    self.layout_at(function, span.start),
1190                    "a cache hit must give the layout its inputs compute"
1191                );
1192                return id;
1193            }
1194        }
1195
1196        let layout = self.layout_at(function, span.start);
1197        let id = match self.layout {
1198            Some(id) if layouts[id as usize] == layout => id,
1199            _ => {
1200                layouts.push(layout);
1201                (layouts.len() - 1) as u32
1202            }
1203        };
1204        self.layout = Some(id);
1205        let inputs = &mut self.layout_inputs;
1206        inputs.params_base = self.params_base;
1207        inputs.returns.clone_from(&self.returns);
1208        inputs.locals_base = self.locals_base;
1209        inputs.in_scope.clear();
1210        inputs.in_scope.extend(
1211            function
1212                .locals
1213                .iter()
1214                .map(|local| local.scope.contains(span.start)),
1215        );
1216        id
1217    }
1218
1219    /// The frame's variables at `offset` in its function's own code, as the parameters'
1220    /// base, the return slots, and the locals' base place them.
1221    fn layout_at(&self, function: &SourceFunction, offset: u64) -> Vec<InferredVariable> {
1222        let mut layout = Vec::new();
1223        if let Some(base) = self.params_base {
1224            let mut slot = base;
1225            for parameter in &function.params {
1226                let size = parameter_stack_slots(parameter);
1227                layout.push(InferredVariable {
1228                    name: parameter.name.clone(),
1229                    ty: match &parameter.location {
1230                        Some(location) => format!("{} {location}", parameter.ty),
1231                        None => parameter.ty.clone(),
1232                    },
1233                    kind: VariableKind::Parameter,
1234                    slot: Some(slot),
1235                    words: size,
1236                });
1237                slot += size;
1238            }
1239        }
1240        for (index, parameter) in function.returns.iter().enumerate() {
1241            layout.push(InferredVariable {
1242                name: parameter.name.clone(),
1243                ty: parameter.declared_type(),
1244                kind: VariableKind::Return,
1245                slot: self.returns[index],
1246                words: 1,
1247            });
1248        }
1249        for (index, local) in function.locals.iter().enumerate() {
1250            if local.scope.contains(offset) {
1251                layout.push(InferredVariable {
1252                    name: local.name.clone(),
1253                    ty: local.declared_type(),
1254                    kind: VariableKind::Local,
1255                    slot: self
1256                        .locals_base
1257                        .map(|base| base + live_index(function, index, offset)),
1258                    words: 1,
1259                });
1260            }
1261        }
1262        layout
1263    }
1264}
1265
1266/// How many of the function's locals declared before `index` are in scope at `offset`:
1267/// the slots the legacy code generator has taken for locals at that point.
1268fn live_index(function: &SourceFunction, index: usize, offset: u64) -> usize {
1269    let declaration = function.locals[index].declaration.start;
1270    function
1271        .locals
1272        .iter()
1273        .filter(|other| other.scope.contains(offset) && other.declaration.start < declaration)
1274        .count()
1275}
1276
1277/// The calldata of the frame executing `step`: the input of its top-level call in `roots`
1278/// at the root, the recorded call's input in a nested frame, and empty when the backend
1279/// did not record that call.
1280pub(crate) fn calldata_for_step<'a>(
1281    trace: &'a TransactionTrace,
1282    roots: &[RootCall],
1283    step: usize,
1284) -> &'a str {
1285    let root_input = roots
1286        .get(root_index(roots, step))
1287        .map_or("", |root| root.input(trace));
1288    frame_calldata(trace, step, root_input)
1289}
1290
1291/// The calldata of the frame executing `step`, given its top-level call's input.
1292fn frame_calldata<'a>(trace: &'a TransactionTrace, step: usize, root_input: &'a str) -> &'a str {
1293    let root_depth = trace.steps.first().map_or(0, |first| first.depth());
1294    let depth = trace
1295        .steps
1296        .get(step)
1297        .map_or(root_depth, |current| current.depth());
1298    if depth == root_depth {
1299        return root_input;
1300    }
1301    trace
1302        .artifacts
1303        .calls
1304        .iter()
1305        .filter(|call| {
1306            call.entry_step.is_some_and(|entry| entry <= step)
1307                && call.exit_step.is_none_or(|exit| step < exit)
1308        })
1309        .max_by_key(|call| call.depth)
1310        .map_or("", |call| call.input.as_str())
1311}
1312
1313/// How many stack words a parameter takes: a `calldata` slice of a dynamic type is an
1314/// offset and a length, an external function is an address and a selector, and
1315/// everything else is one word.
1316fn parameter_stack_slots(parameter: &SourceParam) -> usize {
1317    let dynamic =
1318        parameter.ty == "bytes" || parameter.ty == "string" || parameter.ty.ends_with("[]");
1319    if parameter.location.as_deref() == Some("calldata") && dynamic
1320        || parameter.ty.starts_with("function")
1321    {
1322        2
1323    } else {
1324        1
1325    }
1326}
1327
1328fn parameter_slots(parameters: &[SourceParam]) -> usize {
1329    parameters.iter().map(parameter_stack_slots).sum()
1330}
1331
1332/// The stack slots the first `count` modifiers of `function` hold while the code they
1333/// wrap runs: each modifier's parameters, and the locals it has declared by its `_`.
1334/// `None` when a modifier's declaration is not among the parsed functions (a base
1335/// contract's source that was not loaded, or a base constructor call in the header).
1336fn modifier_slots(
1337    contract: &ContractDebugInfo,
1338    function: &SourceFunction,
1339    count: usize,
1340) -> Option<usize> {
1341    let mut slots = 0;
1342    for name in function.modifiers.iter().take(count) {
1343        let modifier = contract.functions.iter().find(|candidate| {
1344            candidate.placeholder.is_some()
1345                && candidate.name == *name
1346                && candidate.source_id == function.source_id
1347        });
1348        let modifier = modifier.or_else(|| {
1349            contract
1350                .functions
1351                .iter()
1352                .find(|candidate| candidate.placeholder.is_some() && candidate.name == *name)
1353        })?;
1354        let placeholder = modifier.placeholder?;
1355        let live = modifier
1356            .locals
1357            .iter()
1358            .filter(|local| {
1359                local.declaration.start < placeholder && local.scope.contains(placeholder)
1360            })
1361            .count();
1362        slots += parameter_slots(&modifier.params) + live;
1363    }
1364    Some(slots)
1365}
1366
1367/// Whether a contract's variables can be inferred from the stack, or why not.
1368fn locals_support(contract: &ContractDebugInfo, evidence: Evidence) -> Result<(), &'static str> {
1369    match (contract.code_generator, evidence.layout()) {
1370        (_, Some(ArgumentLayout::Ordered(ArgumentOrder::FirstOnTop))) => Err(
1371            "this trace shows the via-IR calling convention, whose stack layout cannot be \
1372             recovered without compiler-reported variable locations",
1373        ),
1374        (Some(CodeGenerator::ViaIr), _) => Err(
1375            "this contract was compiled through the via-IR pipeline, whose stack layout cannot \
1376             be recovered without compiler-reported variable locations",
1377        ),
1378        (Some(CodeGenerator::Legacy), _) | (None, Some(ArgumentLayout::Ordered(_))) => Ok(()),
1379        (None, _) => Err(
1380            "the code generator is not known; only solc's legacy pipeline keeps variables at \
1381             fixed stack slots",
1382        ),
1383    }
1384}
1385
1386/// The inferred Solidity state of one EVM frame.
1387#[derive(Default)]
1388struct InternalFrame {
1389    /// The frames active in this EVM frame, innermost last.
1390    functions: Vec<FrameEntry>,
1391    /// How many of `functions` are functions rather than helper placeholders.
1392    named: usize,
1393    /// How many functions the EVM frames below this one have active, which cannot
1394    /// change while this one runs.
1395    named_below: usize,
1396    /// The last real statement executed in this frame, which generated code belongs to.
1397    statement: Option<LocationRef>,
1398    /// The variables of frames that were left without returning, by function: a modifier
1399    /// hands over to the body it wraps and resumes after it with its slots still there.
1400    remembered: FxHashMap<usize, FrameVariables>,
1401}
1402
1403impl InternalFrame {
1404    /// The innermost function, looking past helper placeholders.
1405    fn active(&self) -> Option<usize> {
1406        self.functions.iter().rev().find_map(|entry| entry.function)
1407    }
1408
1409    fn is_active(&self, function: usize) -> bool {
1410        self.active() == Some(function)
1411    }
1412
1413    fn push_function(
1414        &mut self,
1415        function: usize,
1416        return_pc: Option<u64>,
1417        variables: Option<FrameVariables>,
1418    ) {
1419        self.functions.push(FrameEntry {
1420            function: Some(function),
1421            return_pc,
1422            variables,
1423        });
1424        self.named += 1;
1425    }
1426
1427    /// Leaves the innermost function or placeholder.
1428    fn pop(&mut self) {
1429        if self
1430            .functions
1431            .pop()
1432            .is_some_and(|entry| entry.function.is_some())
1433        {
1434            self.named -= 1;
1435        }
1436    }
1437
1438    /// Leaves every function and placeholder.
1439    fn clear(&mut self) {
1440        self.functions.clear();
1441        self.named = 0;
1442    }
1443
1444    /// The functions active in this EVM frame and the ones below it.
1445    fn depth(&self) -> usize {
1446        self.named_below + self.named
1447    }
1448
1449    fn push_placeholder(&mut self) {
1450        self.functions.push(FrameEntry {
1451            function: None,
1452            return_pc: None,
1453            variables: None,
1454        });
1455    }
1456
1457    /// Whether a jump landing on `pc` returns from the innermost frame.
1458    fn returns_to(&self, pc: u64) -> bool {
1459        self.functions
1460            .last()
1461            .is_some_and(|entry| entry.return_pc == Some(pc))
1462    }
1463}
1464
1465/// Places the parameters of `function` when the step at `index` lands on a `JUMPDEST`
1466/// carrying its declaration with the decoded parameters as the top words of the stack:
1467/// its body's entry tag, jumped to by the dispatcher (`from_call`), or the dispatcher's
1468/// landing back from the decoder, which is where the body follows directly when the
1469/// optimizer inlined it. That is the calling convention, which the optimizer keeps. The
1470/// frame's calldata, when it names this function, must agree with those words, or
1471/// nothing is placed; nor is anything placed once the body has run, which rules out the
1472/// dispatcher's return tag.
1473#[allow(clippy::too_many_arguments)]
1474fn place_at_entry_landing(
1475    frame: &mut InternalFrame,
1476    contract: &ContractDebugInfo,
1477    function: usize,
1478    trace: &TransactionTrace,
1479    root_input: &str,
1480    index: usize,
1481    own: LocationRef,
1482    height: usize,
1483    from_call: bool,
1484) {
1485    let Some(declared) = contract.functions.get(function) else {
1486        return;
1487    };
1488    let at_declaration = contract.is_jumpdest(trace.steps.step(index).pc())
1489        && own.offset == declared.declaration_start;
1490    let from_dispatcher =
1491        !from_call || (index > 0 && framed_declaration(trace, contract, index - 1, declared));
1492    if !at_declaration || !from_dispatcher {
1493        return;
1494    }
1495    let entry = frame
1496        .functions
1497        .iter_mut()
1498        .rev()
1499        .find(|entry| entry.function == Some(function));
1500    let Some(variables) = entry.and_then(|entry| entry.variables.as_mut()) else {
1501        return;
1502    };
1503    if variables.params_base.is_some() || variables.body_seen {
1504        return;
1505    }
1506    let stack = trace.steps.step(index).stack().to_vec();
1507    let calldata = frame_calldata(trace, index, root_input);
1508    if argument_evidence(declared, calldata, &stack) == Evidence::Contradicted {
1509        return;
1510    }
1511    variables.params_base = height.checked_sub(parameter_slots(&declared.params));
1512}
1513
1514/// Whether the step at `index` is a `JUMP` attributed to the declaration of `function`
1515/// as a whole: the dispatcher's jump into its body.
1516fn framed_declaration(
1517    trace: &TransactionTrace,
1518    contract: &ContractDebugInfo,
1519    index: usize,
1520    function: &SourceFunction,
1521) -> bool {
1522    let step = trace.steps.step(index);
1523    if step.op() != opcode::JUMP {
1524        return false;
1525    }
1526    contract.location_at_pc(step.pc()).is_some_and(|location| {
1527        location.source_id == function.source_id && location.offset == function.declaration_start
1528    })
1529}
1530
1531/// The slot of the first parameter of the modifier `function`, from the placed function
1532/// it runs for: above that function's parameters and return parameters, and the slots of
1533/// the modifiers invoked before it.
1534fn modifier_base(
1535    frame: &InternalFrame,
1536    contract: &ContractDebugInfo,
1537    function: usize,
1538) -> Option<usize> {
1539    let modifier = contract.functions.get(function)?;
1540    modifier.placeholder?;
1541    let parent = frame
1542        .functions
1543        .iter()
1544        .rev()
1545        .find(|entry| entry.function.is_some())?;
1546    let parent_function = contract.functions.get(parent.function?)?;
1547    let position = parent_function
1548        .modifiers
1549        .iter()
1550        .position(|name| *name == modifier.name)?;
1551    let parent_base = parent.variables.as_ref()?.params_base?;
1552    let before = modifier_slots(contract, parent_function, position)?;
1553    Some(
1554        parent_base
1555            + parameter_slots(&parent_function.params)
1556            + parent_function.returns.len()
1557            + before,
1558    )
1559}
1560
1561impl StepMap {
1562    /// Maps every step of `trace` through the contracts' debug info.
1563    ///
1564    /// The root frame executes the transaction's target, or the created contract for a
1565    /// deployment; a trace with several top-level calls has a root frame for each. A
1566    /// contract given with an address describes any frame that executes it; when only one
1567    /// contract is given and the trace has one root, it also describes the root whatever
1568    /// address it names, so a single `--ethdebug-dir` applies to the transaction it was
1569    /// passed for.
1570    #[must_use]
1571    pub fn new(trace: &TransactionTrace, contracts: Vec<ContractDebugInfo>) -> Self {
1572        Self::build(trace, contracts, true)
1573    }
1574
1575    /// Keeps single-instruction source stops when testing compiler debug information.
1576    ///
1577    /// Interactive stepping smooths brief line excursions. A compiler regression test
1578    /// must observe them: an optimized getter can map its entire body to one `SLOAD`.
1579    #[must_use]
1580    pub fn for_debug_diff(trace: &TransactionTrace, contracts: Vec<ContractDebugInfo>) -> Self {
1581        Self::build(trace, contracts, false)
1582    }
1583
1584    fn build(trace: &TransactionTrace, contracts: Vec<ContractDebugInfo>, smooth: bool) -> Self {
1585        let mut addresses = Vec::<String>::new();
1586        let mut address_index = FxHashMap::<String, usize>::default();
1587        let mut intern = |address: &str| -> usize {
1588            let address = normalize_address(address);
1589            *address_index.entry(address.clone()).or_insert_with(|| {
1590                addresses.push(address);
1591                addresses.len() - 1
1592            })
1593        };
1594        // The contract describing the code a frame at `address` runs. A contract's creation
1595        // and deployed programs are separate artifacts at the same address, so the one for
1596        // the frame's environment is preferred, and one that names no environment describes
1597        // both. When only the other program is loaded it still names the address's sources,
1598        // and is used as before.
1599        let roots = root_calls(trace);
1600        let single_root = roots.len() == 1;
1601        let contract_for =
1602            |address: Option<&str>, environment: CodeEnvironment, root: bool| -> Option<usize> {
1603                let address = address.map(normalize_address);
1604                if let Some(address) = &address {
1605                    let mut at_address = contracts
1606                        .iter()
1607                        .enumerate()
1608                        .filter(|(_, contract)| contract.address.as_deref() == Some(address));
1609                    let exact = at_address.clone().find(|(_, contract)| {
1610                        contract
1611                            .code_environment()
1612                            .is_none_or(|candidate| candidate == environment)
1613                    });
1614                    if let Some((index, _)) = exact.or_else(|| at_address.next()) {
1615                        return Some(index);
1616                    }
1617                }
1618                (root && single_root && contracts.len() == 1).then_some(0)
1619            };
1620
1621        // Pass 1: EVM frames, executing addresses, and each step's own span.
1622        let root_depth = trace.steps.first().map_or(0, |step| step.depth());
1623        let mut next_frame_id = 0_u32;
1624        let mut evm_frames = Vec::<EvmFrame>::new();
1625        let mut upcoming_roots = roots.iter().enumerate().peekable();
1626        let mut current_root = 0;
1627        let mut reverted = Vec::new();
1628        let mut framed = Vec::with_capacity(trace.steps.len());
1629        let recorded = FrameEntered::recorded(trace);
1630        for (index, step) in trace.steps.iter().enumerate() {
1631            let evm_depth = step.depth().saturating_sub(root_depth) as usize;
1632            if let Some((position, root)) =
1633                upcoming_roots.next_if(|(_, root)| root.entry_step == index)
1634            {
1635                current_root = position;
1636                // A top-level call starts here and replaces the previous one's frames.
1637                let environment = if root.creation(trace) {
1638                    CodeEnvironment::Create
1639                } else {
1640                    CodeEnvironment::Call
1641                };
1642                let root_address = root.address(trace);
1643                let address = root_address.map(&mut intern);
1644                evm_frames.clear();
1645                evm_frames.push(EvmFrame {
1646                    id: next_frame_id,
1647                    contract: contract_for(root_address, environment, true),
1648                    address,
1649                    storage: address,
1650                    entry_step: index,
1651                });
1652                next_frame_id += 1;
1653            }
1654            evm_frames.truncate(evm_depth + 1);
1655            while evm_frames.len() <= evm_depth {
1656                // A new EVM frame. The backend's record of the call or creation entered at
1657                // this step names the code it runs; without one, the callee's code address
1658                // is on the caller's stack at the call instruction, the step before this
1659                // one, and a `CREATE` leaves no address there at all.
1660                let call = (evm_frames.len() == evm_depth)
1661                    .then(|| index.checked_sub(1))
1662                    .flatten();
1663                let entered = recorded
1664                    .get(&index)
1665                    .filter(|_| evm_frames.len() == evm_depth)
1666                    .cloned()
1667                    .unwrap_or_else(|| FrameEntered::from_call(trace, call));
1668                let address = entered.address.as_deref().map(&mut intern);
1669                // A `DELEGATECALL` or `CALLCODE` runs the callee's code against the
1670                // caller's storage; every other call has the callee's own.
1671                let storage = if entered.delegated {
1672                    evm_frames.last().and_then(|frame| frame.storage)
1673                } else {
1674                    address
1675                };
1676                evm_frames.push(EvmFrame {
1677                    id: next_frame_id,
1678                    contract: contract_for(entered.address.as_deref(), entered.environment, false),
1679                    address,
1680                    storage,
1681                    entry_step: index,
1682                });
1683                next_frame_id += 1;
1684            }
1685            if step.op() == opcode::REVERT || step.error().is_some() {
1686                let entry_step = evm_frames
1687                    .last()
1688                    .expect("the root frame is never popped")
1689                    .entry_step;
1690                reverted.push((entry_step, index));
1691            }
1692            let frame = evm_frames.last().expect("the root frame is never popped");
1693            let facts = frame
1694                .contract
1695                .and_then(|index| contracts[index].facts_at_pc(step.pc()));
1696            let location = frame.contract.zip(facts).and_then(|(contract, facts)| {
1697                let location = facts.location.as_ref()?;
1698                Some(LocationRef {
1699                    key: LineKey {
1700                        contract,
1701                        source_id: location.source_id,
1702                        line: facts.line?,
1703                    },
1704                    offset: location.offset,
1705                    length: location.length,
1706                    function: facts.function,
1707                })
1708            });
1709            framed.push(FramedStep {
1710                evm_depth: evm_depth as u32,
1711                frame_id: frame.id,
1712                root: current_root,
1713                contract: frame.contract,
1714                address: frame.address,
1715                storage: frame.storage,
1716                location,
1717                entry: facts.and_then(|facts| facts.entry),
1718                marker: facts.map_or(JumpMarker::None, |facts| facts.marker),
1719            });
1720        }
1721
1722        // Pass 2: a step outside every function is generated code for the statement that
1723        // is executing, unless no function runs later in its EVM frame; then it is the
1724        // dispatcher finishing up after the function returned.
1725        let structured = |contract: Option<usize>| {
1726            contract.is_some_and(|index| !contracts[index].functions.is_empty())
1727        };
1728        // Frame ids are dense from zero, so a flag per id stands in for a set of them.
1729        let mut function_follows = vec![false; next_frame_id as usize];
1730        let mut epilogue = vec![false; framed.len()];
1731        for (index, step) in framed.iter().enumerate().rev() {
1732            match step.location.map(|location| location.function) {
1733                Some(Some(_)) => {
1734                    function_follows[step.frame_id as usize] = true;
1735                }
1736                Some(None) if structured(step.contract) => {
1737                    epilogue[index] = !function_follows[step.frame_id as usize];
1738                }
1739                _ => {}
1740            }
1741        }
1742
1743        let argument_layouts = prove_argument_layouts(trace, &contracts, &framed);
1744        let locals_support = contracts
1745            .iter()
1746            .zip(&argument_layouts)
1747            .map(|(contract, evidence)| locals_support(contract, *evidence))
1748            .collect::<Vec<_>>();
1749
1750        // Pass 3: the virtual function stack per EVM frame, frame depths, the line each
1751        // step counts as, and the variables of the frame executing it.
1752        let mut internal = Vec::<InternalFrame>::new();
1753        let mut steps = Vec::with_capacity(framed.len());
1754        let mut pcs = Vec::with_capacity(framed.len());
1755        let mut variable_layouts = Vec::<Vec<InferredVariable>>::new();
1756        let mut step_variables = Vec::with_capacity(framed.len());
1757        for (index, step) in framed.iter().enumerate() {
1758            let height = trace.steps.step(index).stack().len();
1759            // Variables are tracked for a frame whose contract keeps them at fixed slots.
1760            // A frame entered by a jump from inside another function has its parameters
1761            // on top of the stack at entry, which places them; a public function entered
1762            // from the dispatcher is placed later, by its first reservation.
1763            let frame_variables = |function: usize| -> Option<FrameVariables> {
1764                let contract_index = step.contract?;
1765                locals_support[contract_index].ok()?;
1766                let contract = &contracts[contract_index];
1767                let function = contract.functions.get(function)?;
1768                let landed_from_function = index > 0
1769                    && framed[index - 1].frame_id == step.frame_id
1770                    && trace.steps.step(index - 1).op() == opcode::JUMP
1771                    && framed[index - 1]
1772                        .location
1773                        .is_some_and(|location| location.function.is_some());
1774                let params_base = landed_from_function
1775                    .then(|| height.checked_sub(parameter_slots(&function.params)))
1776                    .flatten();
1777                Some(FrameVariables::new(
1778                    function,
1779                    params_base,
1780                    modifier_slots(contract, function, function.modifiers.len()),
1781                ))
1782            };
1783            let contract_info = step.contract.map(|index| &contracts[index]);
1784            let evm_depth = step.evm_depth as usize;
1785            let mut frame_entry = false;
1786            // A new top-level call starts with no functions.
1787            let new_root = index > 0 && framed[index - 1].root != step.root;
1788            let root_input = roots[step.root].input(trace);
1789            internal.truncate(if new_root { evm_depth } else { evm_depth + 1 });
1790            while internal.len() <= evm_depth {
1791                let named_below = internal.last().map_or(0, InternalFrame::depth);
1792                internal.push(InternalFrame {
1793                    named_below,
1794                    ..InternalFrame::default()
1795                });
1796                frame_entry = true;
1797            }
1798            let frame = internal.last_mut().expect("the root frame is never popped");
1799
1800            // A step reached by a `JUMP` from the same EVM frame is a landing: the jump's
1801            // marker, or landing on a function's entry point, says whether it was a call.
1802            let landed_by_jump = index > 0
1803                && framed[index - 1].frame_id == step.frame_id
1804                && trace.steps.step(index - 1).op() == opcode::JUMP;
1805            // Whether the previous step ran another function's own code in this EVM
1806            // frame, as opposed to the dispatcher's.
1807            let from_function_code = index > 0
1808                && framed[index - 1].frame_id == step.frame_id
1809                && framed[index - 1].location.is_some_and(|location| {
1810                    location.function.is_some()
1811                        && location.function != step.location.and_then(|own| own.function)
1812                });
1813            let previous_marker = if landed_by_jump {
1814                framed[index - 1].marker
1815            } else {
1816                JumpMarker::None
1817            };
1818            let structured = structured(step.contract);
1819            let mut generated = false;
1820            // A return pops whatever the matching call pushed, wherever it lands: the
1821            // artifact marks it, or the jump lands on the return address recorded at the
1822            // call. A marked call is resolved once the landing's function is known.
1823            let mut pending_call = false;
1824            if landed_by_jump {
1825                let pc = trace.steps.step(index).pc();
1826                match previous_marker {
1827                    JumpMarker::Call => pending_call = true,
1828                    JumpMarker::Return => frame.pop(),
1829                    JumpMarker::None if frame.returns_to(pc) => frame.pop(),
1830                    JumpMarker::None => {}
1831                }
1832            }
1833            // The return address a call being made here would come back to, read off
1834            // the caller's stack at the jump: the tag pushed before the arguments.
1835            let return_address = |function: usize| -> Option<u64> {
1836                if !landed_by_jump {
1837                    return None;
1838                }
1839                let contract = &contracts[step.contract?];
1840                let arguments = contract.functions.get(function)?.params.len();
1841                let word = trace.steps.step(index - 1).stack().peek(1 + arguments)?;
1842                let pc = word.to_u64()?;
1843                contract.is_jumpdest(pc).then_some(pc)
1844            };
1845            let (location, key) = match step.location {
1846                None => {
1847                    // Code without source, such as a generated helper: a marked call into
1848                    // it gets a placeholder its marked return pops.
1849                    if pending_call {
1850                        frame.push_placeholder();
1851                    }
1852                    (None, None)
1853                }
1854                Some(own) if !structured => {
1855                    // No parsed functions: every mapped step is a line of its own.
1856                    frame.statement = Some(own);
1857                    (Some(own), Some(own.key))
1858                }
1859                Some(own) => match own.function {
1860                    Some(function) => {
1861                        let entering = landed_by_jump && step.entry == Some(function);
1862                        // Back from the decoder in the function's own frame: the
1863                        // parameters are on top, whether a jump to the body follows or
1864                        // the body was inlined here.
1865                        if landed_by_jump
1866                            && previous_marker == JumpMarker::Return
1867                            && frame.is_active(function)
1868                        {
1869                            if let Some(contract) = contract_info {
1870                                place_at_entry_landing(
1871                                    frame, contract, function, trace, root_input, index, own,
1872                                    height, false,
1873                                );
1874                            }
1875                        }
1876                        if entering || (pending_call && !frame.is_active(function)) {
1877                            // A call: onto the entry point, or marked and into a function
1878                            // other than the active one.
1879                            frame.push_function(
1880                                function,
1881                                return_address(function),
1882                                frame_variables(function),
1883                            );
1884                            frame_entry = true;
1885                        } else if pending_call {
1886                            // A marked call into the active function away from its entry
1887                            // point: recursion always enters at the entry point, so this
1888                            // is the dispatcher jumping to the body of the function whose
1889                            // parameters it decoded, which places them, or a generated
1890                            // helper whose span is the calling line.
1891                            if let Some(contract) = contract_info {
1892                                place_at_entry_landing(
1893                                    frame, contract, function, trace, root_input, index, own,
1894                                    height, true,
1895                                );
1896                            }
1897                            frame.push_placeholder();
1898                        } else if !frame.is_active(function) {
1899                            match frame
1900                                .functions
1901                                .iter()
1902                                .rposition(|entry| entry.function == Some(function))
1903                            {
1904                                Some(position) => {
1905                                    // A modifier handing over to the body keeps its slots;
1906                                    // remember them for when it resumes.
1907                                    frame.named -= frame.functions[position + 1..]
1908                                        .iter()
1909                                        .filter(|entry| entry.function.is_some())
1910                                        .count();
1911                                    for left in frame.functions.drain(position + 1..) {
1912                                        if let (Some(function), Some(variables)) =
1913                                            (left.function, left.variables)
1914                                        {
1915                                            frame.remembered.insert(function, variables);
1916                                        }
1917                                    }
1918                                }
1919                                None => {
1920                                    let mut variables = frame.remembered.remove(&function);
1921                                    if variables.is_none() {
1922                                        variables = frame_variables(function);
1923                                        if let (Some(variables), Some(contract)) =
1924                                            (variables.as_mut(), contract_info)
1925                                        {
1926                                            let declared = &contract.functions[function];
1927                                            if declared.placeholder.is_some() {
1928                                                // A modifier's parameters sit right above
1929                                                // the parameters and return parameters of
1930                                                // the function it runs for, and the slots
1931                                                // of the modifiers before it; a placed
1932                                                // function places its modifiers.
1933                                                if variables.params_base.is_none() {
1934                                                    variables.params_base =
1935                                                        modifier_base(frame, contract, function);
1936                                                }
1937                                            } else if declared.name != "constructor"
1938                                                && from_function_code
1939                                                && step.entry != Some(function)
1940                                            {
1941                                                // A function reached from inside another
1942                                                // without a jump onto its entry: the
1943                                                // optimizer inlined the call.
1944                                                variables.inlined = true;
1945                                            }
1946                                        }
1947                                    }
1948                                    frame.push_function(function, None, variables);
1949                                    frame_entry = true;
1950                                }
1951                            }
1952                        }
1953                        frame.statement = Some(own);
1954                        (Some(own), Some(own.key))
1955                    }
1956                    None if epilogue[index] => {
1957                        frame.clear();
1958                        frame.statement = None;
1959                        (Some(own), None)
1960                    }
1961                    None => {
1962                        if pending_call {
1963                            frame.push_placeholder();
1964                        }
1965                        match frame.statement {
1966                            Some(statement) => {
1967                                generated = true;
1968                                (Some(statement), Some(statement.key))
1969                            }
1970                            // The dispatcher before any function: shown as it is, stepped
1971                            // through as no line at all.
1972                            None => (Some(own), None),
1973                        }
1974                    }
1975                },
1976            };
1977            // The variables of the innermost function frame: followed through a step of
1978            // the function's own code, inherited by any other step.
1979            let layout = {
1980                let frame = internal.last_mut().expect("the root frame is never popped");
1981                let contract = step.contract.map(|index| &contracts[index]);
1982                let active = frame
1983                    .functions
1984                    .iter()
1985                    .rposition(|entry| entry.function.is_some());
1986                match (active, step.location, contract) {
1987                    (Some(position), Some(own), Some(contract))
1988                        if own.function.is_some()
1989                            && own.function == frame.functions[position].function =>
1990                    {
1991                        let function_index = own.function.unwrap_or_default();
1992                        let function = &contract.functions[function_index];
1993                        let span = ByteRange {
1994                            start: own.offset,
1995                            end: own.offset + own.length,
1996                        };
1997                        let (below, rest) = frame.functions.split_at_mut(position);
1998                        let entry = &mut rest[0];
1999                        let layout = entry.variables.as_mut().map(|variables| {
2000                            variables.track(function, span, height, &mut variable_layouts)
2001                        });
2002                        // A modifier's parameters sit right above the function's
2003                        // parameters and return parameters and the slots of the modifiers
2004                        // before it, so placing the modifier places the function it runs
2005                        // for when nothing else has.
2006                        let modifier_base = entry
2007                            .variables
2008                            .as_ref()
2009                            .and_then(|variables| variables.params_base);
2010                        let parent = below
2011                            .iter_mut()
2012                            .rev()
2013                            .find(|entry| entry.function.is_some());
2014                        if let (Some(modifier_base), Some(parent)) = (modifier_base, parent) {
2015                            let parent_function =
2016                                &contract.functions[parent.function.unwrap_or_default()];
2017                            let position = parent_function
2018                                .modifiers
2019                                .iter()
2020                                .position(|name| *name == function.name);
2021                            if let (Some(position), Some(variables)) =
2022                                (position, parent.variables.as_mut())
2023                            {
2024                                if variables.params_base.is_none() {
2025                                    variables.params_base =
2026                                        modifier_slots(contract, parent_function, position)
2027                                            .and_then(|before| {
2028                                                modifier_base.checked_sub(
2029                                                    parameter_slots(&parent_function.params)
2030                                                        + parent_function.returns.len()
2031                                                        + before,
2032                                                )
2033                                            });
2034                                }
2035                            }
2036                        }
2037                        layout
2038                    }
2039                    (Some(position), _, _) => frame.functions[position]
2040                        .variables
2041                        .as_ref()
2042                        .and_then(|variables| variables.layout),
2043                    _ => None,
2044                }
2045            };
2046            step_variables.push(layout.unwrap_or(NO_LAYOUT));
2047            let internal_total = internal.last().map_or(0, InternalFrame::depth);
2048            steps.push(StepInfo {
2049                location,
2050                generated,
2051                key,
2052                contract: step.contract,
2053                address: step.address,
2054                storage: step.storage,
2055                evm_depth: step.evm_depth,
2056                starts_root: new_root,
2057                frame_depth: (evm_depth + internal_total) as u32,
2058                frame_entry,
2059                line_start: false,
2060            });
2061            pcs.push(trace.steps.step(index).pc());
2062        }
2063
2064        let mut map = Self {
2065            contracts,
2066            steps,
2067            addresses,
2068            pcs,
2069            reverted,
2070            argument_layouts,
2071            locals_support,
2072            variable_layouts,
2073            step_variables,
2074            roots,
2075        };
2076        if smooth {
2077            Self::smooth_single_step_excursions(&mut map.steps);
2078        }
2079        map.mark_line_starts();
2080        map
2081    }
2082
2083    /// A lone instruction attributed to another line, between two runs of the same line
2084    /// at the same depth, counts as that line. solc emits such instructions at function
2085    /// entry, and stopping on them would show a later line before an earlier one. The runs
2086    /// are looked for within the step's own top-level call.
2087    ///
2088    /// One pass in each direction finds every step's neighbours, so deep recursion does
2089    /// not make each step search the frames it calls.
2090    fn smooth_single_step_excursions(steps: &mut [StepInfo]) {
2091        // The step after each one with its line, as an index: the forward pass reads its
2092        // key before changing anything after it.
2093        let mut after = vec![None; steps.len()];
2094        let mut lines = NearestLines::default();
2095        for (index, step) in steps.iter().enumerate().rev() {
2096            let own = step.key.and_then(|_| u32::try_from(index).ok());
2097            after[index] = lines.visit(step.frame_depth, own);
2098            // A step that starts a root ends the search of the steps before it.
2099            if step.starts_root {
2100                lines.clear();
2101            }
2102        }
2103        let mut lines = NearestLines::default();
2104        for (index, after) in after.into_iter().enumerate() {
2105            let step = &steps[index];
2106            if step.starts_root {
2107                lines.clear();
2108            }
2109            let before = lines.visit(step.frame_depth, step.key);
2110            let Some(key) = step.key else {
2111                continue;
2112            };
2113            let after = after.and_then(|after| steps[after as usize].key);
2114            if before.is_some() && before == after && before != Some(key) {
2115                steps[index].key = before;
2116            }
2117        }
2118    }
2119
2120    /// Marks the steps that begin a run of one line at their depth. Deeper frames and
2121    /// steps without a line do not end a run.
2122    fn mark_line_starts(&mut self) {
2123        // The key of the last step at or below each depth seen since the root started, as
2124        // runs of depths sharing one: a step sets its depth and every deeper one, which is
2125        // one run, so each step pushes and pops at most once.
2126        let mut runs = Vec::<(usize, Option<LineKey>)>::new();
2127        let mut depths = 0;
2128        for step in &mut self.steps {
2129            if step.starts_root {
2130                runs.clear();
2131                depths = 0;
2132            }
2133            let depth = step.frame_depth as usize;
2134            if depths <= depth {
2135                // Depths first reached now have no key yet.
2136                if runs.last().is_none_or(|(_, key)| key.is_some()) {
2137                    runs.push((depths, None));
2138                }
2139                depths = depth + 1;
2140            }
2141            let Some(key) = step.key else {
2142                step.line_start = false;
2143                continue;
2144            };
2145            while runs.last().is_some_and(|(from, _)| *from > depth) {
2146                runs.pop();
2147            }
2148            let last = runs.last_mut().expect("a run starts at depth zero");
2149            step.line_start = last.1 != Some(key);
2150            if last.0 == depth {
2151                last.1 = Some(key);
2152            } else {
2153                runs.push((depth, Some(key)));
2154            }
2155        }
2156    }
2157
2158    #[must_use]
2159    pub fn contracts(&self) -> &[ContractDebugInfo] {
2160        &self.contracts
2161    }
2162
2163    /// The contract whose code the step executes, when its debug info was loaded.
2164    #[must_use]
2165    pub fn contract_at_step(&self, step: usize) -> Option<&ContractDebugInfo> {
2166        self.contracts.get(self.steps.get(step)?.contract?)
2167    }
2168
2169    #[must_use]
2170    pub fn step_count(&self) -> usize {
2171        self.steps.len()
2172    }
2173
2174    /// Whether any step maps to a source line at all.
2175    #[must_use]
2176    pub fn has_source(&self) -> bool {
2177        self.steps.iter().any(|step| step.location.is_some())
2178    }
2179
2180    /// The line a step counts as for stepping and breakpoints.
2181    #[must_use]
2182    pub fn line_key(&self, step: usize) -> Option<LineKey> {
2183        self.steps.get(step)?.key
2184    }
2185
2186    #[must_use]
2187    pub fn frame_depth(&self, step: usize) -> Option<u32> {
2188        self.steps.get(step).map(|step| step.frame_depth)
2189    }
2190
2191    #[must_use]
2192    pub fn is_line_start(&self, step: usize) -> bool {
2193        self.steps.get(step).is_some_and(|step| step.line_start)
2194    }
2195
2196    #[must_use]
2197    pub fn is_frame_entry(&self, step: usize) -> bool {
2198        self.steps.get(step).is_some_and(|step| step.frame_entry)
2199    }
2200
2201    /// The function the step runs in, including generated code for one of its statements.
2202    #[must_use]
2203    pub fn function_id(&self, step: usize) -> Option<FunctionId> {
2204        let location = self.steps.get(step)?.location?;
2205        location.function.map(|function| FunctionId {
2206            contract: location.key.contract,
2207            function,
2208        })
2209    }
2210
2211    /// The address whose code the step executes, when it could be determined.
2212    #[must_use]
2213    pub fn executing_address(&self, step: usize) -> Option<&str> {
2214        let index = self.steps.get(step)?.address?;
2215        self.addresses.get(index).map(String::as_str)
2216    }
2217
2218    /// The account whose storage the step reads and writes: the executing address, or
2219    /// the caller's under a `DELEGATECALL`.
2220    #[must_use]
2221    pub fn storage_address(&self, step: usize) -> Option<&str> {
2222        let index = self.storage_context_index(step)?;
2223        self.addresses.get(index).map(String::as_str)
2224    }
2225
2226    /// An opaque identifier for that account, for indexing storage by context.
2227    #[must_use]
2228    pub fn storage_context_index(&self, step: usize) -> Option<usize> {
2229        self.steps.get(step)?.storage
2230    }
2231
2232    /// Every EVM frame that reverted, as the step it was entered at and the step it
2233    /// reverted on. A frame's writes, and its callees', do not survive it.
2234    #[must_use]
2235    pub fn reverted_spans(&self) -> &[(usize, usize)] {
2236        &self.reverted
2237    }
2238
2239    /// What this trace proved about where a contract's compiler leaves function
2240    /// parameters on the stack. `None` when the trace carried no frame that could show
2241    /// it, or when a frame contradicted it.
2242    #[must_use]
2243    pub fn argument_layout(&self, contract: usize) -> Option<ArgumentLayout> {
2244        self.argument_layouts.get(contract).copied()?.layout()
2245    }
2246
2247    /// The variables of the function executing at `step`, inferred from the stack, or why
2248    /// none can be.
2249    ///
2250    /// Inference follows solc's legacy code generator, which keeps every variable at a
2251    /// fixed stack slot: the parameters below the entry height, and each return parameter
2252    /// and local at the slot the frame had when its declaration executed. A contract known
2253    /// to come from the via-IR pipeline, or whose trace shows that pipeline's calling
2254    /// convention, is not inferred. The result is a reading of the stack, not of compiler
2255    /// variable locations, and a frontend should say so; see [`INFERRED_LOCALS_WARNING`].
2256    #[must_use]
2257    pub fn locals_at(&self, step: usize) -> LocalsStatus<'_> {
2258        let Some(info) = self.steps.get(step) else {
2259            return LocalsStatus::Unavailable("the step is outside the trace");
2260        };
2261        let Some(contract) = info.contract else {
2262            return LocalsStatus::Unavailable(
2263                "no sources matched the contract executing at this step",
2264            );
2265        };
2266        if let Err(reason) = self.locals_support[contract] {
2267            return LocalsStatus::Unavailable(reason);
2268        }
2269        match self.step_variables.get(step).copied() {
2270            Some(INLINED_LAYOUT) => LocalsStatus::Unavailable(
2271                "this function was inlined by the optimizer, so its variables have no stack \
2272                 slots of their own",
2273            ),
2274            Some(id) if id != NO_LAYOUT => {
2275                LocalsStatus::Inferred(&self.variable_layouts[id as usize])
2276            }
2277            _ => LocalsStatus::Unavailable("no function is executing at this step"),
2278        }
2279    }
2280
2281    /// The calldata of the frame executing `step`; empty in a nested frame whose call the
2282    /// backend did not record.
2283    #[must_use]
2284    pub fn calldata_at<'a>(&self, trace: &'a TransactionTrace, step: usize) -> &'a str {
2285        calldata_for_step(trace, &self.roots, step)
2286    }
2287
2288    /// The contract whose code declares the function executing at `step`: where a bare
2289    /// type name written in that function resolves.
2290    pub(crate) fn scope_at_step(&self, step: usize) -> Option<&str> {
2291        let info = self.steps.get(step)?;
2292        let contract = self.contracts.get(info.contract?)?;
2293        let function = contract.functions.get(info.location?.function?)?;
2294        contract
2295            .types
2296            .scope_at(function.source_id, function.declaration_start)
2297    }
2298
2299    /// The value of `path` at `step`: a local variable in scope, or a member, element,
2300    /// mapping entry, or `length` reached from one, such as `item.tags[1]` or
2301    /// `stored.owners[0xabc]`. `None` when no local in scope has the path's first name,
2302    /// so the caller can look it up as a state variable instead; `Some(Err)` says why a
2303    /// local's path could not be followed.
2304    #[must_use]
2305    pub fn local_path(
2306        &self,
2307        trace: &TransactionTrace,
2308        step: usize,
2309        words: Option<&StorageWords<'_>>,
2310        path: &str,
2311    ) -> Option<Result<DebugVariable, String>> {
2312        let found = match self.local_place(trace, step, words, path)? {
2313            Ok(found) => found,
2314            Err(reason) => return Some(Err(reason)),
2315        };
2316        let (reader, slot, place) = found;
2317        let (ty, value) = match place {
2318            Ok(place) => (place.ty(), reader.show(&place)),
2319            Err((ty, shown)) => (ty, shown),
2320        };
2321        Some(Ok(DebugVariable {
2322            name: path.trim().to_owned(),
2323            ty,
2324            location: DebugLocation {
2325                kind: "stack".to_owned(),
2326                offset: slot as u64,
2327            },
2328            value,
2329        }))
2330    }
2331
2332    /// The value of `path` as a breakpoint condition compares it; see
2333    /// [`StepMap::local_path`].
2334    #[must_use]
2335    pub fn local_condition_value(
2336        &self,
2337        trace: &TransactionTrace,
2338        step: usize,
2339        words: Option<&StorageWords<'_>>,
2340        path: &str,
2341    ) -> Option<Result<Value, String>> {
2342        let (reader, _, place) = match self.local_place(trace, step, words, path)? {
2343            Ok(found) => found,
2344            Err(reason) => return Some(Err(reason)),
2345        };
2346        Some(match place {
2347            Ok(place) => reader.condition_value(&place, path.trim()),
2348            Err((_, shown)) => Err(format!("`{}` is {}", path.trim(), shown.display)),
2349        })
2350    }
2351
2352    /// Follows `path` from the local its first name names. The place is `Err` with the
2353    /// value to show when the variable's own words lead nowhere readable (memory the
2354    /// backend did not capture, a storage pointer without a layout), so a bare name still
2355    /// shows what `vars` would.
2356    #[allow(clippy::type_complexity)]
2357    fn local_place<'a>(
2358        &'a self,
2359        trace: &'a TransactionTrace,
2360        step: usize,
2361        words: Option<&'a StorageWords<'a>>,
2362        path: &str,
2363    ) -> Option<Result<(ValueReader<'a>, usize, Result<Place, (String, DebugValue)>), String>> {
2364        let LocalsStatus::Inferred(layout) = self.locals_at(step) else {
2365            return None;
2366        };
2367        let segments = parse_path(path).ok()?;
2368        let Some(PathSegment::Name(name)) = segments.first() else {
2369            return None;
2370        };
2371        // The last declared wins, as the innermost scope's does in the language.
2372        let variable = layout
2373            .iter()
2374            .rev()
2375            .find(|variable| variable.name == *name)?;
2376        let contract = self.contract_at_step(step)?;
2377        let view = trace.steps.get(step);
2378        let stack = view.map(|view| view.stack().to_vec()).unwrap_or_default();
2379        let reader = ValueReader {
2380            memory: view.and_then(|view| view.memory()),
2381            calldata: self.calldata_at(trace, step),
2382            storage: words,
2383            layout: contract.storage_layout.as_ref(),
2384            types: &contract.types,
2385            scope: self.scope_at_step(step),
2386        };
2387        let Some(slot) = variable.slot else {
2388            return Some(Err(format!(
2389                "`{name}` is in scope but its stack slot could not be placed here"
2390            )));
2391        };
2392        let Some(stack_words) = stack.get(slot..slot + variable.words) else {
2393            return Some(Err(format!(
2394                "`{name}` is in scope but its stack slot is above the stack here"
2395            )));
2396        };
2397        let stack_words = stack_words
2398            .iter()
2399            .map(EvmWord::to_string)
2400            .collect::<Vec<_>>();
2401        let stack_words = stack_words.iter().map(String::as_str).collect::<Vec<_>>();
2402        let mut place = match reader.root(&variable.ty, &stack_words) {
2403            Ok(place) => place,
2404            Err(shown) if segments.len() == 1 => {
2405                return Some(Ok((reader, slot, Err((variable.ty.clone(), shown)))));
2406            }
2407            Err(shown) => return Some(Err(format!("`{name}` is {}", shown.display))),
2408        };
2409        let mut so_far = name.clone();
2410        for segment in &segments[1..] {
2411            place = match reader.follow(place, segment, &so_far) {
2412                Ok(place) => place,
2413                Err(reason) => return Some(Err(reason)),
2414            };
2415            match segment {
2416                PathSegment::Member(member) => so_far = format!("{so_far}.{member}"),
2417                PathSegment::Index(key) => so_far = format!("{so_far}[{key}]"),
2418                PathSegment::Name(_) => {}
2419            }
2420        }
2421        Some(Ok((reader, slot, Ok(place))))
2422    }
2423
2424    /// The inferred variables at `step`, read off its stack and decoded by type: a value
2425    /// type from its word, a reference type through the memory, storage, or calldata its
2426    /// word points into. `words` are the storage words known at the step, for storage
2427    /// pointers; without them a storage pointer is shown as its slot.
2428    ///
2429    /// A variable whose slot is not known, or lies above the stack, is reported as
2430    /// unavailable rather than as a wrong word.
2431    #[must_use]
2432    pub fn inferred_variables(
2433        &self,
2434        trace: &TransactionTrace,
2435        step: usize,
2436        words: Option<&StorageWords<'_>>,
2437    ) -> Vec<DebugVariable> {
2438        let LocalsStatus::Inferred(layout) = self.locals_at(step) else {
2439            return Vec::new();
2440        };
2441        let Some(contract) = self.contract_at_step(step) else {
2442            return Vec::new();
2443        };
2444        let view = trace.steps.get(step);
2445        let stack = view.map(|view| view.stack().to_vec()).unwrap_or_default();
2446        let reader = ValueReader {
2447            memory: view.and_then(|view| view.memory()),
2448            calldata: self.calldata_at(trace, step),
2449            storage: words,
2450            layout: contract.storage_layout.as_ref(),
2451            types: &contract.types,
2452            scope: self.scope_at_step(step),
2453        };
2454        let unavailable = || DebugValue {
2455            display: "<unavailable>".to_owned(),
2456            raw: None,
2457            status: DebugValueStatus::Unavailable,
2458        };
2459        layout
2460            .iter()
2461            .map(|variable| {
2462                let words = variable
2463                    .slot
2464                    .and_then(|slot| stack.get(slot..slot + variable.words))
2465                    .map(|words| words.iter().map(EvmWord::to_string).collect::<Vec<_>>());
2466                let value = match words {
2467                    Some(words) => {
2468                        let words = words.iter().map(String::as_str).collect::<Vec<_>>();
2469                        reader.variable(&variable.ty, &words)
2470                    }
2471                    None => unavailable(),
2472                };
2473                DebugVariable {
2474                    name: variable.name.clone(),
2475                    ty: variable.ty.clone(),
2476                    location: DebugLocation {
2477                        kind: "stack".to_owned(),
2478                        offset: variable.slot.unwrap_or_default() as u64,
2479                    },
2480                    value,
2481                }
2482            })
2483            .collect()
2484    }
2485
2486    /// The arguments `frame` was entered with, given the stack at its entry step.
2487    ///
2488    /// Only a frame entered at its function's entry point carries them, and only once
2489    /// this trace has shown that the compiler leaves them there: which word is which
2490    /// parameter depends on the code generator, and a legacy public function is entered
2491    /// through a dispatcher wrapper that has not decoded them yet. See
2492    /// [`StepMap::argument_layout`].
2493    #[must_use]
2494    pub fn frame_arguments(&self, frame: &Frame, state: FrameState<'_>) -> Vec<FrameArgument> {
2495        let Some(info) = self.steps.get(frame.entry_step) else {
2496            return Vec::new();
2497        };
2498        let Some(location) = info.location else {
2499            return Vec::new();
2500        };
2501        let Some(function_index) = location.function else {
2502            return Vec::new();
2503        };
2504        let Some(contract) = self.contracts.get(location.key.contract) else {
2505            return Vec::new();
2506        };
2507        if contract.function_entry_at_pc(self.pcs[frame.entry_step]) != Some(function_index) {
2508            return Vec::new();
2509        }
2510        let Some(function) = contract.functions.get(function_index) else {
2511            return Vec::new();
2512        };
2513        let Some(layout) = self.argument_layout(location.key.contract) else {
2514            return Vec::new();
2515        };
2516        decode_arguments(
2517            &function.params,
2518            state,
2519            layout,
2520            &contract.types,
2521            contract
2522                .types
2523                .scope_at(function.source_id, function.declaration_start),
2524        )
2525    }
2526
2527    #[must_use]
2528    pub fn location(&self, step: usize) -> Option<StepLocation> {
2529        let info = self.steps.get(step)?;
2530        self.describe(&info.location?, info.generated)
2531    }
2532
2533    fn describe(&self, location: &LocationRef, generated: bool) -> Option<StepLocation> {
2534        let contract = self.contracts.get(location.key.contract)?;
2535        let function = location
2536            .function
2537            .and_then(|index| contract.functions.get(index));
2538        Some(StepLocation {
2539            key: location.key,
2540            generated,
2541            contract_name: contract.name.clone(),
2542            path: contract
2543                .source_path(location.key.source_id)
2544                .map(str::to_owned)
2545                .unwrap_or_else(|| format!("source:{}", location.key.source_id)),
2546            offset: location.offset,
2547            length: location.length,
2548            line: location.key.line,
2549            column: contract
2550                .column_of(location.key.source_id, location.offset)
2551                .unwrap_or(0),
2552            function: location.function.map(|function| FunctionId {
2553                contract: location.key.contract,
2554                function,
2555            }),
2556            function_name: function.map(|function| function.name.clone()),
2557        })
2558    }
2559
2560    /// The step where the next source line begins at this frame or a caller's, stepping
2561    /// over calls. `None` when the recording ends first.
2562    ///
2563    /// Steps the compiler attached no source to, such as generated helper code, never
2564    /// stop a line step at the same depth: they belong to whatever line is executing.
2565    ///
2566    /// From code outside every function, such as the dispatcher, this enters the function
2567    /// it jumps to instead of stepping over it: there is no caller to come back to, and
2568    /// a user at the first step wants to reach the first line, not the last.
2569    #[must_use]
2570    pub fn next_source(&self, step: usize) -> Option<usize> {
2571        let current = *self.steps.get(step)?;
2572        if self.outside_functions(&current) {
2573            return self.step_into(step);
2574        }
2575        let key = current.key;
2576        ((step + 1)..self.steps.len()).find(|&index| {
2577            let candidate = &self.steps[index];
2578            if candidate.frame_depth > current.frame_depth {
2579                return false;
2580            }
2581            candidate.frame_depth < current.frame_depth
2582                || candidate
2583                    .key
2584                    .is_some_and(|candidate| Some(candidate) != key)
2585        })
2586    }
2587
2588    /// The step where the next source line begins anywhere, entering calls. A call into
2589    /// code without source stops at its first step, so the user sees where they are.
2590    #[must_use]
2591    pub fn step_into(&self, step: usize) -> Option<usize> {
2592        let current = *self.steps.get(step)?;
2593        let key = current.key;
2594        ((step + 1)..self.steps.len()).find(|&index| {
2595            let candidate = &self.steps[index];
2596            candidate.frame_depth != current.frame_depth
2597                || candidate
2598                    .key
2599                    .is_some_and(|candidate| Some(candidate) != key)
2600        })
2601    }
2602
2603    /// The step where the current frame has returned to its caller. A root frame returns
2604    /// where the next top-level call starts.
2605    #[must_use]
2606    pub fn finish(&self, step: usize) -> Option<usize> {
2607        let current = *self.steps.get(step)?;
2608        ((step + 1)..self.steps.len()).find(|&index| {
2609            let candidate = &self.steps[index];
2610            candidate.frame_depth < current.frame_depth || candidate.starts_root
2611        })
2612    }
2613
2614    /// The start of the previous source line at this frame or a caller's, skipping the
2615    /// calls it made. From the middle of a line this is the start of that line. From code
2616    /// outside every function it enters the function that just returned, the mirror of
2617    /// [`StepMap::next_source`].
2618    #[must_use]
2619    pub fn previous_source(&self, step: usize) -> Option<usize> {
2620        let current = *self.steps.get(step)?;
2621        if self.outside_functions(&current) {
2622            return self.reverse_step_into(step);
2623        }
2624        self.previous_line_start(step, true)
2625    }
2626
2627    /// The start of the previous source line anywhere, entering the calls it made.
2628    #[must_use]
2629    pub fn reverse_step_into(&self, step: usize) -> Option<usize> {
2630        self.steps.get(step)?;
2631        self.previous_line_start(step, false)
2632    }
2633
2634    fn previous_line_start(&self, step: usize, skip_deeper: bool) -> Option<usize> {
2635        let current = self.steps[step];
2636        // From a step without a line, the previous line is the nearest step with one.
2637        let anchor = if current.key.is_some() {
2638            step
2639        } else {
2640            self.previous_mapped(step, current.frame_depth, skip_deeper)?
2641        };
2642        let start = self.run_start(anchor, skip_deeper);
2643        if start < step {
2644            return Some(start);
2645        }
2646        let previous = self.previous_mapped(start, current.frame_depth, skip_deeper)?;
2647        Some(self.run_start(previous, skip_deeper))
2648    }
2649
2650    /// The step in the caller that entered the current frame. A root frame was entered
2651    /// where its top-level call starts.
2652    #[must_use]
2653    pub fn reverse_finish(&self, step: usize) -> Option<usize> {
2654        let current = *self.steps.get(step)?;
2655        (0..step).rev().find(|&index| {
2656            let candidate = &self.steps[index];
2657            candidate.frame_depth < current.frame_depth || candidate.starts_root
2658        })
2659    }
2660
2661    /// The first step of the run of one line that `step` belongs to. With `skip_deeper`,
2662    /// steps in frames the line called into do not break the run; steps without source at
2663    /// the same depth never do. A run never starts in an earlier top-level call.
2664    fn run_start(&self, step: usize, skip_deeper: bool) -> usize {
2665        let info = self.steps[step];
2666        let key = info.key;
2667        let root = root_index(&self.roots, step);
2668        let mut current = step;
2669        while let Some(previous) = self.previous_mapped(current, info.frame_depth, skip_deeper) {
2670            let candidate = self.steps[previous];
2671            if candidate.frame_depth != info.frame_depth || candidate.key != key {
2672                break;
2673            }
2674            if root_index(&self.roots, previous) != root {
2675                break;
2676            }
2677            current = previous;
2678        }
2679        current
2680    }
2681
2682    /// Whether a step is in code that belongs to no line: the dispatcher before the
2683    /// function it jumps to, or after that function returned.
2684    fn outside_functions(&self, info: &StepInfo) -> bool {
2685        info.location.is_some() && info.key.is_none()
2686    }
2687
2688    /// The nearest earlier step with a source location at `depth` or, with `skip_deeper`,
2689    /// at most `depth`; without it, the nearest earlier mapped step at any depth.
2690    fn previous_mapped(&self, step: usize, depth: u32, skip_deeper: bool) -> Option<usize> {
2691        (0..step).rev().find(|&index| {
2692            let candidate = &self.steps[index];
2693            candidate.key.is_some() && (!skip_deeper || candidate.frame_depth <= depth)
2694        })
2695    }
2696
2697    /// Resolves a `file:line` breakpoint against every loaded source. With no file, the
2698    /// line must be unambiguous across the loaded sources.
2699    pub fn resolve_line(&self, file: Option<&str>, line: u64) -> Result<Vec<ResolvedLine>, String> {
2700        let mut sources = Vec::<(usize, u64, String)>::new();
2701        for (contract_index, contract) in self.contracts.iter().enumerate() {
2702            for (source_id, path) in &contract.info.sources {
2703                if file.is_none_or(|file| source_path_matches(path, file)) {
2704                    sources.push((contract_index, *source_id, path.clone()));
2705                }
2706            }
2707        }
2708        if sources.is_empty() {
2709            return Err(match file {
2710                Some(file) => format!("source file not found: {file}"),
2711                None => "no source files are available".to_owned(),
2712            });
2713        }
2714        if file.is_none() {
2715            let mut paths = sources.iter().map(|(_, _, path)| path).collect::<Vec<_>>();
2716            paths.sort();
2717            paths.dedup();
2718            if paths.len() > 1 {
2719                return Err("line breakpoint is ambiguous; use break <file>:<line>".to_owned());
2720            }
2721        }
2722
2723        let mut resolved = sources
2724            .into_iter()
2725            .filter_map(|(contract_index, source_id, path)| {
2726                let effective = self.contracts[contract_index].effective_line(source_id, line)?;
2727                Some(ResolvedLine {
2728                    key: LineKey {
2729                        contract: contract_index,
2730                        source_id,
2731                        line: effective,
2732                    },
2733                    path,
2734                    requested_line: line,
2735                })
2736            })
2737            .collect::<Vec<_>>();
2738        if resolved.is_empty() {
2739            return Err(match file {
2740                Some(file) => format!("no instruction maps to {file}:{line}"),
2741                None => format!("no instruction maps to line {line}"),
2742            });
2743        }
2744        // A contract's creation and deployed programs share their sources but map different
2745        // lines: a constructor body has code only in the creation program. Where any program
2746        // maps the line itself, the others' fallback to the statement containing it would
2747        // stop somewhere the user did not ask for, so only the exact matches are kept.
2748        if resolved.iter().any(|candidate| candidate.key.line == line) {
2749            resolved.retain(|candidate| candidate.key.line == line);
2750        }
2751        Ok(resolved)
2752    }
2753
2754    /// Resolves a function breakpoint by name, optionally qualified as
2755    /// `Contract.function`.
2756    pub fn resolve_function(&self, name: &str) -> Result<Vec<ResolvedFunction>, String> {
2757        let (contract_name, function_name) = match name.rsplit_once('.') {
2758            Some((contract, function)) => (Some(contract), function),
2759            None => (None, name),
2760        };
2761        let resolved = self
2762            .contracts
2763            .iter()
2764            .enumerate()
2765            .filter(|(_, contract)| contract_name.is_none_or(|name| contract.name == name))
2766            .flat_map(|(contract_index, contract)| {
2767                contract
2768                    .functions
2769                    .iter()
2770                    .enumerate()
2771                    .filter(move |(_, function)| function.name == function_name)
2772                    .map(move |(function_index, function)| ResolvedFunction {
2773                        id: FunctionId {
2774                            contract: contract_index,
2775                            function: function_index,
2776                        },
2777                        name: function.name.clone(),
2778                        contract_name: contract.name.clone(),
2779                        path: contract
2780                            .source_path(function.source_id)
2781                            .map(str::to_owned)
2782                            .unwrap_or_else(|| format!("source:{}", function.source_id)),
2783                        line: function.declaration_line,
2784                    })
2785            })
2786            .collect::<Vec<_>>();
2787        if resolved.is_empty() {
2788            return Err(format!("no function named `{name}` in the loaded sources"));
2789        }
2790        Ok(resolved)
2791    }
2792
2793    /// The call structure at `step`, innermost frame first.
2794    #[must_use]
2795    pub fn frames(&self, step: usize) -> Vec<Frame> {
2796        struct Record {
2797            entry_step: usize,
2798            last_step: usize,
2799            external: bool,
2800        }
2801        if step >= self.steps.len() {
2802            return Vec::new();
2803        }
2804        let mut stack = Vec::<Record>::new();
2805        for (index, info) in self.steps.iter().enumerate().take(step + 1) {
2806            let depth = info.frame_depth as usize;
2807            stack.truncate(if info.starts_root { 0 } else { depth + 1 });
2808            // Frames entered at this step. When an external call and a function both begin
2809            // here, the outer one is the call.
2810            let entered_call =
2811                index == 0 || info.starts_root || info.evm_depth > self.steps[index - 1].evm_depth;
2812            let mut first_new = true;
2813            while stack.len() <= depth {
2814                stack.push(Record {
2815                    entry_step: index,
2816                    last_step: index,
2817                    external: first_new && entered_call,
2818                });
2819                first_new = false;
2820            }
2821            if let Some(top) = stack.last_mut() {
2822                top.last_step = index;
2823            }
2824        }
2825
2826        stack
2827            .iter()
2828            .enumerate()
2829            .rev()
2830            .map(|(depth, record)| {
2831                let info = self.steps[record.last_step];
2832                let location = info
2833                    .location
2834                    .and_then(|location| self.describe(&location, info.generated));
2835                Frame {
2836                    depth: depth as u32,
2837                    external: record.external,
2838                    address: info
2839                        .address
2840                        .and_then(|index| self.addresses.get(index))
2841                        .cloned(),
2842                    contract_name: info
2843                        .contract
2844                        .and_then(|index| self.contracts.get(index))
2845                        .map(|contract| contract.name.clone()),
2846                    function_name: location
2847                        .as_ref()
2848                        .and_then(|location| location.function_name.clone()),
2849                    entry_step: record.entry_step,
2850                    step: record.last_step,
2851                    pc: self.pcs[record.last_step],
2852                    location,
2853                    arguments: Vec::new(),
2854                    kind: self.root_kind(depth, record.entry_step),
2855                }
2856            })
2857            .collect()
2858    }
2859
2860    /// The kind of the top-level call a frame at `depth` entered at `entry_step` runs, for
2861    /// a root frame: it is entered where its top-level call starts.
2862    fn root_kind(&self, depth: usize, entry_step: usize) -> Option<String> {
2863        if depth != 0 {
2864            return None;
2865        }
2866        let root = self.roots.get(root_index(&self.roots, entry_step))?;
2867        if root.entry_step != entry_step {
2868            return None;
2869        }
2870        root.kind.clone()
2871    }
2872
2873    /// The source around `step`: `radius` lines on each side of its line.
2874    #[must_use]
2875    pub fn source_listing(&self, step: usize, radius: u64) -> Option<SourceListing> {
2876        let location = self.steps.get(step)?.location?;
2877        let contract = self.contracts.get(location.key.contract)?;
2878        let count = contract.line_count(location.key.source_id)?;
2879        let first = location.key.line.saturating_sub(radius).max(1);
2880        let last = location.key.line.saturating_add(radius).min(count);
2881        let lines = (first..=last)
2882            .filter_map(|line| {
2883                contract
2884                    .line_text(location.key.source_id, line)
2885                    .map(|text| (line, text.to_owned()))
2886            })
2887            .collect();
2888        Some(SourceListing {
2889            path: contract
2890                .source_path(location.key.source_id)
2891                .map(str::to_owned)
2892                .unwrap_or_else(|| format!("source:{}", location.key.source_id)),
2893            current_line: location.key.line,
2894            lines,
2895        })
2896    }
2897}
2898
2899/// Proves, per contract, whether its compiler leaves function parameters on top of the
2900/// stack at a function's entry point, and in which order.
2901///
2902/// The evidence is a frame whose arguments the trace already tells us: the first function
2903/// entered in an EVM frame whose calldata is known is the function that calldata selected,
2904/// so its arguments are the calldata words. Comparing those with the words on the entry
2905/// stack shows whether the parameters are there at all, and with more than one parameter,
2906/// which end the first one is at. Only frames whose parameters are all value types are
2907/// used, since each is then exactly one word.
2908///
2909/// A frame that disagrees contradicts the whole contract: solc's legacy pipeline enters a
2910/// public function through a dispatcher wrapper that has not decoded the arguments yet, so
2911/// the words on top there are not the parameters, and nothing about that contract's frames
2912/// can be trusted to hold them.
2913fn prove_argument_layouts(
2914    trace: &TransactionTrace,
2915    contracts: &[ContractDebugInfo],
2916    framed: &[FramedStep],
2917) -> Vec<Evidence> {
2918    let mut evidence = vec![Evidence::Unknown; contracts.len()];
2919    if contracts.is_empty() || framed.is_empty() {
2920        return evidence;
2921    }
2922    // The calldata each EVM frame was entered with: the transaction's input at the root,
2923    // and the recorded input of a call for a nested frame.
2924    let mut calldata_by_entry = FxHashMap::<usize, &str>::default();
2925    calldata_by_entry.insert(0, trace.input_data.as_str());
2926    for call in &trace.artifacts.calls {
2927        if let Some(entry_step) = call.entry_step {
2928            calldata_by_entry.insert(entry_step, call.input.as_str());
2929        }
2930    }
2931
2932    let mut examined = std::collections::HashSet::<u32>::new();
2933    let mut frame_calldata = FxHashMap::<u32, &str>::default();
2934    for (index, step) in framed.iter().enumerate() {
2935        let entered = index == 0 || framed[index - 1].frame_id != step.frame_id;
2936        if entered {
2937            if let Some(calldata) = calldata_by_entry.get(&index) {
2938                frame_calldata.insert(step.frame_id, calldata);
2939            }
2940        }
2941        let (Some(contract_index), Some(function_index)) = (step.contract, step.entry) else {
2942            continue;
2943        };
2944        // Only the first function entered in an EVM frame: a later one was called from
2945        // inside the code, not by this calldata.
2946        if !examined.insert(step.frame_id) {
2947            continue;
2948        }
2949        let Some(calldata) = frame_calldata.get(&step.frame_id) else {
2950            continue;
2951        };
2952        let Some(function) = contracts[contract_index].functions.get(function_index) else {
2953            continue;
2954        };
2955        let stack = trace.steps.step(index).stack().to_vec();
2956        let observed = argument_evidence(function, calldata, &stack);
2957        evidence[contract_index] = evidence[contract_index].merge(observed);
2958    }
2959    evidence
2960}
2961
2962/// What one frame's entry stack says, given the calldata that selected its function.
2963///
2964/// Every parameter takes one head word in the calldata and one word on the stack, so the
2965/// two line up position by position. Only the value-type positions can be compared — a
2966/// reference parameter is a calldata offset in one and a memory pointer in the other —
2967/// and that is enough as long as one of them sits at a different depth under the two
2968/// orders.
2969fn argument_evidence(function: &SourceFunction, calldata: &str, stack: &[EvmWord]) -> Evidence {
2970    let count = function.params.len();
2971    if count == 0 || stack.len() < count {
2972        return Evidence::Unknown;
2973    }
2974    // A parameter whose width is uncertain would misalign every position after it.
2975    if !function.params.iter().all(readable_parameter) {
2976        return Evidence::Unknown;
2977    }
2978    let signature = format!(
2979        "{}({})",
2980        function.name,
2981        function
2982            .params
2983            .iter()
2984            .map(|param| canonical_value_type(&param.ty))
2985            .collect::<Vec<_>>()
2986            .join(",")
2987    );
2988    let Ok(selector) = function_selector(&signature) else {
2989        return Evidence::Unknown;
2990    };
2991    let data = calldata.trim_start_matches("0x");
2992    let Some((encoded_selector, arguments)) = data.split_at_checked(8) else {
2993        return Evidence::Unknown;
2994    };
2995    // A different function's calldata says nothing about this frame.
2996    if encoded_selector.to_ascii_lowercase() != hex_bytes(&selector) {
2997        return Evidence::Unknown;
2998    }
2999    let expected = (0..count)
3000        .map(|index| {
3001            arguments
3002                .get(index * 64..(index + 1) * 64)
3003                .map(str::to_ascii_lowercase)
3004        })
3005        .collect::<Option<Vec<_>>>();
3006    let Some(expected) = expected else {
3007        return Evidence::Unknown;
3008    };
3009    // The positions whose calldata word is the argument itself, rather than an offset to
3010    // it.
3011    let checkable = (0..count)
3012        .filter(|index| is_value_type(&function.params[*index].ty))
3013        .collect::<Vec<_>>();
3014    if checkable.is_empty() {
3015        return Evidence::Unknown;
3016    }
3017    let matches = |order: ArgumentOrder| {
3018        checkable.iter().all(|index| {
3019            let candidate = &stack[order.word_index(*index, count, stack.len())];
3020            hex_bytes(candidate.as_bytes()) == expected[*index]
3021        })
3022    };
3023    // A position at the same depth under both orders proves nothing about the order.
3024    let distinguishing = checkable.iter().any(|index| {
3025        ArgumentOrder::FirstOnTop.word_index(*index, count, stack.len())
3026            != ArgumentOrder::LastOnTop.word_index(*index, count, stack.len())
3027    });
3028    let first = matches(ArgumentOrder::FirstOnTop);
3029    let last = matches(ArgumentOrder::LastOnTop);
3030    match (first, last) {
3031        // Both orders agree where they could be told apart: the arguments are on top, but
3032        // which is which is still open.
3033        (true, true) => Evidence::TopWords,
3034        (true, false) if distinguishing => Evidence::Ordered(ArgumentOrder::FirstOnTop),
3035        (false, true) if distinguishing => Evidence::Ordered(ArgumentOrder::LastOnTop),
3036        // One order matched only because the other was never tested where they differ.
3037        (true, false) | (false, true) => Evidence::TopWords,
3038        // The arguments are not on the entry stack: this is not where they live.
3039        (false, false) => Evidence::Contradicted,
3040    }
3041}
3042
3043/// The ABI name of a value type as written in the source: the bare integer names are
3044/// their 256-bit forms, and `address payable` encodes as `address`.
3045fn canonical_value_type(ty: &str) -> String {
3046    match ty {
3047        "uint" => "uint256".to_owned(),
3048        "int" => "int256".to_owned(),
3049        "address payable" => "address".to_owned(),
3050        other => other.to_owned(),
3051    }
3052}
3053
3054fn hex_bytes(bytes: &[u8]) -> String {
3055    bytes.iter().map(|byte| format!("{byte:02x}")).collect()
3056}
3057
3058/// The code address a call instruction targets, read off its stack.
3059#[must_use]
3060pub fn call_target(step: StepView<'_>) -> Option<String> {
3061    if !opcode::is_call(step.op()) {
3062        return None;
3063    }
3064    address_from_word(&step.stack().peek(1)?.to_string())
3065}
3066
3067/// The address in the low 20 bytes of a stack word, or `None` for the zero address or a
3068/// word that is not hex.
3069#[must_use]
3070pub fn address_from_word(word: &str) -> Option<String> {
3071    let hex = word.trim_start_matches("0x");
3072    if !hex.bytes().all(|byte| byte.is_ascii_hexdigit()) {
3073        return None;
3074    }
3075    let padded = format!("{hex:0>40}");
3076    let address = padded.get(padded.len() - 40..)?;
3077    if address.bytes().all(|byte| byte == b'0') {
3078        return None;
3079    }
3080    Some(format!("0x{}", address.to_ascii_lowercase()))
3081}
3082
3083#[cfg(test)]
3084mod tests {
3085    use std::collections::BTreeMap;
3086
3087    use serde_json::json;
3088    use soldb_core::{ContractCreation, ExecutionCall, StepSnapshot, TraceStep, TransactionTrace};
3089    use soldb_ethdebug::{
3090        function_selector, source_range, CodeGenerator, EthdebugInfo, Instruction,
3091    };
3092
3093    use soldb_core::{EvmWord, Word as StackWord};
3094
3095    use crate::FrameState;
3096
3097    use super::{
3098        address_from_word, normalize_address, ArgumentLayout, ArgumentOrder, ContractDebugInfo,
3099        InferredVariable, JumpMarker, LineKey, LocalsStatus, NearestLines, SourceCache, StepInfo,
3100        StepMap, VariableKind,
3101    };
3102
3103    // Two functions; `outer` calls `inner` internally. Line numbers are one-based.
3104    const SOURCE: &str = "\
3105contract C {
3106    function outer(uint256 a) public {
3107        uint256 b = a + 1;
3108        inner(b);
3109        b = 0;
3110    }
3111    function inner(uint256 x) internal {
3112        x += 1;
3113    }
3114}
3115";
3116
3117    fn offset_of(needle: &str) -> u64 {
3118        SOURCE.find(needle).expect(needle) as u64
3119    }
3120
3121    fn instruction(pc: u64, offset: u64, length: u64) -> Instruction {
3122        instruction_with(pc, offset, length, json!({}))
3123    }
3124
3125    /// An instruction with extra ETHDebug context, such as a jump marker.
3126    fn instruction_with(
3127        pc: u64,
3128        offset: u64,
3129        length: u64,
3130        extra: serde_json::Value,
3131    ) -> Instruction {
3132        let mut context = json!({"code": source_range(0, offset, length)});
3133        for (key, value) in extra.as_object().expect("object") {
3134            context[key] = value.clone();
3135        }
3136        Instruction::new(pc, "JUMPDEST", Some(context))
3137    }
3138
3139    fn contract(address: Option<&str>) -> ContractDebugInfo {
3140        let whole = SOURCE.len() as u64;
3141        let info = EthdebugInfo {
3142            compilation: serde_json::Value::Null,
3143            contract_name: "C".to_owned(),
3144            environment: "runtime".to_owned(),
3145            instructions: vec![
3146                // Dispatcher: the whole-contract span.
3147                instruction(0, 0, whole),
3148                instruction(1, 0, whole),
3149                // outer's declaration, then its three statements.
3150                instruction(10, offset_of("function outer"), 90),
3151                instruction(11, offset_of("uint256 b = a + 1;"), 18),
3152                instruction(12, offset_of("inner(b);"), 9),
3153                instruction(13, offset_of("inner(b);"), 9),
3154                instruction(14, offset_of("b = 0;"), 6),
3155                // inner's declaration and body.
3156                instruction(20, offset_of("function inner"), 50),
3157                instruction(21, offset_of("x += 1;"), 7),
3158                // Back in the dispatcher.
3159                instruction(30, 0, whole),
3160                // A generated helper, carrying the whole-contract span like the dispatcher.
3161                instruction(40, 0, whole),
3162            ],
3163            sources: BTreeMap::from([(0, "C.sol".to_owned())]),
3164            variable_locations: BTreeMap::new(),
3165        };
3166        ContractDebugInfo::new(address, "C", info, BTreeMap::from([(0, SOURCE.to_owned())]))
3167    }
3168
3169    fn step(pc: u64, depth: u64, op: &str, stack: &[&str]) -> TraceStep {
3170        TraceStep {
3171            pc,
3172            op: op.into(),
3173            gas: 0,
3174            gas_cost: 0,
3175            depth,
3176            stack: stack.iter().map(|word| StackWord::from(*word)).collect(),
3177            memory: None,
3178            storage: None,
3179            error: None,
3180            snapshot: StepSnapshot::default(),
3181        }
3182    }
3183
3184    fn trace(steps: Vec<TraceStep>) -> TransactionTrace {
3185        TransactionTrace {
3186            tx_hash: None,
3187            from_addr: "0x1".to_owned(),
3188            to_addr: Some("0xAAaa000000000000000000000000000000000001".to_owned()),
3189            value: "0x0".to_owned(),
3190            input_data: "0x".to_owned(),
3191            gas_used: 0,
3192            output: "0x".to_owned(),
3193            success: true,
3194            error: None,
3195            debug_trace_available: true,
3196            contract_address: None,
3197            backend: None,
3198            capabilities: Default::default(),
3199            artifacts: Default::default(),
3200            steps: steps.into(),
3201        }
3202    }
3203
3204    // Steps: dispatcher (0,1), outer decl (2), line 3 (3), line 4 before the call (4),
3205    // inner decl (5), inner line 8 (6, 7), back on line 4 (8), line 5 (9), dispatcher (10).
3206    fn outer_calls_inner() -> TransactionTrace {
3207        trace(vec![
3208            step(0, 1, "PUSH1", &[]),
3209            step(1, 1, "JUMPI", &[]),
3210            step(10, 1, "JUMPDEST", &[]),
3211            step(11, 1, "ADD", &[]),
3212            step(12, 1, "PUSH1", &[]),
3213            step(20, 1, "JUMPDEST", &[]),
3214            step(21, 1, "ADD", &[]),
3215            step(21, 1, "SWAP1", &[]),
3216            step(13, 1, "JUMPDEST", &[]),
3217            step(14, 1, "POP", &[]),
3218            step(30, 1, "STOP", &[]),
3219        ])
3220    }
3221
3222    // A contract whose functions take two parameters, so the order they are passed in
3223    // can be told apart.
3224    const PAY_SOURCE: &str = "\
3225contract P {
3226    function pay(address to, uint256 amount) public {
3227        total(to, amount);
3228    }
3229    function total(address to, uint256 amount) internal {
3230        amount += 1;
3231    }
3232}
3233";
3234
3235    fn pay_contract() -> ContractDebugInfo {
3236        let whole = PAY_SOURCE.len() as u64;
3237        let at = |needle: &str| PAY_SOURCE.find(needle).expect(needle) as u64;
3238        let info = EthdebugInfo {
3239            compilation: serde_json::Value::Null,
3240            contract_name: "P".to_owned(),
3241            environment: "runtime".to_owned(),
3242            instructions: vec![
3243                instruction(0, 0, whole),
3244                instruction(10, at("function pay"), 80),
3245                instruction(11, at("total(to, amount);"), 18),
3246                instruction(20, at("function total"), 70),
3247                instruction(21, at("amount += 1;"), 12),
3248            ],
3249            sources: BTreeMap::from([(0, "P.sol".to_owned())]),
3250            variable_locations: BTreeMap::new(),
3251        };
3252        ContractDebugInfo::new(
3253            None,
3254            "P",
3255            info,
3256            BTreeMap::from([(0, PAY_SOURCE.to_owned())]),
3257        )
3258    }
3259
3260    /// A stack as the words a table holds.
3261    fn evm_words(stack: &[StackWord]) -> Vec<EvmWord> {
3262        stack
3263            .iter()
3264            .map(|word| EvmWord::parse(word).expect("test word"))
3265            .collect()
3266    }
3267
3268    fn word_of(value: &str) -> StackWord {
3269        StackWord::from(format!("0x{:0>64}", value.trim_start_matches("0x")).as_str())
3270    }
3271
3272    /// A call to `pay(to, amount)` that calls `total(to, amount)` internally, with the
3273    /// entry stacks the caller chooses, bottom-first as a backend reports them.
3274    fn pay_trace(entry_stack: &[StackWord], inner_stack: &[StackWord]) -> TransactionTrace {
3275        let selector = soldb_ethdebug::function_selector("pay(address,uint256)").expect("selector");
3276        let calldata = format!(
3277            "0x{}{}{}",
3278            selector
3279                .iter()
3280                .map(|byte| format!("{byte:02x}"))
3281                .collect::<String>(),
3282            &word_of(PAY_TO)[2..],
3283            &word_of(PAY_AMOUNT)[2..]
3284        );
3285        fn borrow(stack: &[StackWord]) -> Vec<&str> {
3286            stack.iter().map(|word| &**word).collect()
3287        }
3288        let mut trace = trace(vec![
3289            step(0, 1, "PUSH1", &[]),
3290            step(10, 1, "JUMPDEST", &borrow(entry_stack)),
3291            step(11, 1, "JUMP", &borrow(inner_stack)),
3292            step(20, 1, "JUMPDEST", &borrow(inner_stack)),
3293            step(21, 1, "ADD", &[]),
3294        ]);
3295        trace.input_data = calldata;
3296        trace
3297    }
3298
3299    const PAY_TO: &str = "00000000000000000000000000000000000000aa";
3300    const PAY_AMOUNT: &str = "7";
3301
3302    #[test]
3303    fn the_trace_proves_which_end_the_first_argument_is_at() {
3304        let to = word_of(PAY_TO);
3305        let amount = word_of(PAY_AMOUNT);
3306        let tag = word_of("2a");
3307
3308        // via-IR pushes the parameters right to left, so the first is on top.
3309        let first_on_top = [tag.clone(), amount.clone(), to.clone()];
3310        let trace = pay_trace(&first_on_top, &first_on_top);
3311        let map = StepMap::new(&trace, vec![pay_contract()]);
3312        assert_eq!(
3313            map.argument_layout(0),
3314            Some(ArgumentLayout::Ordered(ArgumentOrder::FirstOnTop))
3315        );
3316        let frames = map.frames(4);
3317        let inner = frames.first().expect("innermost frame");
3318        assert_eq!(inner.function_name.as_deref(), Some("total"));
3319        let arguments = map.frame_arguments(
3320            inner,
3321            FrameState {
3322                stack: &evm_words(&first_on_top),
3323                memory: None,
3324            },
3325        );
3326        assert_eq!(
3327            arguments
3328                .iter()
3329                .map(|argument| format!("{} = {}", argument.name, argument.value.display))
3330                .collect::<Vec<_>>(),
3331            [
3332                "to = 0x00000000000000000000000000000000000000aa".to_owned(),
3333                "amount = 7".to_owned()
3334            ]
3335        );
3336
3337        // The legacy pipeline pushes them left to right, so the last is on top.
3338        let last_on_top = [tag.clone(), to.clone(), amount.clone()];
3339        let trace = pay_trace(&last_on_top, &last_on_top);
3340        let map = StepMap::new(&trace, vec![pay_contract()]);
3341        assert_eq!(
3342            map.argument_layout(0),
3343            Some(ArgumentLayout::Ordered(ArgumentOrder::LastOnTop))
3344        );
3345        let frames = map.frames(4);
3346        let arguments = map.frame_arguments(
3347            frames.first().expect("frame"),
3348            FrameState {
3349                stack: &evm_words(&last_on_top),
3350                memory: None,
3351            },
3352        );
3353        assert_eq!(
3354            arguments[0].value.display,
3355            "0x00000000000000000000000000000000000000aa"
3356        );
3357        assert_eq!(arguments[1].value.display, "7");
3358
3359        // A frame entered through a dispatcher wrapper does not hold the arguments yet.
3360        // That contradicts the whole contract: nothing is reported for any frame of it.
3361        let wrapper = [tag.clone(), word_of("dead"), word_of("beef")];
3362        let trace = pay_trace(&wrapper, &first_on_top);
3363        let map = StepMap::new(&trace, vec![pay_contract()]);
3364        assert_eq!(map.argument_layout(0), None);
3365        let frames = map.frames(4);
3366        assert!(map
3367            .frame_arguments(
3368                frames.first().expect("frame"),
3369                FrameState {
3370                    stack: &evm_words(&first_on_top),
3371                    memory: None
3372                }
3373            )
3374            .is_empty());
3375
3376        // Without the calldata to compare against, nothing is proven and nothing shown.
3377        let mut unknown = pay_trace(&first_on_top, &first_on_top);
3378        unknown.input_data = "0x".to_owned();
3379        let map = StepMap::new(&unknown, vec![pay_contract()]);
3380        assert_eq!(map.argument_layout(0), None);
3381        let frames = map.frames(4);
3382        assert!(map
3383            .frame_arguments(
3384                frames.first().expect("frame"),
3385                FrameState {
3386                    stack: &evm_words(&first_on_top),
3387                    memory: None
3388                }
3389            )
3390            .is_empty());
3391    }
3392
3393    #[test]
3394    fn one_parameter_is_on_top_under_either_order() {
3395        // `outer(uint256 a)` takes one parameter: matching it against the calldata shows
3396        // the arguments are on the entry stack, which is all a one-parameter frame needs.
3397        let mut trace = outer_calls_inner();
3398        let selector = soldb_ethdebug::function_selector("outer(uint256)").expect("selector");
3399        let amount = word_of("5");
3400        trace.input_data = format!(
3401            "0x{}{}",
3402            selector
3403                .iter()
3404                .map(|byte| format!("{byte:02x}"))
3405                .collect::<String>(),
3406            &amount[2..]
3407        );
3408        let stack = [word_of("2a"), amount.clone()];
3409        let mut steps = trace.steps.to_steps();
3410        steps[2].snapshot =
3411            StepSnapshot::new(stack.to_vec(), None, BTreeMap::new(), BTreeMap::new());
3412        trace.steps = steps.into();
3413        let map = StepMap::new(&trace, vec![contract(None)]);
3414        assert_eq!(map.argument_layout(0), Some(ArgumentLayout::TopWords));
3415        let frames = map.frames(3);
3416        let outer = frames
3417            .iter()
3418            .find(|frame| frame.function_name.as_deref() == Some("outer"))
3419            .expect("outer frame");
3420        let arguments = map.frame_arguments(
3421            outer,
3422            FrameState {
3423                stack: &evm_words(&stack),
3424                memory: None,
3425            },
3426        );
3427        assert_eq!(arguments.len(), 1);
3428        assert_eq!(arguments[0].name, "a");
3429        assert_eq!(arguments[0].value.display, "5");
3430
3431        // Two parameters need the order, which one parameter cannot show.
3432        let map = StepMap::new(&trace, vec![pay_contract()]);
3433        assert_eq!(map.argument_layout(0), None);
3434    }
3435
3436    fn key(line: u64) -> Option<LineKey> {
3437        Some(LineKey {
3438            contract: 0,
3439            source_id: 0,
3440            line,
3441        })
3442    }
3443
3444    #[test]
3445    fn maps_steps_to_lines_and_infers_internal_frames() {
3446        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
3447        assert!(map.has_source());
3448        // The dispatcher shows its span but counts as no line.
3449        assert_eq!(map.line_key(0), None);
3450        assert_eq!(map.location(0).expect("location").line, 1);
3451        assert!(!map.location(0).expect("location").generated);
3452        assert_eq!(map.line_key(2), key(2));
3453        assert_eq!(map.line_key(3), key(3));
3454        assert_eq!(map.line_key(4), key(4));
3455        assert_eq!(map.line_key(5), key(7));
3456        assert_eq!(map.line_key(6), key(8));
3457        assert_eq!(map.line_key(8), key(4));
3458        assert_eq!(map.line_key(10), None);
3459
3460        // Dispatcher at depth 0, outer at 1, inner at 2, then back down.
3461        let depths = (0..11)
3462            .map(|step| map.frame_depth(step).expect("depth"))
3463            .collect::<Vec<_>>();
3464        assert_eq!(depths, vec![0, 0, 1, 1, 1, 2, 2, 2, 1, 1, 0]);
3465        assert!(map.is_frame_entry(2));
3466        assert!(map.is_frame_entry(5));
3467        assert!(!map.is_frame_entry(8));
3468        assert_eq!(
3469            map.location(6).expect("location").function_name.as_deref(),
3470            Some("inner")
3471        );
3472        assert_eq!(map.location(6).expect("location").path, "C.sol");
3473        assert_eq!(map.location(6).expect("location").column, 9);
3474    }
3475
3476    #[test]
3477    fn next_steps_over_calls_and_step_enters_them() {
3478        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
3479        // From the dispatcher, the next line is outer's declaration.
3480        assert_eq!(map.next_source(0), Some(2));
3481        assert_eq!(map.next_source(2), Some(3));
3482        // Line 4 calls inner; `next` lands on line 5, `step` lands in inner.
3483        assert_eq!(map.next_source(4), Some(9));
3484        assert_eq!(map.step_into(4), Some(5));
3485        assert_eq!(map.step_into(5), Some(6));
3486        // Inside inner, `next` at its last line returns to the caller mid-line 4.
3487        assert_eq!(map.next_source(6), Some(8));
3488        assert_eq!(map.finish(6), Some(8));
3489        // Leaving outer lands in the dispatcher; nothing follows the last step.
3490        assert_eq!(map.next_source(9), Some(10));
3491        assert_eq!(map.finish(9), Some(10));
3492        assert_eq!(map.next_source(10), None);
3493        assert_eq!(map.finish(10), None);
3494    }
3495
3496    #[test]
3497    fn reverse_stepping_lands_on_line_starts() {
3498        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
3499        // From line 5, reverse-next goes to the start of line 4, over the call.
3500        assert_eq!(map.previous_source(9), Some(4));
3501        // From the middle of a line, first to its start.
3502        assert_eq!(map.previous_source(7), Some(6));
3503        assert_eq!(map.previous_source(6), Some(5));
3504        // Reverse-step from line 5 enters inner's last line instead of skipping it.
3505        assert_eq!(map.reverse_step_into(9), Some(8));
3506        assert_eq!(map.reverse_step_into(8), Some(6));
3507        // Reverse-finish from inside inner returns to the call site.
3508        assert_eq!(map.reverse_finish(6), Some(4));
3509        assert_eq!(map.reverse_finish(0), None);
3510        assert_eq!(map.previous_source(0), None);
3511        // From the dispatcher after outer returned, reverse-next enters outer's last line
3512        // rather than skipping the whole function as one call.
3513        assert_eq!(map.previous_source(10), Some(9));
3514    }
3515
3516    #[test]
3517    fn line_starts_skip_returns_into_the_middle_of_a_line() {
3518        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
3519        assert!(map.is_line_start(3));
3520        assert!(map.is_line_start(4));
3521        assert!(map.is_line_start(6));
3522        assert!(!map.is_line_start(7));
3523        // Step 8 resumes line 4 after inner returned: not a fresh entry to the line.
3524        assert!(!map.is_line_start(8));
3525        assert!(map.is_line_start(9));
3526    }
3527
3528    #[test]
3529    fn resolves_line_and_function_breakpoints() {
3530        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
3531        let resolved = map.resolve_line(Some("C.sol"), 4).expect("line 4");
3532        assert_eq!(resolved.len(), 1);
3533        assert_eq!(resolved[0].key, key(4).unwrap());
3534        assert_eq!(resolved[0].path, "C.sol");
3535        // Without a file the single source is unambiguous.
3536        assert_eq!(
3537            map.resolve_line(None, 8).expect("line 8")[0].key,
3538            key(8).unwrap()
3539        );
3540        // A line with no code of its own resolves to the statement containing it: the
3541        // whole-contract span is the only one covering line 10, so it wins with line 1.
3542        assert_eq!(
3543            map.resolve_line(None, 10).expect("line 10")[0].key,
3544            key(1).unwrap()
3545        );
3546        assert_eq!(
3547            map.resolve_line(None, 99).expect_err("past the end"),
3548            "no instruction maps to line 99"
3549        );
3550        assert_eq!(
3551            map.resolve_line(Some("D.sol"), 1)
3552                .expect_err("unknown file"),
3553            "source file not found: D.sol"
3554        );
3555
3556        let inner = map.resolve_function("inner").expect("inner");
3557        assert_eq!(inner.len(), 1);
3558        assert_eq!(inner[0].line, 7);
3559        assert_eq!(inner[0].contract_name, "C");
3560        assert_eq!(
3561            map.resolve_function("C.outer").expect("qualified")[0].line,
3562            2
3563        );
3564        assert!(map.resolve_function("D.outer").is_err());
3565        assert!(map.resolve_function("missing").is_err());
3566    }
3567
3568    #[test]
3569    fn frames_list_the_call_structure_innermost_first() {
3570        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
3571        let frames = map.frames(6);
3572        assert_eq!(frames.len(), 3);
3573        assert_eq!(frames[0].function_name.as_deref(), Some("inner"));
3574        assert_eq!(frames[0].depth, 2);
3575        assert!(!frames[0].external);
3576        assert_eq!(frames[0].step, 6);
3577        assert_eq!(frames[1].function_name.as_deref(), Some("outer"));
3578        assert_eq!(
3579            frames[1].location.as_ref().map(|location| location.line),
3580            Some(4)
3581        );
3582        assert_eq!(frames[1].step, 4);
3583        assert!(frames[2].external);
3584        assert_eq!(frames[2].depth, 0);
3585        assert_eq!(
3586            frames[2].address.as_deref(),
3587            Some("0xaaaa000000000000000000000000000000000001")
3588        );
3589        assert!(map.frames(99).is_empty());
3590
3591        let listing = map.source_listing(6, 1).expect("listing");
3592        assert_eq!(listing.current_line, 8);
3593        assert_eq!(listing.lines.len(), 3);
3594        assert_eq!(listing.lines[1].0, 8);
3595        assert_eq!(listing.lines[1].1.trim(), "x += 1;");
3596    }
3597
3598    #[test]
3599    fn external_calls_map_through_the_callee_contract() {
3600        // The root contract calls a second one; the callee's steps map through its info.
3601        let callee = "0xbbbb000000000000000000000000000000000002";
3602        let word = format!("0x{:0>64}", callee.trim_start_matches("0x"));
3603        let steps = vec![
3604            step(0, 1, "PUSH1", &[]),
3605            step(
3606                12,
3607                1,
3608                "CALL",
3609                &["0x0", "0x0", "0x0", "0x0", "0x0", &word, "0x0"],
3610            ),
3611            step(21, 2, "JUMPDEST", &[]),
3612            step(21, 2, "ADD", &[]),
3613            step(13, 1, "JUMPDEST", &[]),
3614            step(30, 1, "STOP", &[]),
3615        ];
3616        let root = contract(Some("0xAAAA000000000000000000000000000000000001"));
3617        let map = StepMap::new(&trace(steps), vec![root, contract(Some(callee))]);
3618        assert_eq!(
3619            map.executing_address(1),
3620            Some("0xaaaa000000000000000000000000000000000001")
3621        );
3622        assert_eq!(map.executing_address(2), Some(callee));
3623        assert_eq!(
3624            map.line_key(2),
3625            Some(LineKey {
3626                contract: 1,
3627                source_id: 0,
3628                line: 8
3629            })
3630        );
3631        assert_eq!(map.frame_depth(1), Some(1));
3632        // The callee has no dispatcher step here, so inner is entered straight away.
3633        assert_eq!(map.frame_depth(2), Some(3));
3634        assert!(map.is_frame_entry(2));
3635        assert_eq!(map.frame_depth(4), Some(1));
3636        // `next` over the external call skips the callee entirely.
3637        assert_eq!(map.next_source(1), Some(5));
3638        assert_eq!(map.step_into(1), Some(2));
3639        let frames = map.frames(2);
3640        assert_eq!(frames.len(), 4);
3641        assert!(frames[1].external);
3642        assert_eq!(frames[1].address.as_deref(), Some(callee));
3643        assert_eq!(frames[0].function_name.as_deref(), Some("inner"));
3644    }
3645
3646    /// A program at `address` for one environment, mapping each listed program counter to
3647    /// the span of a source snippet.
3648    fn program(address: &str, environment: &str, spans: &[(u64, &str)]) -> ContractDebugInfo {
3649        let info = EthdebugInfo {
3650            compilation: serde_json::Value::Null,
3651            contract_name: "C".to_owned(),
3652            environment: environment.to_owned(),
3653            instructions: spans
3654                .iter()
3655                .map(|(pc, needle)| instruction(*pc, offset_of(needle), needle.len() as u64))
3656                .collect(),
3657            sources: BTreeMap::from([(0, "C.sol".to_owned())]),
3658            variable_locations: BTreeMap::new(),
3659        };
3660        ContractDebugInfo::new(
3661            Some(address),
3662            "C",
3663            info,
3664            BTreeMap::from([(0, SOURCE.to_owned())]),
3665        )
3666    }
3667
3668    const ROOT: &str = "0xaaaa000000000000000000000000000000000001";
3669    const CREATED: &str = "0xcccc000000000000000000000000000000000003";
3670
3671    fn creation(entry_step: usize, exit_step: usize, address: Option<&str>) -> ContractCreation {
3672        ContractCreation {
3673            id: 0,
3674            parent_id: None,
3675            depth: 2,
3676            entry_step: Some(entry_step),
3677            exit_step: Some(exit_step),
3678            create_type: "CREATE".to_owned(),
3679            caller: ROOT.to_owned(),
3680            address: address.map(str::to_owned),
3681            value: "0x0".to_owned(),
3682            init_code: "0x".to_owned(),
3683            gas_limit: 0,
3684            gas_used: None,
3685            output: None,
3686            success: Some(address.is_some()),
3687            error: None,
3688        }
3689    }
3690
3691    fn call(
3692        id: usize,
3693        entry_step: usize,
3694        exit_step: usize,
3695        call_type: &str,
3696        bytecode_address: &str,
3697    ) -> ExecutionCall {
3698        ExecutionCall {
3699            id,
3700            parent_id: None,
3701            depth: 2,
3702            entry_step: Some(entry_step),
3703            exit_step: Some(exit_step),
3704            call_type: call_type.to_owned(),
3705            from: ROOT.to_owned(),
3706            to: ROOT.to_owned(),
3707            bytecode_address: bytecode_address.to_owned(),
3708            value: "0x0".to_owned(),
3709            input: "0x".to_owned(),
3710            gas_limit: 0,
3711            gas_used: None,
3712            output: None,
3713            success: Some(true),
3714            error: None,
3715        }
3716    }
3717
3718    /// The root creates a contract, then calls it: steps 2 and 3 run the creation code,
3719    /// step 6 the deployed code.
3720    fn root_creates_then_calls() -> TransactionTrace {
3721        let word = format!("0x{:0>64}", CREATED.trim_start_matches("0x"));
3722        let mut trace = trace(vec![
3723            step(0, 1, "PUSH1", &[]),
3724            // A `CREATE` leaves no code address on the stack.
3725            step(12, 1, "CREATE", &["0x0", "0x0", "0x0"]),
3726            step(21, 2, "JUMPDEST", &[]),
3727            step(21, 2, "RETURN", &[]),
3728            step(13, 1, "JUMPDEST", &[]),
3729            step(
3730                14,
3731                1,
3732                "CALL",
3733                &["0x0", "0x0", "0x0", "0x0", "0x0", &word, "0x0"],
3734            ),
3735            step(21, 2, "JUMPDEST", &[]),
3736            step(30, 1, "STOP", &[]),
3737        ]);
3738        trace
3739            .artifacts
3740            .creations
3741            .push(creation(2, 4, Some(CREATED)));
3742        trace.artifacts.calls.push(call(0, 6, 7, "CALL", CREATED));
3743        trace
3744    }
3745
3746    #[test]
3747    fn creations_map_through_the_created_contracts_creation_program() {
3748        let trace = root_creates_then_calls();
3749        // The creation program maps pc 21 into `inner`, the deployed program into `outer`,
3750        // so which one a step went through shows in its line. The order they are given in
3751        // does not decide it: the environment does.
3752        let deployed = program(CREATED, "call", &[(21, "b = 0;")]);
3753        let creation = program(CREATED, "create", &[(21, "x += 1;")]);
3754        let map = StepMap::new(&trace, vec![contract(Some(ROOT)), deployed, creation]);
3755
3756        assert_eq!(map.executing_address(2), Some(CREATED));
3757        assert_eq!(map.storage_address(2), Some(CREATED));
3758        assert_eq!(
3759            map.line_key(2).map(|key| (key.contract, key.line)),
3760            Some((2, 8))
3761        );
3762        assert_eq!(
3763            map.line_key(6).map(|key| (key.contract, key.line)),
3764            Some((1, 5))
3765        );
3766        // Innermost first: `inner`, then the creation frame that entered it.
3767        let frames = map.frames(2);
3768        assert_eq!(frames[0].function_name.as_deref(), Some("inner"));
3769        assert!(frames[1].external);
3770        assert_eq!(frames[1].address.as_deref(), Some(CREATED));
3771        assert_eq!(map.frame_depth(4), Some(1));
3772    }
3773
3774    #[test]
3775    fn a_line_resolves_only_in_the_programs_that_map_it() {
3776        // Both programs of the created contract see `C.sol`; only the creation program has
3777        // code on line 8, and the deployed program's whole-contract span must not turn the
3778        // breakpoint into a stop on line 1.
3779        let trace = root_creates_then_calls();
3780        let whole = SOURCE.len() as u64;
3781        let mut deployed = program(CREATED, "call", &[(21, "b = 0;")]);
3782        deployed.info.instructions.push(instruction(20, 0, whole));
3783        let mut creation = program(CREATED, "create", &[(21, "x += 1;")]);
3784        creation.info.instructions.push(instruction(20, 0, whole));
3785        let map = StepMap::new(&trace, vec![deployed, creation]);
3786        let resolved = map.resolve_line(Some("C.sol"), 8).expect("line 8");
3787        assert_eq!(resolved.len(), 1);
3788        assert_eq!(resolved[0].key.contract, 1);
3789        assert_eq!(resolved[0].key.line, 8);
3790        // A line no program maps still resolves to the statements containing it.
3791        let resolved = map.resolve_line(Some("C.sol"), 10).expect("line 10");
3792        assert_eq!(resolved.len(), 2);
3793        assert!(resolved.iter().all(|line| line.key.line == 1));
3794    }
3795
3796    #[test]
3797    fn a_creation_uses_the_only_program_at_its_address() {
3798        // Only the deployed program is loaded: it still names the address's sources, and
3799        // describes the creation frame the way it did before environments were told apart.
3800        let trace = root_creates_then_calls();
3801        let deployed = program(CREATED, "call", &[(21, "b = 0;")]);
3802        let map = StepMap::new(&trace, vec![contract(Some(ROOT)), deployed]);
3803        assert_eq!(
3804            map.line_key(2).map(|key| (key.contract, key.line)),
3805            Some((1, 5))
3806        );
3807    }
3808
3809    #[test]
3810    fn a_creation_the_backend_did_not_record_has_no_contract() {
3811        let mut trace = root_creates_then_calls();
3812        trace.artifacts.creations.clear();
3813        let creation = program(CREATED, "create", &[(21, "x += 1;")]);
3814        let map = StepMap::new(&trace, vec![contract(Some(ROOT)), creation]);
3815        assert_eq!(map.executing_address(2), None);
3816        assert_eq!(map.line_key(2), None);
3817        // The call is still resolved from the stack.
3818        assert_eq!(map.executing_address(6), Some(CREATED));
3819    }
3820
3821    #[test]
3822    fn a_deployment_root_prefers_the_creation_program() {
3823        let mut trace = trace(vec![step(21, 1, "JUMPDEST", &[]), step(30, 1, "STOP", &[])]);
3824        trace.to_addr = None;
3825        trace.contract_address = Some(CREATED.to_owned());
3826        let deployed = program(CREATED, "call", &[(21, "b = 0;")]);
3827        let creation = program(CREATED, "create", &[(21, "x += 1;")]);
3828        let map = StepMap::new(&trace, vec![deployed, creation]);
3829        assert_eq!(
3830            map.line_key(0).map(|key| (key.contract, key.line)),
3831            Some((1, 8))
3832        );
3833    }
3834
3835    #[test]
3836    fn frames_are_resolved_from_recorded_calls_before_the_stack() {
3837        let callee = "0xbbbb000000000000000000000000000000000002";
3838        let precompile = "0x0000000000000000000000000000000000000001";
3839        // No stack was recorded, so nothing can be read off the call instructions.
3840        let mut trace = trace(vec![
3841            step(0, 1, "PUSH1", &[]),
3842            step(11, 1, "STATICCALL", &[]),
3843            step(12, 1, "DELEGATECALL", &[]),
3844            step(21, 2, "JUMPDEST", &[]),
3845            step(21, 2, "ADD", &[]),
3846            step(13, 1, "JUMPDEST", &[]),
3847        ]);
3848        // The precompile ran no steps: it starts and ends at the caller's next step, and is
3849        // not the frame entered there.
3850        trace
3851            .artifacts
3852            .calls
3853            .push(call(0, 2, 2, "STATICCALL", precompile));
3854        trace
3855            .artifacts
3856            .calls
3857            .push(call(1, 3, 5, "DELEGATECALL", callee));
3858        let map = StepMap::new(&trace, vec![contract(Some(ROOT)), contract(Some(callee))]);
3859
3860        assert_eq!(map.executing_address(2), Some(ROOT));
3861        assert_eq!(map.frame_depth(2), Some(1));
3862        assert_eq!(map.executing_address(3), Some(callee));
3863        assert_eq!(map.line_key(3).map(|key| key.contract), Some(1));
3864        // A delegated frame keeps its caller's storage.
3865        assert_eq!(map.storage_address(3), Some(ROOT));
3866    }
3867
3868    /// A contract whose functions declare parameters, returns, block locals, and a
3869    /// modifier, laid out the way solc's legacy code generator attributes them.
3870    const LEGACY_SOURCE: &str = "contract L {
3871    uint256 total;
3872    modifier tracked(uint256 tag) {
3873        uint256 before = total;
3874        _;
3875        total = before + tag;
3876    }
3877    function outer(uint256 a) public returns (uint256 sum) {
3878        uint256 twice = a * 2;
3879        if (twice > 2) {
3880            uint256 inner = twice - 2;
3881            sum = inner;
3882        }
3883        sum = twice + helper(twice);
3884    }
3885    function helper(uint256 x) internal pure returns (uint256 r) {
3886        uint256 y = x + 1;
3887        r = y;
3888    }
3889    function guarded(uint256 v) public tracked(9) {
3890        uint256 kept = v;
3891        total = kept;
3892    }
3893    function set(uint256 v) public {
3894        total = v;
3895    }
3896    function take(bytes calldata data, uint256[] calldata items, uint256 n) public {
3897        total = n;
3898    }
3899}
3900";
3901
3902    fn legacy_offset(needle: &str) -> u64 {
3903        LEGACY_SOURCE.find(needle).expect(needle) as u64
3904    }
3905
3906    /// The program of `LEGACY_SOURCE`: each program counter carries the span of one
3907    /// source snippet, the way the compiler attributes reservations to declarations,
3908    /// initialisers to their expressions, and function entries to their headers.
3909    fn legacy_contract(code_generator: Option<CodeGenerator>) -> ContractDebugInfo {
3910        let spans: &[(u64, &str)] = &[
3911            (0, "contract L"),
3912            // outer: entry, the return's reservation, the local's, its initialiser,
3913            // the block's local, a statement after the block, the call, and the return.
3914            (10, "function outer"),
3915            (11, "uint256 sum"),
3916            (12, "uint256 twice"),
3917            (13, "a * 2"),
3918            (14, "twice > 2"),
3919            (15, "uint256 inner"),
3920            (16, "sum = inner"),
3921            (17, "helper(twice)"),
3922            (18, "sum = twice + helper(twice)"),
3923            (19, "function outer"),
3924            // helper: entry, its local, the assignment, its return, and the exit.
3925            (20, "function helper"),
3926            (21, "uint256 y"),
3927            (22, "r = y"),
3928            (23, "function helper"),
3929            (24, "uint256 r"),
3930            // guarded and its modifier: entry, the modifier's local, the body's, the
3931            // body's statement, the modifier's statement after `_`, and the dispatcher's
3932            // jump to the body tag with the body tag itself.
3933            (30, "function guarded"),
3934            (31, "uint256 before"),
3935            (32, "uint256 kept"),
3936            (33, "total = kept"),
3937            (34, "total = before + tag"),
3938            (35, "function guarded"),
3939            (36, "function guarded"),
3940            // set: entry, its only statement, and the dispatcher's jump to the body tag
3941            // with the body tag itself.
3942            (40, "function set"),
3943            (41, "total = v"),
3944            (42, "function set"),
3945            (43, "function set"),
3946            // take: entry and its only statement.
3947            (50, "function take"),
3948            (51, "total = n"),
3949        ];
3950        let instructions = spans
3951            .iter()
3952            .map(|(pc, needle)| {
3953                // The dispatcher's jump into a body is marked as a call.
3954                if matches!(pc, 35 | 42) {
3955                    instruction_with(
3956                        *pc,
3957                        legacy_offset(needle),
3958                        needle.len() as u64,
3959                        json!({"invoke": {}}),
3960                    )
3961                } else {
3962                    instruction(*pc, legacy_offset(needle), needle.len() as u64)
3963                }
3964            })
3965            .collect();
3966        let info = EthdebugInfo {
3967            compilation: serde_json::Value::Null,
3968            contract_name: "L".to_owned(),
3969            environment: "call".to_owned(),
3970            instructions,
3971            sources: BTreeMap::from([(0, "L.sol".to_owned())]),
3972            variable_locations: BTreeMap::new(),
3973        };
3974        ContractDebugInfo::new(
3975            Some(ROOT),
3976            "L",
3977            info,
3978            BTreeMap::from([(0, LEGACY_SOURCE.to_owned())]),
3979        )
3980        .with_code_generator(code_generator)
3981    }
3982
3983    /// `outer(5)` entered from the dispatcher: the return is reserved, `twice` declared,
3984    /// the block entered and left, `helper` called, and the frame returned from.
3985    fn outer_trace() -> TransactionTrace {
3986        let ret = "0x9";
3987        trace(vec![
3988            step(0, 1, "PUSH1", &[]),
3989            step(10, 1, "JUMPDEST", &[ret, "0x5"]),
3990            step(11, 1, "PUSH0", &[ret, "0x5"]),
3991            step(12, 1, "PUSH0", &[ret, "0x5", "0x0"]),
3992            step(13, 1, "MUL", &[ret, "0x5", "0x0", "0x0"]),
3993            step(14, 1, "GT", &[ret, "0x5", "0x0", "0xa"]),
3994            step(15, 1, "PUSH0", &[ret, "0x5", "0x0", "0xa"]),
3995            step(16, 1, "DUP1", &[ret, "0x5", "0x0", "0xa", "0x8"]),
3996            step(18, 1, "DUP2", &[ret, "0x5", "0x8", "0xa"]),
3997            step(17, 1, "JUMP", &[ret, "0x5", "0x8", "0xa", "0x12", "0xa"]),
3998            step(
3999                20,
4000                1,
4001                "JUMPDEST",
4002                &[ret, "0x5", "0x8", "0xa", "0x12", "0xa"],
4003            ),
4004            step(24, 1, "PUSH0", &[ret, "0x5", "0x8", "0xa", "0x12", "0xa"]),
4005            step(
4006                21,
4007                1,
4008                "PUSH0",
4009                &[ret, "0x5", "0x8", "0xa", "0x12", "0xa", "0x0"],
4010            ),
4011            step(
4012                22,
4013                1,
4014                "DUP1",
4015                &[ret, "0x5", "0x8", "0xa", "0x12", "0xa", "0x0", "0xb"],
4016            ),
4017            step(23, 1, "JUMP", &[ret, "0x5", "0x8", "0xa", "0xb", "0x12"]),
4018            step(18, 1, "ADD", &[ret, "0x5", "0x8", "0xa", "0xb"]),
4019            step(19, 1, "JUMP", &[ret, "0x5", "0x15", "0xa"]),
4020            step(0, 1, "STOP", &[]),
4021        ])
4022    }
4023
4024    fn named(variables: &[InferredVariable]) -> Vec<(&str, VariableKind, Option<usize>)> {
4025        variables
4026            .iter()
4027            .map(|variable| (variable.name.as_str(), variable.kind, variable.slot))
4028            .collect()
4029    }
4030
4031    #[test]
4032    fn locals_are_inferred_from_the_legacy_stack_layout() {
4033        let map = StepMap::new(
4034            &outer_trace(),
4035            vec![legacy_contract(Some(CodeGenerator::Legacy))],
4036        );
4037        let trace = outer_trace();
4038
4039        // The dispatcher runs no function.
4040        assert_eq!(
4041            map.locals_at(0),
4042            LocalsStatus::Unavailable("no function is executing at this step")
4043        );
4044        // At entry from the dispatcher nothing is placed yet: the return parameter is in
4045        // scope but unreserved, and the parameters are found by its reservation, one
4046        // word above them.
4047        let LocalsStatus::Inferred(at_entry) = map.locals_at(1) else {
4048            panic!("{:?}", map.locals_at(1));
4049        };
4050        assert_eq!(named(at_entry), [("sum", VariableKind::Return, None)]);
4051        let LocalsStatus::Inferred(at_return) = map.locals_at(2) else {
4052            panic!("{:?}", map.locals_at(2));
4053        };
4054        assert_eq!(
4055            named(at_return),
4056            [
4057                ("a", VariableKind::Parameter, Some(1)),
4058                ("sum", VariableKind::Return, Some(2))
4059            ]
4060        );
4061        // `twice` takes the next slot at its reservation and reads back once assigned.
4062        let LocalsStatus::Inferred(at_twice) = map.locals_at(5) else {
4063            panic!("{:?}", map.locals_at(5));
4064        };
4065        assert_eq!(
4066            named(at_twice),
4067            [
4068                ("a", VariableKind::Parameter, Some(1)),
4069                ("sum", VariableKind::Return, Some(2)),
4070                ("twice", VariableKind::Local, Some(3))
4071            ]
4072        );
4073        let values = map.inferred_variables(&trace, 5, None);
4074        assert_eq!(values[0].value.display, "5");
4075        assert_eq!(values[2].value.display, "10");
4076        assert_eq!(values[2].location.offset, 3);
4077        // The block's local is in scope inside the block and released after it.
4078        let LocalsStatus::Inferred(in_block) = map.locals_at(7) else {
4079            panic!("{:?}", map.locals_at(7));
4080        };
4081        assert_eq!(
4082            named(in_block).last(),
4083            Some(&("inner", VariableKind::Local, Some(4)))
4084        );
4085        assert_eq!(
4086            map.inferred_variables(&trace, 7, None)[3].value.display,
4087            "8"
4088        );
4089        let LocalsStatus::Inferred(after_block) = map.locals_at(8) else {
4090            panic!("{:?}", map.locals_at(8));
4091        };
4092        assert_eq!(after_block.len(), 3);
4093        assert_eq!(
4094            map.inferred_variables(&trace, 8, None)[1].value.display,
4095            "8"
4096        );
4097
4098        // `helper` is entered by a jump from `outer`, which places its parameter at
4099        // once and its return parameter above; a slot above the stack reads as
4100        // unavailable until it is pushed. The local follows once declared, and the
4101        // caller's variables are back after the return.
4102        let LocalsStatus::Inferred(helper_entry) = map.locals_at(10) else {
4103            panic!("{:?}", map.locals_at(10));
4104        };
4105        assert_eq!(
4106            named(helper_entry),
4107            [
4108                ("x", VariableKind::Parameter, Some(5)),
4109                ("r", VariableKind::Return, Some(6))
4110            ]
4111        );
4112        let at_entry = map.inferred_variables(&trace, 10, None);
4113        assert_eq!(
4114            at_entry[1].value.status,
4115            crate::DebugValueStatus::Unavailable
4116        );
4117        let helper_values = map.inferred_variables(&trace, 13, None);
4118        assert_eq!(
4119            helper_values
4120                .iter()
4121                .map(|variable| (variable.name.as_str(), variable.value.display.as_str()))
4122                .collect::<Vec<_>>(),
4123            [("x", "10"), ("r", "0"), ("y", "11")]
4124        );
4125        assert_eq!(helper_values[2].location.offset, 7);
4126        let LocalsStatus::Inferred(back) = map.locals_at(15) else {
4127            panic!("{:?}", map.locals_at(15));
4128        };
4129        assert_eq!(named(back).len(), 3);
4130        assert_eq!(
4131            map.inferred_variables(&trace, 15, None)[2].value.display,
4132            "10"
4133        );
4134    }
4135
4136    #[test]
4137    fn a_modifier_places_itself_and_the_function_it_runs_for() {
4138        let ret = "0x9";
4139        let trace = trace(vec![
4140            step(30, 1, "JUMPDEST", &[ret, "0x7"]),
4141            // The modifier runs first, its argument pushed above the parameter; its own
4142            // local is reserved above that.
4143            step(31, 1, "PUSH0", &[ret, "0x7", "0x9"]),
4144            // Then the body, whose local sits above the modifier's.
4145            step(32, 1, "PUSH0", &[ret, "0x7", "0x9", "0x3"]),
4146            step(33, 1, "SSTORE", &[ret, "0x7", "0x9", "0x3", "0x7"]),
4147            // The modifier resumes after the body with its slots still in place.
4148            step(34, 1, "SSTORE", &[ret, "0x7", "0x9", "0x3"]),
4149        ]);
4150        let map = StepMap::new(&trace, vec![legacy_contract(Some(CodeGenerator::Legacy))]);
4151        let LocalsStatus::Inferred(modifier) = map.locals_at(1) else {
4152            panic!("{:?}", map.locals_at(1));
4153        };
4154        assert_eq!(
4155            named(modifier),
4156            [
4157                ("tag", VariableKind::Parameter, Some(2)),
4158                ("before", VariableKind::Local, Some(3))
4159            ]
4160        );
4161        // The function's parameter sits right below the modifier's, and its body's local
4162        // right above the modifier's slots.
4163        let LocalsStatus::Inferred(body) = map.locals_at(3) else {
4164            panic!("{:?}", map.locals_at(3));
4165        };
4166        assert_eq!(
4167            named(body),
4168            [
4169                ("v", VariableKind::Parameter, Some(1)),
4170                ("kept", VariableKind::Local, Some(4))
4171            ]
4172        );
4173        assert_eq!(
4174            map.inferred_variables(&trace, 3, None)[1].value.display,
4175            "7"
4176        );
4177        let LocalsStatus::Inferred(resumed) = map.locals_at(4) else {
4178            panic!("{:?}", map.locals_at(4));
4179        };
4180        assert_eq!(
4181            named(resumed),
4182            [
4183                ("tag", VariableKind::Parameter, Some(2)),
4184                ("before", VariableKind::Local, Some(3))
4185            ]
4186        );
4187        assert_eq!(
4188            map.inferred_variables(&trace, 4, None)[1].value.display,
4189            "3"
4190        );
4191    }
4192
4193    #[test]
4194    fn calldata_slices_read_the_frames_calldata() {
4195        // `take("ab", [3, 4], 5)` as the legacy decoder leaves it: each slice is an offset
4196        // into the calldata and a length, the offset pointing at the data itself. The
4197        // elements of `items` follow the selector at offset 0x4, and the bytes "ab" follow
4198        // them at 0x44.
4199        let ret = "0x9";
4200        let mut trace = trace(vec![
4201            step(
4202                50,
4203                1,
4204                "JUMPDEST",
4205                &[ret, "0x44", "0x2", "0x4", "0x2", "0x5"],
4206            ),
4207            step(51, 1, "PUSH0", &[ret, "0x44", "0x2", "0x4", "0x2", "0x5"]),
4208        ]);
4209        trace.input_data = format!(
4210            "0xaabbccdd{}{}6162",
4211            "0000000000000000000000000000000000000000000000000000000000000003",
4212            "0000000000000000000000000000000000000000000000000000000000000004"
4213        );
4214        let map = StepMap::new(&trace, vec![legacy_contract(Some(CodeGenerator::Legacy))]);
4215        let LocalsStatus::Inferred(layout) = map.locals_at(1) else {
4216            panic!("{:?}", map.locals_at(1));
4217        };
4218        assert_eq!(
4219            layout
4220                .iter()
4221                .map(|variable| (variable.name.as_str(), variable.slot, variable.words))
4222                .collect::<Vec<_>>(),
4223            [
4224                ("data", Some(1), 2),
4225                ("items", Some(3), 2),
4226                ("n", Some(5), 1)
4227            ]
4228        );
4229        let values = map.inferred_variables(&trace, 1, None);
4230        assert_eq!(
4231            values
4232                .iter()
4233                .map(|variable| (variable.name.as_str(), variable.value.display.as_str()))
4234                .collect::<Vec<_>>(),
4235            [("data", "0x6162"), ("items", "[3, 4]"), ("n", "5")]
4236        );
4237        assert_eq!(values[0].value.raw.as_deref(), Some("0x6162"));
4238    }
4239
4240    #[test]
4241    fn parameters_are_placed_by_the_first_statement_of_a_body_without_locals() {
4242        // Entered from the dispatcher, with neither a return parameter nor a local to
4243        // reserve a slot: the body's first instruction still runs right above the
4244        // parameter.
4245        let trace = trace(vec![
4246            step(40, 1, "JUMPDEST", &["0x9", "0x7"]),
4247            step(41, 1, "DUP1", &["0x9", "0x7"]),
4248            step(41, 1, "SSTORE", &["0x9", "0x7", "0x7", "0x0"]),
4249        ]);
4250        let map = StepMap::new(&trace, vec![legacy_contract(Some(CodeGenerator::Legacy))]);
4251        let LocalsStatus::Inferred(at_entry) = map.locals_at(0) else {
4252            panic!("{:?}", map.locals_at(0));
4253        };
4254        assert!(at_entry.is_empty());
4255        let LocalsStatus::Inferred(in_body) = map.locals_at(2) else {
4256            panic!("{:?}", map.locals_at(2));
4257        };
4258        assert_eq!(named(in_body), [("v", VariableKind::Parameter, Some(1))]);
4259        assert_eq!(
4260            map.inferred_variables(&trace, 2, None)[0].value.display,
4261            "7"
4262        );
4263    }
4264
4265    #[test]
4266    fn the_dispatchers_jump_to_the_body_places_a_public_function() {
4267        // The dispatcher enters the function's declaration before decoding, then jumps to
4268        // its body with the decoded parameter on top of the return tag. The optimizer
4269        // has hoisted a temporary above it before the first statement, which would
4270        // misplace a frame read off that statement's height.
4271        let selector = "0x9c";
4272        let mut trace = trace(vec![
4273            step(0, 1, "PUSH1", &[]),
4274            step(40, 1, "JUMPDEST", &[selector]),
4275            step(42, 1, "JUMP", &[selector, "0x9", "0x7", "0x2b"]),
4276            step(43, 1, "JUMPDEST", &[selector, "0x9", "0x7"]),
4277            step(41, 1, "SSTORE", &[selector, "0x9", "0x7", "0x0"]),
4278        ]);
4279        let map = StepMap::new(&trace, vec![legacy_contract(Some(CodeGenerator::Legacy))]);
4280        let LocalsStatus::Inferred(at_entry) = map.locals_at(1) else {
4281            panic!("{:?}", map.locals_at(1));
4282        };
4283        assert!(at_entry.is_empty());
4284        let LocalsStatus::Inferred(in_body) = map.locals_at(4) else {
4285            panic!("{:?}", map.locals_at(4));
4286        };
4287        assert_eq!(named(in_body), [("v", VariableKind::Parameter, Some(2))]);
4288        assert_eq!(
4289            map.inferred_variables(&trace, 4, None)[0].value.display,
4290            "7"
4291        );
4292
4293        // Calldata that selects the function but disagrees with the words at the body
4294        // tag says this is not where the parameters are: the placement is left to the
4295        // body's first statement, as before.
4296        let selector = super::hex_bytes(&function_selector("set(uint256)").expect("selector"));
4297        trace.input_data = format!("0x{selector}{:064x}", 8);
4298        let map = StepMap::new(&trace, vec![legacy_contract(Some(CodeGenerator::Legacy))]);
4299        let LocalsStatus::Inferred(in_body) = map.locals_at(4) else {
4300            panic!("{:?}", map.locals_at(4));
4301        };
4302        assert_eq!(named(in_body), [("v", VariableKind::Parameter, Some(3))]);
4303        // Calldata that agrees confirms it.
4304        trace.input_data = format!("0x{selector}{:064x}", 7);
4305        let map = StepMap::new(&trace, vec![legacy_contract(Some(CodeGenerator::Legacy))]);
4306        let LocalsStatus::Inferred(in_body) = map.locals_at(4) else {
4307            panic!("{:?}", map.locals_at(4));
4308        };
4309        assert_eq!(named(in_body), [("v", VariableKind::Parameter, Some(2))]);
4310    }
4311
4312    #[test]
4313    fn a_placed_function_places_its_modifiers() {
4314        // The optimizer merged the modifier's argument with its local's reservation, so
4315        // the first instruction attributed to the modifier runs with both already on
4316        // the stack; read off that height the modifier would land one slot too high.
4317        // The function was placed at its body tag, and the modifier's parameter sits
4318        // right above the function's, whatever the modifier's own instructions say.
4319        let selector = "0x9c";
4320        let ret = "0x9";
4321        let trace = trace(vec![
4322            step(0, 1, "PUSH1", &[]),
4323            step(30, 1, "JUMPDEST", &[selector]),
4324            step(35, 1, "JUMP", &[selector, ret, "0x5", "0x24"]),
4325            step(36, 1, "JUMPDEST", &[selector, ret, "0x5"]),
4326            step(31, 1, "DUP1", &[selector, ret, "0x5", "0x9", "0x9"]),
4327            step(32, 1, "PUSH0", &[selector, ret, "0x5", "0x9", "0x9"]),
4328            step(
4329                33,
4330                1,
4331                "SSTORE",
4332                &[selector, ret, "0x5", "0x9", "0x9", "0x5"],
4333            ),
4334            step(34, 1, "SSTORE", &[selector, ret, "0x5", "0x9", "0x9"]),
4335        ]);
4336        let map = StepMap::new(&trace, vec![legacy_contract(Some(CodeGenerator::Legacy))]);
4337        let LocalsStatus::Inferred(modifier) = map.locals_at(4) else {
4338            panic!("{:?}", map.locals_at(4));
4339        };
4340        assert_eq!(
4341            named(modifier),
4342            [
4343                ("tag", VariableKind::Parameter, Some(3)),
4344                ("before", VariableKind::Local, Some(4))
4345            ]
4346        );
4347        let LocalsStatus::Inferred(body) = map.locals_at(6) else {
4348            panic!("{:?}", map.locals_at(6));
4349        };
4350        assert_eq!(
4351            named(body),
4352            [
4353                ("v", VariableKind::Parameter, Some(2)),
4354                ("kept", VariableKind::Local, Some(5))
4355            ]
4356        );
4357        assert_eq!(
4358            map.inferred_variables(&trace, 6, None)[1].value.display,
4359            "5"
4360        );
4361        let LocalsStatus::Inferred(resumed) = map.locals_at(7) else {
4362            panic!("{:?}", map.locals_at(7));
4363        };
4364        assert_eq!(
4365            named(resumed),
4366            [
4367                ("tag", VariableKind::Parameter, Some(3)),
4368                ("before", VariableKind::Local, Some(4))
4369            ]
4370        );
4371        let values = map.inferred_variables(&trace, 7, None);
4372        assert_eq!(values[0].value.display, "9");
4373        assert_eq!(values[1].value.display, "9");
4374    }
4375
4376    #[test]
4377    fn a_function_reached_by_fallthrough_was_inlined_and_has_no_slots() {
4378        // `helper`'s body runs inside `outer` without a jump onto its entry: the
4379        // optimizer inlined the call. The frame is shown, its variables are not read,
4380        // and the caller's own placement survives the excursion.
4381        let ret = "0x9";
4382        let trace = trace(vec![
4383            step(0, 1, "PUSH1", &[]),
4384            step(10, 1, "JUMPDEST", &[ret, "0x5"]),
4385            step(11, 1, "PUSH0", &[ret, "0x5"]),
4386            step(12, 1, "PUSH0", &[ret, "0x5", "0x0"]),
4387            step(21, 1, "PUSH0", &[ret, "0x5", "0x0", "0xa"]),
4388            step(22, 1, "DUP1", &[ret, "0x5", "0x0", "0xa", "0xb"]),
4389            step(18, 1, "ADD", &[ret, "0x5", "0x0", "0xa", "0xb"]),
4390        ]);
4391        let map = StepMap::new(&trace, vec![legacy_contract(Some(CodeGenerator::Legacy))]);
4392        assert_eq!(
4393            map.location(4).and_then(|location| location.function_name),
4394            Some("helper".to_owned())
4395        );
4396        assert_eq!(
4397            map.locals_at(4),
4398            LocalsStatus::Unavailable(
4399                "this function was inlined by the optimizer, so its variables have no stack \
4400                 slots of their own"
4401            )
4402        );
4403        assert_eq!(
4404            map.location(6).and_then(|location| location.function_name),
4405            Some("outer".to_owned())
4406        );
4407        let LocalsStatus::Inferred(back) = map.locals_at(6) else {
4408            panic!("{:?}", map.locals_at(6));
4409        };
4410        assert_eq!(
4411            named(back),
4412            [
4413                ("a", VariableKind::Parameter, Some(1)),
4414                ("sum", VariableKind::Return, Some(2)),
4415                ("twice", VariableKind::Local, Some(3))
4416            ]
4417        );
4418    }
4419
4420    #[test]
4421    fn locals_are_not_inferred_for_the_via_ir_pipeline_or_an_unknown_one() {
4422        let via_ir = StepMap::new(
4423            &outer_trace(),
4424            vec![legacy_contract(Some(CodeGenerator::ViaIr))],
4425        );
4426        assert!(matches!(
4427            via_ir.locals_at(5),
4428            LocalsStatus::Unavailable(reason) if reason.contains("via-IR pipeline")
4429        ));
4430        assert!(via_ir
4431            .inferred_variables(&outer_trace(), 5, None)
4432            .is_empty());
4433        let unknown = StepMap::new(&outer_trace(), vec![legacy_contract(None)]);
4434        assert!(matches!(
4435            unknown.locals_at(5),
4436            LocalsStatus::Unavailable(reason) if reason.contains("not known")
4437        ));
4438    }
4439
4440    #[test]
4441    fn steps_without_debug_info_have_no_location() {
4442        let map = StepMap::new(&outer_calls_inner(), Vec::new());
4443        assert!(!map.has_source());
4444        assert_eq!(map.line_key(3), None);
4445        assert_eq!(map.frame_depth(3), Some(0));
4446        // Without lines there is nothing to stop at in either direction.
4447        assert_eq!(map.next_source(3), None);
4448        assert_eq!(map.previous_source(3), None);
4449        assert!(map.resolve_line(None, 3).is_err());
4450        assert_eq!(map.frames(3).len(), 1);
4451    }
4452
4453    #[test]
4454    fn steps_without_source_are_transparent_to_line_stepping() {
4455        // Line 3 runs, then a generated helper with no span, then line 3 again.
4456        let map = StepMap::new(
4457            &trace(vec![
4458                step(0, 1, "PUSH1", &[]),
4459                step(10, 1, "JUMPDEST", &[]),
4460                step(11, 1, "ADD", &[]),
4461                step(99, 1, "MUL", &[]),
4462                step(11, 1, "SWAP1", &[]),
4463                step(14, 1, "POP", &[]),
4464                step(30, 1, "STOP", &[]),
4465            ]),
4466            vec![contract(None)],
4467        );
4468        assert_eq!(map.line_key(3), None);
4469        assert_eq!(map.frame_depth(3), Some(1));
4470        // The helper neither stops `next` nor starts line 3 over again.
4471        assert_eq!(map.next_source(2), Some(5));
4472        assert_eq!(map.step_into(2), Some(5));
4473        assert!(!map.is_line_start(3));
4474        assert!(!map.is_line_start(4));
4475        // Reverse stepping treats the interrupted run as one line.
4476        assert_eq!(map.previous_source(5), Some(2));
4477        assert_eq!(map.previous_source(4), Some(2));
4478        assert_eq!(map.previous_source(3), Some(2));
4479        assert_eq!(map.previous_source(2), Some(1));
4480    }
4481
4482    #[test]
4483    fn generated_code_belongs_to_the_statement_and_the_epilogue_pops_the_function() {
4484        // Line 3 runs, then a helper carrying the whole-contract span (pc 40), then line 3
4485        // again, then line 5; the trailing dispatcher step is the epilogue.
4486        let map = StepMap::new(
4487            &trace(vec![
4488                step(0, 1, "PUSH1", &[]),
4489                step(10, 1, "JUMPDEST", &[]),
4490                step(11, 1, "ADD", &[]),
4491                step(40, 1, "JUMPDEST", &[]),
4492                step(40, 1, "SSTORE", &[]),
4493                step(11, 1, "SWAP1", &[]),
4494                step(14, 1, "POP", &[]),
4495                step(30, 1, "STOP", &[]),
4496            ]),
4497            vec![contract(None)],
4498        );
4499        let helper = map.location(4).expect("location");
4500        assert!(helper.generated);
4501        assert_eq!(helper.line, 3);
4502        assert_eq!(helper.function_name.as_deref(), Some("outer"));
4503        assert_eq!(map.line_key(4), key(3));
4504        assert_eq!(map.function_id(4).map(|id| id.function), Some(0));
4505        assert!(!map.is_line_start(4));
4506        assert!(!map.is_line_start(5));
4507        // The helper does not pop outer; the epilogue does.
4508        assert_eq!(map.frame_depth(4), Some(1));
4509        assert_eq!(map.frame_depth(7), Some(0));
4510        assert_eq!(map.line_key(7), None);
4511        assert!(!map.location(7).expect("location").generated);
4512        assert_eq!(map.next_source(2), Some(6));
4513        assert_eq!(map.finish(2), Some(7));
4514        assert_eq!(map.reverse_finish(4), Some(0));
4515        assert_eq!(map.previous_source(6), Some(2));
4516    }
4517
4518    #[test]
4519    fn a_lone_instruction_on_another_line_belongs_to_the_line_around_it() {
4520        // outer's entry, one instruction attributed to line 4, outer's declaration again,
4521        // then line 3: `next` from the entry reaches line 3 without stopping on the blip.
4522        let map = StepMap::new(
4523            &trace(vec![
4524                step(0, 1, "PUSH1", &[]),
4525                step(10, 1, "JUMPDEST", &[]),
4526                step(12, 1, "PUSH2", &[]),
4527                step(10, 1, "SWAP1", &[]),
4528                step(11, 1, "ADD", &[]),
4529                step(14, 1, "POP", &[]),
4530                step(30, 1, "STOP", &[]),
4531            ]),
4532            vec![contract(None)],
4533        );
4534        assert_eq!(map.line_key(2), key(2));
4535        assert_eq!(map.location(2).expect("location").line, 4);
4536        assert!(!map.is_line_start(2));
4537        assert!(!map.is_line_start(3));
4538        assert_eq!(map.next_source(1), Some(4));
4539        assert_eq!(map.previous_source(4), Some(1));
4540        // A real line of one instruction between two different lines is kept.
4541        assert_eq!(map.line_key(5), key(5));
4542        assert!(map.is_line_start(5));
4543    }
4544
4545    #[test]
4546    fn a_jump_onto_a_function_entry_is_a_call_even_when_it_is_active() {
4547        // outer calls inner, inner calls outer again, and both return: mutual recursion.
4548        // The entry points (pc 10 for outer, pc 20 for inner) make every jump onto them
4549        // a call, where landing in an active function used to read as a return.
4550        let map = StepMap::new(
4551            &trace(vec![
4552                step(0, 1, "PUSH1", &[]),
4553                step(1, 1, "JUMP", &[]),
4554                step(10, 1, "JUMPDEST", &[]),
4555                step(12, 1, "JUMP", &[]),
4556                step(20, 1, "JUMPDEST", &[]),
4557                step(21, 1, "JUMP", &[]),
4558                step(10, 1, "JUMPDEST", &[]),
4559                step(14, 1, "JUMP", &[]),
4560                step(21, 1, "SWAP1", &[]),
4561                step(21, 1, "JUMP", &[]),
4562                step(13, 1, "JUMPDEST", &[]),
4563                step(14, 1, "POP", &[]),
4564                step(30, 1, "STOP", &[]),
4565            ]),
4566            vec![contract(None)],
4567        );
4568        let contract = &map.contracts()[0];
4569        assert_eq!(contract.function_entry_at_pc(10), Some(0));
4570        assert_eq!(contract.function_entry_at_pc(20), Some(1));
4571        assert_eq!(contract.function_entry_at_pc(11), None);
4572        let depths = (0..13)
4573            .map(|step| map.frame_depth(step).expect("depth"))
4574            .collect::<Vec<_>>();
4575        assert_eq!(depths, vec![0, 0, 1, 1, 2, 2, 3, 3, 2, 2, 1, 1, 0]);
4576        assert!(map.is_frame_entry(6));
4577        let frames = map.frames(6);
4578        assert_eq!(
4579            frames
4580                .iter()
4581                .map(|frame| frame.function_name.as_deref().unwrap_or("-"))
4582                .collect::<Vec<_>>(),
4583            vec!["outer", "inner", "outer", "-"]
4584        );
4585        assert_eq!(map.finish(6), Some(8));
4586        assert_eq!(map.reverse_finish(6), Some(5));
4587        assert_eq!(map.finish(4), Some(10));
4588    }
4589
4590    #[test]
4591    fn a_recursive_call_returns_at_the_address_read_off_the_stack() {
4592        // outer calls outer: the jump at pc 12 carries [return tag 13, argument, entry 10]
4593        // on its stack. Landing on the entry point pushes a second outer frame; the jump
4594        // that lands on pc 13 is its return, which no span could tell apart from staying
4595        // in outer.
4596        let map = StepMap::new(
4597            &trace(vec![
4598                step(0, 1, "PUSH1", &[]),
4599                step(1, 1, "JUMP", &[]),
4600                step(10, 1, "JUMPDEST", &[]),
4601                step(12, 1, "JUMP", &["0xd", "0x1", "0xa"]),
4602                step(10, 1, "JUMPDEST", &["0x1"]),
4603                step(11, 1, "ADD", &["0x2"]),
4604                step(14, 1, "JUMP", &["0xd"]),
4605                step(13, 1, "JUMPDEST", &[]),
4606                step(14, 1, "POP", &[]),
4607                step(30, 1, "STOP", &[]),
4608            ]),
4609            vec![contract(None)],
4610        );
4611        let depths = (0..10)
4612            .map(|step| map.frame_depth(step).expect("depth"))
4613            .collect::<Vec<_>>();
4614        assert_eq!(depths, vec![0, 0, 1, 1, 2, 2, 2, 1, 1, 0]);
4615        assert!(map.is_frame_entry(4));
4616        assert_eq!(map.finish(4), Some(7));
4617        assert_eq!(map.frames(5).len(), 3);
4618        assert_eq!(map.frames(7).len(), 2);
4619        assert!(map.contracts()[0].is_jumpdest(13));
4620        assert!(!map.contracts()[0].is_jumpdest(99));
4621    }
4622
4623    #[test]
4624    fn jump_markers_decide_calls_and_returns_without_entry_points() {
4625        // The jump at pc 12 is marked as a call and lands mid-outer (pc 11): a helper
4626        // whose span is the calling line, since recursion would enter at pc 10. It gets
4627        // a placeholder frame the marked return at pc 14 pops, so outer stays on the
4628        // stack throughout and its depth never moves.
4629        let whole = SOURCE.len() as u64;
4630        let info = EthdebugInfo {
4631            compilation: serde_json::Value::Null,
4632            contract_name: "C".to_owned(),
4633            environment: "runtime".to_owned(),
4634            instructions: vec![
4635                instruction(0, 0, whole),
4636                instruction(1, 0, whole),
4637                instruction(10, offset_of("function outer"), 90),
4638                instruction(11, offset_of("uint256 b = a + 1;"), 18),
4639                instruction_with(
4640                    12,
4641                    offset_of("inner(b);"),
4642                    9,
4643                    json!({"invoke": {"identifier": "outer"}}),
4644                ),
4645                instruction(13, offset_of("inner(b);"), 9),
4646                instruction_with(14, offset_of("b = 0;"), 6, json!({"return": {}})),
4647                instruction(30, 0, whole),
4648            ],
4649            sources: BTreeMap::from([(0, "C.sol".to_owned())]),
4650            variable_locations: BTreeMap::new(),
4651        };
4652        let marked =
4653            ContractDebugInfo::new(None, "C", info, BTreeMap::from([(0, SOURCE.to_owned())]));
4654        assert_eq!(marked.jump_marker_at_pc(12), JumpMarker::Call);
4655        assert_eq!(marked.jump_marker_at_pc(14), JumpMarker::Return);
4656        assert_eq!(marked.jump_marker_at_pc(13), JumpMarker::None);
4657        let map = StepMap::new(
4658            &trace(vec![
4659                step(0, 1, "PUSH1", &[]),
4660                step(1, 1, "JUMP", &[]),
4661                step(10, 1, "JUMPDEST", &[]),
4662                step(12, 1, "JUMP", &[]),
4663                step(11, 1, "ADD", &[]),
4664                step(14, 1, "JUMP", &[]),
4665                step(13, 1, "JUMPDEST", &[]),
4666                step(14, 1, "POP", &[]),
4667                step(30, 1, "STOP", &[]),
4668            ]),
4669            vec![marked],
4670        );
4671        let depths = (0..9)
4672            .map(|step| map.frame_depth(step).expect("depth"))
4673            .collect::<Vec<_>>();
4674        assert_eq!(depths, vec![0, 0, 1, 1, 1, 1, 1, 1, 0]);
4675        assert!(!map.is_frame_entry(4));
4676        assert_eq!(map.frames(4).len(), 2);
4677        assert_eq!(map.finish(4), Some(8));
4678        // A marked call from outer that lands on inner's entry point is a real call.
4679        let map = StepMap::new(
4680            &trace(vec![
4681                step(0, 1, "PUSH1", &[]),
4682                step(1, 1, "JUMP", &[]),
4683                step(10, 1, "JUMPDEST", &[]),
4684                step(12, 1, "JUMP", &[]),
4685                step(20, 1, "JUMPDEST", &[]),
4686                step(21, 1, "JUMP", &[]),
4687                step(13, 1, "JUMPDEST", &[]),
4688                step(30, 1, "STOP", &[]),
4689            ]),
4690            vec![contract(None)],
4691        );
4692        assert_eq!(map.frame_depth(4), Some(2));
4693        assert_eq!(map.frame_depth(6), Some(1));
4694    }
4695
4696    #[test]
4697    fn marked_jumps_into_generated_helpers_do_not_pop_the_function() {
4698        // Inside outer, a marked call jumps to helper code with the whole-contract span
4699        // (pc 40) and a marked return comes back: outer stays on the stack throughout,
4700        // and the helper counts as no frame the user steps by.
4701        let whole = SOURCE.len() as u64;
4702        let info = EthdebugInfo {
4703            compilation: serde_json::Value::Null,
4704            contract_name: "C".to_owned(),
4705            environment: "runtime".to_owned(),
4706            instructions: vec![
4707                instruction(0, 0, whole),
4708                instruction(1, 0, whole),
4709                instruction(10, offset_of("function outer"), 90),
4710                instruction_with(
4711                    11,
4712                    offset_of("uint256 b = a + 1;"),
4713                    18,
4714                    json!({"invoke": {}}),
4715                ),
4716                instruction_with(40, 0, whole, json!({"return": {}})),
4717                instruction(14, offset_of("b = 0;"), 6),
4718                instruction(30, 0, whole),
4719            ],
4720            sources: BTreeMap::from([(0, "C.sol".to_owned())]),
4721            variable_locations: BTreeMap::new(),
4722        };
4723        let contract =
4724            ContractDebugInfo::new(None, "C", info, BTreeMap::from([(0, SOURCE.to_owned())]));
4725        let map = StepMap::new(
4726            &trace(vec![
4727                step(0, 1, "PUSH1", &[]),
4728                step(1, 1, "JUMP", &[]),
4729                step(10, 1, "JUMPDEST", &[]),
4730                step(11, 1, "JUMP", &[]),
4731                step(40, 1, "JUMPDEST", &[]),
4732                step(40, 1, "JUMP", &[]),
4733                step(14, 1, "JUMPDEST", &[]),
4734                step(30, 1, "STOP", &[]),
4735            ]),
4736            vec![contract],
4737        );
4738        let depths = (0..8)
4739            .map(|step| map.frame_depth(step).expect("depth"))
4740            .collect::<Vec<_>>();
4741        assert_eq!(depths, vec![0, 0, 1, 1, 1, 1, 1, 0]);
4742        assert!(map.location(4).expect("location").generated);
4743        assert_eq!(
4744            map.location(6).expect("location").function_name.as_deref(),
4745            Some("outer")
4746        );
4747        assert_eq!(map.next_source(3), Some(6));
4748        assert_eq!(map.finish(3), Some(7));
4749    }
4750
4751    /// Random steps over a few depths, lines, and top-level calls.
4752    fn random_steps(seed: u64, count: usize) -> Vec<StepInfo> {
4753        let mut state = seed;
4754        let mut next = |bound: u64| {
4755            state = state
4756                .wrapping_mul(6_364_136_223_846_793_005)
4757                .wrapping_add(1_442_695_040_888_963_407);
4758            (state >> 33) % bound
4759        };
4760        (0..count)
4761            .map(|_| {
4762                let line = next(5);
4763                StepInfo {
4764                    location: None,
4765                    generated: false,
4766                    key: (line > 0).then_some(LineKey {
4767                        contract: 0,
4768                        source_id: 0,
4769                        line,
4770                    }),
4771                    contract: None,
4772                    address: None,
4773                    storage: None,
4774                    evm_depth: 0,
4775                    starts_root: next(40) == 0,
4776                    frame_depth: next(6) as u32,
4777                    frame_entry: false,
4778                    line_start: false,
4779                }
4780            })
4781            .collect()
4782    }
4783
4784    #[test]
4785    fn line_passes_match_their_definitions() {
4786        for seed in 0..200 {
4787            let steps = random_steps(seed, 300);
4788
4789            // Smoothing, as it was done with every step's next line held in full.
4790            let mut smoothed = steps.clone();
4791            StepMap::smooth_single_step_excursions(&mut smoothed);
4792            let mut expected = steps.clone();
4793            let mut after = vec![None; expected.len()];
4794            let mut lines = NearestLines::default();
4795            for (index, step) in expected.iter().enumerate().rev() {
4796                after[index] = lines.visit(step.frame_depth, step.key);
4797                if step.starts_root {
4798                    lines.clear();
4799                }
4800            }
4801            let mut lines = NearestLines::default();
4802            for (step, after) in expected.iter_mut().zip(after) {
4803                if step.starts_root {
4804                    lines.clear();
4805                }
4806                let before = lines.visit(step.frame_depth, step.key);
4807                let Some(key) = step.key else {
4808                    continue;
4809                };
4810                if before.is_some() && before == after && before != Some(key) {
4811                    step.key = before;
4812                }
4813            }
4814            assert_eq!(smoothed, expected, "smoothing, seed {seed}");
4815
4816            // Line starts: a step starts its line unless the last step at or below its
4817            // depth since that depth was first reached in the root had the same line.
4818            let mut map = StepMap::new(&trace(Vec::new()), Vec::new());
4819            map.steps = steps.clone();
4820            map.mark_line_starts();
4821            let mut last = Vec::<Option<LineKey>>::new();
4822            for (index, step) in steps.iter().enumerate() {
4823                if step.starts_root {
4824                    last.clear();
4825                }
4826                let depth = step.frame_depth as usize;
4827                if last.len() <= depth {
4828                    last.resize(depth + 1, None);
4829                }
4830                let Some(key) = step.key else {
4831                    assert!(!map.steps[index].line_start, "seed {seed} step {index}");
4832                    continue;
4833                };
4834                assert_eq!(
4835                    map.steps[index].line_start,
4836                    last[depth] != Some(key),
4837                    "line start, seed {seed} step {index}"
4838                );
4839                for entry in last.iter_mut().skip(depth) {
4840                    *entry = Some(key);
4841                }
4842            }
4843        }
4844    }
4845
4846    #[test]
4847    fn contracts_sharing_a_source_read_it_once() {
4848        let built = contract(Some("0xabcd"));
4849        let mut cache = SourceCache::default();
4850        let mut through_cache = || {
4851            ContractDebugInfo::with_source_cache(
4852                Some("0xabcd"),
4853                "C",
4854                built.info.clone(),
4855                built.source_contents.clone(),
4856                &mut cache,
4857            )
4858        };
4859        let first = through_cache();
4860        let second = through_cache();
4861        assert_eq!(first, built);
4862        assert_eq!(second, built);
4863        assert_eq!(cache.sources[&0].len(), 1);
4864
4865        // The same id with other text, as another compilation numbers its sources, is
4866        // read on its own.
4867        let other = ContractDebugInfo::with_source_cache(
4868            None,
4869            "D",
4870            built.info.clone(),
4871            BTreeMap::from([(0, "contract D { function g() public {} }".to_owned())]),
4872            &mut cache,
4873        );
4874        assert_eq!(other.functions[0].name, "g");
4875        assert_eq!(cache.sources[&0].len(), 2);
4876    }
4877
4878    #[test]
4879    fn contract_info_indexes_lines_and_functions() {
4880        let contract = contract(Some("0xABCD"));
4881        assert_eq!(contract.address.as_deref(), Some("0xabcd"));
4882        assert_eq!(contract.line_of(0, 0), Some(1));
4883        assert_eq!(
4884            contract.line_of(0, SOURCE.find("inner(b)").unwrap() as u64),
4885            Some(4)
4886        );
4887        assert_eq!(contract.line_count(0), Some(11));
4888        assert_eq!(contract.line_text(0, 8).map(str::trim), Some("x += 1;"));
4889        assert_eq!(contract.line_text(0, 99), None);
4890        assert_eq!(
4891            contract.function_at_pc(21).map(|f| f.name.as_str()),
4892            Some("inner")
4893        );
4894        assert_eq!(contract.function_at_pc(0), None);
4895        assert_eq!(contract.function_at_pc(999), None);
4896        assert_eq!(contract.functions[0].declaration_line, 2);
4897        assert_eq!(contract.effective_line(0, 3), Some(3));
4898        assert_eq!(contract.effective_line(0, 6), Some(1));
4899        assert_eq!(contract.effective_line(0, 0), None);
4900        assert_eq!(contract.effective_line(0, 12), None);
4901        assert_eq!(contract.effective_line(7, 1), None);
4902
4903        assert_eq!(normalize_address("0XAbC"), "0xabc");
4904        assert_eq!(
4905            address_from_word("0x000000000000000000000000AAAA000000000000000000000000000000000001"),
4906            Some("0xaaaa000000000000000000000000000000000001".to_owned())
4907        );
4908        assert_eq!(address_from_word("0x0"), None);
4909        assert_eq!(address_from_word("zz"), None);
4910    }
4911
4912    /// An instruction with any mnemonic and any context, or none.
4913    #[test]
4914    fn prepared_instruction_facts_match_the_instructions() {
4915        let whole = SOURCE.len() as u64;
4916        let outer = offset_of("function outer");
4917        let statement = offset_of("x += 1;");
4918        let instructions = vec![
4919            Instruction::new(0, "PUSH1", Some(json!({"code": source_range(0, 0, whole)}))),
4920            // Jump markers: a call, a return, and a revert, which marks no jump.
4921            Instruction::new(
4922                21,
4923                "JUMP",
4924                Some(json!({"code": source_range(0, statement, 7), "invoke": {"identifier": "f"}})),
4925            ),
4926            Instruction::new(
4927                22,
4928                "JUMP",
4929                Some(json!({"code": source_range(0, statement, 7), "return": {}})),
4930            ),
4931            Instruction::new(
4932                23,
4933                "REVERT",
4934                Some(json!({"code": source_range(0, statement, 7), "revert": {}})),
4935            ),
4936            // No context, and a context without code.
4937            Instruction::new(30, "STOP", None),
4938            Instruction::new(31, "POP", Some(json!({}))),
4939            // Two alternatives with different spans have no single location; an
4940            // invocation inside one still marks a call. Two equal spans have one.
4941            Instruction::new(
4942                40,
4943                "JUMP",
4944                Some(json!({"pick": [
4945                    {"code": source_range(0, statement, 7), "invoke": {}},
4946                    {"code": source_range(0, outer, 90)}
4947                ]})),
4948            ),
4949            Instruction::new(
4950                42,
4951                "ADD",
4952                Some(json!({"pick": [
4953                    {"code": source_range(0, statement, 7)},
4954                    {"code": source_range(0, statement, 7)}
4955                ]})),
4956            ),
4957            // A span in a source whose text is not loaded has no line.
4958            Instruction::new(
4959                50,
4960                "ADD",
4961                Some(json!({"code": source_range(7, statement, 7)})),
4962            ),
4963            // Two instructions at one offset: the later one describes it.
4964            Instruction::new(60, "ADD", Some(json!({"code": source_range(0, 0, whole)}))),
4965            Instruction::new(
4966                60,
4967                "JUMPDEST",
4968                Some(json!({"code": source_range(0, statement, 7)})),
4969            ),
4970            // An offset past any real code is still indexed.
4971            Instruction::new(
4972                1 << 40,
4973                "JUMPDEST",
4974                Some(json!({"code": source_range(0, statement, 7), "return": {}})),
4975            ),
4976        ];
4977        let info = EthdebugInfo {
4978            compilation: serde_json::Value::Null,
4979            contract_name: "C".to_owned(),
4980            environment: "call".to_owned(),
4981            instructions,
4982            sources: BTreeMap::from([(0, "C.sol".to_owned())]),
4983            variable_locations: BTreeMap::new(),
4984        };
4985        let contract =
4986            ContractDebugInfo::new(None, "C", info, BTreeMap::from([(0, SOURCE.to_owned())]));
4987        let line = |pc| contract.facts_at_pc(pc).and_then(|facts| facts.line);
4988        let function = |pc| {
4989            contract
4990                .function_at_pc(pc)
4991                .map(|function| function.name.as_str())
4992        };
4993
4994        assert_eq!(contract.jump_marker_at_pc(21), JumpMarker::Call);
4995        assert_eq!(contract.jump_marker_at_pc(22), JumpMarker::Return);
4996        assert_eq!(contract.jump_marker_at_pc(23), JumpMarker::None);
4997        assert_eq!(contract.jump_marker_at_pc(40), JumpMarker::Call);
4998        assert_eq!(contract.location_at_pc(30), None);
4999        assert_eq!(contract.location_at_pc(31), None);
5000        assert_eq!(contract.location_at_pc(40), None);
5001        assert_eq!(contract.location_at_pc(42), contract.location_at_pc(21));
5002        assert_eq!(line(21), contract.line_of(0, statement));
5003        assert_eq!(function(21), Some("inner"));
5004        assert!(contract.location_at_pc(50).is_some());
5005        assert_eq!(line(50), None);
5006        assert_eq!(function(60), Some("inner"));
5007        assert!(contract.is_jumpdest(60));
5008        assert_eq!(contract.jump_marker_at_pc(1 << 40), JumpMarker::Return);
5009        assert!(contract.pc_index.sparse.contains_key(&(1 << 40)));
5010        // No instruction starts at these program counters.
5011        for pc in [1, 61, (1 << 40) + 1, u64::MAX] {
5012            assert_eq!(contract.instruction_at_pc(pc), None, "pc {pc}");
5013            assert_eq!(contract.location_at_pc(pc), None, "pc {pc}");
5014        }
5015    }
5016
5017    #[test]
5018    fn function_entries_are_the_earliest_jumpdest_at_each_declaration() {
5019        // The same text in two sources, so each declaration offset names one function in
5020        // each source.
5021        let source = "\
5022contract D {
5023    function a() public { uint256 x = 1; }
5024    function b() public { uint256 y = 2; }
5025    function c() public { uint256 z = 3; }
5026    function d() public { uint256 w = 4; }
5027}
5028";
5029        let start = |needle: &str| source.find(needle).expect(needle) as u64;
5030        let (a, b, c) = (
5031            start("function a"),
5032            start("function b"),
5033            start("function c"),
5034        );
5035        let instructions = vec![
5036            // A declaration span on something other than a `JUMPDEST` enters nothing.
5037            Instruction::new(3, "JUMP", Some(json!({"code": source_range(0, a, 10)}))),
5038            // Two `JUMPDEST`s carry a's span; the lower pc is its entry, not the first listed.
5039            Instruction::new(
5040                30,
5041                "JUMPDEST",
5042                Some(json!({"code": source_range(0, a, 10)})),
5043            ),
5044            Instruction::new(
5045                10,
5046                "JUMPDEST",
5047                Some(json!({"code": source_range(0, a, 10)})),
5048            ),
5049            Instruction::new(
5050                40,
5051                "JUMPDEST",
5052                Some(json!({"code": source_range(0, b, 10)})),
5053            ),
5054            // c has a `JUMPDEST` in its body and a `JUMP` at its declaration, and d has
5055            // no instruction at all: neither has an entry point.
5056            Instruction::new(50, "JUMP", Some(json!({"code": source_range(0, c, 10)}))),
5057            Instruction::new(
5058                51,
5059                "JUMPDEST",
5060                Some(json!({"code": source_range(0, c + 22, 10)})),
5061            ),
5062            // The same offset in the other source enters the other source's function.
5063            Instruction::new(5, "JUMPDEST", Some(json!({"code": source_range(1, a, 10)}))),
5064            // At a repeated offset, a `JUMPDEST` the later instruction hides still counts.
5065            Instruction::new(
5066                20,
5067                "JUMPDEST",
5068                Some(json!({"code": source_range(1, b, 10)})),
5069            ),
5070            Instruction::new(20, "ADD", Some(json!({"code": source_range(1, b, 10)}))),
5071        ];
5072        let info = EthdebugInfo {
5073            compilation: serde_json::Value::Null,
5074            contract_name: "D".to_owned(),
5075            environment: "call".to_owned(),
5076            instructions,
5077            sources: BTreeMap::from([(0, "D.sol".to_owned()), (1, "E.sol".to_owned())]),
5078            variable_locations: BTreeMap::new(),
5079        };
5080        let contract = ContractDebugInfo::new(
5081            None,
5082            "D",
5083            info,
5084            BTreeMap::from([(0, source.to_owned()), (1, source.to_owned())]),
5085        );
5086        let names = contract
5087            .functions
5088            .iter()
5089            .map(|function| (function.source_id, function.name.as_str()))
5090            .collect::<Vec<_>>();
5091        assert_eq!(
5092            names,
5093            [
5094                (0, "a"),
5095                (0, "b"),
5096                (0, "c"),
5097                (0, "d"),
5098                (1, "a"),
5099                (1, "b"),
5100                (1, "c"),
5101                (1, "d")
5102            ]
5103        );
5104        let entries = [3, 5, 10, 20, 30, 40, 50, 51, 999]
5105            .into_iter()
5106            .map(|pc| (pc, contract.function_entry_at_pc(pc)))
5107            .collect::<Vec<_>>();
5108        assert_eq!(
5109            entries,
5110            [
5111                (3, None),
5112                (5, Some(4)),
5113                (10, Some(0)),
5114                (20, Some(5)),
5115                (30, None),
5116                (40, Some(1)),
5117                (50, None),
5118                (51, None),
5119                (999, None)
5120            ]
5121        );
5122        assert!(!contract.is_jumpdest(20));
5123    }
5124
5125    const SECOND_ROOT: &str = "0xbbbb000000000000000000000000000000000002";
5126
5127    /// Two top-level calls one after the other, as a test runner records `setUp` and the
5128    /// test: `outer_calls_inner` at `ROOT` (steps 0 to 10), then again at `SECOND_ROOT`
5129    /// (steps 11 to 21).
5130    fn two_roots() -> TransactionTrace {
5131        let mut trace = outer_calls_inner();
5132        let again = trace.steps.to_steps();
5133        trace.steps.extend(again);
5134        let mut first = call(0, 0, 11, "CALL", ROOT);
5135        first.input = "0x01".to_owned();
5136        let mut second = call(1, 11, 22, "CALL", SECOND_ROOT);
5137        second.input = "0x02".to_owned();
5138        trace.artifacts.calls = vec![first, second];
5139        trace
5140    }
5141
5142    #[test]
5143    fn each_root_call_maps_through_its_own_contract() {
5144        let trace = two_roots();
5145        let map = StepMap::new(
5146            &trace,
5147            vec![contract(Some(ROOT)), contract(Some(SECOND_ROOT))],
5148        );
5149        let address_at = |step: usize| {
5150            map.contract_at_step(step)
5151                .and_then(|contract| contract.address.clone())
5152        };
5153        assert_eq!(address_at(3).as_deref(), Some(ROOT));
5154        assert_eq!(address_at(14).as_deref(), Some(SECOND_ROOT));
5155
5156        // The second root starts with no functions of the first on its stack.
5157        let frames = map.frames(17);
5158        assert_eq!(frames.len(), 3);
5159        assert_eq!(frames[0].function_name.as_deref(), Some("inner"));
5160        assert!(frames[2].external);
5161        assert_eq!(frames[2].entry_step, 11);
5162        assert_eq!(frames[2].address.as_deref(), Some(SECOND_ROOT));
5163
5164        // `next` from the end of the first call goes on into the second.
5165        let next = map.next_source(10).expect("a line in the second call");
5166        assert!(next > 10);
5167        assert_eq!(address_at(next).as_deref(), Some(SECOND_ROOT));
5168
5169        assert_eq!(map.calldata_at(&trace, 3), "0x01");
5170        assert_eq!(map.calldata_at(&trace, 14), "0x02");
5171    }
5172
5173    #[test]
5174    fn finish_from_a_root_frame_stops_where_the_next_root_starts() {
5175        let map = StepMap::new(
5176            &two_roots(),
5177            vec![contract(Some(ROOT)), contract(Some(SECOND_ROOT))],
5178        );
5179        // In the first call's root frame, before and after its function.
5180        assert_eq!(map.finish(1), Some(11));
5181        assert_eq!(map.finish(10), Some(11));
5182        // The second call's root frame goes back to where that call starts.
5183        assert_eq!(map.reverse_finish(12), Some(11));
5184        // The last call still runs to the end, and the first back to the start.
5185        assert_eq!(map.finish(12), None);
5186        assert_eq!(map.reverse_finish(1), None);
5187    }
5188
5189    #[test]
5190    fn reverse_next_stays_within_a_root() {
5191        // Both calls run `outer`. The first ends on its declaration line, as solc's return
5192        // jump does (steps 0 to 10); the second enters `outer` on that line at step 13.
5193        let mut trace = outer_calls_inner();
5194        trace.steps.truncate(10);
5195        trace.steps.push(step(10, 1, "JUMP", &[]));
5196        trace.steps.extend(outer_calls_inner().steps.to_steps());
5197        trace.artifacts.calls = vec![call(0, 0, 11, "CALL", ROOT), call(1, 11, 22, "CALL", ROOT)];
5198        let map = StepMap::new(&trace, vec![contract(Some(ROOT))]);
5199        assert_eq!(map.line_key(10), map.line_key(13));
5200        assert_eq!(map.previous_source(14), Some(13));
5201    }
5202
5203    #[test]
5204    fn a_single_contract_describes_only_a_single_root() {
5205        let map = StepMap::new(&two_roots(), vec![contract(None)]);
5206        assert!(map.contract_at_step(3).is_none());
5207        assert!(map.contract_at_step(14).is_none());
5208        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
5209        assert!(map.contract_at_step(3).is_some());
5210    }
5211
5212    #[test]
5213    fn smoothing_stays_within_a_root() {
5214        // The first call ends on `b = 0;` in `outer`; the second enters `outer` at its
5215        // declaration, a lone step before `b = 0;` again.
5216        let mut trace = outer_calls_inner();
5217        trace.steps.truncate(10);
5218        trace.steps.push(step(10, 1, "JUMPDEST", &[]));
5219        trace.steps.push(step(14, 1, "POP", &[]));
5220        trace.steps.push(step(30, 1, "STOP", &[]));
5221        trace.artifacts.calls = vec![call(0, 0, 10, "CALL", ROOT), call(1, 10, 13, "CALL", ROOT)];
5222        let map = StepMap::new(&trace, vec![contract(Some(ROOT))]);
5223        assert_eq!(map.frames(10)[0].function_name.as_deref(), Some("outer"));
5224        assert_ne!(map.line_key(10), map.line_key(9));
5225        assert_eq!(map.line_key(9), map.line_key(11));
5226    }
5227
5228    /// A step at `depth` on `line` of contract 0, or on no line.
5229    fn step_info(depth: u32, line: Option<u64>, starts_root: bool) -> StepInfo {
5230        StepInfo {
5231            location: None,
5232            generated: false,
5233            key: line.and_then(key),
5234            contract: None,
5235            address: None,
5236            storage: None,
5237            evm_depth: 0,
5238            starts_root,
5239            frame_depth: depth,
5240            frame_entry: false,
5241            line_start: false,
5242        }
5243    }
5244
5245    #[test]
5246    fn smoothing_looks_past_deeper_frames_only() {
5247        let mut steps = vec![
5248            step_info(1, Some(1), false),
5249            // A lone line 2 between two runs of line 1, with deeper frames and a step
5250            // without a line before the second run.
5251            step_info(1, Some(2), false),
5252            step_info(2, Some(3), false),
5253            step_info(3, Some(4), false),
5254            step_info(4, Some(5), false),
5255            step_info(3, Some(6), false),
5256            step_info(1, None, false),
5257            step_info(1, Some(1), false),
5258            // A shallower step comes between line 8 and the line 1 before it.
5259            step_info(0, Some(7), false),
5260            step_info(1, Some(8), false),
5261            step_info(1, Some(1), false),
5262            // A shallower step comes between line 9 and the line 1 after it.
5263            step_info(1, Some(9), false),
5264            step_info(0, Some(7), false),
5265            step_info(1, Some(1), false),
5266            // A root starts between line 2 and the line 1 after it.
5267            step_info(1, Some(2), false),
5268            step_info(1, Some(1), true),
5269        ];
5270        let mut expected = steps
5271            .iter()
5272            .map(|step| step.key.map(|key| key.line))
5273            .collect::<Vec<_>>();
5274        expected[1] = Some(1);
5275        StepMap::smooth_single_step_excursions(&mut steps);
5276        let lines = steps
5277            .iter()
5278            .map(|step| step.key.map(|key| key.line))
5279            .collect::<Vec<_>>();
5280        assert_eq!(lines, expected);
5281    }
5282}