diff --git a/CHANGELOG.md b/CHANGELOG.md index 214b2531..c2719e44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/ethers-core/src/types/trace/geth.rs b/ethers-core/src/types/trace/geth.rs index c346a299..ededdadb 100644 --- a/ethers-core/src/types/trace/geth.rs +++ b/ethers-core/src/types/trace/geth.rs @@ -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, @@ -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, pub gas: u64, #[serde(rename = "gasCost")] pub gas_cost: u64, /// ref - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub memory: Option>, 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, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub stack: Option>, - #[serde(skip_serializing_if = "Option::is_none")] + #[serde(default, skip_serializing_if = "Option::is_none")] pub storage: Option>, } +// 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, + pub from: Address, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub to: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub value: Option, + #[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, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub error: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub calls: Option>, +} + #[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 -pub enum GethDebugTracerType { - /// callTracer (native) +#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)] +pub enum GethDebugBuiltInTracerType { #[serde(rename = "callTracer")] CallTracer, } +/// Available tracers +/// +/// See and +#[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 @@ -79,11 +129,3 @@ pub struct GethDebugTracingCallOptions { pub tracing_options: GethDebugTracingOptions, // TODO: Add stateoverrides and blockoverrides options } - -fn serialize_bytes(x: T, s: S) -> Result -where - S: Serializer, - T: AsRef<[u8]>, -{ - s.serialize_str(&hex::encode(x.as_ref())) -} diff --git a/examples/transactions/examples/trace.rs b/examples/transactions/examples/trace.rs deleted file mode 100644 index 35ea370d..00000000 --- a/examples/transactions/examples/trace.rs +++ /dev/null @@ -1,22 +0,0 @@ -use ethers::{ - core::types::{GethDebugTracingOptions, H256}, - providers::{Http, Middleware, Provider}, -}; -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::::try_from(url)?; - let tx_hash = "0x97a02abf405d36939e5b232a5d4ef5206980c5a6661845436058f30600c52df7"; - let h: H256 = H256::from_str(tx_hash)?; - let options: GethDebugTracingOptions = GethDebugTracingOptions::default(); - let traces = client.debug_trace_transaction(h, options).await?; - println!("{traces:?}"); - } - - Ok(()) -} diff --git a/examples/geth_trace_call.rs b/examples/transactions/examples/trace_call.rs similarity index 74% rename from examples/geth_trace_call.rs rename to examples/transactions/examples/trace_call.rs index 00851951..67551d6e 100644 --- a/examples/geth_trace_call.rs +++ b/examples/transactions/examples/trace_call.rs @@ -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; @@ -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() }, }; diff --git a/examples/transactions/examples/trace_transaction.rs b/examples/transactions/examples/trace_transaction.rs new file mode 100644 index 00000000..028e4162 --- /dev/null +++ b/examples/transactions/examples/trace_transaction.rs @@ -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::::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(()) +}