Skip to content

Commit

Permalink
refactor: use revm-inspectors traces for debugger (#8249)
Browse files Browse the repository at this point in the history
* move calldata to DebugNode

* refactor: use tracer from inspectors for debugger

* fix: rm hex

* clippy

* bump inspectors

* newline

* docs

* fix

* fmt
  • Loading branch information
klkvr committed Jun 27, 2024
1 parent c8db1e4 commit 52c2086
Show file tree
Hide file tree
Showing 29 changed files with 280 additions and 644 deletions.
7 changes: 3 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ solang-parser = "=0.3.3"
# no default features to avoid c-kzg
revm = { version = "10.0.0", default-features = false }
revm-primitives = { version = "5.0.0", default-features = false }
revm-inspectors = { version = "0.1.2", features = ["serde"] }
revm-inspectors = { version = "0.2", features = ["serde"] }

## ethers
ethers-contract-abigen = { version = "2.0.14", default-features = false }
Expand Down Expand Up @@ -205,7 +205,6 @@ quote = "1.0"
syn = "2.0"
prettyplease = "0.2.20"
ahash = "0.8"
arrayvec = "0.7"
base64 = "0.22"
chrono = { version = "0.4", default-features = false, features = [
"clock",
Expand Down
13 changes: 3 additions & 10 deletions crates/cli/src/utils/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ use foundry_compilers::{
use foundry_config::{error::ExtractConfigError, figment::Figment, Chain, Config, NamedChain};
use foundry_debugger::Debugger;
use foundry_evm::{
debug::DebugArena,
executors::{DeployResult, EvmError, RawCallResult},
opts::EvmOpts,
traces::{
Expand Down Expand Up @@ -315,20 +314,14 @@ pub fn read_constructor_args_file(constructor_args_path: PathBuf) -> Result<Vec<
pub struct TraceResult {
pub success: bool,
pub traces: Option<Traces>,
pub debug: Option<DebugArena>,
pub gas_used: u64,
}

impl TraceResult {
/// Create a new [`TraceResult`] from a [`RawCallResult`].
pub fn from_raw(raw: RawCallResult, trace_kind: TraceKind) -> Self {
let RawCallResult { gas_used, traces, reverted, debug, .. } = raw;
Self {
success: !reverted,
traces: traces.map(|arena| vec![(trace_kind, arena)]),
debug,
gas_used,
}
let RawCallResult { gas_used, traces, reverted, .. } = raw;
Self { success: !reverted, traces: traces.map(|arena| vec![(trace_kind, arena)]), gas_used }
}
}

Expand Down Expand Up @@ -391,7 +384,7 @@ pub async fn handle_traces(
Default::default()
};
let mut debugger = Debugger::builder()
.debug_arena(result.debug.as_ref().expect("missing debug arena"))
.traces(result.traces.expect("missing traces"))
.decoder(&decoder)
.sources(sources)
.build();
Expand Down
1 change: 1 addition & 0 deletions crates/debugger/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ eyre.workspace = true
ratatui = { version = "0.26", default-features = false, features = ["crossterm"] }
revm.workspace = true
tracing.workspace = true
serde.workspace = true
3 changes: 3 additions & 0 deletions crates/debugger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@ mod op;

mod tui;
pub use tui::{Debugger, DebuggerBuilder, ExitReason};

mod node;
pub use node::DebugNode;
85 changes: 85 additions & 0 deletions crates/debugger/src/node.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
use alloy_primitives::{Address, Bytes};
use foundry_evm_traces::{CallKind, CallTraceArena};
use revm_inspectors::tracing::types::{CallTraceStep, TraceMemberOrder};
use serde::{Deserialize, Serialize};

/// Represents a part of the execution frame before the next call or end of the execution.
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct DebugNode {
/// Execution context.
///
/// Note that this is the address of the *code*, not necessarily the address of the storage.
pub address: Address,
/// The kind of call this is.
pub kind: CallKind,
/// Calldata of the call.
pub calldata: Bytes,
/// The debug steps.
pub steps: Vec<CallTraceStep>,
}

impl DebugNode {
/// Creates a new debug node.
pub fn new(
address: Address,
kind: CallKind,
steps: Vec<CallTraceStep>,
calldata: Bytes,
) -> Self {
Self { address, kind, steps, calldata }
}
}

/// Flattens given [CallTraceArena] into a list of [DebugNode]s.
///
/// This is done by recursively traversing the call tree and collecting the steps in-between the
/// calls.
pub fn flatten_call_trace(arena: CallTraceArena, out: &mut Vec<DebugNode>) {
#[derive(Debug, Clone, Copy)]
struct PendingNode {
node_idx: usize,
steps_count: usize,
}

fn inner(arena: &CallTraceArena, node_idx: usize, out: &mut Vec<PendingNode>) {
let mut pending = PendingNode { node_idx, steps_count: 0 };
let node = &arena.nodes()[node_idx];
for order in node.ordering.iter() {
match order {
TraceMemberOrder::Call(idx) => {
out.push(pending);
pending.steps_count = 0;
inner(arena, node.children[*idx], out);
}
TraceMemberOrder::Step(_) => {
pending.steps_count += 1;
}
_ => {}
}
}
out.push(pending);
}
let mut nodes = Vec::new();
inner(&arena, 0, &mut nodes);

let mut arena_nodes = arena.into_nodes();

for pending in nodes {
let steps = {
let other_steps =
arena_nodes[pending.node_idx].trace.steps.split_off(pending.steps_count);
std::mem::replace(&mut arena_nodes[pending.node_idx].trace.steps, other_steps)
};

// Skip nodes with empty steps as there's nothing to display for them.
if steps.is_empty() {
continue
}

let call = &arena_nodes[pending.node_idx].trace;
let calldata = if call.kind.is_any_create() { Bytes::new() } else { call.data.clone() };
let node = DebugNode::new(call.address, call.kind, steps, calldata);

out.push(node);
}
}
17 changes: 8 additions & 9 deletions crates/debugger/src/tui/builder.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
//! TUI debugger builder.

use crate::Debugger;
use crate::{node::flatten_call_trace, DebugNode, Debugger};
use alloy_primitives::Address;
use foundry_common::{compile::ContractSources, evm::Breakpoints, get_contract_name};
use foundry_evm_core::debug::{DebugArena, DebugNodeFlat};
use foundry_evm_traces::CallTraceDecoder;
use foundry_evm_traces::{CallTraceArena, CallTraceDecoder, Traces};
use std::collections::HashMap;

/// Debugger builder.
#[derive(Debug, Default)]
#[must_use = "builders do nothing unless you call `build` on them"]
pub struct DebuggerBuilder {
/// Debug traces returned from the EVM execution.
debug_arena: Vec<DebugNodeFlat>,
debug_arena: Vec<DebugNode>,
/// Identified contracts.
identified_contracts: HashMap<Address, String>,
/// Map of source files.
Expand All @@ -30,17 +29,17 @@ impl DebuggerBuilder {

/// Extends the debug arena.
#[inline]
pub fn debug_arenas(mut self, arena: &[DebugArena]) -> Self {
for arena in arena {
self = self.debug_arena(arena);
pub fn traces(mut self, traces: Traces) -> Self {
for (_, arena) in traces {
self = self.trace_arena(arena);
}
self
}

/// Extends the debug arena.
#[inline]
pub fn debug_arena(mut self, arena: &DebugArena) -> Self {
arena.flatten_to(0, &mut self.debug_arena);
pub fn trace_arena(mut self, arena: CallTraceArena) -> Self {
flatten_call_trace(arena, &mut self.debug_arena);
self
}

Expand Down
48 changes: 36 additions & 12 deletions crates/debugger/src/tui/context.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Debugger context and event handler implementation.

use crate::{Debugger, ExitReason};
use alloy_primitives::Address;
use crate::{DebugNode, Debugger, ExitReason};
use alloy_primitives::{hex, Address};
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers, MouseEvent, MouseEventKind};
use foundry_evm_core::debug::{DebugNodeFlat, DebugStep};
use revm_inspectors::tracing::types::CallKind;
use revm::interpreter::opcode;
use revm_inspectors::tracing::types::{CallKind, CallTraceStep};
use std::ops::ControlFlow;

/// This is currently used to remember last scroll position so screen doesn't wiggle as much.
Expand Down Expand Up @@ -84,11 +84,11 @@ impl<'a> DebuggerContext<'a> {
self.gen_opcode_list();
}

pub(crate) fn debug_arena(&self) -> &[DebugNodeFlat] {
pub(crate) fn debug_arena(&self) -> &[DebugNode] {
&self.debugger.debug_arena
}

pub(crate) fn debug_call(&self) -> &DebugNodeFlat {
pub(crate) fn debug_call(&self) -> &DebugNode {
&self.debug_arena()[self.draw_memory.inner_call_index]
}

Expand All @@ -103,19 +103,21 @@ impl<'a> DebuggerContext<'a> {
}

/// Returns the current debug steps.
pub(crate) fn debug_steps(&self) -> &[DebugStep] {
pub(crate) fn debug_steps(&self) -> &[CallTraceStep] {
&self.debug_call().steps
}

/// Returns the current debug step.
pub(crate) fn current_step(&self) -> &DebugStep {
pub(crate) fn current_step(&self) -> &CallTraceStep {
&self.debug_steps()[self.current_step]
}

fn gen_opcode_list(&mut self) {
self.opcode_list.clear();
let debug_steps = &self.debugger.debug_arena[self.draw_memory.inner_call_index].steps;
self.opcode_list.extend(debug_steps.iter().map(DebugStep::pretty_opcode));
for (i, step) in debug_steps.iter().enumerate() {
self.opcode_list.push(pretty_opcode(step, debug_steps.get(i + 1)));
}
}

fn gen_opcode_list_if_necessary(&mut self) {
Expand All @@ -127,8 +129,8 @@ impl<'a> DebuggerContext<'a> {

fn active_buffer(&self) -> &[u8] {
match self.active_buffer {
BufferKind::Memory => &self.current_step().memory,
BufferKind::Calldata => &self.current_step().calldata,
BufferKind::Memory => self.current_step().memory.as_bytes(),
BufferKind::Calldata => &self.debug_call().calldata,
BufferKind::Returndata => &self.current_step().returndata,
}
}
Expand Down Expand Up @@ -186,7 +188,8 @@ impl DebuggerContext<'_> {
}),
// Scroll down the stack
KeyCode::Char('J') => self.repeat(|this| {
let max_stack = this.current_step().stack.len().saturating_sub(1);
let max_stack =
this.current_step().stack.as_ref().map_or(0, |s| s.len()).saturating_sub(1);
if this.draw_memory.current_stack_startline < max_stack {
this.draw_memory.current_stack_startline += 1;
}
Expand Down Expand Up @@ -345,3 +348,24 @@ fn buffer_as_number(s: &str) -> usize {
const MAX: usize = 100_000;
s.parse().unwrap_or(MIN).clamp(MIN, MAX)
}

fn pretty_opcode(step: &CallTraceStep, next_step: Option<&CallTraceStep>) -> String {
let op = step.op;
let instruction = op.get();
let push_size = if (opcode::PUSH1..=opcode::PUSH32).contains(&instruction) {
(instruction - opcode::PUSH0) as usize
} else {
0
};
if push_size == 0 {
return step.op.to_string();
}

// Get push byte as the top-most stack item on the next step
if let Some(pushed) = next_step.and_then(|s| s.stack.as_ref()).and_then(|s| s.last()) {
let bytes = &pushed.to_be_bytes_vec()[32 - push_size..];
format!("{op}(0x{})", hex::encode(bytes))
} else {
op.to_string()
}
}
Loading

0 comments on commit 52c2086

Please sign in to comment.