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