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