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

Commit 14be402

Browse files
authored
feat: add support for Geth built-in tracer and config (#2121)
* feat: add 4byteTracer * feat: add prestateTracer * feat: add noopTracer * refactor: geth tracer layout * feat: call tracer config * fix: compatible with legacy call trace * feat: pre state tracer config * test: serialize/deserialize tracer * refactor: deserialize type * style: update refs * chore: update CHANGELOG.md * style: json string to object
1 parent e5e4da0 commit 14be402

File tree

13 files changed

+460
-24
lines changed

13 files changed

+460
-24
lines changed

Diff for: CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
- Add abigen support for hardhat generated bytecode json format [#2012](https://github.com/gakonst/ethers-rs/pull/2012)
1414
- Fix typo in `RwClient` docs for `write_client` method.
1515
- Add support for Geth `debug_traceCall` [#1949](https://github.com/gakonst/ethers-rs/pull/1949)
16+
- Add support for Geth built-in tracer and config [#2121](https://github.com/gakonst/ethers-rs/pull/2121)
1617
- Graceful handling of WebSocket transport errors [#1889](https://github.com/gakonst/ethers-rs/issues/1889) [#1815](https://github.com/gakonst/ethers-rs/issues/1815)
1718
- `MiddlewareBuilder` trait to instantiate a `Provider` as `Middleware` layers.
1819
- An `Event` builder can be instantiated specifying the event filter type, without the need to instantiate a contract.

Diff for: ethers-core/src/types/trace/geth.rs

+60-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1+
mod call;
2+
mod four_byte;
3+
mod noop;
4+
mod pre_state;
5+
6+
pub use self::{
7+
call::{CallConfig, CallFrame},
8+
four_byte::FourByteFrame,
9+
noop::NoopFrame,
10+
pre_state::{PreStateConfig, PreStateFrame},
11+
};
112
use crate::{
2-
types::{Address, Bytes, NameOrAddress, H256, U256},
13+
types::{Bytes, H256, U256},
314
utils::from_int_or_hex,
415
};
516
use serde::{Deserialize, Serialize};
@@ -40,34 +51,14 @@ pub struct StructLog {
4051
pub storage: Option<BTreeMap<H256, H256>>,
4152
}
4253

43-
// https://github.com/ethereum/go-ethereum/blob/a9ef135e2dd53682d106c6a2aede9187026cc1de/eth/tracers/native/call.go#L37
44-
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
45-
pub struct CallFrame {
46-
#[serde(rename = "type")]
47-
pub typ: String,
48-
pub from: Address,
49-
#[serde(default, skip_serializing_if = "Option::is_none")]
50-
pub to: Option<NameOrAddress>,
51-
#[serde(default, skip_serializing_if = "Option::is_none")]
52-
pub value: Option<U256>,
53-
#[serde(deserialize_with = "from_int_or_hex")]
54-
pub gas: U256,
55-
#[serde(deserialize_with = "from_int_or_hex", rename = "gasUsed")]
56-
pub gas_used: U256,
57-
pub input: Bytes,
58-
#[serde(default, skip_serializing_if = "Option::is_none")]
59-
pub output: Option<Bytes>,
60-
#[serde(default, skip_serializing_if = "Option::is_none")]
61-
pub error: Option<String>,
62-
#[serde(default, skip_serializing_if = "Option::is_none")]
63-
pub calls: Option<Vec<CallFrame>>,
64-
}
65-
6654
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
6755
#[serde(untagged)]
6856
pub enum GethTraceFrame {
6957
Default(DefaultFrame),
58+
NoopTracer(NoopFrame),
59+
FourByteTracer(FourByteFrame),
7060
CallTracer(CallFrame),
61+
PreStateTracer(PreStateFrame),
7162
}
7263

7364
impl From<DefaultFrame> for GethTraceFrame {
@@ -76,12 +67,30 @@ impl From<DefaultFrame> for GethTraceFrame {
7667
}
7768
}
7869

70+
impl From<FourByteFrame> for GethTraceFrame {
71+
fn from(value: FourByteFrame) -> Self {
72+
GethTraceFrame::FourByteTracer(value)
73+
}
74+
}
75+
7976
impl From<CallFrame> for GethTraceFrame {
8077
fn from(value: CallFrame) -> Self {
8178
GethTraceFrame::CallTracer(value)
8279
}
8380
}
8481

82+
impl From<PreStateFrame> for GethTraceFrame {
83+
fn from(value: PreStateFrame) -> Self {
84+
GethTraceFrame::PreStateTracer(value)
85+
}
86+
}
87+
88+
impl From<NoopFrame> for GethTraceFrame {
89+
fn from(value: NoopFrame) -> Self {
90+
GethTraceFrame::NoopTracer(value)
91+
}
92+
}
93+
8594
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
8695
#[serde(untagged)]
8796
pub enum GethTrace {
@@ -106,8 +115,21 @@ impl From<Value> for GethTrace {
106115
/// See <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>
107116
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
108117
pub enum GethDebugBuiltInTracerType {
118+
#[serde(rename = "4byteTracer")]
119+
FourByteTracer,
109120
#[serde(rename = "callTracer")]
110121
CallTracer,
122+
#[serde(rename = "prestateTracer")]
123+
PreStateTracer,
124+
#[serde(rename = "noopTracer")]
125+
NoopTracer,
126+
}
127+
128+
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
129+
#[serde(untagged)]
130+
pub enum GethDebugBuiltInTracerConfig {
131+
CallTracer(CallConfig),
132+
PreStateTracer(PreStateConfig),
111133
}
112134

113135
/// Available tracers
@@ -123,6 +145,16 @@ pub enum GethDebugTracerType {
123145
JsTracer(String),
124146
}
125147

148+
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
149+
#[serde(untagged)]
150+
pub enum GethDebugTracerConfig {
151+
/// built-in tracer
152+
BuiltInTracer(GethDebugBuiltInTracerConfig),
153+
154+
/// custom JS tracer
155+
JsTracer(Value),
156+
}
157+
126158
/// Bindings for additional `debug_traceTransaction` options
127159
///
128160
/// See <https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracetransaction>
@@ -139,6 +171,10 @@ pub struct GethDebugTracingOptions {
139171
pub enable_return_data: Option<bool>,
140172
#[serde(default, skip_serializing_if = "Option::is_none")]
141173
pub tracer: Option<GethDebugTracerType>,
174+
/// tracerConfig is slated for Geth v1.11.0
175+
/// See <https://github.com/ethereum/go-ethereum/issues/26513>
176+
#[serde(default, skip_serializing_if = "Option::is_none")]
177+
pub tracer_config: Option<GethDebugTracerConfig>,
142178
#[serde(default, skip_serializing_if = "Option::is_none")]
143179
pub timeout: Option<String>,
144180
}

Diff for: ethers-core/src/types/trace/geth/call.rs

+87
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
use crate::{
2+
types::{Address, Bytes, NameOrAddress, H256, U256},
3+
utils::from_int_or_hex,
4+
};
5+
use serde::{Deserialize, Serialize};
6+
7+
// https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/call.go#L44
8+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
9+
pub struct CallFrame {
10+
#[serde(rename = "type")]
11+
pub typ: String,
12+
pub from: Address,
13+
#[serde(default, skip_serializing_if = "Option::is_none")]
14+
pub to: Option<NameOrAddress>,
15+
#[serde(default, skip_serializing_if = "Option::is_none")]
16+
pub value: Option<U256>,
17+
#[serde(default, deserialize_with = "from_int_or_hex")]
18+
pub gas: U256,
19+
#[serde(default, deserialize_with = "from_int_or_hex", rename = "gasUsed")]
20+
pub gas_used: U256,
21+
pub input: Bytes,
22+
#[serde(default, skip_serializing_if = "Option::is_none")]
23+
pub output: Option<Bytes>,
24+
#[serde(default, skip_serializing_if = "Option::is_none")]
25+
pub error: Option<String>,
26+
#[serde(default, skip_serializing_if = "Option::is_none")]
27+
pub calls: Option<Vec<CallFrame>>,
28+
#[serde(default, skip_serializing_if = "Option::is_none")]
29+
pub logs: Option<Vec<CallLogFrame>>,
30+
}
31+
32+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
33+
pub struct CallLogFrame {
34+
#[serde(default, skip_serializing_if = "Option::is_none")]
35+
address: Option<Address>,
36+
#[serde(default, skip_serializing_if = "Option::is_none")]
37+
topics: Option<Vec<H256>>,
38+
#[serde(default, skip_serializing_if = "Option::is_none")]
39+
data: Option<Bytes>,
40+
}
41+
42+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
43+
#[serde(rename_all = "camelCase")]
44+
pub struct CallConfig {
45+
#[serde(default, skip_serializing_if = "Option::is_none")]
46+
pub only_top_call: Option<bool>,
47+
#[serde(default, skip_serializing_if = "Option::is_none")]
48+
pub with_log: Option<bool>,
49+
}
50+
51+
#[cfg(test)]
52+
mod tests {
53+
use super::*;
54+
use crate::types::*;
55+
56+
// See <https://github.com/ethereum/go-ethereum/tree/master/eth/tracers/internal/tracetest/testdata>
57+
const DEFAULT: &str = include_str!("./test_data/call_tracer/default.json");
58+
const LEGACY: &str = include_str!("./test_data/call_tracer/legacy.json");
59+
const ONLY_TOP_CALL: &str = include_str!("./test_data/call_tracer/only_top_call.json");
60+
const WITH_LOG: &str = include_str!("./test_data/call_tracer/with_log.json");
61+
62+
#[test]
63+
fn test_serialize_call_trace() {
64+
let mut opts = GethDebugTracingCallOptions::default();
65+
opts.tracing_options.disable_storage = Some(false);
66+
opts.tracing_options.tracer =
67+
Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer));
68+
opts.tracing_options.tracer_config =
69+
Some(GethDebugTracerConfig::BuiltInTracer(GethDebugBuiltInTracerConfig::CallTracer(
70+
CallConfig { only_top_call: Some(true), with_log: Some(true) },
71+
)));
72+
73+
assert_eq!(
74+
serde_json::to_string(&opts).unwrap(),
75+
r#"{"disableStorage":false,"tracer":"callTracer","tracerConfig":{"onlyTopCall":true,"withLog":true}}"#
76+
);
77+
}
78+
79+
#[test]
80+
fn test_deserialize_call_trace() {
81+
let _trace: CallFrame = serde_json::from_str(DEFAULT).unwrap();
82+
let _trace: CallFrame = serde_json::from_str(LEGACY).unwrap();
83+
let _trace: CallFrame = serde_json::from_str(ONLY_TOP_CALL).unwrap();
84+
let trace: CallFrame = serde_json::from_str(WITH_LOG).unwrap();
85+
let _logs = trace.logs.unwrap();
86+
}
87+
}

Diff for: ethers-core/src/types/trace/geth/four_byte.rs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use serde::{Deserialize, Serialize};
2+
use std::collections::BTreeMap;
3+
4+
// https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/4byte.go#L48
5+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
6+
pub struct FourByteFrame(pub BTreeMap<String, u64>);
7+
8+
#[cfg(test)]
9+
mod tests {
10+
use super::*;
11+
use crate::types::*;
12+
13+
const DEFAULT: &str = r#"{
14+
"0x27dc297e-128": 1,
15+
"0x38cc4831-0": 2,
16+
"0x524f3889-96": 1,
17+
"0xadf59f99-288": 1,
18+
"0xc281d19e-0": 1
19+
}"#;
20+
21+
#[test]
22+
fn test_serialize_four_byte_trace() {
23+
let mut opts = GethDebugTracingCallOptions::default();
24+
opts.tracing_options.tracer =
25+
Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FourByteTracer));
26+
27+
assert_eq!(serde_json::to_string(&opts).unwrap(), r#"{"tracer":"4byteTracer"}"#);
28+
}
29+
30+
#[test]
31+
fn test_deserialize_four_byte_trace() {
32+
let _trace: FourByteFrame = serde_json::from_str(DEFAULT).unwrap();
33+
}
34+
}

Diff for: ethers-core/src/types/trace/geth/noop.rs

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use serde::{Deserialize, Serialize};
2+
use std::collections::BTreeMap;
3+
4+
// https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/noop.go#L34
5+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
6+
pub struct NoopFrame(BTreeMap<Null, Null>);
7+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
8+
struct Null;
9+
10+
#[cfg(test)]
11+
mod tests {
12+
use super::*;
13+
use crate::types::*;
14+
15+
const DEFAULT: &str = r#"{}"#;
16+
17+
#[test]
18+
fn test_serialize_noop_trace() {
19+
let mut opts = GethDebugTracingCallOptions::default();
20+
opts.tracing_options.tracer =
21+
Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::NoopTracer));
22+
23+
assert_eq!(serde_json::to_string(&opts).unwrap(), r#"{"tracer":"noopTracer"}"#);
24+
}
25+
26+
#[test]
27+
fn test_deserialize_noop_trace() {
28+
let _trace: NoopFrame = serde_json::from_str(DEFAULT).unwrap();
29+
}
30+
}

Diff for: ethers-core/src/types/trace/geth/pre_state.rs

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
use crate::{
2+
types::{Address, H256, U256},
3+
utils::from_int_or_hex_opt,
4+
};
5+
use serde::{Deserialize, Serialize};
6+
use std::collections::BTreeMap;
7+
8+
// https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/prestate.go#L38
9+
#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
10+
#[serde(untagged)]
11+
pub enum PreStateFrame {
12+
Default(PreStateMode),
13+
Diff(DiffMode),
14+
}
15+
16+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
17+
pub struct PreStateMode(pub BTreeMap<Address, AccountState>);
18+
19+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
20+
pub struct DiffMode {
21+
pub pre: BTreeMap<Address, AccountState>,
22+
pub post: BTreeMap<Address, AccountState>,
23+
}
24+
25+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
26+
pub struct AccountState {
27+
#[serde(
28+
default,
29+
deserialize_with = "from_int_or_hex_opt",
30+
skip_serializing_if = "Option::is_none"
31+
)]
32+
pub balance: Option<U256>,
33+
#[serde(default, skip_serializing_if = "Option::is_none")]
34+
pub code: Option<String>,
35+
#[serde(
36+
default,
37+
deserialize_with = "from_int_or_hex_opt",
38+
skip_serializing_if = "Option::is_none"
39+
)]
40+
pub nonce: Option<U256>,
41+
#[serde(default, skip_serializing_if = "Option::is_none")]
42+
pub storage: Option<BTreeMap<H256, H256>>,
43+
}
44+
45+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
46+
#[serde(rename_all = "camelCase")]
47+
pub struct PreStateConfig {
48+
#[serde(default, skip_serializing_if = "Option::is_none")]
49+
pub diff_mode: Option<bool>,
50+
}
51+
52+
#[cfg(test)]
53+
mod tests {
54+
use super::*;
55+
use crate::types::*;
56+
57+
// See <https://github.com/ethereum/go-ethereum/tree/master/eth/tracers/internal/tracetest/testdata>
58+
const DEFAULT: &str = include_str!("./test_data/pre_state_tracer/default.json");
59+
const LEGACY: &str = include_str!("./test_data/pre_state_tracer/legacy.json");
60+
const DIFF_MODE: &str = include_str!("./test_data/pre_state_tracer/diff_mode.json");
61+
62+
#[test]
63+
fn test_serialize_pre_state_trace() {
64+
let mut opts = GethDebugTracingCallOptions::default();
65+
opts.tracing_options.disable_storage = Some(false);
66+
opts.tracing_options.tracer =
67+
Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::PreStateTracer));
68+
opts.tracing_options.tracer_config = Some(GethDebugTracerConfig::BuiltInTracer(
69+
GethDebugBuiltInTracerConfig::PreStateTracer(PreStateConfig { diff_mode: Some(true) }),
70+
));
71+
72+
assert_eq!(
73+
serde_json::to_string(&opts).unwrap(),
74+
r#"{"disableStorage":false,"tracer":"prestateTracer","tracerConfig":{"diffMode":true}}"#
75+
);
76+
}
77+
78+
#[test]
79+
fn test_deserialize_pre_state_trace() {
80+
let trace: PreStateFrame = serde_json::from_str(DEFAULT).unwrap();
81+
match trace {
82+
PreStateFrame::Default(PreStateMode(_)) => {}
83+
_ => unreachable!(),
84+
}
85+
let _trace: PreStateFrame = serde_json::from_str(LEGACY).unwrap();
86+
let trace: PreStateFrame = serde_json::from_str(DIFF_MODE).unwrap();
87+
match trace {
88+
PreStateFrame::Diff(DiffMode { pre: _pre, post: _post }) => {}
89+
_ => unreachable!(),
90+
}
91+
}
92+
}

0 commit comments

Comments
 (0)