1use std::collections::{BTreeMap, HashMap};
33
34use soldb_core::{TransactionTrace, Word as StackWord};
35use soldb_ethdebug::{
36 function_selector, EthdebugInfo, FunctionExit, Instruction, SourceLocation, StorageLayout,
37};
38
39use crate::{
40 decode_arguments, is_value_type, parse_source_functions, readable_parameter, ArgumentLayout,
41 ArgumentOrder, FrameArgument, FrameState, SourceFunction,
42};
43
44const CALL_OPCODES: [&str; 4] = ["CALL", "CALLCODE", "DELEGATECALL", "STATICCALL"];
45
46#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct ContractDebugInfo {
51 pub address: Option<String>,
53 pub name: String,
54 pub info: EthdebugInfo,
55 pub source_contents: BTreeMap<u64, String>,
56 pub functions: Vec<SourceFunction>,
57 pub storage_layout: Option<StorageLayout>,
60 line_starts: BTreeMap<u64, Vec<usize>>,
62 pc_index: HashMap<u64, usize>,
64 function_entries: HashMap<u64, usize>,
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum JumpMarker {
72 None,
73 Call,
75 Return,
77}
78
79impl ContractDebugInfo {
80 #[must_use]
84 pub fn new(
85 address: Option<&str>,
86 name: &str,
87 info: EthdebugInfo,
88 source_contents: BTreeMap<u64, String>,
89 ) -> Self {
90 let functions = source_contents
91 .iter()
92 .flat_map(|(source_id, source)| parse_source_functions(*source_id, source))
93 .collect::<Vec<SourceFunction>>();
94 let line_starts = source_contents
95 .iter()
96 .map(|(source_id, source)| (*source_id, line_starts(source)))
97 .collect();
98 let pc_index = info
99 .instructions
100 .iter()
101 .enumerate()
102 .map(|(index, instruction)| (instruction.offset, index))
103 .collect();
104 let function_entries = functions
105 .iter()
106 .enumerate()
107 .filter_map(|(function_index, function)| {
108 info.instructions
109 .iter()
110 .filter(|instruction| instruction.mnemonic() == Some("JUMPDEST"))
111 .filter(|instruction| {
112 instruction.source_location().is_some_and(|location| {
113 location.source_id == function.source_id
114 && location.offset == function.declaration_start
115 })
116 })
117 .map(|instruction| instruction.offset)
118 .min()
119 .map(|pc| (pc, function_index))
120 })
121 .collect();
122 Self {
123 address: address.map(normalize_address),
124 name: name.to_owned(),
125 info,
126 source_contents,
127 functions,
128 storage_layout: None,
129 line_starts,
130 pc_index,
131 function_entries,
132 }
133 }
134
135 #[must_use]
137 pub fn with_storage_layout(mut self, storage_layout: Option<StorageLayout>) -> Self {
138 self.storage_layout = storage_layout;
139 self
140 }
141
142 #[must_use]
144 pub fn function_entry_at_pc(&self, pc: u64) -> Option<usize> {
145 self.function_entries.get(&pc).copied()
146 }
147
148 #[must_use]
150 pub fn is_jumpdest(&self, pc: u64) -> bool {
151 self.pc_index
152 .get(&pc)
153 .and_then(|index| self.info.instructions.get(*index))
154 .is_some_and(|instruction| instruction.mnemonic() == Some("JUMPDEST"))
155 }
156
157 #[must_use]
159 pub fn jump_marker_at_pc(&self, pc: u64) -> JumpMarker {
160 let Some(instruction) = self
161 .pc_index
162 .get(&pc)
163 .and_then(|index| self.info.instructions.get(*index))
164 else {
165 return JumpMarker::None;
166 };
167 if !instruction.function_invocations().is_empty() {
168 JumpMarker::Call
169 } else if instruction.function_exit() == Some(FunctionExit::Return) {
170 JumpMarker::Return
171 } else {
172 JumpMarker::None
173 }
174 }
175
176 #[must_use]
177 pub fn source_path(&self, source_id: u64) -> Option<&str> {
178 self.info.sources.get(&source_id).map(String::as_str)
179 }
180
181 #[must_use]
183 pub fn line_of(&self, source_id: u64, offset: u64) -> Option<u64> {
184 let starts = self.line_starts.get(&source_id)?;
185 let offset = usize::try_from(offset).ok()?;
186 Some(starts.partition_point(|start| *start <= offset) as u64)
187 }
188
189 #[must_use]
190 pub fn column_of(&self, source_id: u64, offset: u64) -> Option<u64> {
191 let starts = self.line_starts.get(&source_id)?;
192 let line = self.line_of(source_id, offset)?;
193 let line_start = *starts.get(usize::try_from(line).ok()?.checked_sub(1)?)?;
194 Some(offset.saturating_sub(line_start as u64) + 1)
195 }
196
197 #[must_use]
198 pub fn line_count(&self, source_id: u64) -> Option<u64> {
199 self.line_starts
200 .get(&source_id)
201 .map(|starts| starts.len() as u64)
202 }
203
204 #[must_use]
206 pub fn line_text(&self, source_id: u64, line: u64) -> Option<&str> {
207 let source = self.source_contents.get(&source_id)?;
208 let index = usize::try_from(line).ok()?.checked_sub(1)?;
209 source.lines().nth(index)
210 }
211
212 #[must_use]
214 pub fn location_at_pc(&self, pc: u64) -> Option<SourceLocation> {
215 let index = *self.pc_index.get(&pc)?;
216 self.info.instructions.get(index)?.source_location()
217 }
218
219 #[must_use]
221 pub fn instruction_at_pc(&self, pc: u64) -> Option<&Instruction> {
222 self.info.instructions.get(*self.pc_index.get(&pc)?)
223 }
224
225 #[must_use]
227 pub fn modifier_depth_at_pc(&self, pc: u64) -> Option<i64> {
228 let index = *self.pc_index.get(&pc)?;
229 self.info.instructions.get(index)?.modifier_depth()
230 }
231
232 #[must_use]
234 pub fn function_for_location(&self, location: &SourceLocation) -> Option<usize> {
235 self.functions
236 .iter()
237 .enumerate()
238 .filter(|(_, function)| {
239 function.source_id == location.source_id
240 && function.declaration_start <= location.offset
241 && location.offset <= function.body_end
242 })
243 .min_by_key(|(_, function)| {
244 function.body_end.saturating_sub(function.declaration_start)
245 })
246 .map(|(index, _)| index)
247 }
248
249 #[must_use]
250 pub fn function_at_pc(&self, pc: u64) -> Option<&SourceFunction> {
251 let location = self.location_at_pc(pc)?;
252 self.function_for_location(&location)
253 .and_then(|index| self.functions.get(index))
254 }
255
256 #[must_use]
263 pub fn effective_line(&self, source_id: u64, line: u64) -> Option<u64> {
264 if line == 0 || line > self.line_count(source_id)? {
265 return None;
266 }
267 let mut narrowest = None::<(u64, u64)>;
268 for instruction in &self.info.instructions {
269 let Some(location) = instruction.source_location() else {
270 continue;
271 };
272 if location.source_id != source_id {
273 continue;
274 }
275 let start = self.line_of(source_id, location.offset)?;
276 let end = self.line_of(
277 source_id,
278 location
279 .offset
280 .saturating_add(location.length.saturating_sub(1)),
281 )?;
282 if start > line || line > end {
283 continue;
284 }
285 if start == line {
286 return Some(line);
287 }
288 if narrowest.is_none_or(|(length, _)| location.length < length) {
289 narrowest = Some((location.length, start));
290 }
291 }
292 narrowest.map(|(_, start)| start)
293 }
294}
295
296#[must_use]
299pub fn normalize_address(value: &str) -> String {
300 let hex = value.trim_start_matches("0x").trim_start_matches("0X");
301 format!("0x{}", hex.to_ascii_lowercase())
302}
303
304#[must_use]
308pub fn source_path_matches(source_path: &str, requested: &str) -> bool {
309 fn ends_with_path(longer: &str, shorter: &str) -> bool {
310 longer.ends_with(shorter)
311 && longer
312 .as_bytes()
313 .get(longer.len() - shorter.len() - 1)
314 .is_none_or(|byte| *byte == b'/')
315 }
316 fn file_name(path: &str) -> &str {
317 path.rsplit('/').next().unwrap_or(path)
318 }
319 source_path == requested
320 || ends_with_path(source_path, requested)
321 || ends_with_path(requested, source_path)
322 || (!requested.is_empty() && file_name(source_path) == file_name(requested))
323}
324
325fn line_starts(source: &str) -> Vec<usize> {
326 let mut starts = vec![0];
327 starts.extend(
328 source
329 .bytes()
330 .enumerate()
331 .filter(|(_, byte)| *byte == b'\n')
332 .map(|(index, _)| index + 1),
333 );
334 starts
335}
336
337#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
339pub struct LineKey {
340 pub contract: usize,
341 pub source_id: u64,
342 pub line: u64,
343}
344
345#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
347pub struct FunctionId {
348 pub contract: usize,
349 pub function: usize,
350}
351
352#[derive(Debug, Clone, PartialEq, Eq)]
354pub struct StepLocation {
355 pub key: LineKey,
356 pub generated: bool,
359 pub contract_name: String,
360 pub path: String,
361 pub offset: u64,
362 pub length: u64,
363 pub line: u64,
364 pub column: u64,
365 pub function: Option<FunctionId>,
366 pub function_name: Option<String>,
367}
368
369#[derive(Debug, Clone, PartialEq, Eq)]
371pub struct ResolvedLine {
372 pub key: LineKey,
373 pub path: String,
374 pub requested_line: u64,
376}
377
378#[derive(Debug, Clone, PartialEq, Eq)]
380pub struct ResolvedFunction {
381 pub id: FunctionId,
382 pub name: String,
383 pub contract_name: String,
384 pub path: String,
385 pub line: u64,
386}
387
388#[derive(Debug, Clone, PartialEq, Eq)]
390pub struct Frame {
391 pub depth: u32,
393 pub external: bool,
396 pub address: Option<String>,
397 pub contract_name: Option<String>,
398 pub function_name: Option<String>,
399 pub entry_step: usize,
401 pub step: usize,
404 pub pc: u64,
405 pub location: Option<StepLocation>,
406 pub arguments: Vec<FrameArgument>,
410}
411
412#[derive(Debug, Clone, PartialEq, Eq)]
414pub struct SourceListing {
415 pub path: String,
416 pub current_line: u64,
417 pub lines: Vec<(u64, String)>,
419}
420
421#[derive(Debug, Clone, Copy, PartialEq, Eq)]
422struct LocationRef {
423 key: LineKey,
424 offset: u64,
425 length: u64,
426 function: Option<usize>,
427}
428
429#[derive(Debug, Clone, Copy, PartialEq, Eq)]
430struct StepInfo {
431 location: Option<LocationRef>,
434 generated: bool,
436 key: Option<LineKey>,
439 contract: Option<usize>,
441 address: Option<usize>,
443 storage: Option<usize>,
446 evm_depth: u32,
448 frame_depth: u32,
450 frame_entry: bool,
452 line_start: bool,
455}
456
457#[derive(Debug, Clone, PartialEq, Eq)]
459pub struct StepMap {
460 contracts: Vec<ContractDebugInfo>,
461 steps: Vec<StepInfo>,
462 addresses: Vec<String>,
463 pcs: Vec<u64>,
464 reverted: Vec<(usize, usize)>,
467 argument_layouts: Vec<Evidence>,
470}
471
472#[derive(Debug, Clone, Copy, PartialEq, Eq)]
476enum Evidence {
477 Unknown,
479 Contradicted,
481 TopWords,
483 Ordered(ArgumentOrder),
485}
486
487impl Evidence {
488 fn merge(self, other: Self) -> Self {
491 match (self, other) {
492 (Self::Contradicted, _) | (_, Self::Contradicted) => Self::Contradicted,
493 (Self::Ordered(order), _) | (_, Self::Ordered(order)) => Self::Ordered(order),
494 (Self::TopWords, _) | (_, Self::TopWords) => Self::TopWords,
495 _ => Self::Unknown,
496 }
497 }
498
499 fn layout(self) -> Option<ArgumentLayout> {
500 match self {
501 Self::Ordered(order) => Some(ArgumentLayout::Ordered(order)),
502 Self::TopWords => Some(ArgumentLayout::TopWords),
503 Self::Unknown | Self::Contradicted => None,
504 }
505 }
506}
507
508#[derive(Debug, Clone, Copy)]
510struct FramedStep {
511 evm_depth: u32,
512 frame_id: u32,
513 contract: Option<usize>,
514 address: Option<usize>,
515 storage: Option<usize>,
516 location: Option<LocationRef>,
517 entry: Option<usize>,
519 marker: JumpMarker,
521}
522
523struct EvmFrame {
524 id: u32,
525 contract: Option<usize>,
526 address: Option<usize>,
527 storage: Option<usize>,
529 entry_step: usize,
531}
532
533#[derive(Debug, Clone, Copy, PartialEq, Eq)]
537struct FrameEntry {
538 function: Option<usize>,
539 return_pc: Option<u64>,
544}
545
546#[derive(Default)]
548struct InternalFrame {
549 functions: Vec<FrameEntry>,
551 statement: Option<LocationRef>,
553}
554
555impl InternalFrame {
556 fn active(&self) -> Option<usize> {
558 self.functions.iter().rev().find_map(|entry| entry.function)
559 }
560
561 fn is_active(&self, function: usize) -> bool {
562 self.active() == Some(function)
563 }
564
565 fn push_function(&mut self, function: usize, return_pc: Option<u64>) {
566 self.functions.push(FrameEntry {
567 function: Some(function),
568 return_pc,
569 });
570 }
571
572 fn push_placeholder(&mut self) {
573 self.functions.push(FrameEntry {
574 function: None,
575 return_pc: None,
576 });
577 }
578
579 fn returns_to(&self, pc: u64) -> bool {
581 self.functions
582 .last()
583 .is_some_and(|entry| entry.return_pc == Some(pc))
584 }
585}
586
587impl StepMap {
588 #[must_use]
595 pub fn new(trace: &TransactionTrace, contracts: Vec<ContractDebugInfo>) -> Self {
596 Self::build(trace, contracts, true)
597 }
598
599 #[must_use]
604 pub fn for_debug_diff(trace: &TransactionTrace, contracts: Vec<ContractDebugInfo>) -> Self {
605 Self::build(trace, contracts, false)
606 }
607
608 fn build(trace: &TransactionTrace, contracts: Vec<ContractDebugInfo>, smooth: bool) -> Self {
609 let mut addresses = Vec::<String>::new();
610 let mut address_index = HashMap::<String, usize>::new();
611 let mut intern = |address: &str| -> usize {
612 let address = normalize_address(address);
613 *address_index.entry(address.clone()).or_insert_with(|| {
614 addresses.push(address);
615 addresses.len() - 1
616 })
617 };
618 let contract_for = |address: Option<&str>, root: bool| -> Option<usize> {
619 let address = address.map(normalize_address);
620 if let Some(address) = &address {
621 if let Some(index) = contracts
622 .iter()
623 .position(|contract| contract.address.as_deref() == Some(address))
624 {
625 return Some(index);
626 }
627 }
628 (root && contracts.len() == 1).then_some(0)
629 };
630
631 let root_address = trace
633 .to_addr
634 .as_deref()
635 .or(trace.contract_address.as_deref());
636 let root_depth = trace.steps.first().map_or(0, |step| step.depth);
637 let mut next_frame_id = 1_u32;
638 let root_storage = root_address.map(&mut intern);
639 let mut evm_frames = vec![EvmFrame {
640 id: 0,
641 contract: contract_for(root_address, true),
642 address: root_storage,
643 storage: root_storage,
644 entry_step: 0,
645 }];
646 let mut reverted = Vec::new();
647 let mut framed = Vec::with_capacity(trace.steps.len());
648 for (index, step) in trace.steps.iter().enumerate() {
649 let evm_depth = step.depth.saturating_sub(root_depth) as usize;
650 evm_frames.truncate(evm_depth + 1);
651 while evm_frames.len() <= evm_depth {
652 let call = (evm_frames.len() == evm_depth)
655 .then(|| index.checked_sub(1))
656 .flatten();
657 let target = call.and_then(|caller| call_target(&trace.steps[caller]));
658 let address = target.as_deref().map(&mut intern);
659 let delegated = call.is_some_and(|caller| {
662 matches!(&*trace.steps[caller].op, "DELEGATECALL" | "CALLCODE")
663 });
664 let storage = if delegated {
665 evm_frames.last().and_then(|frame| frame.storage)
666 } else {
667 address
668 };
669 evm_frames.push(EvmFrame {
670 id: next_frame_id,
671 contract: contract_for(target.as_deref(), false),
672 address,
673 storage,
674 entry_step: index,
675 });
676 next_frame_id += 1;
677 }
678 if &*step.op == "REVERT" || step.error.is_some() {
679 let entry_step = evm_frames
680 .last()
681 .expect("the root frame is never popped")
682 .entry_step;
683 reverted.push((entry_step, index));
684 }
685 let frame = evm_frames.last().expect("the root frame is never popped");
686 let location = frame.contract.and_then(|contract_index| {
687 let contract = &contracts[contract_index];
688 let location = contract.location_at_pc(step.pc)?;
689 let line = contract.line_of(location.source_id, location.offset)?;
690 Some(LocationRef {
691 key: LineKey {
692 contract: contract_index,
693 source_id: location.source_id,
694 line,
695 },
696 offset: location.offset,
697 length: location.length,
698 function: contract.function_for_location(&location),
699 })
700 });
701 let contract = frame.contract.map(|index| &contracts[index]);
702 framed.push(FramedStep {
703 evm_depth: evm_depth as u32,
704 frame_id: frame.id,
705 contract: frame.contract,
706 address: frame.address,
707 storage: frame.storage,
708 location,
709 entry: contract.and_then(|contract| contract.function_entry_at_pc(step.pc)),
710 marker: contract.map_or(JumpMarker::None, |contract| {
711 contract.jump_marker_at_pc(step.pc)
712 }),
713 });
714 }
715
716 let structured = |contract: Option<usize>| {
720 contract.is_some_and(|index| !contracts[index].functions.is_empty())
721 };
722 let mut function_follows = std::collections::HashSet::<u32>::new();
723 let mut epilogue = vec![false; framed.len()];
724 for (index, step) in framed.iter().enumerate().rev() {
725 match step.location.map(|location| location.function) {
726 Some(Some(_)) => {
727 function_follows.insert(step.frame_id);
728 }
729 Some(None) if structured(step.contract) => {
730 epilogue[index] = !function_follows.contains(&step.frame_id);
731 }
732 _ => {}
733 }
734 }
735
736 let mut internal = Vec::<InternalFrame>::new();
739 let mut steps = Vec::with_capacity(framed.len());
740 let mut pcs = Vec::with_capacity(framed.len());
741 for (index, step) in framed.iter().enumerate() {
742 let evm_depth = step.evm_depth as usize;
743 let mut frame_entry = false;
744 internal.truncate(evm_depth + 1);
745 while internal.len() <= evm_depth {
746 internal.push(InternalFrame::default());
747 frame_entry = true;
748 }
749 let frame = internal.last_mut().expect("the root frame is never popped");
750
751 let landed_by_jump = index > 0
754 && framed[index - 1].evm_depth == step.evm_depth
755 && &*trace.steps[index - 1].op == "JUMP";
756 let previous_marker = if landed_by_jump {
757 framed[index - 1].marker
758 } else {
759 JumpMarker::None
760 };
761 let structured = structured(step.contract);
762 let mut generated = false;
763 let mut pending_call = false;
767 if landed_by_jump {
768 let pc = trace.steps[index].pc;
769 match previous_marker {
770 JumpMarker::Call => pending_call = true,
771 JumpMarker::Return => {
772 frame.functions.pop();
773 }
774 JumpMarker::None if frame.returns_to(pc) => {
775 frame.functions.pop();
776 }
777 JumpMarker::None => {}
778 }
779 }
780 let return_address = |function: usize| -> Option<u64> {
783 if !landed_by_jump {
784 return None;
785 }
786 let contract = &contracts[step.contract?];
787 let arguments = contract.functions.get(function)?.params.len();
788 let stack = trace.steps[index - 1].snapshot_ref().stack;
789 let word = stack.get(stack.len().checked_sub(2 + arguments)?)?;
790 let pc = u64::from_str_radix(word.trim_start_matches("0x"), 16).ok()?;
791 contract.is_jumpdest(pc).then_some(pc)
792 };
793 let (location, key) = match step.location {
794 None => {
795 if pending_call {
798 frame.push_placeholder();
799 }
800 (None, None)
801 }
802 Some(own) if !structured => {
803 frame.statement = Some(own);
805 (Some(own), Some(own.key))
806 }
807 Some(own) => match own.function {
808 Some(function) => {
809 let entering = landed_by_jump && step.entry == Some(function);
810 if entering || (pending_call && !frame.is_active(function)) {
811 frame.push_function(function, return_address(function));
814 frame_entry = true;
815 } else if pending_call {
816 frame.push_placeholder();
820 } else if !frame.is_active(function) {
821 match frame
822 .functions
823 .iter()
824 .rposition(|entry| entry.function == Some(function))
825 {
826 Some(position) => frame.functions.truncate(position + 1),
827 None => {
828 frame.push_function(function, None);
829 frame_entry = true;
830 }
831 }
832 }
833 frame.statement = Some(own);
834 (Some(own), Some(own.key))
835 }
836 None if epilogue[index] => {
837 frame.functions.clear();
838 frame.statement = None;
839 (Some(own), None)
840 }
841 None => {
842 if pending_call {
843 frame.push_placeholder();
844 }
845 match frame.statement {
846 Some(statement) => {
847 generated = true;
848 (Some(statement), Some(statement.key))
849 }
850 None => (Some(own), None),
853 }
854 }
855 },
856 };
857 let internal_total = internal
858 .iter()
859 .map(|frame| {
860 frame
861 .functions
862 .iter()
863 .filter(|entry| entry.function.is_some())
864 .count()
865 })
866 .sum::<usize>();
867 steps.push(StepInfo {
868 location,
869 generated,
870 key,
871 contract: step.contract,
872 address: step.address,
873 storage: step.storage,
874 evm_depth: step.evm_depth,
875 frame_depth: (evm_depth + internal_total) as u32,
876 frame_entry,
877 line_start: false,
878 });
879 pcs.push(trace.steps[index].pc);
880 }
881
882 let argument_layouts = prove_argument_layouts(trace, &contracts, &framed);
883 let mut map = Self {
884 contracts,
885 steps,
886 addresses,
887 pcs,
888 reverted,
889 argument_layouts,
890 };
891 if smooth {
892 map.smooth_single_step_excursions();
893 }
894 map.mark_line_starts();
895 map
896 }
897
898 fn smooth_single_step_excursions(&mut self) {
902 let neighbours =
903 |steps: &[StepInfo], index: usize, forward: bool| -> Option<Option<LineKey>> {
904 let depth = steps[index].frame_depth;
905 let range: Box<dyn Iterator<Item = usize>> = if forward {
906 Box::new((index + 1)..steps.len())
907 } else {
908 Box::new((0..index).rev())
909 };
910 for candidate in range {
911 let step = &steps[candidate];
912 if step.frame_depth > depth {
913 continue;
914 }
915 if step.frame_depth < depth {
916 return None;
917 }
918 if step.key.is_some() {
919 return Some(step.key);
920 }
921 }
922 None
923 };
924 let mut smoothed = Vec::new();
925 for index in 0..self.steps.len() {
926 let Some(key) = self.steps[index].key else {
927 continue;
928 };
929 let Some(before) = neighbours(&self.steps, index, false) else {
930 continue;
931 };
932 let Some(after) = neighbours(&self.steps, index, true) else {
933 continue;
934 };
935 if before == after && before.is_some() && before != Some(key) {
936 smoothed.push((index, before));
937 }
938 }
939 for (index, key) in smoothed {
940 self.steps[index].key = key;
941 }
942 }
943
944 fn mark_line_starts(&mut self) {
947 let mut last_key_at_or_below = Vec::<Option<LineKey>>::new();
948 for step in &mut self.steps {
949 let depth = step.frame_depth as usize;
950 if last_key_at_or_below.len() <= depth {
951 last_key_at_or_below.resize(depth + 1, None);
952 }
953 let Some(key) = step.key else {
954 step.line_start = false;
955 continue;
956 };
957 step.line_start = last_key_at_or_below[depth] != Some(key);
958 for entry in last_key_at_or_below.iter_mut().skip(depth) {
959 *entry = Some(key);
960 }
961 }
962 }
963
964 #[must_use]
965 pub fn contracts(&self) -> &[ContractDebugInfo] {
966 &self.contracts
967 }
968
969 #[must_use]
971 pub fn contract_at_step(&self, step: usize) -> Option<&ContractDebugInfo> {
972 self.contracts.get(self.steps.get(step)?.contract?)
973 }
974
975 #[must_use]
976 pub fn step_count(&self) -> usize {
977 self.steps.len()
978 }
979
980 #[must_use]
982 pub fn has_source(&self) -> bool {
983 self.steps.iter().any(|step| step.location.is_some())
984 }
985
986 #[must_use]
988 pub fn line_key(&self, step: usize) -> Option<LineKey> {
989 self.steps.get(step)?.key
990 }
991
992 #[must_use]
993 pub fn frame_depth(&self, step: usize) -> Option<u32> {
994 self.steps.get(step).map(|step| step.frame_depth)
995 }
996
997 #[must_use]
998 pub fn is_line_start(&self, step: usize) -> bool {
999 self.steps.get(step).is_some_and(|step| step.line_start)
1000 }
1001
1002 #[must_use]
1003 pub fn is_frame_entry(&self, step: usize) -> bool {
1004 self.steps.get(step).is_some_and(|step| step.frame_entry)
1005 }
1006
1007 #[must_use]
1009 pub fn function_id(&self, step: usize) -> Option<FunctionId> {
1010 let location = self.steps.get(step)?.location?;
1011 location.function.map(|function| FunctionId {
1012 contract: location.key.contract,
1013 function,
1014 })
1015 }
1016
1017 #[must_use]
1019 pub fn executing_address(&self, step: usize) -> Option<&str> {
1020 let index = self.steps.get(step)?.address?;
1021 self.addresses.get(index).map(String::as_str)
1022 }
1023
1024 #[must_use]
1027 pub fn storage_address(&self, step: usize) -> Option<&str> {
1028 let index = self.storage_context_index(step)?;
1029 self.addresses.get(index).map(String::as_str)
1030 }
1031
1032 #[must_use]
1034 pub fn storage_context_index(&self, step: usize) -> Option<usize> {
1035 self.steps.get(step)?.storage
1036 }
1037
1038 #[must_use]
1041 pub fn reverted_spans(&self) -> &[(usize, usize)] {
1042 &self.reverted
1043 }
1044
1045 #[must_use]
1049 pub fn argument_layout(&self, contract: usize) -> Option<ArgumentLayout> {
1050 self.argument_layouts.get(contract).copied()?.layout()
1051 }
1052
1053 #[must_use]
1061 pub fn frame_arguments(&self, frame: &Frame, state: FrameState<'_>) -> Vec<FrameArgument> {
1062 let Some(info) = self.steps.get(frame.entry_step) else {
1063 return Vec::new();
1064 };
1065 let Some(location) = info.location else {
1066 return Vec::new();
1067 };
1068 let Some(function_index) = location.function else {
1069 return Vec::new();
1070 };
1071 let Some(contract) = self.contracts.get(location.key.contract) else {
1072 return Vec::new();
1073 };
1074 if contract.function_entry_at_pc(self.pcs[frame.entry_step]) != Some(function_index) {
1075 return Vec::new();
1076 }
1077 let Some(function) = contract.functions.get(function_index) else {
1078 return Vec::new();
1079 };
1080 let Some(layout) = self.argument_layout(location.key.contract) else {
1081 return Vec::new();
1082 };
1083 decode_arguments(&function.params, state, layout)
1084 }
1085
1086 #[must_use]
1087 pub fn location(&self, step: usize) -> Option<StepLocation> {
1088 let info = self.steps.get(step)?;
1089 self.describe(&info.location?, info.generated)
1090 }
1091
1092 fn describe(&self, location: &LocationRef, generated: bool) -> Option<StepLocation> {
1093 let contract = self.contracts.get(location.key.contract)?;
1094 let function = location
1095 .function
1096 .and_then(|index| contract.functions.get(index));
1097 Some(StepLocation {
1098 key: location.key,
1099 generated,
1100 contract_name: contract.name.clone(),
1101 path: contract
1102 .source_path(location.key.source_id)
1103 .map(str::to_owned)
1104 .unwrap_or_else(|| format!("source:{}", location.key.source_id)),
1105 offset: location.offset,
1106 length: location.length,
1107 line: location.key.line,
1108 column: contract
1109 .column_of(location.key.source_id, location.offset)
1110 .unwrap_or(0),
1111 function: location.function.map(|function| FunctionId {
1112 contract: location.key.contract,
1113 function,
1114 }),
1115 function_name: function.map(|function| function.name.clone()),
1116 })
1117 }
1118
1119 #[must_use]
1129 pub fn next_source(&self, step: usize) -> Option<usize> {
1130 let current = *self.steps.get(step)?;
1131 if self.outside_functions(¤t) {
1132 return self.step_into(step);
1133 }
1134 let key = current.key;
1135 ((step + 1)..self.steps.len()).find(|&index| {
1136 let candidate = &self.steps[index];
1137 if candidate.frame_depth > current.frame_depth {
1138 return false;
1139 }
1140 candidate.frame_depth < current.frame_depth
1141 || candidate
1142 .key
1143 .is_some_and(|candidate| Some(candidate) != key)
1144 })
1145 }
1146
1147 #[must_use]
1150 pub fn step_into(&self, step: usize) -> Option<usize> {
1151 let current = *self.steps.get(step)?;
1152 let key = current.key;
1153 ((step + 1)..self.steps.len()).find(|&index| {
1154 let candidate = &self.steps[index];
1155 candidate.frame_depth != current.frame_depth
1156 || candidate
1157 .key
1158 .is_some_and(|candidate| Some(candidate) != key)
1159 })
1160 }
1161
1162 #[must_use]
1164 pub fn finish(&self, step: usize) -> Option<usize> {
1165 let current = *self.steps.get(step)?;
1166 ((step + 1)..self.steps.len())
1167 .find(|&index| self.steps[index].frame_depth < current.frame_depth)
1168 }
1169
1170 #[must_use]
1175 pub fn previous_source(&self, step: usize) -> Option<usize> {
1176 let current = *self.steps.get(step)?;
1177 if self.outside_functions(¤t) {
1178 return self.reverse_step_into(step);
1179 }
1180 self.previous_line_start(step, true)
1181 }
1182
1183 #[must_use]
1185 pub fn reverse_step_into(&self, step: usize) -> Option<usize> {
1186 self.steps.get(step)?;
1187 self.previous_line_start(step, false)
1188 }
1189
1190 fn previous_line_start(&self, step: usize, skip_deeper: bool) -> Option<usize> {
1191 let current = self.steps[step];
1192 let anchor = if current.key.is_some() {
1194 step
1195 } else {
1196 self.previous_mapped(step, current.frame_depth, skip_deeper)?
1197 };
1198 let start = self.run_start(anchor, skip_deeper);
1199 if start < step {
1200 return Some(start);
1201 }
1202 let previous = self.previous_mapped(start, current.frame_depth, skip_deeper)?;
1203 Some(self.run_start(previous, skip_deeper))
1204 }
1205
1206 #[must_use]
1208 pub fn reverse_finish(&self, step: usize) -> Option<usize> {
1209 let current = *self.steps.get(step)?;
1210 (0..step)
1211 .rev()
1212 .find(|&index| self.steps[index].frame_depth < current.frame_depth)
1213 }
1214
1215 fn run_start(&self, step: usize, skip_deeper: bool) -> usize {
1219 let info = self.steps[step];
1220 let key = info.key;
1221 let mut current = step;
1222 while let Some(previous) = self.previous_mapped(current, info.frame_depth, skip_deeper) {
1223 let candidate = self.steps[previous];
1224 if candidate.frame_depth != info.frame_depth || candidate.key != key {
1225 break;
1226 }
1227 current = previous;
1228 }
1229 current
1230 }
1231
1232 fn outside_functions(&self, info: &StepInfo) -> bool {
1235 info.location.is_some() && info.key.is_none()
1236 }
1237
1238 fn previous_mapped(&self, step: usize, depth: u32, skip_deeper: bool) -> Option<usize> {
1241 (0..step).rev().find(|&index| {
1242 let candidate = &self.steps[index];
1243 candidate.key.is_some() && (!skip_deeper || candidate.frame_depth <= depth)
1244 })
1245 }
1246
1247 pub fn resolve_line(&self, file: Option<&str>, line: u64) -> Result<Vec<ResolvedLine>, String> {
1250 let mut sources = Vec::<(usize, u64, String)>::new();
1251 for (contract_index, contract) in self.contracts.iter().enumerate() {
1252 for (source_id, path) in &contract.info.sources {
1253 if file.is_none_or(|file| source_path_matches(path, file)) {
1254 sources.push((contract_index, *source_id, path.clone()));
1255 }
1256 }
1257 }
1258 if sources.is_empty() {
1259 return Err(match file {
1260 Some(file) => format!("source file not found: {file}"),
1261 None => "no source files are available".to_owned(),
1262 });
1263 }
1264 if file.is_none() {
1265 let mut paths = sources.iter().map(|(_, _, path)| path).collect::<Vec<_>>();
1266 paths.sort();
1267 paths.dedup();
1268 if paths.len() > 1 {
1269 return Err("line breakpoint is ambiguous; use break <file>:<line>".to_owned());
1270 }
1271 }
1272
1273 let resolved = sources
1274 .into_iter()
1275 .filter_map(|(contract_index, source_id, path)| {
1276 let effective = self.contracts[contract_index].effective_line(source_id, line)?;
1277 Some(ResolvedLine {
1278 key: LineKey {
1279 contract: contract_index,
1280 source_id,
1281 line: effective,
1282 },
1283 path,
1284 requested_line: line,
1285 })
1286 })
1287 .collect::<Vec<_>>();
1288 if resolved.is_empty() {
1289 return Err(match file {
1290 Some(file) => format!("no instruction maps to {file}:{line}"),
1291 None => format!("no instruction maps to line {line}"),
1292 });
1293 }
1294 Ok(resolved)
1295 }
1296
1297 pub fn resolve_function(&self, name: &str) -> Result<Vec<ResolvedFunction>, String> {
1300 let (contract_name, function_name) = match name.rsplit_once('.') {
1301 Some((contract, function)) => (Some(contract), function),
1302 None => (None, name),
1303 };
1304 let resolved = self
1305 .contracts
1306 .iter()
1307 .enumerate()
1308 .filter(|(_, contract)| contract_name.is_none_or(|name| contract.name == name))
1309 .flat_map(|(contract_index, contract)| {
1310 contract
1311 .functions
1312 .iter()
1313 .enumerate()
1314 .filter(move |(_, function)| function.name == function_name)
1315 .map(move |(function_index, function)| ResolvedFunction {
1316 id: FunctionId {
1317 contract: contract_index,
1318 function: function_index,
1319 },
1320 name: function.name.clone(),
1321 contract_name: contract.name.clone(),
1322 path: contract
1323 .source_path(function.source_id)
1324 .map(str::to_owned)
1325 .unwrap_or_else(|| format!("source:{}", function.source_id)),
1326 line: function.declaration_line,
1327 })
1328 })
1329 .collect::<Vec<_>>();
1330 if resolved.is_empty() {
1331 return Err(format!("no function named `{name}` in the loaded sources"));
1332 }
1333 Ok(resolved)
1334 }
1335
1336 #[must_use]
1338 pub fn frames(&self, step: usize) -> Vec<Frame> {
1339 struct Record {
1340 entry_step: usize,
1341 last_step: usize,
1342 external: bool,
1343 }
1344 if step >= self.steps.len() {
1345 return Vec::new();
1346 }
1347 let mut stack = Vec::<Record>::new();
1348 for (index, info) in self.steps.iter().enumerate().take(step + 1) {
1349 let depth = info.frame_depth as usize;
1350 stack.truncate(depth + 1);
1351 let entered_call = index == 0 || info.evm_depth > self.steps[index - 1].evm_depth;
1354 let mut first_new = true;
1355 while stack.len() <= depth {
1356 stack.push(Record {
1357 entry_step: index,
1358 last_step: index,
1359 external: first_new && entered_call,
1360 });
1361 first_new = false;
1362 }
1363 if let Some(top) = stack.last_mut() {
1364 top.last_step = index;
1365 }
1366 }
1367
1368 stack
1369 .iter()
1370 .enumerate()
1371 .rev()
1372 .map(|(depth, record)| {
1373 let info = self.steps[record.last_step];
1374 let location = info
1375 .location
1376 .and_then(|location| self.describe(&location, info.generated));
1377 Frame {
1378 depth: depth as u32,
1379 external: record.external,
1380 address: info
1381 .address
1382 .and_then(|index| self.addresses.get(index))
1383 .cloned(),
1384 contract_name: info
1385 .contract
1386 .and_then(|index| self.contracts.get(index))
1387 .map(|contract| contract.name.clone()),
1388 function_name: location
1389 .as_ref()
1390 .and_then(|location| location.function_name.clone()),
1391 entry_step: record.entry_step,
1392 step: record.last_step,
1393 pc: self.pcs[record.last_step],
1394 location,
1395 arguments: Vec::new(),
1396 }
1397 })
1398 .collect()
1399 }
1400
1401 #[must_use]
1403 pub fn source_listing(&self, step: usize, radius: u64) -> Option<SourceListing> {
1404 let location = self.steps.get(step)?.location?;
1405 let contract = self.contracts.get(location.key.contract)?;
1406 let count = contract.line_count(location.key.source_id)?;
1407 let first = location.key.line.saturating_sub(radius).max(1);
1408 let last = location.key.line.saturating_add(radius).min(count);
1409 let lines = (first..=last)
1410 .filter_map(|line| {
1411 contract
1412 .line_text(location.key.source_id, line)
1413 .map(|text| (line, text.to_owned()))
1414 })
1415 .collect();
1416 Some(SourceListing {
1417 path: contract
1418 .source_path(location.key.source_id)
1419 .map(str::to_owned)
1420 .unwrap_or_else(|| format!("source:{}", location.key.source_id)),
1421 current_line: location.key.line,
1422 lines,
1423 })
1424 }
1425}
1426
1427fn prove_argument_layouts(
1442 trace: &TransactionTrace,
1443 contracts: &[ContractDebugInfo],
1444 framed: &[FramedStep],
1445) -> Vec<Evidence> {
1446 let mut evidence = vec![Evidence::Unknown; contracts.len()];
1447 if contracts.is_empty() || framed.is_empty() {
1448 return evidence;
1449 }
1450 let mut calldata_by_entry = HashMap::<usize, &str>::new();
1453 calldata_by_entry.insert(0, trace.input_data.as_str());
1454 for call in &trace.artifacts.calls {
1455 if let Some(entry_step) = call.entry_step {
1456 calldata_by_entry.insert(entry_step, call.input.as_str());
1457 }
1458 }
1459
1460 let mut examined = std::collections::HashSet::<u32>::new();
1461 let mut frame_calldata = HashMap::<u32, &str>::new();
1462 for (index, step) in framed.iter().enumerate() {
1463 let entered = index == 0 || framed[index - 1].frame_id != step.frame_id;
1464 if entered {
1465 if let Some(calldata) = calldata_by_entry.get(&index) {
1466 frame_calldata.insert(step.frame_id, calldata);
1467 }
1468 }
1469 let (Some(contract_index), Some(function_index)) = (step.contract, step.entry) else {
1470 continue;
1471 };
1472 if !examined.insert(step.frame_id) {
1475 continue;
1476 }
1477 let Some(calldata) = frame_calldata.get(&step.frame_id) else {
1478 continue;
1479 };
1480 let Some(function) = contracts[contract_index].functions.get(function_index) else {
1481 continue;
1482 };
1483 let stack = trace.steps[index].snapshot_ref().stack;
1484 let observed = argument_evidence(function, calldata, stack);
1485 evidence[contract_index] = evidence[contract_index].merge(observed);
1486 }
1487 evidence
1488}
1489
1490fn argument_evidence(function: &SourceFunction, calldata: &str, stack: &[StackWord]) -> Evidence {
1498 let count = function.params.len();
1499 if count == 0 || stack.len() < count {
1500 return Evidence::Unknown;
1501 }
1502 if !function.params.iter().all(readable_parameter) {
1504 return Evidence::Unknown;
1505 }
1506 let signature = format!(
1507 "{}({})",
1508 function.name,
1509 function
1510 .params
1511 .iter()
1512 .map(|param| canonical_value_type(¶m.ty))
1513 .collect::<Vec<_>>()
1514 .join(",")
1515 );
1516 let Ok(selector) = function_selector(&signature) else {
1517 return Evidence::Unknown;
1518 };
1519 let data = calldata.trim_start_matches("0x");
1520 let Some((encoded_selector, arguments)) = data.split_at_checked(8) else {
1521 return Evidence::Unknown;
1522 };
1523 if encoded_selector.to_ascii_lowercase() != hex_bytes(&selector) {
1525 return Evidence::Unknown;
1526 }
1527 let expected = (0..count)
1528 .map(|index| {
1529 arguments
1530 .get(index * 64..(index + 1) * 64)
1531 .map(str::to_ascii_lowercase)
1532 })
1533 .collect::<Option<Vec<_>>>();
1534 let Some(expected) = expected else {
1535 return Evidence::Unknown;
1536 };
1537 let checkable = (0..count)
1540 .filter(|index| is_value_type(&function.params[*index].ty))
1541 .collect::<Vec<_>>();
1542 if checkable.is_empty() {
1543 return Evidence::Unknown;
1544 }
1545 let matches = |order: ArgumentOrder| {
1546 checkable.iter().all(|index| {
1547 let candidate = &stack[order.word_index(*index, count, stack.len())];
1548 normalize_word(candidate) == expected[*index]
1549 })
1550 };
1551 let distinguishing = checkable.iter().any(|index| {
1553 ArgumentOrder::FirstOnTop.word_index(*index, count, stack.len())
1554 != ArgumentOrder::LastOnTop.word_index(*index, count, stack.len())
1555 });
1556 let first = matches(ArgumentOrder::FirstOnTop);
1557 let last = matches(ArgumentOrder::LastOnTop);
1558 match (first, last) {
1559 (true, true) => Evidence::TopWords,
1562 (true, false) if distinguishing => Evidence::Ordered(ArgumentOrder::FirstOnTop),
1563 (false, true) if distinguishing => Evidence::Ordered(ArgumentOrder::LastOnTop),
1564 (true, false) | (false, true) => Evidence::TopWords,
1566 (false, false) => Evidence::Contradicted,
1568 }
1569}
1570
1571fn canonical_value_type(ty: &str) -> String {
1574 match ty {
1575 "uint" => "uint256".to_owned(),
1576 "int" => "int256".to_owned(),
1577 "address payable" => "address".to_owned(),
1578 other => other.to_owned(),
1579 }
1580}
1581
1582fn normalize_word(word: &str) -> String {
1584 let digits = word.trim_start_matches("0x").trim_start_matches('0');
1585 format!("{:0>64}", digits.to_ascii_lowercase())
1586}
1587
1588fn hex_bytes(bytes: &[u8]) -> String {
1589 bytes.iter().map(|byte| format!("{byte:02x}")).collect()
1590}
1591
1592#[must_use]
1594pub fn call_target(step: &soldb_core::TraceStep) -> Option<String> {
1595 if !CALL_OPCODES.contains(&&*step.op) {
1596 return None;
1597 }
1598 let stack = step.snapshot_ref().stack;
1599 let word = stack.get(stack.len().checked_sub(2)?)?;
1600 address_from_word(word)
1601}
1602
1603#[must_use]
1606pub fn address_from_word(word: &str) -> Option<String> {
1607 let hex = word.trim_start_matches("0x");
1608 if !hex.bytes().all(|byte| byte.is_ascii_hexdigit()) {
1609 return None;
1610 }
1611 let padded = format!("{hex:0>40}");
1612 let address = padded.get(padded.len() - 40..)?;
1613 if address.bytes().all(|byte| byte == b'0') {
1614 return None;
1615 }
1616 Some(format!("0x{}", address.to_ascii_lowercase()))
1617}
1618
1619#[cfg(test)]
1620mod tests {
1621 use std::collections::BTreeMap;
1622
1623 use serde_json::json;
1624 use soldb_core::{StepSnapshot, TraceStep, TransactionTrace};
1625 use soldb_ethdebug::{EthdebugInfo, Instruction};
1626
1627 use soldb_core::Word as StackWord;
1628
1629 use crate::FrameState;
1630
1631 use super::{
1632 address_from_word, normalize_address, ArgumentLayout, ArgumentOrder, ContractDebugInfo,
1633 JumpMarker, LineKey, StepMap,
1634 };
1635
1636 const SOURCE: &str = "\
1638contract C {
1639 function outer(uint256 a) public {
1640 uint256 b = a + 1;
1641 inner(b);
1642 b = 0;
1643 }
1644 function inner(uint256 x) internal {
1645 x += 1;
1646 }
1647}
1648";
1649
1650 fn offset_of(needle: &str) -> u64 {
1651 SOURCE.find(needle).expect(needle) as u64
1652 }
1653
1654 fn instruction(pc: u64, offset: u64, length: u64) -> Instruction {
1655 instruction_with(pc, offset, length, json!({}))
1656 }
1657
1658 fn instruction_with(
1660 pc: u64,
1661 offset: u64,
1662 length: u64,
1663 extra: serde_json::Value,
1664 ) -> Instruction {
1665 let mut context =
1666 json!({"code": {"source": {"id": 0}, "range": {"offset": offset, "length": length}}});
1667 for (key, value) in extra.as_object().expect("object") {
1668 context[key] = value.clone();
1669 }
1670 serde_json::from_value(json!({
1671 "offset": pc,
1672 "operation": {"mnemonic": "JUMPDEST"},
1673 "context": context
1674 }))
1675 .expect("instruction")
1676 }
1677
1678 fn contract(address: Option<&str>) -> ContractDebugInfo {
1679 let whole = SOURCE.len() as u64;
1680 let info = EthdebugInfo {
1681 compilation: serde_json::Value::Null,
1682 contract_name: "C".to_owned(),
1683 environment: "runtime".to_owned(),
1684 instructions: vec![
1685 instruction(0, 0, whole),
1687 instruction(1, 0, whole),
1688 instruction(10, offset_of("function outer"), 90),
1690 instruction(11, offset_of("uint256 b = a + 1;"), 18),
1691 instruction(12, offset_of("inner(b);"), 9),
1692 instruction(13, offset_of("inner(b);"), 9),
1693 instruction(14, offset_of("b = 0;"), 6),
1694 instruction(20, offset_of("function inner"), 50),
1696 instruction(21, offset_of("x += 1;"), 7),
1697 instruction(30, 0, whole),
1699 instruction(40, 0, whole),
1701 ],
1702 sources: BTreeMap::from([(0, "C.sol".to_owned())]),
1703 variable_locations: BTreeMap::new(),
1704 };
1705 ContractDebugInfo::new(address, "C", info, BTreeMap::from([(0, SOURCE.to_owned())]))
1706 }
1707
1708 fn step(pc: u64, depth: u64, op: &str, stack: &[&str]) -> TraceStep {
1709 TraceStep {
1710 pc,
1711 op: op.into(),
1712 gas: 0,
1713 gas_cost: 0,
1714 depth,
1715 stack: stack.iter().map(|word| StackWord::from(*word)).collect(),
1716 memory: None,
1717 storage: None,
1718 error: None,
1719 snapshot: StepSnapshot::default(),
1720 }
1721 }
1722
1723 fn trace(steps: Vec<TraceStep>) -> TransactionTrace {
1724 TransactionTrace {
1725 tx_hash: None,
1726 from_addr: "0x1".to_owned(),
1727 to_addr: Some("0xAAaa000000000000000000000000000000000001".to_owned()),
1728 value: "0x0".to_owned(),
1729 input_data: "0x".to_owned(),
1730 gas_used: 0,
1731 output: "0x".to_owned(),
1732 success: true,
1733 error: None,
1734 debug_trace_available: true,
1735 contract_address: None,
1736 backend: None,
1737 capabilities: Default::default(),
1738 artifacts: Default::default(),
1739 steps,
1740 }
1741 }
1742
1743 fn outer_calls_inner() -> TransactionTrace {
1746 trace(vec![
1747 step(0, 1, "PUSH1", &[]),
1748 step(1, 1, "JUMPI", &[]),
1749 step(10, 1, "JUMPDEST", &[]),
1750 step(11, 1, "ADD", &[]),
1751 step(12, 1, "PUSH1", &[]),
1752 step(20, 1, "JUMPDEST", &[]),
1753 step(21, 1, "ADD", &[]),
1754 step(21, 1, "SWAP1", &[]),
1755 step(13, 1, "JUMPDEST", &[]),
1756 step(14, 1, "POP", &[]),
1757 step(30, 1, "STOP", &[]),
1758 ])
1759 }
1760
1761 const PAY_SOURCE: &str = "\
1764contract P {
1765 function pay(address to, uint256 amount) public {
1766 total(to, amount);
1767 }
1768 function total(address to, uint256 amount) internal {
1769 amount += 1;
1770 }
1771}
1772";
1773
1774 fn pay_contract() -> ContractDebugInfo {
1775 let whole = PAY_SOURCE.len() as u64;
1776 let at = |needle: &str| PAY_SOURCE.find(needle).expect(needle) as u64;
1777 let span = |pc: u64, offset: u64, length: u64| {
1778 serde_json::from_value::<Instruction>(json!({
1779 "offset": pc,
1780 "operation": {"mnemonic": "JUMPDEST"},
1781 "context": {"code": {
1782 "source": {"id": 0},
1783 "range": {"offset": offset, "length": length}
1784 }}
1785 }))
1786 .expect("instruction")
1787 };
1788 let info = EthdebugInfo {
1789 compilation: serde_json::Value::Null,
1790 contract_name: "P".to_owned(),
1791 environment: "runtime".to_owned(),
1792 instructions: vec![
1793 span(0, 0, whole),
1794 span(10, at("function pay"), 80),
1795 span(11, at("total(to, amount);"), 18),
1796 span(20, at("function total"), 70),
1797 span(21, at("amount += 1;"), 12),
1798 ],
1799 sources: BTreeMap::from([(0, "P.sol".to_owned())]),
1800 variable_locations: BTreeMap::new(),
1801 };
1802 ContractDebugInfo::new(
1803 None,
1804 "P",
1805 info,
1806 BTreeMap::from([(0, PAY_SOURCE.to_owned())]),
1807 )
1808 }
1809
1810 fn word_of(value: &str) -> StackWord {
1811 StackWord::from(format!("0x{:0>64}", value.trim_start_matches("0x")).as_str())
1812 }
1813
1814 fn pay_trace(entry_stack: &[StackWord], inner_stack: &[StackWord]) -> TransactionTrace {
1817 let selector = soldb_ethdebug::function_selector("pay(address,uint256)").expect("selector");
1818 let calldata = format!(
1819 "0x{}{}{}",
1820 selector
1821 .iter()
1822 .map(|byte| format!("{byte:02x}"))
1823 .collect::<String>(),
1824 &word_of(PAY_TO)[2..],
1825 &word_of(PAY_AMOUNT)[2..]
1826 );
1827 fn borrow(stack: &[StackWord]) -> Vec<&str> {
1828 stack.iter().map(|word| &**word).collect()
1829 }
1830 let mut trace = trace(vec![
1831 step(0, 1, "PUSH1", &[]),
1832 step(10, 1, "JUMPDEST", &borrow(entry_stack)),
1833 step(11, 1, "JUMP", &borrow(inner_stack)),
1834 step(20, 1, "JUMPDEST", &borrow(inner_stack)),
1835 step(21, 1, "ADD", &[]),
1836 ]);
1837 trace.input_data = calldata;
1838 trace
1839 }
1840
1841 const PAY_TO: &str = "00000000000000000000000000000000000000aa";
1842 const PAY_AMOUNT: &str = "7";
1843
1844 #[test]
1845 fn the_trace_proves_which_end_the_first_argument_is_at() {
1846 let to = word_of(PAY_TO);
1847 let amount = word_of(PAY_AMOUNT);
1848 let tag = word_of("2a");
1849
1850 let first_on_top = [tag.clone(), amount.clone(), to.clone()];
1852 let trace = pay_trace(&first_on_top, &first_on_top);
1853 let map = StepMap::new(&trace, vec![pay_contract()]);
1854 assert_eq!(
1855 map.argument_layout(0),
1856 Some(ArgumentLayout::Ordered(ArgumentOrder::FirstOnTop))
1857 );
1858 let frames = map.frames(4);
1859 let inner = frames.first().expect("innermost frame");
1860 assert_eq!(inner.function_name.as_deref(), Some("total"));
1861 let arguments = map.frame_arguments(
1862 inner,
1863 FrameState {
1864 stack: &first_on_top,
1865 memory: None,
1866 },
1867 );
1868 assert_eq!(
1869 arguments
1870 .iter()
1871 .map(|argument| format!("{} = {}", argument.name, argument.value.display))
1872 .collect::<Vec<_>>(),
1873 [
1874 "to = 0x00000000000000000000000000000000000000aa".to_owned(),
1875 "amount = 7".to_owned()
1876 ]
1877 );
1878
1879 let last_on_top = [tag.clone(), to.clone(), amount.clone()];
1881 let trace = pay_trace(&last_on_top, &last_on_top);
1882 let map = StepMap::new(&trace, vec![pay_contract()]);
1883 assert_eq!(
1884 map.argument_layout(0),
1885 Some(ArgumentLayout::Ordered(ArgumentOrder::LastOnTop))
1886 );
1887 let frames = map.frames(4);
1888 let arguments = map.frame_arguments(
1889 frames.first().expect("frame"),
1890 FrameState {
1891 stack: &last_on_top,
1892 memory: None,
1893 },
1894 );
1895 assert_eq!(
1896 arguments[0].value.display,
1897 "0x00000000000000000000000000000000000000aa"
1898 );
1899 assert_eq!(arguments[1].value.display, "7");
1900
1901 let wrapper = [tag.clone(), word_of("dead"), word_of("beef")];
1904 let trace = pay_trace(&wrapper, &first_on_top);
1905 let map = StepMap::new(&trace, vec![pay_contract()]);
1906 assert_eq!(map.argument_layout(0), None);
1907 let frames = map.frames(4);
1908 assert!(map
1909 .frame_arguments(
1910 frames.first().expect("frame"),
1911 FrameState {
1912 stack: &first_on_top,
1913 memory: None
1914 }
1915 )
1916 .is_empty());
1917
1918 let mut unknown = pay_trace(&first_on_top, &first_on_top);
1920 unknown.input_data = "0x".to_owned();
1921 let map = StepMap::new(&unknown, vec![pay_contract()]);
1922 assert_eq!(map.argument_layout(0), None);
1923 let frames = map.frames(4);
1924 assert!(map
1925 .frame_arguments(
1926 frames.first().expect("frame"),
1927 FrameState {
1928 stack: &first_on_top,
1929 memory: None
1930 }
1931 )
1932 .is_empty());
1933 }
1934
1935 #[test]
1936 fn one_parameter_is_on_top_under_either_order() {
1937 let mut trace = outer_calls_inner();
1940 let selector = soldb_ethdebug::function_selector("outer(uint256)").expect("selector");
1941 let amount = word_of("5");
1942 trace.input_data = format!(
1943 "0x{}{}",
1944 selector
1945 .iter()
1946 .map(|byte| format!("{byte:02x}"))
1947 .collect::<String>(),
1948 &amount[2..]
1949 );
1950 let stack = [word_of("2a"), amount.clone()];
1951 trace.steps[2].snapshot =
1952 StepSnapshot::new(stack.to_vec(), None, BTreeMap::new(), BTreeMap::new());
1953 let map = StepMap::new(&trace, vec![contract(None)]);
1954 assert_eq!(map.argument_layout(0), Some(ArgumentLayout::TopWords));
1955 let frames = map.frames(3);
1956 let outer = frames
1957 .iter()
1958 .find(|frame| frame.function_name.as_deref() == Some("outer"))
1959 .expect("outer frame");
1960 let arguments = map.frame_arguments(
1961 outer,
1962 FrameState {
1963 stack: &stack,
1964 memory: None,
1965 },
1966 );
1967 assert_eq!(arguments.len(), 1);
1968 assert_eq!(arguments[0].name, "a");
1969 assert_eq!(arguments[0].value.display, "5");
1970
1971 let map = StepMap::new(&trace, vec![pay_contract()]);
1973 assert_eq!(map.argument_layout(0), None);
1974 }
1975
1976 fn key(line: u64) -> Option<LineKey> {
1977 Some(LineKey {
1978 contract: 0,
1979 source_id: 0,
1980 line,
1981 })
1982 }
1983
1984 #[test]
1985 fn maps_steps_to_lines_and_infers_internal_frames() {
1986 let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
1987 assert!(map.has_source());
1988 assert_eq!(map.line_key(0), None);
1990 assert_eq!(map.location(0).expect("location").line, 1);
1991 assert!(!map.location(0).expect("location").generated);
1992 assert_eq!(map.line_key(2), key(2));
1993 assert_eq!(map.line_key(3), key(3));
1994 assert_eq!(map.line_key(4), key(4));
1995 assert_eq!(map.line_key(5), key(7));
1996 assert_eq!(map.line_key(6), key(8));
1997 assert_eq!(map.line_key(8), key(4));
1998 assert_eq!(map.line_key(10), None);
1999
2000 let depths = (0..11)
2002 .map(|step| map.frame_depth(step).expect("depth"))
2003 .collect::<Vec<_>>();
2004 assert_eq!(depths, vec![0, 0, 1, 1, 1, 2, 2, 2, 1, 1, 0]);
2005 assert!(map.is_frame_entry(2));
2006 assert!(map.is_frame_entry(5));
2007 assert!(!map.is_frame_entry(8));
2008 assert_eq!(
2009 map.location(6).expect("location").function_name.as_deref(),
2010 Some("inner")
2011 );
2012 assert_eq!(map.location(6).expect("location").path, "C.sol");
2013 assert_eq!(map.location(6).expect("location").column, 9);
2014 }
2015
2016 #[test]
2017 fn next_steps_over_calls_and_step_enters_them() {
2018 let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
2019 assert_eq!(map.next_source(0), Some(2));
2021 assert_eq!(map.next_source(2), Some(3));
2022 assert_eq!(map.next_source(4), Some(9));
2024 assert_eq!(map.step_into(4), Some(5));
2025 assert_eq!(map.step_into(5), Some(6));
2026 assert_eq!(map.next_source(6), Some(8));
2028 assert_eq!(map.finish(6), Some(8));
2029 assert_eq!(map.next_source(9), Some(10));
2031 assert_eq!(map.finish(9), Some(10));
2032 assert_eq!(map.next_source(10), None);
2033 assert_eq!(map.finish(10), None);
2034 }
2035
2036 #[test]
2037 fn reverse_stepping_lands_on_line_starts() {
2038 let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
2039 assert_eq!(map.previous_source(9), Some(4));
2041 assert_eq!(map.previous_source(7), Some(6));
2043 assert_eq!(map.previous_source(6), Some(5));
2044 assert_eq!(map.reverse_step_into(9), Some(8));
2046 assert_eq!(map.reverse_step_into(8), Some(6));
2047 assert_eq!(map.reverse_finish(6), Some(4));
2049 assert_eq!(map.reverse_finish(0), None);
2050 assert_eq!(map.previous_source(0), None);
2051 assert_eq!(map.previous_source(10), Some(9));
2054 }
2055
2056 #[test]
2057 fn line_starts_skip_returns_into_the_middle_of_a_line() {
2058 let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
2059 assert!(map.is_line_start(3));
2060 assert!(map.is_line_start(4));
2061 assert!(map.is_line_start(6));
2062 assert!(!map.is_line_start(7));
2063 assert!(!map.is_line_start(8));
2065 assert!(map.is_line_start(9));
2066 }
2067
2068 #[test]
2069 fn resolves_line_and_function_breakpoints() {
2070 let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
2071 let resolved = map.resolve_line(Some("C.sol"), 4).expect("line 4");
2072 assert_eq!(resolved.len(), 1);
2073 assert_eq!(resolved[0].key, key(4).unwrap());
2074 assert_eq!(resolved[0].path, "C.sol");
2075 assert_eq!(
2077 map.resolve_line(None, 8).expect("line 8")[0].key,
2078 key(8).unwrap()
2079 );
2080 assert_eq!(
2083 map.resolve_line(None, 10).expect("line 10")[0].key,
2084 key(1).unwrap()
2085 );
2086 assert_eq!(
2087 map.resolve_line(None, 99).expect_err("past the end"),
2088 "no instruction maps to line 99"
2089 );
2090 assert_eq!(
2091 map.resolve_line(Some("D.sol"), 1)
2092 .expect_err("unknown file"),
2093 "source file not found: D.sol"
2094 );
2095
2096 let inner = map.resolve_function("inner").expect("inner");
2097 assert_eq!(inner.len(), 1);
2098 assert_eq!(inner[0].line, 7);
2099 assert_eq!(inner[0].contract_name, "C");
2100 assert_eq!(
2101 map.resolve_function("C.outer").expect("qualified")[0].line,
2102 2
2103 );
2104 assert!(map.resolve_function("D.outer").is_err());
2105 assert!(map.resolve_function("missing").is_err());
2106 }
2107
2108 #[test]
2109 fn frames_list_the_call_structure_innermost_first() {
2110 let map = StepMap::new(&outer_calls_inner(), vec![contract(None)]);
2111 let frames = map.frames(6);
2112 assert_eq!(frames.len(), 3);
2113 assert_eq!(frames[0].function_name.as_deref(), Some("inner"));
2114 assert_eq!(frames[0].depth, 2);
2115 assert!(!frames[0].external);
2116 assert_eq!(frames[0].step, 6);
2117 assert_eq!(frames[1].function_name.as_deref(), Some("outer"));
2118 assert_eq!(
2119 frames[1].location.as_ref().map(|location| location.line),
2120 Some(4)
2121 );
2122 assert_eq!(frames[1].step, 4);
2123 assert!(frames[2].external);
2124 assert_eq!(frames[2].depth, 0);
2125 assert_eq!(
2126 frames[2].address.as_deref(),
2127 Some("0xaaaa000000000000000000000000000000000001")
2128 );
2129 assert!(map.frames(99).is_empty());
2130
2131 let listing = map.source_listing(6, 1).expect("listing");
2132 assert_eq!(listing.current_line, 8);
2133 assert_eq!(listing.lines.len(), 3);
2134 assert_eq!(listing.lines[1].0, 8);
2135 assert_eq!(listing.lines[1].1.trim(), "x += 1;");
2136 }
2137
2138 #[test]
2139 fn external_calls_map_through_the_callee_contract() {
2140 let callee = "0xbbbb000000000000000000000000000000000002";
2142 let word = format!("0x{:0>64}", callee.trim_start_matches("0x"));
2143 let steps = vec![
2144 step(0, 1, "PUSH1", &[]),
2145 step(
2146 12,
2147 1,
2148 "CALL",
2149 &["0x0", "0x0", "0x0", "0x0", "0x0", &word, "0x0"],
2150 ),
2151 step(21, 2, "JUMPDEST", &[]),
2152 step(21, 2, "ADD", &[]),
2153 step(13, 1, "JUMPDEST", &[]),
2154 step(30, 1, "STOP", &[]),
2155 ];
2156 let root = contract(Some("0xAAAA000000000000000000000000000000000001"));
2157 let map = StepMap::new(&trace(steps), vec![root, contract(Some(callee))]);
2158 assert_eq!(
2159 map.executing_address(1),
2160 Some("0xaaaa000000000000000000000000000000000001")
2161 );
2162 assert_eq!(map.executing_address(2), Some(callee));
2163 assert_eq!(
2164 map.line_key(2),
2165 Some(LineKey {
2166 contract: 1,
2167 source_id: 0,
2168 line: 8
2169 })
2170 );
2171 assert_eq!(map.frame_depth(1), Some(1));
2172 assert_eq!(map.frame_depth(2), Some(3));
2174 assert!(map.is_frame_entry(2));
2175 assert_eq!(map.frame_depth(4), Some(1));
2176 assert_eq!(map.next_source(1), Some(5));
2178 assert_eq!(map.step_into(1), Some(2));
2179 let frames = map.frames(2);
2180 assert_eq!(frames.len(), 4);
2181 assert!(frames[1].external);
2182 assert_eq!(frames[1].address.as_deref(), Some(callee));
2183 assert_eq!(frames[0].function_name.as_deref(), Some("inner"));
2184 }
2185
2186 #[test]
2187 fn steps_without_debug_info_have_no_location() {
2188 let map = StepMap::new(&outer_calls_inner(), Vec::new());
2189 assert!(!map.has_source());
2190 assert_eq!(map.line_key(3), None);
2191 assert_eq!(map.frame_depth(3), Some(0));
2192 assert_eq!(map.next_source(3), None);
2194 assert_eq!(map.previous_source(3), None);
2195 assert!(map.resolve_line(None, 3).is_err());
2196 assert_eq!(map.frames(3).len(), 1);
2197 }
2198
2199 #[test]
2200 fn steps_without_source_are_transparent_to_line_stepping() {
2201 let map = StepMap::new(
2203 &trace(vec![
2204 step(0, 1, "PUSH1", &[]),
2205 step(10, 1, "JUMPDEST", &[]),
2206 step(11, 1, "ADD", &[]),
2207 step(99, 1, "MUL", &[]),
2208 step(11, 1, "SWAP1", &[]),
2209 step(14, 1, "POP", &[]),
2210 step(30, 1, "STOP", &[]),
2211 ]),
2212 vec![contract(None)],
2213 );
2214 assert_eq!(map.line_key(3), None);
2215 assert_eq!(map.frame_depth(3), Some(1));
2216 assert_eq!(map.next_source(2), Some(5));
2218 assert_eq!(map.step_into(2), Some(5));
2219 assert!(!map.is_line_start(3));
2220 assert!(!map.is_line_start(4));
2221 assert_eq!(map.previous_source(5), Some(2));
2223 assert_eq!(map.previous_source(4), Some(2));
2224 assert_eq!(map.previous_source(3), Some(2));
2225 assert_eq!(map.previous_source(2), Some(1));
2226 }
2227
2228 #[test]
2229 fn generated_code_belongs_to_the_statement_and_the_epilogue_pops_the_function() {
2230 let map = StepMap::new(
2233 &trace(vec![
2234 step(0, 1, "PUSH1", &[]),
2235 step(10, 1, "JUMPDEST", &[]),
2236 step(11, 1, "ADD", &[]),
2237 step(40, 1, "JUMPDEST", &[]),
2238 step(40, 1, "SSTORE", &[]),
2239 step(11, 1, "SWAP1", &[]),
2240 step(14, 1, "POP", &[]),
2241 step(30, 1, "STOP", &[]),
2242 ]),
2243 vec![contract(None)],
2244 );
2245 let helper = map.location(4).expect("location");
2246 assert!(helper.generated);
2247 assert_eq!(helper.line, 3);
2248 assert_eq!(helper.function_name.as_deref(), Some("outer"));
2249 assert_eq!(map.line_key(4), key(3));
2250 assert_eq!(map.function_id(4).map(|id| id.function), Some(0));
2251 assert!(!map.is_line_start(4));
2252 assert!(!map.is_line_start(5));
2253 assert_eq!(map.frame_depth(4), Some(1));
2255 assert_eq!(map.frame_depth(7), Some(0));
2256 assert_eq!(map.line_key(7), None);
2257 assert!(!map.location(7).expect("location").generated);
2258 assert_eq!(map.next_source(2), Some(6));
2259 assert_eq!(map.finish(2), Some(7));
2260 assert_eq!(map.reverse_finish(4), Some(0));
2261 assert_eq!(map.previous_source(6), Some(2));
2262 }
2263
2264 #[test]
2265 fn a_lone_instruction_on_another_line_belongs_to_the_line_around_it() {
2266 let map = StepMap::new(
2269 &trace(vec![
2270 step(0, 1, "PUSH1", &[]),
2271 step(10, 1, "JUMPDEST", &[]),
2272 step(12, 1, "PUSH2", &[]),
2273 step(10, 1, "SWAP1", &[]),
2274 step(11, 1, "ADD", &[]),
2275 step(14, 1, "POP", &[]),
2276 step(30, 1, "STOP", &[]),
2277 ]),
2278 vec![contract(None)],
2279 );
2280 assert_eq!(map.line_key(2), key(2));
2281 assert_eq!(map.location(2).expect("location").line, 4);
2282 assert!(!map.is_line_start(2));
2283 assert!(!map.is_line_start(3));
2284 assert_eq!(map.next_source(1), Some(4));
2285 assert_eq!(map.previous_source(4), Some(1));
2286 assert_eq!(map.line_key(5), key(5));
2288 assert!(map.is_line_start(5));
2289 }
2290
2291 #[test]
2292 fn a_jump_onto_a_function_entry_is_a_call_even_when_it_is_active() {
2293 let map = StepMap::new(
2297 &trace(vec![
2298 step(0, 1, "PUSH1", &[]),
2299 step(1, 1, "JUMP", &[]),
2300 step(10, 1, "JUMPDEST", &[]),
2301 step(12, 1, "JUMP", &[]),
2302 step(20, 1, "JUMPDEST", &[]),
2303 step(21, 1, "JUMP", &[]),
2304 step(10, 1, "JUMPDEST", &[]),
2305 step(14, 1, "JUMP", &[]),
2306 step(21, 1, "SWAP1", &[]),
2307 step(21, 1, "JUMP", &[]),
2308 step(13, 1, "JUMPDEST", &[]),
2309 step(14, 1, "POP", &[]),
2310 step(30, 1, "STOP", &[]),
2311 ]),
2312 vec![contract(None)],
2313 );
2314 let contract = &map.contracts()[0];
2315 assert_eq!(contract.function_entry_at_pc(10), Some(0));
2316 assert_eq!(contract.function_entry_at_pc(20), Some(1));
2317 assert_eq!(contract.function_entry_at_pc(11), None);
2318 let depths = (0..13)
2319 .map(|step| map.frame_depth(step).expect("depth"))
2320 .collect::<Vec<_>>();
2321 assert_eq!(depths, vec![0, 0, 1, 1, 2, 2, 3, 3, 2, 2, 1, 1, 0]);
2322 assert!(map.is_frame_entry(6));
2323 let frames = map.frames(6);
2324 assert_eq!(
2325 frames
2326 .iter()
2327 .map(|frame| frame.function_name.as_deref().unwrap_or("-"))
2328 .collect::<Vec<_>>(),
2329 vec!["outer", "inner", "outer", "-"]
2330 );
2331 assert_eq!(map.finish(6), Some(8));
2332 assert_eq!(map.reverse_finish(6), Some(5));
2333 assert_eq!(map.finish(4), Some(10));
2334 }
2335
2336 #[test]
2337 fn a_recursive_call_returns_at_the_address_read_off_the_stack() {
2338 let map = StepMap::new(
2343 &trace(vec![
2344 step(0, 1, "PUSH1", &[]),
2345 step(1, 1, "JUMP", &[]),
2346 step(10, 1, "JUMPDEST", &[]),
2347 step(12, 1, "JUMP", &["0xd", "0x1", "0xa"]),
2348 step(10, 1, "JUMPDEST", &["0x1"]),
2349 step(11, 1, "ADD", &["0x2"]),
2350 step(14, 1, "JUMP", &["0xd"]),
2351 step(13, 1, "JUMPDEST", &[]),
2352 step(14, 1, "POP", &[]),
2353 step(30, 1, "STOP", &[]),
2354 ]),
2355 vec![contract(None)],
2356 );
2357 let depths = (0..10)
2358 .map(|step| map.frame_depth(step).expect("depth"))
2359 .collect::<Vec<_>>();
2360 assert_eq!(depths, vec![0, 0, 1, 1, 2, 2, 2, 1, 1, 0]);
2361 assert!(map.is_frame_entry(4));
2362 assert_eq!(map.finish(4), Some(7));
2363 assert_eq!(map.frames(5).len(), 3);
2364 assert_eq!(map.frames(7).len(), 2);
2365 assert!(map.contracts()[0].is_jumpdest(13));
2366 assert!(!map.contracts()[0].is_jumpdest(99));
2367 }
2368
2369 #[test]
2370 fn jump_markers_decide_calls_and_returns_without_entry_points() {
2371 let whole = SOURCE.len() as u64;
2376 let info = EthdebugInfo {
2377 compilation: serde_json::Value::Null,
2378 contract_name: "C".to_owned(),
2379 environment: "runtime".to_owned(),
2380 instructions: vec![
2381 instruction(0, 0, whole),
2382 instruction(1, 0, whole),
2383 instruction(10, offset_of("function outer"), 90),
2384 instruction(11, offset_of("uint256 b = a + 1;"), 18),
2385 instruction_with(
2386 12,
2387 offset_of("inner(b);"),
2388 9,
2389 json!({"invoke": {"identifier": "outer"}}),
2390 ),
2391 instruction(13, offset_of("inner(b);"), 9),
2392 instruction_with(14, offset_of("b = 0;"), 6, json!({"return": {}})),
2393 instruction(30, 0, whole),
2394 ],
2395 sources: BTreeMap::from([(0, "C.sol".to_owned())]),
2396 variable_locations: BTreeMap::new(),
2397 };
2398 let marked =
2399 ContractDebugInfo::new(None, "C", info, BTreeMap::from([(0, SOURCE.to_owned())]));
2400 assert_eq!(marked.jump_marker_at_pc(12), JumpMarker::Call);
2401 assert_eq!(marked.jump_marker_at_pc(14), JumpMarker::Return);
2402 assert_eq!(marked.jump_marker_at_pc(13), JumpMarker::None);
2403 let map = StepMap::new(
2404 &trace(vec![
2405 step(0, 1, "PUSH1", &[]),
2406 step(1, 1, "JUMP", &[]),
2407 step(10, 1, "JUMPDEST", &[]),
2408 step(12, 1, "JUMP", &[]),
2409 step(11, 1, "ADD", &[]),
2410 step(14, 1, "JUMP", &[]),
2411 step(13, 1, "JUMPDEST", &[]),
2412 step(14, 1, "POP", &[]),
2413 step(30, 1, "STOP", &[]),
2414 ]),
2415 vec![marked],
2416 );
2417 let depths = (0..9)
2418 .map(|step| map.frame_depth(step).expect("depth"))
2419 .collect::<Vec<_>>();
2420 assert_eq!(depths, vec![0, 0, 1, 1, 1, 1, 1, 1, 0]);
2421 assert!(!map.is_frame_entry(4));
2422 assert_eq!(map.frames(4).len(), 2);
2423 assert_eq!(map.finish(4), Some(8));
2424 let map = StepMap::new(
2426 &trace(vec![
2427 step(0, 1, "PUSH1", &[]),
2428 step(1, 1, "JUMP", &[]),
2429 step(10, 1, "JUMPDEST", &[]),
2430 step(12, 1, "JUMP", &[]),
2431 step(20, 1, "JUMPDEST", &[]),
2432 step(21, 1, "JUMP", &[]),
2433 step(13, 1, "JUMPDEST", &[]),
2434 step(30, 1, "STOP", &[]),
2435 ]),
2436 vec![contract(None)],
2437 );
2438 assert_eq!(map.frame_depth(4), Some(2));
2439 assert_eq!(map.frame_depth(6), Some(1));
2440 }
2441
2442 #[test]
2443 fn marked_jumps_into_generated_helpers_do_not_pop_the_function() {
2444 let whole = SOURCE.len() as u64;
2448 let info = EthdebugInfo {
2449 compilation: serde_json::Value::Null,
2450 contract_name: "C".to_owned(),
2451 environment: "runtime".to_owned(),
2452 instructions: vec![
2453 instruction(0, 0, whole),
2454 instruction(1, 0, whole),
2455 instruction(10, offset_of("function outer"), 90),
2456 instruction_with(
2457 11,
2458 offset_of("uint256 b = a + 1;"),
2459 18,
2460 json!({"invoke": {}}),
2461 ),
2462 instruction_with(40, 0, whole, json!({"return": {}})),
2463 instruction(14, offset_of("b = 0;"), 6),
2464 instruction(30, 0, whole),
2465 ],
2466 sources: BTreeMap::from([(0, "C.sol".to_owned())]),
2467 variable_locations: BTreeMap::new(),
2468 };
2469 let contract =
2470 ContractDebugInfo::new(None, "C", info, BTreeMap::from([(0, SOURCE.to_owned())]));
2471 let map = StepMap::new(
2472 &trace(vec![
2473 step(0, 1, "PUSH1", &[]),
2474 step(1, 1, "JUMP", &[]),
2475 step(10, 1, "JUMPDEST", &[]),
2476 step(11, 1, "JUMP", &[]),
2477 step(40, 1, "JUMPDEST", &[]),
2478 step(40, 1, "JUMP", &[]),
2479 step(14, 1, "JUMPDEST", &[]),
2480 step(30, 1, "STOP", &[]),
2481 ]),
2482 vec![contract],
2483 );
2484 let depths = (0..8)
2485 .map(|step| map.frame_depth(step).expect("depth"))
2486 .collect::<Vec<_>>();
2487 assert_eq!(depths, vec![0, 0, 1, 1, 1, 1, 1, 0]);
2488 assert!(map.location(4).expect("location").generated);
2489 assert_eq!(
2490 map.location(6).expect("location").function_name.as_deref(),
2491 Some("outer")
2492 );
2493 assert_eq!(map.next_source(3), Some(6));
2494 assert_eq!(map.finish(3), Some(7));
2495 }
2496
2497 #[test]
2498 fn contract_info_indexes_lines_and_functions() {
2499 let contract = contract(Some("0xABCD"));
2500 assert_eq!(contract.address.as_deref(), Some("0xabcd"));
2501 assert_eq!(contract.line_of(0, 0), Some(1));
2502 assert_eq!(
2503 contract.line_of(0, SOURCE.find("inner(b)").unwrap() as u64),
2504 Some(4)
2505 );
2506 assert_eq!(contract.line_count(0), Some(11));
2507 assert_eq!(contract.line_text(0, 8).map(str::trim), Some("x += 1;"));
2508 assert_eq!(contract.line_text(0, 99), None);
2509 assert_eq!(
2510 contract.function_at_pc(21).map(|f| f.name.as_str()),
2511 Some("inner")
2512 );
2513 assert_eq!(contract.function_at_pc(0), None);
2514 assert_eq!(contract.function_at_pc(999), None);
2515 assert_eq!(contract.functions[0].declaration_line, 2);
2516 assert_eq!(contract.effective_line(0, 3), Some(3));
2517 assert_eq!(contract.effective_line(0, 6), Some(1));
2518 assert_eq!(contract.effective_line(0, 0), None);
2519 assert_eq!(contract.effective_line(0, 12), None);
2520 assert_eq!(contract.effective_line(7, 1), None);
2521
2522 assert_eq!(normalize_address("0XAbC"), "0xabc");
2523 assert_eq!(
2524 address_from_word("0x000000000000000000000000AAAA000000000000000000000000000000000001"),
2525 Some("0xaaaa000000000000000000000000000000000001".to_owned())
2526 );
2527 assert_eq!(address_from_word("0x0"), None);
2528 assert_eq!(address_from_word("zz"), None);
2529 }
2530}