Skip to main content

soldb_debugger/
stepping.rs

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