Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 18 additions & 16 deletions src/cli/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,24 +116,25 @@ pub fn run(args: CallArgs) -> Result<(), TraceError> {
tx_object["value"] = json!(hex_value);
}

let payload = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "debug_traceCall",
"params": [
tx_object,
block_id,
{
"tracer": "callTracer",
"tracerConfig": {
"onlyTopCall": false,
"withLog": args.opts.include_logs,
}
}
]
let payload = trace::rpc_payload(
1,
"debug_traceCall",
json!([
&tx_object,
&block_id,
trace::call_tracer_config(args.opts.include_logs)
]),
);

let prestate_payload = args.opts.include_storage.then(|| {
trace::rpc_payload(
2,
"debug_traceCall",
json!([&tx_object, &block_id, trace::prestate_tracer_config()]),
)
});

trace::execute_and_print(&payload, args.opts)
trace::execute_and_print(&payload, prestate_payload.as_ref(), args.opts)
}

/// Parse a block identifier from user input into a JSON-RPC block parameter.
Expand Down Expand Up @@ -180,6 +181,7 @@ mod tests {
include_args: false,
include_calldata: false,
include_logs: false,
include_storage: false,
no_proxy: false,
no_color: false,
},
Expand Down
92 changes: 79 additions & 13 deletions src/cli/trace.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use crate::utils::{
color::Palette, contract_resolver::ContractResolver, event_formatter::Log, hex_utils, rpc_url,
selector_resolver::SelectorResolver, trace_renderer,
color::Palette,
contract_resolver::ContractResolver,
event_formatter::Log,
hex_utils, rpc_url,
selector_resolver::SelectorResolver,
storage_diff::{self, PrestateDiff},
trace_renderer,
};
use clap::Parser;
use reqwest::blocking::Client;
Expand Down Expand Up @@ -52,6 +57,12 @@ pub struct TraceOpts {
#[arg(long)]
pub include_logs: bool,

/// Include storage changes (state diffs) after the trace.
///
/// Requires an additional RPC call using `prestateTracer` in diff mode.
#[arg(long)]
pub include_storage: bool,

/// Disable system proxy for RPC requests.
#[arg(long)]
pub no_proxy: bool,
Expand Down Expand Up @@ -172,14 +183,39 @@ pub fn validate_hex(data: &str, field: &str) -> Result<(), TraceError> {
Ok(())
}

/// Fetch the chain ID from the RPC node as a decimal string (e.g. `"1"`).
fn fetch_chain_id(client: &Client, rpc_url: &str) -> Result<String, TraceError> {
let payload = serde_json::json!({
/// Build a JSON-RPC request envelope.
pub fn rpc_payload(id: u32, method: &str, params: serde_json::Value) -> serde_json::Value {
let mut v = serde_json::json!({
"jsonrpc": "2.0",
"id": 1,
"method": "eth_chainId",
"params": []
"id": id,
"method": method,
});
v["params"] = params;
v
}

pub fn call_tracer_config(include_logs: bool) -> serde_json::Value {
serde_json::json!({
"tracer": "callTracer",
"tracerConfig": {
"onlyTopCall": false,
"withLog": include_logs,
}
})
}

pub fn prestate_tracer_config() -> serde_json::Value {
serde_json::json!({
"tracer": "prestateTracer",
"tracerConfig": {
"diffMode": true
}
})
}

/// Fetch the chain ID from the RPC node as a decimal string (e.g. `"1"`).
fn fetch_chain_id(client: &Client, rpc_url: &str) -> Result<String, TraceError> {
let payload = rpc_payload(1, "eth_chainId", serde_json::json!([]));

let resp = client.post(rpc_url).json(&payload).send()?;
let body: RpcResponse<String> = resp
Expand Down Expand Up @@ -208,7 +244,14 @@ fn parse_chain_id_hex(hex_str: &str) -> Result<String, TraceError> {
}

/// Send an RPC payload, parse the trace response, and print it.
pub fn execute_and_print(payload: &serde_json::Value, opts: TraceOpts) -> Result<(), TraceError> {
///
/// When `prestate_payload` is provided, a second RPC call is made using
/// `prestateTracer` in diff mode to display storage changes after the trace.
pub fn execute_and_print(
payload: &serde_json::Value,
prestate_payload: Option<&serde_json::Value>,
opts: TraceOpts,
) -> Result<(), TraceError> {
if opts.include_args && !opts.resolve_selectors {
return Err(TraceError::IncludeArgsRequiresResolveSelectors);
}
Expand All @@ -229,8 +272,25 @@ pub fn execute_and_print(payload: &serde_json::Value, opts: TraceOpts) -> Result
};

let resp = client.post(&rpc_url).json(payload).send()?;

let call_trace = parse_rpc_response(resp)?;
let call_trace: CallTrace = parse_rpc_response(resp)?;

let prestate_diff: Option<PrestateDiff> = if let Some(ps) = prestate_payload {
let result = client
.post(&rpc_url)
.json(ps)
.send()
.map_err(TraceError::from)
.and_then(parse_rpc_response);
match result {
Ok(diff) => Some(diff),
Err(e) => {
eprintln!("warning: storage diff unavailable: {e}");
None
}
}
} else {
None
};

let palette = if opts.no_color {
Palette::new(false)
Expand All @@ -250,6 +310,10 @@ pub fn execute_and_print(payload: &serde_json::Value, opts: TraceOpts) -> Result
palette,
);

if let Some(diff) = &prestate_diff {
storage_diff::print_storage_diff(diff, &mut contract_resolver, palette);
}

let warnings: Vec<String> = [
selector_resolver.take_warning(),
contract_resolver.take_warning(),
Expand All @@ -269,7 +333,9 @@ pub fn execute_and_print(payload: &serde_json::Value, opts: TraceOpts) -> Result
}

/// Parse the RPC response, returning the result or an error.
fn parse_rpc_response(resp: reqwest::blocking::Response) -> Result<CallTrace, TraceError> {
fn parse_rpc_response<T: serde::de::DeserializeOwned>(
resp: reqwest::blocking::Response,
) -> Result<T, TraceError> {
let status = resp.status();
let body = resp.text()?;

Expand All @@ -280,7 +346,7 @@ fn parse_rpc_response(resp: reqwest::blocking::Response) -> Result<CallTrace, Tr
)));
}

let rpc_resp: RpcResponse<CallTrace> = serde_json::from_str(&body)
let rpc_resp: RpcResponse<T> = serde_json::from_str(&body)
.map_err(|e| TraceError::Decode(format!("invalid JSON: {e}")))?;

if let Some(err) = rpc_resp.error {
Expand Down
30 changes: 15 additions & 15 deletions src/cli/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,21 @@ pub fn run(args: TxArgs) -> Result<(), TraceError> {
)));
}

let payload = json!({
"jsonrpc": "2.0",
"id": 1,
"method": "debug_traceTransaction",
"params": [
args.tx_hash,
{
"tracer": "callTracer",
"tracerConfig": {
"onlyTopCall": false,
"withLog": args.opts.include_logs,
}
}
]
let TxArgs { tx_hash, opts } = args;

let payload = trace::rpc_payload(
1,
"debug_traceTransaction",
json!([&tx_hash, trace::call_tracer_config(opts.include_logs)]),
);

let prestate_payload = opts.include_storage.then(|| {
trace::rpc_payload(
2,
"debug_traceTransaction",
json!([&tx_hash, trace::prestate_tracer_config()]),
)
});

trace::execute_and_print(&payload, args.opts)
trace::execute_and_print(&payload, prestate_payload.as_ref(), opts)
}
1 change: 1 addition & 0 deletions src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ pub mod hex_utils;
pub mod precompiles;
pub mod rpc_url;
pub mod selector_resolver;
pub mod storage_diff;
pub mod trace_renderer;
pub mod value_parser;
Loading