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, EthdebugInfo, FunctionExit, Instruction, SourceLocation, StorageLayout,
37};
38
39use crate::{
40    decode_arguments, is_value_type, parse_source_functions, readable_parameter, ArgumentLayout,
41    ArgumentOrder, FrameArgument, FrameState, SourceFunction,
42};
43
44const CALL_OPCODES: [&str; 4] = ["CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"];
45
46/// One contract's debug information, prepared for stepping: the ETHDebug metadata, the
47/// source text, the functions parsed from it, and the indexes that make per-step lookups
48/// constant time.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct ContractDebugInfo {
51    /// The deployed address this info describes, lowercase, when known.
52    pub address: Option<String>,
53    pub name: String,
54    pub info: EthdebugInfo,
55    pub source_contents: BTreeMap<u64, String>,
56    pub functions: Vec<SourceFunction>,
57    /// Where the contract's state variables live, when it was compiled with
58    /// `--storage-layout`.
59    pub storage_layout: Option<StorageLayout>,
60    /// Byte offsets at which each line of each source starts.
61    line_starts: BTreeMap<u64, Vec<usize>>,
62    /// Instruction index by program counter.
63    pc_index: HashMap<u64, usize>,
64    /// The program counter each parsed function is entered at: its first `JUMPDEST`
65    /// carrying the declaration's span.
66    function_entries: HashMap<u64, usize>,
67}
68
69/// What an artifact says about the jump an instruction makes.
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum JumpMarker {
72    None,
73    /// The jump enters a function.
74    Call,
75    /// The jump returns from one.
76    Return,
77}
78
79impl ContractDebugInfo {
80    /// Prepares one contract's debug info. Functions are parsed from the source text,
81    /// which serves ETHDebug and legacy source maps alike: both attach byte ranges of the
82    /// same sources to instructions.
83    #[must_use]
84    pub fn new(
85        address: Option<&str>,
86        name: &str,
87        info: EthdebugInfo,
88        source_contents: BTreeMap<u64, String>,
89    ) -> Self {
90        let functions = source_contents
91            .iter()
92            .flat_map(|(source_id, source)| parse_source_functions(*source_id, source))
93            .collect::<Vec<SourceFunction>>();
94        let line_starts = source_contents
95            .iter()
96            .map(|(source_id, source)| (*source_id, line_starts(source)))
97            .collect();
98        let pc_index = info
99            .instructions
100            .iter()
101            .enumerate()
102            .map(|(index, instruction)| (instruction.offset, index))
103            .collect();
104        let function_entries = functions
105            .iter()
106            .enumerate()
107            .filter_map(|(function_index, function)| {
108                info.instructions
109                    .iter()
110                    .filter(|instruction| instruction.mnemonic() == Some("JUMPDEST"))
111                    .filter(|instruction| {
112                        instruction.source_location().is_some_and(|location| {
113                            location.source_id == function.source_id
114                                && location.offset == function.declaration_start
115                        })
116                    })
117                    .map(|instruction| instruction.offset)
118                    .min()
119                    .map(|pc| (pc, function_index))
120            })
121            .collect();
122        Self {
123            address: address.map(normalize_address),
124            name: name.to_owned(),
125            info,
126            source_contents,
127            functions,
128            storage_layout: None,
129            line_starts,
130            pc_index,
131            function_entries,
132        }
133    }
134
135    /// Attaches the contract's storage layout, so state variables can be read by name.
136    #[must_use]
137    pub fn with_storage_layout(mut self, storage_layout: Option<StorageLayout>) -> Self {
138        self.storage_layout = storage_layout;
139        self
140    }
141
142    /// The function whose entry point `pc` is, when it is one.
143    #[must_use]
144    pub fn function_entry_at_pc(&self, pc: u64) -> Option<usize> {
145        self.function_entries.get(&pc).copied()
146    }
147
148    /// Whether the instruction at `pc` is a `JUMPDEST`.
149    #[must_use]
150    pub fn is_jumpdest(&self, pc: u64) -> bool {
151        self.pc_index
152            .get(&pc)
153            .and_then(|index| self.info.instructions.get(*index))
154            .is_some_and(|instruction| instruction.mnemonic() == Some("JUMPDEST"))
155    }
156
157    /// What the artifact says about the jump the instruction at `pc` makes.
158    #[must_use]
159    pub fn jump_marker_at_pc(&self, pc: u64) -> JumpMarker {
160        let Some(instruction) = self
161            .pc_index
162            .get(&pc)
163            .and_then(|index| self.info.instructions.get(*index))
164        else {
165            return JumpMarker::None;
166        };
167        if !instruction.function_invocations().is_empty() {
168            JumpMarker::Call
169        } else if instruction.function_exit() == Some(FunctionExit::Return) {
170            JumpMarker::Return
171        } else {
172            JumpMarker::None
173        }
174    }
175
176    #[must_use]
177    pub fn source_path(&self, source_id: u64) -> Option<&str> {
178        self.info.sources.get(&source_id).map(String::as_str)
179    }
180
181    /// The one-based line a byte offset falls on, when the source text is available.
182    #[must_use]
183    pub fn line_of(&self, source_id: u64, offset: u64) -> Option<u64> {
184        let starts = self.line_starts.get(&source_id)?;
185        let offset = usize::try_from(offset).ok()?;
186        Some(starts.partition_point(|start| *start <= offset) as u64)
187    }
188
189    #[must_use]
190    pub fn column_of(&self, source_id: u64, offset: u64) -> Option<u64> {
191        let starts = self.line_starts.get(&source_id)?;
192        let line = self.line_of(source_id, offset)?;
193        let line_start = *starts.get(usize::try_from(line).ok()?.checked_sub(1)?)?;
194        Some(offset.saturating_sub(line_start as u64) + 1)
195    }
196
197    #[must_use]
198    pub fn line_count(&self, source_id: u64) -> Option<u64> {
199        self.line_starts
200            .get(&source_id)
201            .map(|starts| starts.len() as u64)
202    }
203
204    /// The text of one line, without its line ending.
205    #[must_use]
206    pub fn line_text(&self, source_id: u64, line: u64) -> Option<&str> {
207        let source = self.source_contents.get(&source_id)?;
208        let index = usize::try_from(line).ok()?.checked_sub(1)?;
209        source.lines().nth(index)
210    }
211
212    /// The source span the compiler attached to the instruction at `pc`.
213    #[must_use]
214    pub fn location_at_pc(&self, pc: u64) -> Option<SourceLocation> {
215        let index = *self.pc_index.get(&pc)?;
216        self.info.instructions.get(index)?.source_location()
217    }
218
219    /// The compiler's instruction at `pc`, using the prepared index.
220    #[must_use]
221    pub fn instruction_at_pc(&self, pc: u64) -> Option<&Instruction> {
222        self.info.instructions.get(*self.pc_index.get(&pc)?)
223    }
224
225    /// Legacy source-map modifier depth at `pc`, when the artifact carries it.
226    #[must_use]
227    pub fn modifier_depth_at_pc(&self, pc: u64) -> Option<i64> {
228        let index = *self.pc_index.get(&pc)?;
229        self.info.instructions.get(index)?.modifier_depth()
230    }
231
232    /// The narrowest parsed function whose declaration contains the span.
233    #[must_use]
234    pub fn function_for_location(&self, location: &SourceLocation) -> Option<usize> {
235        self.functions
236            .iter()
237            .enumerate()
238            .filter(|(_, function)| {
239                function.source_id == location.source_id
240                    && function.declaration_start <= location.offset
241                    && location.offset <= function.body_end
242            })
243            .min_by_key(|(_, function)| {
244                function.body_end.saturating_sub(function.declaration_start)
245            })
246            .map(|(index, _)| index)
247    }
248
249    #[must_use]
250    pub fn function_at_pc(&self, pc: u64) -> Option<&SourceFunction> {
251        let location = self.location_at_pc(pc)?;
252        self.function_for_location(&location)
253            .and_then(|index| self.functions.get(index))
254    }
255
256    /// The line a breakpoint on `line` of `source_id` resolves to: the line itself when
257    /// the compiler generated code that begins there, otherwise the first line of the
258    /// narrowest statement that contains it, or nothing when no code maps to the line.
259    ///
260    /// Ranking matters because solc attaches the whole-contract span to the dispatcher,
261    /// and that span intersects every line in the file.
262    #[must_use]
263    pub fn effective_line(&self, source_id: u64, line: u64) -> Option<u64> {
264        if line == 0 || line > self.line_count(source_id)? {
265            return None;
266        }
267        let mut narrowest = None::<(u64, u64)>;
268        for instruction in &self.info.instructions {
269            let Some(location) = instruction.source_location() else {
270                continue;
271            };
272            if location.source_id != source_id {
273                continue;
274            }
275            let start = self.line_of(source_id, location.offset)?;
276            let end = self.line_of(
277                source_id,
278                location
279                    .offset
280                    .saturating_add(location.length.saturating_sub(1)),
281            )?;
282            if start > line || line > end {
283                continue;
284            }
285            if start == line {
286                return Some(line);
287            }
288            if narrowest.is_none_or(|(length, _)| location.length < length) {
289                narrowest = Some((location.length, start));
290            }
291        }
292        narrowest.map(|(_, start)| start)
293    }
294}
295
296/// Lowercases an address and gives it a `0x` prefix, so addresses from the command line,
297/// from a manifest, and from the stack compare equal.
298#[must_use]
299pub fn normalize_address(value: &str) -> String {
300    let hex = value.trim_start_matches("0x").trim_start_matches("0X");
301    format!("0x{}", hex.to_ascii_lowercase())
302}
303
304/// Whether a source path from the compiler matches what a user or an editor asked for:
305/// the whole path, a trailing part of either (an editor sends absolute paths where the
306/// compiler recorded relative ones), or just the file name.
307#[must_use]
308pub fn source_path_matches(source_path: &str, requested: &str) -> bool {
309    fn ends_with_path(longer: &str, shorter: &str) -> bool {
310        longer.ends_with(shorter)
311            && longer
312                .as_bytes()
313                .get(longer.len() - shorter.len() - 1)
314                .is_none_or(|byte| *byte == b'/')
315    }
316    fn file_name(path: &str) -> &str {
317        path.rsplit('/').next().unwrap_or(path)
318    }
319    source_path == requested
320        || ends_with_path(source_path, requested)
321        || ends_with_path(requested, source_path)
322        || (!requested.is_empty() && file_name(source_path) == file_name(requested))
323}
324
325fn line_starts(source: &str) -> Vec<usize> {
326    let mut starts = vec![0];
327    starts.extend(
328        source
329            .bytes()
330            .enumerate()
331            .filter(|(_, byte)| *byte == b'\n')
332            .map(|(index, _)| index + 1),
333    );
334    starts
335}
336
337/// Identifies a source line across every contract in a [`StepMap`].
338#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
339pub struct LineKey {
340    pub contract: usize,
341    pub source_id: u64,
342    pub line: u64,
343}
344
345/// Identifies a parsed source function across every contract in a [`StepMap`].
346#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
347pub struct FunctionId {
348    pub contract: usize,
349    pub function: usize,
350}
351
352/// Where one step is in the source, as a frontend shows it.
353#[derive(Debug, Clone, PartialEq, Eq)]
354pub struct StepLocation {
355    pub key: LineKey,
356    /// True when the instruction itself is compiler-generated helper code and the
357    /// location shown is the statement it was generated for.
358    pub generated: bool,
359    pub contract_name: String,
360    pub path: String,
361    pub offset: u64,
362    pub length: u64,
363    pub line: u64,
364    pub column: u64,
365    pub function: Option<FunctionId>,
366    pub function_name: Option<String>,
367}
368
369/// A line breakpoint target resolved against the loaded sources.
370#[derive(Debug, Clone, PartialEq, Eq)]
371pub struct ResolvedLine {
372    pub key: LineKey,
373    pub path: String,
374    /// The line the user asked for, which may sit inside the statement that `key` names.
375    pub requested_line: u64,
376}
377
378/// A function breakpoint target resolved against the parsed sources.
379#[derive(Debug, Clone, PartialEq, Eq)]
380pub struct ResolvedFunction {
381    pub id: FunctionId,
382    pub name: String,
383    pub contract_name: String,
384    pub path: String,
385    pub line: u64,
386}
387
388/// One frame of the call structure at a step, innermost first in [`StepMap::frames`].
389#[derive(Debug, Clone, PartialEq, Eq)]
390pub struct Frame {
391    /// Zero for the transaction's root frame.
392    pub depth: u32,
393    /// True for a frame the EVM entered with a call or a creation; false for a Solidity
394    /// internal function inferred from the source spans.
395    pub external: bool,
396    pub address: Option<String>,
397    pub contract_name: Option<String>,
398    pub function_name: Option<String>,
399    /// The step the frame was entered at.
400    pub entry_step: usize,
401    /// The step the frame is at: the current step for the innermost frame, the call site
402    /// for every frame above it.
403    pub step: usize,
404    pub pc: u64,
405    pub location: Option<StepLocation>,
406    /// The arguments the function was entered with, when the frame was entered at its
407    /// entry point and the contract's argument order is known. Filled in by
408    /// [`StepMap::frame_arguments`]; empty otherwise.
409    pub arguments: Vec<FrameArgument>,
410}
411
412/// Lines of source around a step.
413#[derive(Debug, Clone, PartialEq, Eq)]
414pub struct SourceListing {
415    pub path: String,
416    pub current_line: u64,
417    /// One-based line numbers with their text.
418    pub lines: Vec<(u64, String)>,
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq)]
422struct LocationRef {
423    key: LineKey,
424    offset: u64,
425    length: u64,
426    function: Option<usize>,
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq)]
430struct StepInfo {
431    /// The location shown for the step: its own span, or the statement generated code
432    /// belongs to.
433    location: Option<LocationRef>,
434    /// True when `location` is inherited from the executing statement.
435    generated: bool,
436    /// The line the step counts as for stepping and breakpoints. `None` for steps without
437    /// source and for the dispatcher, which belongs to no line a user steps through.
438    key: Option<LineKey>,
439    /// Which contract's code this step executes, when known.
440    contract: Option<usize>,
441    /// Index into the address table, when the executing address is known.
442    address: Option<usize>,
443    /// Index into the address table of the account whose storage this step reads and
444    /// writes: the executing address, or the caller's for a `DELEGATECALL`.
445    storage: Option<usize>,
446    /// EVM depth, zero at the root.
447    evm_depth: u32,
448    /// EVM depth plus the inferred internal-function depth, zero at the root.
449    frame_depth: u32,
450    /// True when this step enters a frame: an external call, or an internal function.
451    frame_entry: bool,
452    /// True when this step begins a run of one source line at its depth, skipping over
453    /// deeper frames it calls into.
454    line_start: bool,
455}
456
457/// Per-step source locations and frame depths over one trace.
458#[derive(Debug, Clone, PartialEq, Eq)]
459pub struct StepMap {
460    contracts: Vec<ContractDebugInfo>,
461    steps: Vec<StepInfo>,
462    addresses: Vec<String>,
463    pcs: Vec<u64>,
464    /// Every EVM frame that reverted, as the step it was entered at and the step it
465    /// reverted on, innermost first.
466    reverted: Vec<(usize, usize)>,
467    /// What this trace proved about where each contract's compiler leaves function
468    /// parameters on the stack.
469    argument_layouts: Vec<Evidence>,
470}
471
472/// What a trace has shown about one contract's argument passing. Only a proof is used;
473/// a contradiction disables arguments for the contract entirely, because it means the
474/// entry point we detect is not where the parameters are.
475#[derive(Debug, Clone, Copy, PartialEq, Eq)]
476enum Evidence {
477    /// Nothing in the trace said either way.
478    Unknown,
479    /// A frame whose arguments we knew did not have them on top of its entry stack.
480    Contradicted,
481    /// The parameters are the top words; the order was not established.
482    TopWords,
483    /// The parameters are the top words, in this order.
484    Ordered(ArgumentOrder),
485}
486
487impl Evidence {
488    /// Keeps the stronger of two observations, and a contradiction over everything: one
489    /// frame that disagrees is enough to stop guessing for the whole contract.
490    fn merge(self, other: Self) -> Self {
491        match (self, other) {
492            (Self::Contradicted, _) | (_, Self::Contradicted) => Self::Contradicted,
493            (Self::Ordered(order), _) | (_, Self::Ordered(order)) => Self::Ordered(order),
494            (Self::TopWords, _) | (_, Self::TopWords) => Self::TopWords,
495            _ => Self::Unknown,
496        }
497    }
498
499    fn layout(self) -> Option<ArgumentLayout> {
500        match self {
501            Self::Ordered(order) => Some(ArgumentLayout::Ordered(order)),
502            Self::TopWords => Some(ArgumentLayout::TopWords),
503            Self::Unknown | Self::Contradicted => None,
504        }
505    }
506}
507
508/// What the first pass records about a step: its EVM frame and its own span.
509#[derive(Debug, Clone, Copy)]
510struct FramedStep {
511    evm_depth: u32,
512    frame_id: u32,
513    contract: Option<usize>,
514    address: Option<usize>,
515    storage: Option<usize>,
516    location: Option<LocationRef>,
517    /// The function this step's program counter is the entry point of.
518    entry: Option<usize>,
519    /// What the artifact says about the jump this step makes.
520    marker: JumpMarker,
521}
522
523struct EvmFrame {
524    id: u32,
525    contract: Option<usize>,
526    address: Option<usize>,
527    /// The account whose storage the frame reads and writes.
528    storage: Option<usize>,
529    /// The step the frame was entered at.
530    entry_step: usize,
531}
532
533/// One internal frame: a function, or a placeholder for a compiler-generated helper
534/// entered through a marked call, which absorbs the matching marked return and counts as
535/// no frame of its own.
536#[derive(Debug, Clone, Copy, PartialEq, Eq)]
537struct FrameEntry {
538    function: Option<usize>,
539    /// Where the frame returns to: the tag the caller pushed before the arguments, read
540    /// off the stack at the call. A jump landing there is the return, whatever function
541    /// it lands in, which is what tells a return from a recursive call apart from
542    /// staying in the function.
543    return_pc: Option<u64>,
544}
545
546/// The inferred Solidity state of one EVM frame.
547#[derive(Default)]
548struct InternalFrame {
549    /// The frames active in this EVM frame, innermost last.
550    functions: Vec<FrameEntry>,
551    /// The last real statement executed in this frame, which generated code belongs to.
552    statement: Option<LocationRef>,
553}
554
555impl InternalFrame {
556    /// The innermost function, looking past helper placeholders.
557    fn active(&self) -> Option<usize> {
558        self.functions.iter().rev().find_map(|entry| entry.function)
559    }
560
561    fn is_active(&self, function: usize) -> bool {
562        self.active() == Some(function)
563    }
564
565    fn push_function(&mut self, function: usize, return_pc: Option<u64>) {
566        self.functions.push(FrameEntry {
567            function: Some(function),
568            return_pc,
569        });
570    }
571
572    fn push_placeholder(&mut self) {
573        self.functions.push(FrameEntry {
574            function: None,
575            return_pc: None,
576        });
577    }
578
579    /// Whether a jump landing on `pc` returns from the innermost frame.
580    fn returns_to(&self, pc: u64) -> bool {
581        self.functions
582            .last()
583            .is_some_and(|entry| entry.return_pc == Some(pc))
584    }
585}
586
587impl StepMap {
588    /// Maps every step of `trace` through the contracts' debug info.
589    ///
590    /// The root frame executes the transaction's target, or the created contract for a
591    /// deployment. A contract given with an address describes any frame that executes
592    /// it; when only one contract is given, it also describes the root whatever address
593    /// it names, so a single `--ethdebug-dir` applies to the transaction it was passed for.
594    #[must_use]
595    pub fn new(trace: &TransactionTrace, contracts: Vec<ContractDebugInfo>) -> Self {
596        Self::build(trace, contracts, true)
597    }
598
599    /// Keeps single-instruction source stops when testing compiler debug information.
600    ///
601    /// Interactive stepping smooths brief line excursions. A compiler regression test
602    /// must observe them: an optimized getter can map its entire body to one `SLOAD`.
603    #[must_use]
604    pub fn for_debug_diff(trace: &TransactionTrace, contracts: Vec<ContractDebugInfo>) -> Self {
605        Self::build(trace, contracts, false)
606    }
607
608    fn build(trace: &TransactionTrace, contracts: Vec<ContractDebugInfo>, smooth: bool) -> Self {
609        let mut addresses = Vec::<String>::new();
610        let mut address_index = HashMap::<String, usize>::new();
611        let mut intern = |address: &str| -> usize {
612            let address = normalize_address(address);
613            *address_index.entry(address.clone()).or_insert_with(|| {
614                addresses.push(address);
615                addresses.len() - 1
616            })
617        };
618        let contract_for = |address: Option<&str>, root: bool| -> Option<usize> {
619            let address = address.map(normalize_address);
620            if let Some(address) = &address {
621                if let Some(index) = contracts
622                    .iter()
623                    .position(|contract| contract.address.as_deref() == Some(address))
624                {
625                    return Some(index);
626                }
627            }
628            (root && contracts.len() == 1).then_some(0)
629        };
630
631        // Pass 1: EVM frames, executing addresses, and each step's own span.
632        let root_address = trace
633            .to_addr
634            .as_deref()
635            .or(trace.contract_address.as_deref());
636        let root_depth = trace.steps.first().map_or(0, |step| step.depth);
637        let mut next_frame_id = 1_u32;
638        let root_storage = root_address.map(&mut intern);
639        let mut evm_frames = vec![EvmFrame {
640            id: 0,
641            contract: contract_for(root_address, true),
642            address: root_storage,
643            storage: root_storage,
644            entry_step: 0,
645        }];
646        let mut reverted = Vec::new();
647        let mut framed = Vec::with_capacity(trace.steps.len());
648        for (index, step) in trace.steps.iter().enumerate() {
649            let evm_depth = step.depth.saturating_sub(root_depth) as usize;
650            evm_frames.truncate(evm_depth + 1);
651            while evm_frames.len() <= evm_depth {
652                // A new EVM frame: the callee's code address is on the caller's stack at
653                // the call instruction, the step before this one.
654                let call = (evm_frames.len() == evm_depth)
655                    .then(|| index.checked_sub(1))
656                    .flatten();
657                let target = call.and_then(|caller| call_target(&trace.steps[caller]));
658                let address = target.as_deref().map(&mut intern);
659                // A `DELEGATECALL` or `CALLCODE` runs the callee's code against the
660                // caller's storage; every other call has the callee's own.
661                let delegated = call.is_some_and(|caller| {
662                    matches!(&*trace.steps[caller].op, "DELEGATECALL" | "CALLCODE")
663                });
664                let storage = if delegated {
665                    evm_frames.last().and_then(|frame| frame.storage)
666                } else {
667                    address
668                };
669                evm_frames.push(EvmFrame {
670                    id: next_frame_id,
671                    contract: contract_for(target.as_deref(), false),
672                    address,
673                    storage,
674                    entry_step: index,
675                });
676                next_frame_id += 1;
677            }
678            if &*step.op == "REVERT" || step.error.is_some() {
679                let entry_step = evm_frames
680                    .last()
681                    .expect("the root frame is never popped")
682                    .entry_step;
683                reverted.push((entry_step, index));
684            }
685            let frame = evm_frames.last().expect("the root frame is never popped");
686            let location = frame.contract.and_then(|contract_index| {
687                let contract = &contracts[contract_index];
688                let location = contract.location_at_pc(step.pc)?;
689                let line = contract.line_of(location.source_id, location.offset)?;
690                Some(LocationRef {
691                    key: LineKey {
692                        contract: contract_index,
693                        source_id: location.source_id,
694                        line,
695                    },
696                    offset: location.offset,
697                    length: location.length,
698                    function: contract.function_for_location(&location),
699                })
700            });
701            let contract = frame.contract.map(|index| &contracts[index]);
702            framed.push(FramedStep {
703                evm_depth: evm_depth as u32,
704                frame_id: frame.id,
705                contract: frame.contract,
706                address: frame.address,
707                storage: frame.storage,
708                location,
709                entry: contract.and_then(|contract| contract.function_entry_at_pc(step.pc)),
710                marker: contract.map_or(JumpMarker::None, |contract| {
711                    contract.jump_marker_at_pc(step.pc)
712                }),
713            });
714        }
715
716        // Pass 2: a step outside every function is generated code for the statement that
717        // is executing, unless no function runs later in its EVM frame; then it is the
718        // dispatcher finishing up after the function returned.
719        let structured = |contract: Option<usize>| {
720            contract.is_some_and(|index| !contracts[index].functions.is_empty())
721        };
722        let mut function_follows = std::collections::HashSet::<u32>::new();
723        let mut epilogue = vec![false; framed.len()];
724        for (index, step) in framed.iter().enumerate().rev() {
725            match step.location.map(|location| location.function) {
726                Some(Some(_)) => {
727                    function_follows.insert(step.frame_id);
728                }
729                Some(None) if structured(step.contract) => {
730                    epilogue[index] = !function_follows.contains(&step.frame_id);
731                }
732                _ => {}
733            }
734        }
735
736        // Pass 3: the virtual function stack per EVM frame, frame depths, and the line
737        // each step counts as.
738        let mut internal = Vec::<InternalFrame>::new();
739        let mut steps = Vec::with_capacity(framed.len());
740        let mut pcs = Vec::with_capacity(framed.len());
741        for (index, step) in framed.iter().enumerate() {
742            let evm_depth = step.evm_depth as usize;
743            let mut frame_entry = false;
744            internal.truncate(evm_depth + 1);
745            while internal.len() <= evm_depth {
746                internal.push(InternalFrame::default());
747                frame_entry = true;
748            }
749            let frame = internal.last_mut().expect("the root frame is never popped");
750
751            // A step reached by a `JUMP` from the same EVM frame is a landing: the jump's
752            // marker, or landing on a function's entry point, says whether it was a call.
753            let landed_by_jump = index > 0
754                && framed[index - 1].evm_depth == step.evm_depth
755                && &*trace.steps[index - 1].op == "JUMP";
756            let previous_marker = if landed_by_jump {
757                framed[index - 1].marker
758            } else {
759                JumpMarker::None
760            };
761            let structured = structured(step.contract);
762            let mut generated = false;
763            // A return pops whatever the matching call pushed, wherever it lands: the
764            // artifact marks it, or the jump lands on the return address recorded at the
765            // call. A marked call is resolved once the landing's function is known.
766            let mut pending_call = false;
767            if landed_by_jump {
768                let pc = trace.steps[index].pc;
769                match previous_marker {
770                    JumpMarker::Call => pending_call = true,
771                    JumpMarker::Return => {
772                        frame.functions.pop();
773                    }
774                    JumpMarker::None if frame.returns_to(pc) => {
775                        frame.functions.pop();
776                    }
777                    JumpMarker::None => {}
778                }
779            }
780            // The return address a call being made here would come back to, read off
781            // the caller's stack at the jump: the tag pushed before the arguments.
782            let return_address = |function: usize| -> Option<u64> {
783                if !landed_by_jump {
784                    return None;
785                }
786                let contract = &contracts[step.contract?];
787                let arguments = contract.functions.get(function)?.params.len();
788                let stack = trace.steps[index - 1].snapshot_ref().stack;
789                let word = stack.get(stack.len().checked_sub(2 + arguments)?)?;
790                let pc = u64::from_str_radix(word.trim_start_matches("0x"), 16).ok()?;
791                contract.is_jumpdest(pc).then_some(pc)
792            };
793            let (location, key) = match step.location {
794                None => {
795                    // Code without source, such as a generated helper: a marked call into
796                    // it gets a placeholder its marked return pops.
797                    if pending_call {
798                        frame.push_placeholder();
799                    }
800                    (None, None)
801                }
802                Some(own) if !structured => {
803                    // No parsed functions: every mapped step is a line of its own.
804                    frame.statement = Some(own);
805                    (Some(own), Some(own.key))
806                }
807                Some(own) => match own.function {
808                    Some(function) => {
809                        let entering = landed_by_jump && step.entry == Some(function);
810                        if entering || (pending_call && !frame.is_active(function)) {
811                            // A call: onto the entry point, or marked and into a function
812                            // other than the active one.
813                            frame.push_function(function, return_address(function));
814                            frame_entry = true;
815                        } else if pending_call {
816                            // A marked call into the active function away from its entry
817                            // point is a generated helper whose span is the calling line:
818                            // recursion always enters at the entry point.
819                            frame.push_placeholder();
820                        } else if !frame.is_active(function) {
821                            match frame
822                                .functions
823                                .iter()
824                                .rposition(|entry| entry.function == Some(function))
825                            {
826                                Some(position) => frame.functions.truncate(position + 1),
827                                None => {
828                                    frame.push_function(function, None);
829                                    frame_entry = true;
830                                }
831                            }
832                        }
833                        frame.statement = Some(own);
834                        (Some(own), Some(own.key))
835                    }
836                    None if epilogue[index] => {
837                        frame.functions.clear();
838                        frame.statement = None;
839                        (Some(own), None)
840                    }
841                    None => {
842                        if pending_call {
843                            frame.push_placeholder();
844                        }
845                        match frame.statement {
846                            Some(statement) => {
847                                generated = true;
848                                (Some(statement), Some(statement.key))
849                            }
850                            // The dispatcher before any function: shown as it is, stepped
851                            // through as no line at all.
852                            None => (Some(own), None),
853                        }
854                    }
855                },
856            };
857            let internal_total = internal
858                .iter()
859                .map(|frame| {
860                    frame
861                        .functions
862                        .iter()
863                        .filter(|entry| entry.function.is_some())
864                        .count()
865                })
866                .sum::<usize>();
867            steps.push(StepInfo {
868                location,
869                generated,
870                key,
871                contract: step.contract,
872                address: step.address,
873                storage: step.storage,
874                evm_depth: step.evm_depth,
875                frame_depth: (evm_depth + internal_total) as u32,
876                frame_entry,
877                line_start: false,
878            });
879            pcs.push(trace.steps[index].pc);
880        }
881
882        let argument_layouts = prove_argument_layouts(trace, &contracts, &framed);
883        let mut map = Self {
884            contracts,
885            steps,
886            addresses,
887            pcs,
888            reverted,
889            argument_layouts,
890        };
891        if smooth {
892            map.smooth_single_step_excursions();
893        }
894        map.mark_line_starts();
895        map
896    }
897
898    /// A lone instruction attributed to another line, between two runs of the same line
899    /// at the same depth, counts as that line. solc emits such instructions at function
900    /// entry, and stopping on them would show a later line before an earlier one.
901    fn smooth_single_step_excursions(&mut self) {
902        let neighbours =
903            |steps: &[StepInfo], index: usize, forward: bool| -> Option<Option<LineKey>> {
904                let depth = steps[index].frame_depth;
905                let range: Box<dyn Iterator<Item = usize>> = if forward {
906                    Box::new((index + 1)..steps.len())
907                } else {
908                    Box::new((0..index).rev())
909                };
910                for candidate in range {
911                    let step = &steps[candidate];
912                    if step.frame_depth > depth {
913                        continue;
914                    }
915                    if step.frame_depth < depth {
916                        return None;
917                    }
918                    if step.key.is_some() {
919                        return Some(step.key);
920                    }
921                }
922                None
923            };
924        let mut smoothed = Vec::new();
925        for index in 0..self.steps.len() {
926            let Some(key) = self.steps[index].key else {
927                continue;
928            };
929            let Some(before) = neighbours(&self.steps, index, false) else {
930                continue;
931            };
932            let Some(after) = neighbours(&self.steps, index, true) else {
933                continue;
934            };
935            if before == after && before.is_some() && before != Some(key) {
936                smoothed.push((index, before));
937            }
938        }
939        for (index, key) in smoothed {
940            self.steps[index].key = key;
941        }
942    }
943
944    /// Marks the steps that begin a run of one line at their depth. Deeper frames and
945    /// steps without a line do not end a run.
946    fn mark_line_starts(&mut self) {
947        let mut last_key_at_or_below = Vec::<Option<LineKey>>::new();
948        for step in &mut self.steps {
949            let depth = step.frame_depth as usize;
950            if last_key_at_or_below.len() <= depth {
951                last_key_at_or_below.resize(depth + 1, None);
952            }
953            let Some(key) = step.key else {
954                step.line_start = false;
955                continue;
956            };
957            step.line_start = last_key_at_or_below[depth] != Some(key);
958            for entry in last_key_at_or_below.iter_mut().skip(depth) {
959                *entry = Some(key);
960            }
961        }
962    }
963
964    #[must_use]
965    pub fn contracts(&self) -> &[ContractDebugInfo] {
966        &self.contracts
967    }
968
969    /// The contract whose code the step executes, when its debug info was loaded.
970    #[must_use]
971    pub fn contract_at_step(&self, step: usize) -> Option<&ContractDebugInfo> {
972        self.contracts.get(self.steps.get(step)?.contract?)
973    }
974
975    #[must_use]
976    pub fn step_count(&self) -> usize {
977        self.steps.len()
978    }
979
980    /// Whether any step maps to a source line at all.
981    #[must_use]
982    pub fn has_source(&self) -> bool {
983        self.steps.iter().any(|step| step.location.is_some())
984    }
985
986    /// The line a step counts as for stepping and breakpoints.
987    #[must_use]
988    pub fn line_key(&self, step: usize) -> Option<LineKey> {
989        self.steps.get(step)?.key
990    }
991
992    #[must_use]
993    pub fn frame_depth(&self, step: usize) -> Option<u32> {
994        self.steps.get(step).map(|step| step.frame_depth)
995    }
996
997    #[must_use]
998    pub fn is_line_start(&self, step: usize) -> bool {
999        self.steps.get(step).is_some_and(|step| step.line_start)
1000    }
1001
1002    #[must_use]
1003    pub fn is_frame_entry(&self, step: usize) -> bool {
1004        self.steps.get(step).is_some_and(|step| step.frame_entry)
1005    }
1006
1007    /// The function the step runs in, including generated code for one of its statements.
1008    #[must_use]
1009    pub fn function_id(&self, step: usize) -> Option<FunctionId> {
1010        let location = self.steps.get(step)?.location?;
1011        location.function.map(|function| FunctionId {
1012            contract: location.key.contract,
1013            function,
1014        })
1015    }
1016
1017    /// The address whose code the step executes, when it could be determined.
1018    #[must_use]
1019    pub fn executing_address(&self, step: usize) -> Option<&str> {
1020        let index = self.steps.get(step)?.address?;
1021        self.addresses.get(index).map(String::as_str)
1022    }
1023
1024    /// The account whose storage the step reads and writes: the executing address, or
1025    /// the caller's under a `DELEGATECALL`.
1026    #[must_use]
1027    pub fn storage_address(&self, step: usize) -> Option<&str> {
1028        let index = self.storage_context_index(step)?;
1029        self.addresses.get(index).map(String::as_str)
1030    }
1031
1032    /// An opaque identifier for that account, for indexing storage by context.
1033    #[must_use]
1034    pub fn storage_context_index(&self, step: usize) -> Option<usize> {
1035        self.steps.get(step)?.storage
1036    }
1037
1038    /// Every EVM frame that reverted, as the step it was entered at and the step it
1039    /// reverted on. A frame's writes, and its callees', do not survive it.
1040    #[must_use]
1041    pub fn reverted_spans(&self) -> &[(usize, usize)] {
1042        &self.reverted
1043    }
1044
1045    /// What this trace proved about where a contract's compiler leaves function
1046    /// parameters on the stack. `None` when the trace carried no frame that could show
1047    /// it, or when a frame contradicted it.
1048    #[must_use]
1049    pub fn argument_layout(&self, contract: usize) -> Option<ArgumentLayout> {
1050        self.argument_layouts.get(contract).copied()?.layout()
1051    }
1052
1053    /// The arguments `frame` was entered with, given the stack at its entry step.
1054    ///
1055    /// Only a frame entered at its function's entry point carries them, and only once
1056    /// this trace has shown that the compiler leaves them there: which word is which
1057    /// parameter depends on the code generator, and a legacy public function is entered
1058    /// through a dispatcher wrapper that has not decoded them yet. See
1059    /// [`StepMap::argument_layout`].
1060    #[must_use]
1061    pub fn frame_arguments(&self, frame: &Frame, state: FrameState<'_>) -> Vec<FrameArgument> {
1062        let Some(info) = self.steps.get(frame.entry_step) else {
1063            return Vec::new();
1064        };
1065        let Some(location) = info.location else {
1066            return Vec::new();
1067        };
1068        let Some(function_index) = location.function else {
1069            return Vec::new();
1070        };
1071        let Some(contract) = self.contracts.get(location.key.contract) else {
1072            return Vec::new();
1073        };
1074        if contract.function_entry_at_pc(self.pcs[frame.entry_step]) != Some(function_index) {
1075            return Vec::new();
1076        }
1077        let Some(function) = contract.functions.get(function_index) else {
1078            return Vec::new();
1079        };
1080        let Some(layout) = self.argument_layout(location.key.contract) else {
1081            return Vec::new();
1082        };
1083        decode_arguments(&function.params, state, layout)
1084    }
1085
1086    #[must_use]
1087    pub fn location(&self, step: usize) -> Option<StepLocation> {
1088        let info = self.steps.get(step)?;
1089        self.describe(&info.location?, info.generated)
1090    }
1091
1092    fn describe(&self, location: &LocationRef, generated: bool) -> Option<StepLocation> {
1093        let contract = self.contracts.get(location.key.contract)?;
1094        let function = location
1095            .function
1096            .and_then(|index| contract.functions.get(index));
1097        Some(StepLocation {
1098            key: location.key,
1099            generated,
1100            contract_name: contract.name.clone(),
1101            path: contract
1102                .source_path(location.key.source_id)
1103                .map(str::to_owned)
1104                .unwrap_or_else(|| format!("source:{}", location.key.source_id)),
1105            offset: location.offset,
1106            length: location.length,
1107            line: location.key.line,
1108            column: contract
1109                .column_of(location.key.source_id, location.offset)
1110                .unwrap_or(0),
1111            function: location.function.map(|function| FunctionId {
1112                contract: location.key.contract,
1113                function,
1114            }),
1115            function_name: function.map(|function| function.name.clone()),
1116        })
1117    }
1118
1119    /// The step where the next source line begins at this frame or a caller's, stepping
1120    /// over calls. `None` when the recording ends first.
1121    ///
1122    /// Steps the compiler attached no source to, such as generated helper code, never
1123    /// stop a line step at the same depth: they belong to whatever line is executing.
1124    ///
1125    /// From code outside every function, such as the dispatcher, this enters the function
1126    /// it jumps to instead of stepping over it: there is no caller to come back to, and
1127    /// a user at the first step wants to reach the first line, not the last.
1128    #[must_use]
1129    pub fn next_source(&self, step: usize) -> Option<usize> {
1130        let current = *self.steps.get(step)?;
1131        if self.outside_functions(&current) {
1132            return self.step_into(step);
1133        }
1134        let key = current.key;
1135        ((step + 1)..self.steps.len()).find(|&index| {
1136            let candidate = &self.steps[index];
1137            if candidate.frame_depth > current.frame_depth {
1138                return false;
1139            }
1140            candidate.frame_depth < current.frame_depth
1141                || candidate
1142                    .key
1143                    .is_some_and(|candidate| Some(candidate) != key)
1144        })
1145    }
1146
1147    /// The step where the next source line begins anywhere, entering calls. A call into
1148    /// code without source stops at its first step, so the user sees where they are.
1149    #[must_use]
1150    pub fn step_into(&self, step: usize) -> Option<usize> {
1151        let current = *self.steps.get(step)?;
1152        let key = current.key;
1153        ((step + 1)..self.steps.len()).find(|&index| {
1154            let candidate = &self.steps[index];
1155            candidate.frame_depth != current.frame_depth
1156                || candidate
1157                    .key
1158                    .is_some_and(|candidate| Some(candidate) != key)
1159        })
1160    }
1161
1162    /// The step where the current frame has returned to its caller.
1163    #[must_use]
1164    pub fn finish(&self, step: usize) -> Option<usize> {
1165        let current = *self.steps.get(step)?;
1166        ((step + 1)..self.steps.len())
1167            .find(|&index| self.steps[index].frame_depth < current.frame_depth)
1168    }
1169
1170    /// The start of the previous source line at this frame or a caller's, skipping the
1171    /// calls it made. From the middle of a line this is the start of that line. From code
1172    /// outside every function it enters the function that just returned, the mirror of
1173    /// [`StepMap::next_source`].
1174    #[must_use]
1175    pub fn previous_source(&self, step: usize) -> Option<usize> {
1176        let current = *self.steps.get(step)?;
1177        if self.outside_functions(&current) {
1178            return self.reverse_step_into(step);
1179        }
1180        self.previous_line_start(step, true)
1181    }
1182
1183    /// The start of the previous source line anywhere, entering the calls it made.
1184    #[must_use]
1185    pub fn reverse_step_into(&self, step: usize) -> Option<usize> {
1186        self.steps.get(step)?;
1187        self.previous_line_start(step, false)
1188    }
1189
1190    fn previous_line_start(&self, step: usize, skip_deeper: bool) -> Option<usize> {
1191        let current = self.steps[step];
1192        // From a step without a line, the previous line is the nearest step with one.
1193        let anchor = if current.key.is_some() {
1194            step
1195        } else {
1196            self.previous_mapped(step, current.frame_depth, skip_deeper)?
1197        };
1198        let start = self.run_start(anchor, skip_deeper);
1199        if start < step {
1200            return Some(start);
1201        }
1202        let previous = self.previous_mapped(start, current.frame_depth, skip_deeper)?;
1203        Some(self.run_start(previous, skip_deeper))
1204    }
1205
1206    /// The step in the caller that entered the current frame.
1207    #[must_use]
1208    pub fn reverse_finish(&self, step: usize) -> Option<usize> {
1209        let current = *self.steps.get(step)?;
1210        (0..step)
1211            .rev()
1212            .find(|&index| self.steps[index].frame_depth < current.frame_depth)
1213    }
1214
1215    /// The first step of the run of one line that `step` belongs to. With `skip_deeper`,
1216    /// steps in frames the line called into do not break the run; steps without source at
1217    /// the same depth never do.
1218    fn run_start(&self, step: usize, skip_deeper: bool) -> usize {
1219        let info = self.steps[step];
1220        let key = info.key;
1221        let mut current = step;
1222        while let Some(previous) = self.previous_mapped(current, info.frame_depth, skip_deeper) {
1223            let candidate = self.steps[previous];
1224            if candidate.frame_depth != info.frame_depth || candidate.key != key {
1225                break;
1226            }
1227            current = previous;
1228        }
1229        current
1230    }
1231
1232    /// Whether a step is in code that belongs to no line: the dispatcher before the
1233    /// function it jumps to, or after that function returned.
1234    fn outside_functions(&self, info: &StepInfo) -> bool {
1235        info.location.is_some() && info.key.is_none()
1236    }
1237
1238    /// The nearest earlier step with a source location at `depth` or, with `skip_deeper`,
1239    /// at most `depth`; without it, the nearest earlier mapped step at any depth.
1240    fn previous_mapped(&self, step: usize, depth: u32, skip_deeper: bool) -> Option<usize> {
1241        (0..step).rev().find(|&index| {
1242            let candidate = &self.steps[index];
1243            candidate.key.is_some() && (!skip_deeper || candidate.frame_depth <= depth)
1244        })
1245    }
1246
1247    /// Resolves a `file:line` breakpoint against every loaded source. With no file, the
1248    /// line must be unambiguous across the loaded sources.
1249    pub fn resolve_line(&self, file: Option<&str>, line: u64) -> Result<Vec<ResolvedLine>, String> {
1250        let mut sources = Vec::<(usize, u64, String)>::new();
1251        for (contract_index, contract) in self.contracts.iter().enumerate() {
1252            for (source_id, path) in &contract.info.sources {
1253                if file.is_none_or(|file| source_path_matches(path, file)) {
1254                    sources.push((contract_index, *source_id, path.clone()));
1255                }
1256            }
1257        }
1258        if sources.is_empty() {
1259            return Err(match file {
1260                Some(file) => format!("source file not found: {file}"),
1261                None => "no source files are available".to_owned(),
1262            });
1263        }
1264        if file.is_none() {
1265            let mut paths = sources.iter().map(|(_, _, path)| path).collect::<Vec<_>>();
1266            paths.sort();
1267            paths.dedup();
1268            if paths.len() > 1 {
1269                return Err("line breakpoint is ambiguous; use break <file>:<line>".to_owned());
1270            }
1271        }
1272
1273        let resolved = sources
1274            .into_iter()
1275            .filter_map(|(contract_index, source_id, path)| {
1276                let effective = self.contracts[contract_index].effective_line(source_id, line)?;
1277                Some(ResolvedLine {
1278                    key: LineKey {
1279                        contract: contract_index,
1280                        source_id,
1281                        line: effective,
1282                    },
1283                    path,
1284                    requested_line: line,
1285                })
1286            })
1287            .collect::<Vec<_>>();
1288        if resolved.is_empty() {
1289            return Err(match file {
1290                Some(file) => format!("no instruction maps to {file}:{line}"),
1291                None => format!("no instruction maps to line {line}"),
1292            });
1293        }
1294        Ok(resolved)
1295    }
1296
1297    /// Resolves a function breakpoint by name, optionally qualified as
1298    /// `Contract.function`.
1299    pub fn resolve_function(&self, name: &str) -> Result<Vec<ResolvedFunction>, String> {
1300        let (contract_name, function_name) = match name.rsplit_once('.') {
1301            Some((contract, function)) => (Some(contract), function),
1302            None => (None, name),
1303        };
1304        let resolved = self
1305            .contracts
1306            .iter()
1307            .enumerate()
1308            .filter(|(_, contract)| contract_name.is_none_or(|name| contract.name == name))
1309            .flat_map(|(contract_index, contract)| {
1310                contract
1311                    .functions
1312                    .iter()
1313                    .enumerate()
1314                    .filter(move |(_, function)| function.name == function_name)
1315                    .map(move |(function_index, function)| ResolvedFunction {
1316                        id: FunctionId {
1317                            contract: contract_index,
1318                            function: function_index,
1319                        },
1320                        name: function.name.clone(),
1321                        contract_name: contract.name.clone(),
1322                        path: contract
1323                            .source_path(function.source_id)
1324                            .map(str::to_owned)
1325                            .unwrap_or_else(|| format!("source:{}", function.source_id)),
1326                        line: function.declaration_line,
1327                    })
1328            })
1329            .collect::<Vec<_>>();
1330        if resolved.is_empty() {
1331            return Err(format!("no function named `{name}` in the loaded sources"));
1332        }
1333        Ok(resolved)
1334    }
1335
1336    /// The call structure at `step`, innermost frame first.
1337    #[must_use]
1338    pub fn frames(&self, step: usize) -> Vec<Frame> {
1339        struct Record {
1340            entry_step: usize,
1341            last_step: usize,
1342            external: bool,
1343        }
1344        if step >= self.steps.len() {
1345            return Vec::new();
1346        }
1347        let mut stack = Vec::<Record>::new();
1348        for (index, info) in self.steps.iter().enumerate().take(step + 1) {
1349            let depth = info.frame_depth as usize;
1350            stack.truncate(depth + 1);
1351            // Frames entered at this step. When an external call and a function both begin
1352            // here, the outer one is the call.
1353            let entered_call = index == 0 || info.evm_depth > self.steps[index - 1].evm_depth;
1354            let mut first_new = true;
1355            while stack.len() <= depth {
1356                stack.push(Record {
1357                    entry_step: index,
1358                    last_step: index,
1359                    external: first_new && entered_call,
1360                });
1361                first_new = false;
1362            }
1363            if let Some(top) = stack.last_mut() {
1364                top.last_step = index;
1365            }
1366        }
1367
1368        stack
1369            .iter()
1370            .enumerate()
1371            .rev()
1372            .map(|(depth, record)| {
1373                let info = self.steps[record.last_step];
1374                let location = info
1375                    .location
1376                    .and_then(|location| self.describe(&location, info.generated));
1377                Frame {
1378                    depth: depth as u32,
1379                    external: record.external,
1380                    address: info
1381                        .address
1382                        .and_then(|index| self.addresses.get(index))
1383                        .cloned(),
1384                    contract_name: info
1385                        .contract
1386                        .and_then(|index| self.contracts.get(index))
1387                        .map(|contract| contract.name.clone()),
1388                    function_name: location
1389                        .as_ref()
1390                        .and_then(|location| location.function_name.clone()),
1391                    entry_step: record.entry_step,
1392                    step: record.last_step,
1393                    pc: self.pcs[record.last_step],
1394                    location,
1395                    arguments: Vec::new(),
1396                }
1397            })
1398            .collect()
1399    }
1400
1401    /// The source around `step`: `radius` lines on each side of its line.
1402    #[must_use]
1403    pub fn source_listing(&self, step: usize, radius: u64) -> Option<SourceListing> {
1404        let location = self.steps.get(step)?.location?;
1405        let contract = self.contracts.get(location.key.contract)?;
1406        let count = contract.line_count(location.key.source_id)?;
1407        let first = location.key.line.saturating_sub(radius).max(1);
1408        let last = location.key.line.saturating_add(radius).min(count);
1409        let lines = (first..=last)
1410            .filter_map(|line| {
1411                contract
1412                    .line_text(location.key.source_id, line)
1413                    .map(|text| (line, text.to_owned()))
1414            })
1415            .collect();
1416        Some(SourceListing {
1417            path: contract
1418                .source_path(location.key.source_id)
1419                .map(str::to_owned)
1420                .unwrap_or_else(|| format!("source:{}", location.key.source_id)),
1421            current_line: location.key.line,
1422            lines,
1423        })
1424    }
1425}
1426
1427/// Proves, per contract, whether its compiler leaves function parameters on top of the
1428/// stack at a function's entry point, and in which order.
1429///
1430/// The evidence is a frame whose arguments the trace already tells us: the first function
1431/// entered in an EVM frame whose calldata is known is the function that calldata selected,
1432/// so its arguments are the calldata words. Comparing those with the words on the entry
1433/// stack shows whether the parameters are there at all, and with more than one parameter,
1434/// which end the first one is at. Only frames whose parameters are all value types are
1435/// used, since each is then exactly one word.
1436///
1437/// A frame that disagrees contradicts the whole contract: solc's legacy pipeline enters a
1438/// public function through a dispatcher wrapper that has not decoded the arguments yet, so
1439/// the words on top there are not the parameters, and nothing about that contract's frames
1440/// can be trusted to hold them.
1441fn prove_argument_layouts(
1442    trace: &TransactionTrace,
1443    contracts: &[ContractDebugInfo],
1444    framed: &[FramedStep],
1445) -> Vec<Evidence> {
1446    let mut evidence = vec![Evidence::Unknown; contracts.len()];
1447    if contracts.is_empty() || framed.is_empty() {
1448        return evidence;
1449    }
1450    // The calldata each EVM frame was entered with: the transaction's input at the root,
1451    // and the recorded input of a call for a nested frame.
1452    let mut calldata_by_entry = HashMap::<usize, &str>::new();
1453    calldata_by_entry.insert(0, trace.input_data.as_str());
1454    for call in &trace.artifacts.calls {
1455        if let Some(entry_step) = call.entry_step {
1456            calldata_by_entry.insert(entry_step, call.input.as_str());
1457        }
1458    }
1459
1460    let mut examined = std::collections::HashSet::<u32>::new();
1461    let mut frame_calldata = HashMap::<u32, &str>::new();
1462    for (index, step) in framed.iter().enumerate() {
1463        let entered = index == 0 || framed[index - 1].frame_id != step.frame_id;
1464        if entered {
1465            if let Some(calldata) = calldata_by_entry.get(&index) {
1466                frame_calldata.insert(step.frame_id, calldata);
1467            }
1468        }
1469        let (Some(contract_index), Some(function_index)) = (step.contract, step.entry) else {
1470            continue;
1471        };
1472        // Only the first function entered in an EVM frame: a later one was called from
1473        // inside the code, not by this calldata.
1474        if !examined.insert(step.frame_id) {
1475            continue;
1476        }
1477        let Some(calldata) = frame_calldata.get(&step.frame_id) else {
1478            continue;
1479        };
1480        let Some(function) = contracts[contract_index].functions.get(function_index) else {
1481            continue;
1482        };
1483        let stack = trace.steps[index].snapshot_ref().stack;
1484        let observed = argument_evidence(function, calldata, stack);
1485        evidence[contract_index] = evidence[contract_index].merge(observed);
1486    }
1487    evidence
1488}
1489
1490/// What one frame's entry stack says, given the calldata that selected its function.
1491///
1492/// Every parameter takes one head word in the calldata and one word on the stack, so the
1493/// two line up position by position. Only the value-type positions can be compared — a
1494/// reference parameter is a calldata offset in one and a memory pointer in the other —
1495/// and that is enough as long as one of them sits at a different depth under the two
1496/// orders.
1497fn argument_evidence(function: &SourceFunction, calldata: &str, stack: &[StackWord]) -> Evidence {
1498    let count = function.params.len();
1499    if count == 0 || stack.len() < count {
1500        return Evidence::Unknown;
1501    }
1502    // A parameter whose width is uncertain would misalign every position after it.
1503    if !function.params.iter().all(readable_parameter) {
1504        return Evidence::Unknown;
1505    }
1506    let signature = format!(
1507        "{}({})",
1508        function.name,
1509        function
1510            .params
1511            .iter()
1512            .map(|param| canonical_value_type(&param.ty))
1513            .collect::<Vec<_>>()
1514            .join(",")
1515    );
1516    let Ok(selector) = function_selector(&signature) else {
1517        return Evidence::Unknown;
1518    };
1519    let data = calldata.trim_start_matches("0x");
1520    let Some((encoded_selector, arguments)) = data.split_at_checked(8) else {
1521        return Evidence::Unknown;
1522    };
1523    // A different function's calldata says nothing about this frame.
1524    if encoded_selector.to_ascii_lowercase() != hex_bytes(&selector) {
1525        return Evidence::Unknown;
1526    }
1527    let expected = (0..count)
1528        .map(|index| {
1529            arguments
1530                .get(index * 64..(index + 1) * 64)
1531                .map(str::to_ascii_lowercase)
1532        })
1533        .collect::<Option<Vec<_>>>();
1534    let Some(expected) = expected else {
1535        return Evidence::Unknown;
1536    };
1537    // The positions whose calldata word is the argument itself, rather than an offset to
1538    // it.
1539    let checkable = (0..count)
1540        .filter(|index| is_value_type(&function.params[*index].ty))
1541        .collect::<Vec<_>>();
1542    if checkable.is_empty() {
1543        return Evidence::Unknown;
1544    }
1545    let matches = |order: ArgumentOrder| {
1546        checkable.iter().all(|index| {
1547            let candidate = &stack[order.word_index(*index, count, stack.len())];
1548            normalize_word(candidate) == expected[*index]
1549        })
1550    };
1551    // A position at the same depth under both orders proves nothing about the order.
1552    let distinguishing = checkable.iter().any(|index| {
1553        ArgumentOrder::FirstOnTop.word_index(*index, count, stack.len())
1554            != ArgumentOrder::LastOnTop.word_index(*index, count, stack.len())
1555    });
1556    let first = matches(ArgumentOrder::FirstOnTop);
1557    let last = matches(ArgumentOrder::LastOnTop);
1558    match (first, last) {
1559        // Both orders agree where they could be told apart: the arguments are on top, but
1560        // which is which is still open.
1561        (true, true) => Evidence::TopWords,
1562        (true, false) if distinguishing => Evidence::Ordered(ArgumentOrder::FirstOnTop),
1563        (false, true) if distinguishing => Evidence::Ordered(ArgumentOrder::LastOnTop),
1564        // One order matched only because the other was never tested where they differ.
1565        (true, false) | (false, true) => Evidence::TopWords,
1566        // The arguments are not on the entry stack: this is not where they live.
1567        (false, false) => Evidence::Contradicted,
1568    }
1569}
1570
1571/// The ABI name of a value type as written in the source: the bare integer names are
1572/// their 256-bit forms, and `address payable` encodes as `address`.
1573fn canonical_value_type(ty: &str) -> String {
1574    match ty {
1575        "uint" => "uint256".to_owned(),
1576        "int" => "int256".to_owned(),
1577        "address payable" => "address".to_owned(),
1578        other => other.to_owned(),
1579    }
1580}
1581
1582/// A stack word as 64 lowercase hex digits, however the backend spelled it.
1583fn normalize_word(word: &str) -> String {
1584    let digits = word.trim_start_matches("0x").trim_start_matches('0');
1585    format!("{:0>64}", digits.to_ascii_lowercase())
1586}
1587
1588fn hex_bytes(bytes: &[u8]) -> String {
1589    bytes.iter().map(|byte| format!("{byte:02x}")).collect()
1590}
1591
1592/// The code address a call instruction targets, read off its stack.
1593#[must_use]
1594pub fn call_target(step: &soldb_core::TraceStep) -> Option<String> {
1595    if !CALL_OPCODES.contains(&&*step.op) {
1596        return None;
1597    }
1598    let stack = step.snapshot_ref().stack;
1599    let word = stack.get(stack.len().checked_sub(2)?)?;
1600    address_from_word(word)
1601}
1602
1603/// The address in the low 20 bytes of a stack word, or `None` for the zero address or a
1604/// word that is not hex.
1605#[must_use]
1606pub fn address_from_word(word: &str) -> Option<String> {
1607    let hex = word.trim_start_matches("0x");
1608    if !hex.bytes().all(|byte| byte.is_ascii_hexdigit()) {
1609        return None;
1610    }
1611    let padded = format!("{hex:0>40}");
1612    let address = padded.get(padded.len() - 40..)?;
1613    if address.bytes().all(|byte| byte == b'0') {
1614        return None;
1615    }
1616    Some(format!("0x{}", address.to_ascii_lowercase()))
1617}
1618
1619#[cfg(test)]
1620mod tests {
1621    use std::collections::BTreeMap;
1622
1623    use serde_json::json;
1624    use soldb_core::{StepSnapshot, TraceStep, TransactionTrace};
1625    use soldb_ethdebug::{EthdebugInfo, Instruction};
1626
1627    use soldb_core::Word as StackWord;
1628
1629    use crate::FrameState;
1630
1631    use super::{
1632        address_from_word, normalize_address, ArgumentLayout, ArgumentOrder, ContractDebugInfo,
1633        JumpMarker, LineKey, StepMap,
1634    };
1635
1636    // Two functions; `outer` calls `inner` internally. Line numbers are one-based.
1637    const SOURCE: &str = "\
1638contract C {
1639    function outer(uint256 a) public {
1640        uint256 b = a + 1;
1641        inner(b);
1642        b = 0;
1643    }
1644    function inner(uint256 x) internal {
1645        x += 1;
1646    }
1647}
1648";
1649
1650    fn offset_of(needle: &str) -> u64 {
1651        SOURCE.find(needle).expect(needle) as u64
1652    }
1653
1654    fn instruction(pc: u64, offset: u64, length: u64) -> Instruction {
1655        instruction_with(pc, offset, length, json!({}))
1656    }
1657
1658    /// An instruction with extra ETHDebug context, such as a jump marker.
1659    fn instruction_with(
1660        pc: u64,
1661        offset: u64,
1662        length: u64,
1663        extra: serde_json::Value,
1664    ) -> Instruction {
1665        let mut context =
1666            json!({"code": {"source": {"id": 0}, "range": {"offset": offset, "length": length}}});
1667        for (key, value) in extra.as_object().expect("object") {
1668            context[key] = value.clone();
1669        }
1670        serde_json::from_value(json!({
1671            "offset": pc,
1672            "operation": {"mnemonic": "JUMPDEST"},
1673            "context": context
1674        }))
1675        .expect("instruction")
1676    }
1677
1678    fn contract(address: Option<&str>) -> ContractDebugInfo {
1679        let whole = SOURCE.len() as u64;
1680        let info = EthdebugInfo {
1681            compilation: serde_json::Value::Null,
1682            contract_name: "C".to_owned(),
1683            environment: "runtime".to_owned(),
1684            instructions: vec![
1685                // Dispatcher: the whole-contract span.
1686                instruction(0, 0, whole),
1687                instruction(1, 0, whole),
1688                // outer's declaration, then its three statements.
1689                instruction(10, offset_of("function outer"), 90),
1690                instruction(11, offset_of("uint256 b = a + 1;"), 18),
1691                instruction(12, offset_of("inner(b);"), 9),
1692                instruction(13, offset_of("inner(b);"), 9),
1693                instruction(14, offset_of("b = 0;"), 6),
1694                // inner's declaration and body.
1695                instruction(20, offset_of("function inner"), 50),
1696                instruction(21, offset_of("x += 1;"), 7),
1697                // Back in the dispatcher.
1698                instruction(30, 0, whole),
1699                // A generated helper, carrying the whole-contract span like the dispatcher.
1700                instruction(40, 0, whole),
1701            ],
1702            sources: BTreeMap::from([(0, "C.sol".to_owned())]),
1703            variable_locations: BTreeMap::new(),
1704        };
1705        ContractDebugInfo::new(address, "C", info, BTreeMap::from([(0, SOURCE.to_owned())]))
1706    }
1707
1708    fn step(pc: u64, depth: u64, op: &str, stack: &[&str]) -> TraceStep {
1709        TraceStep {
1710            pc,
1711            op: op.into(),
1712            gas: 0,
1713            gas_cost: 0,
1714            depth,
1715            stack: stack.iter().map(|word| StackWord::from(*word)).collect(),
1716            memory: None,
1717            storage: None,
1718            error: None,
1719            snapshot: StepSnapshot::default(),
1720        }
1721    }
1722
1723    fn trace(steps: Vec<TraceStep>) -> TransactionTrace {
1724        TransactionTrace {
1725            tx_hash: None,
1726            from_addr: "0x1".to_owned(),
1727            to_addr: Some("0xAAaa000000000000000000000000000000000001".to_owned()),
1728            value: "0x0".to_owned(),
1729            input_data: "0x".to_owned(),
1730            gas_used: 0,
1731            output: "0x".to_owned(),
1732            success: true,
1733            error: None,
1734            debug_trace_available: true,
1735            contract_address: None,
1736            backend: None,
1737            capabilities: Default::default(),
1738            artifacts: Default::default(),
1739            steps,
1740        }
1741    }
1742
1743    // Steps: dispatcher (0,1), outer decl (2), line 3 (3), line 4 before the call (4),
1744    // inner decl (5), inner line 8 (6, 7), back on line 4 (8), line 5 (9), dispatcher (10).
1745    fn outer_calls_inner() -> TransactionTrace {
1746        trace(vec![
1747            step(0, 1, "PUSH1", &[]),
1748            step(1, 1, "JUMPI", &[]),
1749            step(10, 1, "JUMPDEST", &[]),
1750            step(11, 1, "ADD", &[]),
1751            step(12, 1, "PUSH1", &[]),
1752            step(20, 1, "JUMPDEST", &[]),
1753            step(21, 1, "ADD", &[]),
1754            step(21, 1, "SWAP1", &[]),
1755            step(13, 1, "JUMPDEST", &[]),
1756            step(14, 1, "POP", &[]),
1757            step(30, 1, "STOP", &[]),
1758        ])
1759    }
1760
1761    // A contract whose functions take two parameters, so the order they are passed in
1762    // can be told apart.
1763    const PAY_SOURCE: &str = "\
1764contract P {
1765    function pay(address to, uint256 amount) public {
1766        total(to, amount);
1767    }
1768    function total(address to, uint256 amount) internal {
1769        amount += 1;
1770    }
1771}
1772";
1773
1774    fn pay_contract() -> ContractDebugInfo {
1775        let whole = PAY_SOURCE.len() as u64;
1776        let at = |needle: &str| PAY_SOURCE.find(needle).expect(needle) as u64;
1777        let span = |pc: u64, offset: u64, length: u64| {
1778            serde_json::from_value::<Instruction>(json!({
1779                "offset": pc,
1780                "operation": {"mnemonic": "JUMPDEST"},
1781                "context": {"code": {
1782                    "source": {"id": 0},
1783                    "range": {"offset": offset, "length": length}
1784                }}
1785            }))
1786            .expect("instruction")
1787        };
1788        let info = EthdebugInfo {
1789            compilation: serde_json::Value::Null,
1790            contract_name: "P".to_owned(),
1791            environment: "runtime".to_owned(),
1792            instructions: vec![
1793                span(0, 0, whole),
1794                span(10, at("function pay"), 80),
1795                span(11, at("total(to, amount);"), 18),
1796                span(20, at("function total"), 70),
1797                span(21, at("amount += 1;"), 12),
1798            ],
1799            sources: BTreeMap::from([(0, "P.sol".to_owned())]),
1800            variable_locations: BTreeMap::new(),
1801        };
1802        ContractDebugInfo::new(
1803            None,
1804            "P",
1805            info,
1806            BTreeMap::from([(0, PAY_SOURCE.to_owned())]),
1807        )
1808    }
1809
1810    fn word_of(value: &str) -> StackWord {
1811        StackWord::from(format!("0x{:0>64}", value.trim_start_matches("0x")).as_str())
1812    }
1813
1814    /// A call to `pay(to, amount)` that calls `total(to, amount)` internally, with the
1815    /// entry stacks the caller chooses, bottom-first as a backend reports them.
1816    fn pay_trace(entry_stack: &[StackWord], inner_stack: &[StackWord]) -> TransactionTrace {
1817        let selector = soldb_ethdebug::function_selector("pay(address,uint256)").expect("selector");
1818        let calldata = format!(
1819            "0x{}{}{}",
1820            selector
1821                .iter()
1822                .map(|byte| format!("{byte:02x}"))
1823                .collect::<String>(),
1824            &word_of(PAY_TO)[2..],
1825            &word_of(PAY_AMOUNT)[2..]
1826        );
1827        fn borrow(stack: &[StackWord]) -> Vec<&str> {
1828            stack.iter().map(|word| &**word).collect()
1829        }
1830        let mut trace = trace(vec![
1831            step(0, 1, "PUSH1", &[]),
1832            step(10, 1, "JUMPDEST", &borrow(entry_stack)),
1833            step(11, 1, "JUMP", &borrow(inner_stack)),
1834            step(20, 1, "JUMPDEST", &borrow(inner_stack)),
1835            step(21, 1, "ADD", &[]),
1836        ]);
1837        trace.input_data = calldata;
1838        trace
1839    }
1840
1841    const PAY_TO: &str = "00000000000000000000000000000000000000aa";
1842    const PAY_AMOUNT: &str = "7";
1843
1844    #[test]
1845    fn the_trace_proves_which_end_the_first_argument_is_at() {
1846        let to = word_of(PAY_TO);
1847        let amount = word_of(PAY_AMOUNT);
1848        let tag = word_of("2a");
1849
1850        // via-IR pushes the parameters right to left, so the first is on top.
1851        let first_on_top = [tag.clone(), amount.clone(), to.clone()];
1852        let trace = pay_trace(&first_on_top, &first_on_top);
1853        let map = StepMap::new(&trace, vec![pay_contract()]);
1854        assert_eq!(
1855            map.argument_layout(0),
1856            Some(ArgumentLayout::Ordered(ArgumentOrder::FirstOnTop))
1857        );
1858        let frames = map.frames(4);
1859        let inner = frames.first().expect("innermost frame");
1860        assert_eq!(inner.function_name.as_deref(), Some("total"));
1861        let arguments = map.frame_arguments(
1862            inner,
1863            FrameState {
1864                stack: &first_on_top,
1865                memory: None,
1866            },
1867        );
1868        assert_eq!(
1869            arguments
1870                .iter()
1871                .map(|argument| format!("{} = {}", argument.name, argument.value.display))
1872                .collect::<Vec<_>>(),
1873            [
1874                "to = 0x00000000000000000000000000000000000000aa".to_owned(),
1875                "amount = 7".to_owned()
1876            ]
1877        );
1878
1879        // The legacy pipeline pushes them left to right, so the last is on top.
1880        let last_on_top = [tag.clone(), to.clone(), amount.clone()];
1881        let trace = pay_trace(&last_on_top, &last_on_top);
1882        let map = StepMap::new(&trace, vec![pay_contract()]);
1883        assert_eq!(
1884            map.argument_layout(0),
1885            Some(ArgumentLayout::Ordered(ArgumentOrder::LastOnTop))
1886        );
1887        let frames = map.frames(4);
1888        let arguments = map.frame_arguments(
1889            frames.first().expect("frame"),
1890            FrameState {
1891                stack: &last_on_top,
1892                memory: None,
1893            },
1894        );
1895        assert_eq!(
1896            arguments[0].value.display,
1897            "0x00000000000000000000000000000000000000aa"
1898        );
1899        assert_eq!(arguments[1].value.display, "7");
1900
1901        // A frame entered through a dispatcher wrapper does not hold the arguments yet.
1902        // That contradicts the whole contract: nothing is reported for any frame of it.
1903        let wrapper = [tag.clone(), word_of("dead"), word_of("beef")];
1904        let trace = pay_trace(&wrapper, &first_on_top);
1905        let map = StepMap::new(&trace, vec![pay_contract()]);
1906        assert_eq!(map.argument_layout(0), None);
1907        let frames = map.frames(4);
1908        assert!(map
1909            .frame_arguments(
1910                frames.first().expect("frame"),
1911                FrameState {
1912                    stack: &first_on_top,
1913                    memory: None
1914                }
1915            )
1916            .is_empty());
1917
1918        // Without the calldata to compare against, nothing is proven and nothing shown.
1919        let mut unknown = pay_trace(&first_on_top, &first_on_top);
1920        unknown.input_data = "0x".to_owned();
1921        let map = StepMap::new(&unknown, vec![pay_contract()]);
1922        assert_eq!(map.argument_layout(0), None);
1923        let frames = map.frames(4);
1924        assert!(map
1925            .frame_arguments(
1926                frames.first().expect("frame"),
1927                FrameState {
1928                    stack: &first_on_top,
1929                    memory: None
1930                }
1931            )
1932            .is_empty());
1933    }
1934
1935    #[test]
1936    fn one_parameter_is_on_top_under_either_order() {
1937        // `outer(uint256 a)` takes one parameter: matching it against the calldata shows
1938        // the arguments are on the entry stack, which is all a one-parameter frame needs.
1939        let mut trace = outer_calls_inner();
1940        let selector = soldb_ethdebug::function_selector("outer(uint256)").expect("selector");
1941        let amount = word_of("5");
1942        trace.input_data = format!(
1943            "0x{}{}",
1944            selector
1945                .iter()
1946                .map(|byte| format!("{byte:02x}"))
1947                .collect::<String>(),
1948            &amount[2..]
1949        );
1950        let stack = [word_of("2a"), amount.clone()];
1951        trace.steps[2].snapshot =
1952            StepSnapshot::new(stack.to_vec(), None, BTreeMap::new(), BTreeMap::new());
1953        let map = StepMap::new(&trace, vec![contract(None)]);
1954        assert_eq!(map.argument_layout(0), Some(ArgumentLayout::TopWords));
1955        let frames = map.frames(3);
1956        let outer = frames
1957            .iter()
1958            .find(|frame| frame.function_name.as_deref() == Some("outer"))
1959            .expect("outer frame");
1960        let arguments = map.frame_arguments(
1961            outer,
1962            FrameState {
1963                stack: &stack,
1964                memory: None,
1965            },
1966        );
1967        assert_eq!(arguments.len(), 1);
1968        assert_eq!(arguments[0].name, "a");
1969        assert_eq!(arguments[0].value.display, "5");
1970
1971        // Two parameters need the order, which one parameter cannot show.
1972        let map = StepMap::new(&trace, vec![pay_contract()]);
1973        assert_eq!(map.argument_layout(0), None);
1974    }
1975
1976    fn key(line: u64) -> Option<LineKey> {
1977        Some(LineKey {
1978            contract: 0,
1979            source_id: 0,
1980            line,
1981        })
1982    }
1983
1984    #[test]
1985    fn maps_steps_to_lines_and_infers_internal_frames() {
1986        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
1987        assert!(map.has_source());
1988        // The dispatcher shows its span but counts as no line.
1989        assert_eq!(map.line_key(0), None);
1990        assert_eq!(map.location(0).expect("location").line, 1);
1991        assert!(!map.location(0).expect("location").generated);
1992        assert_eq!(map.line_key(2), key(2));
1993        assert_eq!(map.line_key(3), key(3));
1994        assert_eq!(map.line_key(4), key(4));
1995        assert_eq!(map.line_key(5), key(7));
1996        assert_eq!(map.line_key(6), key(8));
1997        assert_eq!(map.line_key(8), key(4));
1998        assert_eq!(map.line_key(10), None);
1999
2000        // Dispatcher at depth 0, outer at 1, inner at 2, then back down.
2001        let depths = (0..11)
2002            .map(|step| map.frame_depth(step).expect("depth"))
2003            .collect::<Vec<_>>();
2004        assert_eq!(depths, vec![0, 0, 1, 1, 1, 2, 2, 2, 1, 1, 0]);
2005        assert!(map.is_frame_entry(2));
2006        assert!(map.is_frame_entry(5));
2007        assert!(!map.is_frame_entry(8));
2008        assert_eq!(
2009            map.location(6).expect("location").function_name.as_deref(),
2010            Some("inner")
2011        );
2012        assert_eq!(map.location(6).expect("location").path, "C.sol");
2013        assert_eq!(map.location(6).expect("location").column, 9);
2014    }
2015
2016    #[test]
2017    fn next_steps_over_calls_and_step_enters_them() {
2018        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
2019        // From the dispatcher, the next line is outer's declaration.
2020        assert_eq!(map.next_source(0), Some(2));
2021        assert_eq!(map.next_source(2), Some(3));
2022        // Line 4 calls inner; `next` lands on line 5, `step` lands in inner.
2023        assert_eq!(map.next_source(4), Some(9));
2024        assert_eq!(map.step_into(4), Some(5));
2025        assert_eq!(map.step_into(5), Some(6));
2026        // Inside inner, `next` at its last line returns to the caller mid-line 4.
2027        assert_eq!(map.next_source(6), Some(8));
2028        assert_eq!(map.finish(6), Some(8));
2029        // Leaving outer lands in the dispatcher; nothing follows the last step.
2030        assert_eq!(map.next_source(9), Some(10));
2031        assert_eq!(map.finish(9), Some(10));
2032        assert_eq!(map.next_source(10), None);
2033        assert_eq!(map.finish(10), None);
2034    }
2035
2036    #[test]
2037    fn reverse_stepping_lands_on_line_starts() {
2038        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
2039        // From line 5, reverse-next goes to the start of line 4, over the call.
2040        assert_eq!(map.previous_source(9), Some(4));
2041        // From the middle of a line, first to its start.
2042        assert_eq!(map.previous_source(7), Some(6));
2043        assert_eq!(map.previous_source(6), Some(5));
2044        // Reverse-step from line 5 enters inner's last line instead of skipping it.
2045        assert_eq!(map.reverse_step_into(9), Some(8));
2046        assert_eq!(map.reverse_step_into(8), Some(6));
2047        // Reverse-finish from inside inner returns to the call site.
2048        assert_eq!(map.reverse_finish(6), Some(4));
2049        assert_eq!(map.reverse_finish(0), None);
2050        assert_eq!(map.previous_source(0), None);
2051        // From the dispatcher after outer returned, reverse-next enters outer's last line
2052        // rather than skipping the whole function as one call.
2053        assert_eq!(map.previous_source(10), Some(9));
2054    }
2055
2056    #[test]
2057    fn line_starts_skip_returns_into_the_middle_of_a_line() {
2058        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
2059        assert!(map.is_line_start(3));
2060        assert!(map.is_line_start(4));
2061        assert!(map.is_line_start(6));
2062        assert!(!map.is_line_start(7));
2063        // Step 8 resumes line 4 after inner returned: not a fresh entry to the line.
2064        assert!(!map.is_line_start(8));
2065        assert!(map.is_line_start(9));
2066    }
2067
2068    #[test]
2069    fn resolves_line_and_function_breakpoints() {
2070        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
2071        let resolved = map.resolve_line(Some("C.sol"), 4).expect("line 4");
2072        assert_eq!(resolved.len(), 1);
2073        assert_eq!(resolved[0].key, key(4).unwrap());
2074        assert_eq!(resolved[0].path, "C.sol");
2075        // Without a file the single source is unambiguous.
2076        assert_eq!(
2077            map.resolve_line(None, 8).expect("line 8")[0].key,
2078            key(8).unwrap()
2079        );
2080        // A line with no code of its own resolves to the statement containing it: the
2081        // whole-contract span is the only one covering line 10, so it wins with line 1.
2082        assert_eq!(
2083            map.resolve_line(None, 10).expect("line 10")[0].key,
2084            key(1).unwrap()
2085        );
2086        assert_eq!(
2087            map.resolve_line(None, 99).expect_err("past the end"),
2088            "no instruction maps to line 99"
2089        );
2090        assert_eq!(
2091            map.resolve_line(Some("D.sol"), 1)
2092                .expect_err("unknown file"),
2093            "source file not found: D.sol"
2094        );
2095
2096        let inner = map.resolve_function("inner").expect("inner");
2097        assert_eq!(inner.len(), 1);
2098        assert_eq!(inner[0].line, 7);
2099        assert_eq!(inner[0].contract_name, "C");
2100        assert_eq!(
2101            map.resolve_function("C.outer").expect("qualified")[0].line,
2102            2
2103        );
2104        assert!(map.resolve_function("D.outer").is_err());
2105        assert!(map.resolve_function("missing").is_err());
2106    }
2107
2108    #[test]
2109    fn frames_list_the_call_structure_innermost_first() {
2110        let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
2111        let frames = map.frames(6);
2112        assert_eq!(frames.len(), 3);
2113        assert_eq!(frames[0].function_name.as_deref(), Some("inner"));
2114        assert_eq!(frames[0].depth, 2);
2115        assert!(!frames[0].external);
2116        assert_eq!(frames[0].step, 6);
2117        assert_eq!(frames[1].function_name.as_deref(), Some("outer"));
2118        assert_eq!(
2119            frames[1].location.as_ref().map(|location| location.line),
2120            Some(4)
2121        );
2122        assert_eq!(frames[1].step, 4);
2123        assert!(frames[2].external);
2124        assert_eq!(frames[2].depth, 0);
2125        assert_eq!(
2126            frames[2].address.as_deref(),
2127            Some("0xaaaa000000000000000000000000000000000001")
2128        );
2129        assert!(map.frames(99).is_empty());
2130
2131        let listing = map.source_listing(6, 1).expect("listing");
2132        assert_eq!(listing.current_line, 8);
2133        assert_eq!(listing.lines.len(), 3);
2134        assert_eq!(listing.lines[1].0, 8);
2135        assert_eq!(listing.lines[1].1.trim(), "x += 1;");
2136    }
2137
2138    #[test]
2139    fn external_calls_map_through_the_callee_contract() {
2140        // The root contract calls a second one; the callee's steps map through its info.
2141        let callee = "0xbbbb000000000000000000000000000000000002";
2142        let word = format!("0x{:0>64}", callee.trim_start_matches("0x"));
2143        let steps = vec![
2144            step(0, 1, "PUSH1", &[]),
2145            step(
2146                12,
2147                1,
2148                "CALL",
2149                &["0x0", "0x0", "0x0", "0x0", "0x0", &word, "0x0"],
2150            ),
2151            step(21, 2, "JUMPDEST", &[]),
2152            step(21, 2, "ADD", &[]),
2153            step(13, 1, "JUMPDEST", &[]),
2154            step(30, 1, "STOP", &[]),
2155        ];
2156        let root = contract(Some("0xAAAA000000000000000000000000000000000001"));
2157        let map = StepMap::new(&trace(steps), vec![root, contract(Some(callee))]);
2158        assert_eq!(
2159            map.executing_address(1),
2160            Some("0xaaaa000000000000000000000000000000000001")
2161        );
2162        assert_eq!(map.executing_address(2), Some(callee));
2163        assert_eq!(
2164            map.line_key(2),
2165            Some(LineKey {
2166                contract: 1,
2167                source_id: 0,
2168                line: 8
2169            })
2170        );
2171        assert_eq!(map.frame_depth(1), Some(1));
2172        // The callee has no dispatcher step here, so inner is entered straight away.
2173        assert_eq!(map.frame_depth(2), Some(3));
2174        assert!(map.is_frame_entry(2));
2175        assert_eq!(map.frame_depth(4), Some(1));
2176        // `next` over the external call skips the callee entirely.
2177        assert_eq!(map.next_source(1), Some(5));
2178        assert_eq!(map.step_into(1), Some(2));
2179        let frames = map.frames(2);
2180        assert_eq!(frames.len(), 4);
2181        assert!(frames[1].external);
2182        assert_eq!(frames[1].address.as_deref(), Some(callee));
2183        assert_eq!(frames[0].function_name.as_deref(), Some("inner"));
2184    }
2185
2186    #[test]
2187    fn steps_without_debug_info_have_no_location() {
2188        let map = StepMap::new(&outer_calls_inner(), Vec::new());
2189        assert!(!map.has_source());
2190        assert_eq!(map.line_key(3), None);
2191        assert_eq!(map.frame_depth(3), Some(0));
2192        // Without lines there is nothing to stop at in either direction.
2193        assert_eq!(map.next_source(3), None);
2194        assert_eq!(map.previous_source(3), None);
2195        assert!(map.resolve_line(None, 3).is_err());
2196        assert_eq!(map.frames(3).len(), 1);
2197    }
2198
2199    #[test]
2200    fn steps_without_source_are_transparent_to_line_stepping() {
2201        // Line 3 runs, then a generated helper with no span, then line 3 again.
2202        let map = StepMap::new(
2203            &trace(vec![
2204                step(0, 1, "PUSH1", &[]),
2205                step(10, 1, "JUMPDEST", &[]),
2206                step(11, 1, "ADD", &[]),
2207                step(99, 1, "MUL", &[]),
2208                step(11, 1, "SWAP1", &[]),
2209                step(14, 1, "POP", &[]),
2210                step(30, 1, "STOP", &[]),
2211            ]),
2212            vec![contract(None)],
2213        );
2214        assert_eq!(map.line_key(3), None);
2215        assert_eq!(map.frame_depth(3), Some(1));
2216        // The helper neither stops `next` nor starts line 3 over again.
2217        assert_eq!(map.next_source(2), Some(5));
2218        assert_eq!(map.step_into(2), Some(5));
2219        assert!(!map.is_line_start(3));
2220        assert!(!map.is_line_start(4));
2221        // Reverse stepping treats the interrupted run as one line.
2222        assert_eq!(map.previous_source(5), Some(2));
2223        assert_eq!(map.previous_source(4), Some(2));
2224        assert_eq!(map.previous_source(3), Some(2));
2225        assert_eq!(map.previous_source(2), Some(1));
2226    }
2227
2228    #[test]
2229    fn generated_code_belongs_to_the_statement_and_the_epilogue_pops_the_function() {
2230        // Line 3 runs, then a helper carrying the whole-contract span (pc 40), then line 3
2231        // again, then line 5; the trailing dispatcher step is the epilogue.
2232        let map = StepMap::new(
2233            &trace(vec![
2234                step(0, 1, "PUSH1", &[]),
2235                step(10, 1, "JUMPDEST", &[]),
2236                step(11, 1, "ADD", &[]),
2237                step(40, 1, "JUMPDEST", &[]),
2238                step(40, 1, "SSTORE", &[]),
2239                step(11, 1, "SWAP1", &[]),
2240                step(14, 1, "POP", &[]),
2241                step(30, 1, "STOP", &[]),
2242            ]),
2243            vec![contract(None)],
2244        );
2245        let helper = map.location(4).expect("location");
2246        assert!(helper.generated);
2247        assert_eq!(helper.line, 3);
2248        assert_eq!(helper.function_name.as_deref(), Some("outer"));
2249        assert_eq!(map.line_key(4), key(3));
2250        assert_eq!(map.function_id(4).map(|id| id.function), Some(0));
2251        assert!(!map.is_line_start(4));
2252        assert!(!map.is_line_start(5));
2253        // The helper does not pop outer; the epilogue does.
2254        assert_eq!(map.frame_depth(4), Some(1));
2255        assert_eq!(map.frame_depth(7), Some(0));
2256        assert_eq!(map.line_key(7), None);
2257        assert!(!map.location(7).expect("location").generated);
2258        assert_eq!(map.next_source(2), Some(6));
2259        assert_eq!(map.finish(2), Some(7));
2260        assert_eq!(map.reverse_finish(4), Some(0));
2261        assert_eq!(map.previous_source(6), Some(2));
2262    }
2263
2264    #[test]
2265    fn a_lone_instruction_on_another_line_belongs_to_the_line_around_it() {
2266        // outer's entry, one instruction attributed to line 4, outer's declaration again,
2267        // then line 3: `next` from the entry reaches line 3 without stopping on the blip.
2268        let map = StepMap::new(
2269            &trace(vec![
2270                step(0, 1, "PUSH1", &[]),
2271                step(10, 1, "JUMPDEST", &[]),
2272                step(12, 1, "PUSH2", &[]),
2273                step(10, 1, "SWAP1", &[]),
2274                step(11, 1, "ADD", &[]),
2275                step(14, 1, "POP", &[]),
2276                step(30, 1, "STOP", &[]),
2277            ]),
2278            vec![contract(None)],
2279        );
2280        assert_eq!(map.line_key(2), key(2));
2281        assert_eq!(map.location(2).expect("location").line, 4);
2282        assert!(!map.is_line_start(2));
2283        assert!(!map.is_line_start(3));
2284        assert_eq!(map.next_source(1), Some(4));
2285        assert_eq!(map.previous_source(4), Some(1));
2286        // A real line of one instruction between two different lines is kept.
2287        assert_eq!(map.line_key(5), key(5));
2288        assert!(map.is_line_start(5));
2289    }
2290
2291    #[test]
2292    fn a_jump_onto_a_function_entry_is_a_call_even_when_it_is_active() {
2293        // outer calls inner, inner calls outer again, and both return: mutual recursion.
2294        // The entry points (pc 10 for outer, pc 20 for inner) make every jump onto them
2295        // a call, where landing in an active function used to read as a return.
2296        let map = StepMap::new(
2297            &trace(vec![
2298                step(0, 1, "PUSH1", &[]),
2299                step(1, 1, "JUMP", &[]),
2300                step(10, 1, "JUMPDEST", &[]),
2301                step(12, 1, "JUMP", &[]),
2302                step(20, 1, "JUMPDEST", &[]),
2303                step(21, 1, "JUMP", &[]),
2304                step(10, 1, "JUMPDEST", &[]),
2305                step(14, 1, "JUMP", &[]),
2306                step(21, 1, "SWAP1", &[]),
2307                step(21, 1, "JUMP", &[]),
2308                step(13, 1, "JUMPDEST", &[]),
2309                step(14, 1, "POP", &[]),
2310                step(30, 1, "STOP", &[]),
2311            ]),
2312            vec![contract(None)],
2313        );
2314        let contract = &map.contracts()[0];
2315        assert_eq!(contract.function_entry_at_pc(10), Some(0));
2316        assert_eq!(contract.function_entry_at_pc(20), Some(1));
2317        assert_eq!(contract.function_entry_at_pc(11), None);
2318        let depths = (0..13)
2319            .map(|step| map.frame_depth(step).expect("depth"))
2320            .collect::<Vec<_>>();
2321        assert_eq!(depths, vec![0, 0, 1, 1, 2, 2, 3, 3, 2, 2, 1, 1, 0]);
2322        assert!(map.is_frame_entry(6));
2323        let frames = map.frames(6);
2324        assert_eq!(
2325            frames
2326                .iter()
2327                .map(|frame| frame.function_name.as_deref().unwrap_or("-"))
2328                .collect::<Vec<_>>(),
2329            vec!["outer", "inner", "outer", "-"]
2330        );
2331        assert_eq!(map.finish(6), Some(8));
2332        assert_eq!(map.reverse_finish(6), Some(5));
2333        assert_eq!(map.finish(4), Some(10));
2334    }
2335
2336    #[test]
2337    fn a_recursive_call_returns_at_the_address_read_off_the_stack() {
2338        // outer calls outer: the jump at pc 12 carries [return tag 13, argument, entry 10]
2339        // on its stack. Landing on the entry point pushes a second outer frame; the jump
2340        // that lands on pc 13 is its return, which no span could tell apart from staying
2341        // in outer.
2342        let map = StepMap::new(
2343            &trace(vec![
2344                step(0, 1, "PUSH1", &[]),
2345                step(1, 1, "JUMP", &[]),
2346                step(10, 1, "JUMPDEST", &[]),
2347                step(12, 1, "JUMP", &["0xd", "0x1", "0xa"]),
2348                step(10, 1, "JUMPDEST", &["0x1"]),
2349                step(11, 1, "ADD", &["0x2"]),
2350                step(14, 1, "JUMP", &["0xd"]),
2351                step(13, 1, "JUMPDEST", &[]),
2352                step(14, 1, "POP", &[]),
2353                step(30, 1, "STOP", &[]),
2354            ]),
2355            vec![contract(None)],
2356        );
2357        let depths = (0..10)
2358            .map(|step| map.frame_depth(step).expect("depth"))
2359            .collect::<Vec<_>>();
2360        assert_eq!(depths, vec![0, 0, 1, 1, 2, 2, 2, 1, 1, 0]);
2361        assert!(map.is_frame_entry(4));
2362        assert_eq!(map.finish(4), Some(7));
2363        assert_eq!(map.frames(5).len(), 3);
2364        assert_eq!(map.frames(7).len(), 2);
2365        assert!(map.contracts()[0].is_jumpdest(13));
2366        assert!(!map.contracts()[0].is_jumpdest(99));
2367    }
2368
2369    #[test]
2370    fn jump_markers_decide_calls_and_returns_without_entry_points() {
2371        // The jump at pc 12 is marked as a call and lands mid-outer (pc 11): a helper
2372        // whose span is the calling line, since recursion would enter at pc 10. It gets
2373        // a placeholder frame the marked return at pc 14 pops, so outer stays on the
2374        // stack throughout and its depth never moves.
2375        let whole = SOURCE.len() as u64;
2376        let info = EthdebugInfo {
2377            compilation: serde_json::Value::Null,
2378            contract_name: "C".to_owned(),
2379            environment: "runtime".to_owned(),
2380            instructions: vec![
2381                instruction(0, 0, whole),
2382                instruction(1, 0, whole),
2383                instruction(10, offset_of("function outer"), 90),
2384                instruction(11, offset_of("uint256 b = a + 1;"), 18),
2385                instruction_with(
2386                    12,
2387                    offset_of("inner(b);"),
2388                    9,
2389                    json!({"invoke": {"identifier": "outer"}}),
2390                ),
2391                instruction(13, offset_of("inner(b);"), 9),
2392                instruction_with(14, offset_of("b = 0;"), 6, json!({"return": {}})),
2393                instruction(30, 0, whole),
2394            ],
2395            sources: BTreeMap::from([(0, "C.sol".to_owned())]),
2396            variable_locations: BTreeMap::new(),
2397        };
2398        let marked =
2399            ContractDebugInfo::new(None, "C", info, BTreeMap::from([(0, SOURCE.to_owned())]));
2400        assert_eq!(marked.jump_marker_at_pc(12), JumpMarker::Call);
2401        assert_eq!(marked.jump_marker_at_pc(14), JumpMarker::Return);
2402        assert_eq!(marked.jump_marker_at_pc(13), JumpMarker::None);
2403        let map = StepMap::new(
2404            &trace(vec![
2405                step(0, 1, "PUSH1", &[]),
2406                step(1, 1, "JUMP", &[]),
2407                step(10, 1, "JUMPDEST", &[]),
2408                step(12, 1, "JUMP", &[]),
2409                step(11, 1, "ADD", &[]),
2410                step(14, 1, "JUMP", &[]),
2411                step(13, 1, "JUMPDEST", &[]),
2412                step(14, 1, "POP", &[]),
2413                step(30, 1, "STOP", &[]),
2414            ]),
2415            vec![marked],
2416        );
2417        let depths = (0..9)
2418            .map(|step| map.frame_depth(step).expect("depth"))
2419            .collect::<Vec<_>>();
2420        assert_eq!(depths, vec![0, 0, 1, 1, 1, 1, 1, 1, 0]);
2421        assert!(!map.is_frame_entry(4));
2422        assert_eq!(map.frames(4).len(), 2);
2423        assert_eq!(map.finish(4), Some(8));
2424        // A marked call from outer that lands on inner's entry point is a real call.
2425        let map = StepMap::new(
2426            &trace(vec![
2427                step(0, 1, "PUSH1", &[]),
2428                step(1, 1, "JUMP", &[]),
2429                step(10, 1, "JUMPDEST", &[]),
2430                step(12, 1, "JUMP", &[]),
2431                step(20, 1, "JUMPDEST", &[]),
2432                step(21, 1, "JUMP", &[]),
2433                step(13, 1, "JUMPDEST", &[]),
2434                step(30, 1, "STOP", &[]),
2435            ]),
2436            vec![contract(None)],
2437        );
2438        assert_eq!(map.frame_depth(4), Some(2));
2439        assert_eq!(map.frame_depth(6), Some(1));
2440    }
2441
2442    #[test]
2443    fn marked_jumps_into_generated_helpers_do_not_pop_the_function() {
2444        // Inside outer, a marked call jumps to helper code with the whole-contract span
2445        // (pc 40) and a marked return comes back: outer stays on the stack throughout,
2446        // and the helper counts as no frame the user steps by.
2447        let whole = SOURCE.len() as u64;
2448        let info = EthdebugInfo {
2449            compilation: serde_json::Value::Null,
2450            contract_name: "C".to_owned(),
2451            environment: "runtime".to_owned(),
2452            instructions: vec![
2453                instruction(0, 0, whole),
2454                instruction(1, 0, whole),
2455                instruction(10, offset_of("function outer"), 90),
2456                instruction_with(
2457                    11,
2458                    offset_of("uint256 b = a + 1;"),
2459                    18,
2460                    json!({"invoke": {}}),
2461                ),
2462                instruction_with(40, 0, whole, json!({"return": {}})),
2463                instruction(14, offset_of("b = 0;"), 6),
2464                instruction(30, 0, whole),
2465            ],
2466            sources: BTreeMap::from([(0, "C.sol".to_owned())]),
2467            variable_locations: BTreeMap::new(),
2468        };
2469        let contract =
2470            ContractDebugInfo::new(None, "C", info, BTreeMap::from([(0, SOURCE.to_owned())]));
2471        let map = StepMap::new(
2472            &trace(vec![
2473                step(0, 1, "PUSH1", &[]),
2474                step(1, 1, "JUMP", &[]),
2475                step(10, 1, "JUMPDEST", &[]),
2476                step(11, 1, "JUMP", &[]),
2477                step(40, 1, "JUMPDEST", &[]),
2478                step(40, 1, "JUMP", &[]),
2479                step(14, 1, "JUMPDEST", &[]),
2480                step(30, 1, "STOP", &[]),
2481            ]),
2482            vec![contract],
2483        );
2484        let depths = (0..8)
2485            .map(|step| map.frame_depth(step).expect("depth"))
2486            .collect::<Vec<_>>();
2487        assert_eq!(depths, vec![0, 0, 1, 1, 1, 1, 1, 0]);
2488        assert!(map.location(4).expect("location").generated);
2489        assert_eq!(
2490            map.location(6).expect("location").function_name.as_deref(),
2491            Some("outer")
2492        );
2493        assert_eq!(map.next_source(3), Some(6));
2494        assert_eq!(map.finish(3), Some(7));
2495    }
2496
2497    #[test]
2498    fn contract_info_indexes_lines_and_functions() {
2499        let contract = contract(Some("0xABCD"));
2500        assert_eq!(contract.address.as_deref(), Some("0xabcd"));
2501        assert_eq!(contract.line_of(0, 0), Some(1));
2502        assert_eq!(
2503            contract.line_of(0, SOURCE.find("inner(b)").unwrap() as u64),
2504            Some(4)
2505        );
2506        assert_eq!(contract.line_count(0), Some(11));
2507        assert_eq!(contract.line_text(0, 8).map(str::trim), Some("x += 1;"));
2508        assert_eq!(contract.line_text(0, 99), None);
2509        assert_eq!(
2510            contract.function_at_pc(21).map(|f| f.name.as_str()),
2511            Some("inner")
2512        );
2513        assert_eq!(contract.function_at_pc(0), None);
2514        assert_eq!(contract.function_at_pc(999), None);
2515        assert_eq!(contract.functions[0].declaration_line, 2);
2516        assert_eq!(contract.effective_line(0, 3), Some(3));
2517        assert_eq!(contract.effective_line(0, 6), Some(1));
2518        assert_eq!(contract.effective_line(0, 0), None);
2519        assert_eq!(contract.effective_line(0, 12), None);
2520        assert_eq!(contract.effective_line(7, 1), None);
2521
2522        assert_eq!(normalize_address("0XAbC"), "0xabc");
2523        assert_eq!(
2524            address_from_word("0x000000000000000000000000AAAA000000000000000000000000000000000001"),
2525            Some("0xaaaa000000000000000000000000000000000001".to_owned())
2526        );
2527        assert_eq!(address_from_word("0x0"), None);
2528        assert_eq!(address_from_word("zz"), None);
2529    }
2530}