Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat: add support for js tracer to geth trace, fix different return types #2064

Merged
merged 3 commits into from
Jan 31, 2023
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Unreleased

- Add support for custom JavaScript tracer to `debug_traceCall` and `debug_traceTransaction` [#2064](https://github.com/gakonst/ethers-rs/pull/2064)
- Add a `Send` bound to the `IntoFuture` implementation of `ContractCall` [#2083](https://github.com/gakonst/ethers-rs/pull/2083)
- Bump [`svm-rs`](https://github.com/roynalnaruto/svm-rs) dependency to fix conflicts with Rust Crytpo packages [#2051](https://github.com/gakonst/ethers-rs/pull/2051)
- Avoid unnecessary allocations in `utils` [#2046](https://github.com/gakonst/ethers-rs/pull/2046)
Expand Down
80 changes: 61 additions & 19 deletions ethers-core/src/types/trace/geth.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
use crate::{
types::{Bytes, H256, U256},
types::{Address, Bytes, NameOrAddress, H256, U256},
utils::from_int_or_hex,
};
use serde::{Deserialize, Serialize, Serializer};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::BTreeMap;

// https://github.com/ethereum/go-ethereum/blob/a9ef135e2dd53682d106c6a2aede9187026cc1de/eth/tracers/logger/logger.go#L406-L411
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct GethTrace {
pub struct DefaultFrame {
pub failed: bool,
#[serde(deserialize_with = "from_int_or_hex")]
pub gas: U256,
#[serde(serialize_with = "serialize_bytes", rename = "returnValue")]
#[serde(rename = "returnValue")]
pub return_value: Bytes,
#[serde(rename = "structLogs")]
pub struct_logs: Vec<StructLog>,
Expand All @@ -21,34 +22,83 @@ pub struct GethTrace {
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct StructLog {
pub depth: u64,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
pub gas: u64,
#[serde(rename = "gasCost")]
pub gas_cost: u64,
/// ref <https://github.com/ethereum/go-ethereum/blob/366d2169fbc0e0f803b68c042b77b6b480836dbc/eth/tracers/logger/logger.go#L450-L452>
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub memory: Option<Vec<String>>,
pub op: String,
pub pc: u64,
#[serde(rename = "refund", skip_serializing_if = "Option::is_none")]
#[serde(default, rename = "refund", skip_serializing_if = "Option::is_none")]
pub refund_counter: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub stack: Option<Vec<U256>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default, skip_serializing_if = "Option::is_none")]
pub storage: Option<BTreeMap<H256, H256>>,
}

// https://github.com/ethereum/go-ethereum/blob/a9ef135e2dd53682d106c6a2aede9187026cc1de/eth/tracers/native/call.go#L37
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CallFrame {
#[serde(rename = "type")]
pub typ: String,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vid201 Why use typ instead of type ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KuTuGu AFAIK type is a keyword in Rust and cannot be used for variable/property name.

pub from: Address,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub to: Option<NameOrAddress>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<U256>,
#[serde(deserialize_with = "from_int_or_hex")]
pub gas: U256,
#[serde(deserialize_with = "from_int_or_hex", rename = "gasUsed")]
pub gas_used: U256,
pub input: Bytes,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub output: Option<Bytes>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub calls: Option<Vec<CallFrame>>,
}

#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum GethTraceFrame {
Default(DefaultFrame),
CallTracer(CallFrame),
}

#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum GethTrace {
Known(GethTraceFrame),
Unknown(Value),
}

/// Available built-in tracers
///
/// See <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>
pub enum GethDebugTracerType {
/// callTracer (native)
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
pub enum GethDebugBuiltInTracerType {
#[serde(rename = "callTracer")]
CallTracer,
}

/// Available tracers
///
/// See <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers> and <https://geth.ethereum.org/docs/developers/evm-tracing/custom-tracer>
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
#[serde(untagged)]
pub enum GethDebugTracerType {
/// built-in tracer
BuiltInTracer(GethDebugBuiltInTracerType),

/// custom JS tracer
JsTracer(String),
}

/// Bindings for additional `debug_traceTransaction` options
///
/// See <https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracetransaction>
Expand Down Expand Up @@ -79,11 +129,3 @@ pub struct GethDebugTracingCallOptions {
pub tracing_options: GethDebugTracingOptions,
// TODO: Add stateoverrides and blockoverrides options
}

fn serialize_bytes<S, T>(x: T, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
T: AsRef<[u8]>,
{
s.serialize_str(&hex::encode(x.as_ref()))
}
22 changes: 0 additions & 22 deletions examples/transactions/examples/trace.rs

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
use ethers::prelude::*;
use ethers::{
core::types::GethDebugTracingOptions,
providers::{Http, Middleware, Provider},
types::{
Address, BlockId, Bytes, GethDebugBuiltInTracerType, GethDebugTracerType,
GethDebugTracingCallOptions, TransactionRequest,
},
};
use eyre::Result;
use std::str::FromStr;

Expand All @@ -14,7 +21,9 @@ async fn main() -> Result<()> {
tracing_options: GethDebugTracingOptions {
disable_storage: Some(true),
enable_memory: Some(false),
tracer: Some(GethDebugTracerType::CallTracer),
tracer: Some(GethDebugTracerType::BuiltInTracer(
GethDebugBuiltInTracerType::CallTracer,
)),
..Default::default()
},
};
Expand Down
47 changes: 47 additions & 0 deletions examples/transactions/examples/trace_transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
use ethers::{
core::types::{GethDebugTracingOptions, H256},
providers::{Http, Middleware, Provider},
types::{GethDebugBuiltInTracerType, GethDebugTracerType},
};
use eyre::Result;
use std::str::FromStr;

/// use `debug_traceTransaction` to fetch traces
/// requires, a valid endpoint in `RPC_URL` env var that supports `debug_traceTransaction`
#[tokio::main]
async fn main() -> Result<()> {
if let Ok(url) = std::env::var("RPC_URL") {
let client = Provider::<Http>::try_from(url)?;
let tx_hash = "0x97a02abf405d36939e5b232a5d4ef5206980c5a6661845436058f30600c52df7";
let h: H256 = H256::from_str(tx_hash)?;

// default tracer
let options = GethDebugTracingOptions::default();
let traces = client.debug_trace_transaction(h, options).await?;
println!("{traces:?}");

// call tracer
let options = GethDebugTracingOptions {
disable_storage: Some(true),
enable_memory: Some(false),
tracer: Some(GethDebugTracerType::BuiltInTracer(
GethDebugBuiltInTracerType::CallTracer,
)),
..Default::default()
};
let traces = client.debug_trace_transaction(h, options).await?;
println!("{traces:?}");

// js tracer
let options = GethDebugTracingOptions {
disable_storage: Some(true),
enable_memory: Some(false),
tracer: Some(GethDebugTracerType::JsTracer(String::from("{data: [], fault: function(log) {}, step: function(log) { if(log.op.toString() == \"DELEGATECALL\") this.data.push(log.stack.peek(0)); }, result: function() { return this.data; }}"))),
..Default::default()
};
let traces = client.debug_trace_transaction(h, options).await?;
println!("{traces:?}");
}

Ok(())
}