Repository: gakonst/ethers-rs
Pull request #2103 (2023-02-01 19:13:26)

Refactor factories to use `Borrow<M>`
Depends on #2082 

## Motivation

- Continue refactoring types that take `Arc<M>` to take `B: Borrow<M>` instead.
- Provide backwards-compatible aliases

## Solution

- Refactor `ethers_contracts::factory` module to be abstract over B
- provide type aliases 

## PR Checklist

-   [ ] Added Tests
-   [x] Added Documentation
-   [x] Updated the changelog
-   [ ] Breaking changes



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f945ae34..5510068b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,6 +13,7 @@
 - Add abigen support for hardhat generated bytecode json format [#2012](https://github.com/gakonst/ethers-rs/pull/2012)
 - Fix typo in `RwClient` docs for `write_client` method.
 - Add support for Geth `debug_traceCall` [#1949](https://github.com/gakonst/ethers-rs/pull/1949)
+- Add support for Geth built-in tracer and config [#2121](https://github.com/gakonst/ethers-rs/pull/2121)
 - Graceful handling of WebSocket transport errors [#1889](https://github.com/gakonst/ethers-rs/issues/1889) [#1815](https://github.com/gakonst/ethers-rs/issues/1815)
 - `MiddlewareBuilder` trait to instantiate a `Provider` as `Middleware` layers.
 - An `Event` builder can be instantiated specifying the event filter type, without the need to instantiate a contract.
@@ -298,6 +299,8 @@
 
 ### Unreleased
 
+- Make `Factory` objects generic over the borrow trait, to allow non-arc mware
+  [#2103](https://github.com/gakonst/ethers-rs/pull/2103)
 - Make `Contract` objects generic over the borrow trait, to allow non-arc mware
   [#2082](https://github.com/gakonst/ethers-rs/pull/2082)
 - Return pending transaction from `Multicall::send`
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index c54418a9..b842b120 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -243,7 +243,7 @@ where
     /// Returns an [`Event`](crate::builders::Event) builder for the provided event.
     /// This function operates in a static context, then it does not require a `self`
     /// to reference to instantiate an [`Event`](crate::builders::Event) builder.
-    pub fn event_of_type<D: EthEvent>(client: &Arc<M>) -> Event<M, D> {
+    pub fn event_of_type<D: EthEvent>(client: &M) -> Event<M, D> {
         Event {
             provider: client,
             filter: Filter::new().event(&D::abi_signature()),
diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs
index bffb9045..d3248150 100644
--- a/ethers-contract/src/factory.rs
+++ b/ethers-contract/src/factory.rs
@@ -1,4 +1,4 @@
-use crate::{Contract, ContractError};
+use crate::{ContractError, ContractInstance};
 
 use ethers_core::{
     abi::{Abi, Token, Tokenize},
@@ -15,33 +15,62 @@ use ethers_providers::{
 #[cfg(not(feature = "legacy"))]
 use ethers_core::types::Eip1559TransactionRequest;
 
-use std::{marker::PhantomData, sync::Arc};
+use std::{borrow::Borrow, marker::PhantomData, sync::Arc};
 
-/// Helper which manages the deployment transaction of a smart contract.
+/// `ContractDeployer` is a [`ContractDeploymentTx`] object with an
+/// [`Arc`] middleware. This type alias exists to preserve backwards
+/// compatibility with less-abstract Contracts.
 ///
-/// This is just a wrapper type for [Deployer] with an additional type to convert the [Contract]
-/// that the deployer returns when sending the transaction.
+/// For full usage docs, see [`ContractDeploymentTx`].
+pub type ContractDeployer<M, C> = ContractDeploymentTx<Arc<M>, M, C>;
+
+/// `ContractFactory` is a [`DeploymentTxFactory`] object with an
+/// [`Arc`] middleware. This type alias exists to preserve backwards
+/// compatibility with less-abstract Contracts.
+///
+/// For full usage docs, see [`DeploymentTxFactory`].
+pub type ContractFactory<M> = DeploymentTxFactory<Arc<M>, M>;
+
+/// Helper which manages the deployment transaction of a smart contract. It
+/// wraps a deployment transaction, and retrieves the contract address output
+/// by it.
+///
+/// Currently, we recommend using the [`ContractDeployer`] type alias.
 #[derive(Debug)]
-#[must_use = "Deployer does nothing unless you `send` it"]
-pub struct ContractDeployer<M, C> {
+#[must_use = "DeploymentTx does nothing unless you `send` it"]
+pub struct ContractDeploymentTx<B, M, C> {
     /// the actual deployer, exposed for overriding the defaults
-    pub deployer: Deployer<M>,
+    pub deployer: Deployer<B, M>,
     /// marker for the `Contract` type to create afterwards
     ///
     /// this type will be used to construct it via `From::from(Contract)`
     _contract: PhantomData<C>,
 }
 
-impl<M, C> Clone for ContractDeployer<M, C> {
+impl<B, M, C> Clone for ContractDeploymentTx<B, M, C>
+where
+    B: Clone,
+{
     fn clone(&self) -> Self {
-        ContractDeployer { deployer: self.deployer.clone(), _contract: self._contract }
+        ContractDeploymentTx { deployer: self.deployer.clone(), _contract: self._contract }
     }
 }
 
-impl<M: Middleware, C: From<Contract<M>>> ContractDeployer<M, C> {
-    /// Create a new instance of this [ContractDeployer]
-    pub fn new(deployer: Deployer<M>) -> Self {
-        Self { deployer, _contract: Default::default() }
+impl<B, M, C> From<Deployer<B, M>> for ContractDeploymentTx<B, M, C> {
+    fn from(deployer: Deployer<B, M>) -> Self {
+        Self { deployer, _contract: PhantomData }
+    }
+}
+
+impl<B, M, C> ContractDeploymentTx<B, M, C>
+where
+    B: Borrow<M> + Clone,
+    M: Middleware,
+    C: From<ContractInstance<B, M>>,
+{
+    /// Create a new instance of this from a deployer.
+    pub fn new(deployer: Deployer<B, M>) -> Self {
+        Self { deployer, _contract: PhantomData }
     }
 
     /// Sets the number of confirmations to wait for the contract deployment transaction
@@ -148,7 +177,7 @@ impl<M: Middleware, C: From<Contract<M>>> ContractDeployer<M, C> {
     }
 
     /// Returns a pointer to the deployer's client
-    pub fn client(&self) -> Arc<M> {
+    pub fn client(&self) -> &M {
         self.deployer.client()
     }
 }
@@ -156,16 +185,20 @@ impl<M: Middleware, C: From<Contract<M>>> ContractDeployer<M, C> {
 /// Helper which manages the deployment transaction of a smart contract
 #[derive(Debug)]
 #[must_use = "Deployer does nothing unless you `send` it"]
-pub struct Deployer<M> {
+pub struct Deployer<B, M> {
     /// The deployer's transaction, exposed for overriding the defaults
     pub tx: TypedTransaction,
     abi: Abi,
-    client: Arc<M>,
+    client: B,
     confs: usize,
     block: BlockNumber,
+    _m: PhantomData<M>,
 }
 
-impl<M> Clone for Deployer<M> {
+impl<B, M> Clone for Deployer<B, M>
+where
+    B: Clone,
+{
     fn clone(&self) -> Self {
         Deployer {
             tx: self.tx.clone(),
@@ -173,11 +206,16 @@ impl<M> Clone for Deployer<M> {
             client: self.client.clone(),
             confs: self.confs,
             block: self.block,
+            _m: PhantomData,
         }
     }
 }
 
-impl<M: Middleware> Deployer<M> {
+impl<B, M> Deployer<B, M>
+where
+    B: Borrow<M> + Clone,
+    M: Middleware,
+{
     /// Sets the number of confirmations to wait for the contract deployment transaction
     pub fn confirmations<T: Into<usize>>(mut self, confirmations: T) -> Self {
         self.confs = confirmations.into();
@@ -206,6 +244,7 @@ impl<M: Middleware> Deployer<M> {
     /// Note: this function _does not_ send a transaction from your account
     pub async fn call(&self) -> Result<(), ContractError<M>> {
         self.client
+            .borrow()
             .call(&self.tx, Some(self.block.into()))
             .await
             .map_err(ContractError::MiddlewareError)?;
@@ -220,13 +259,13 @@ impl<M: Middleware> Deployer<M> {
     ///
     /// Note: this function _does not_ send a transaction from your account
     pub fn call_raw(&self) -> CallBuilder<'_, M::Provider> {
-        self.client.provider().call_raw(&self.tx).block(self.block.into())
+        self.client.borrow().provider().call_raw(&self.tx).block(self.block.into())
     }
 
     /// Broadcasts the contract deployment transaction and after waiting for it to
     /// be sufficiently confirmed (default: 1), it returns a [`Contract`](crate::Contract)
     /// struct at the deployed contract's address.
-    pub async fn send(self) -> Result<Contract<M>, ContractError<M>> {
+    pub async fn send(self) -> Result<ContractInstance<B, M>, ContractError<M>> {
         let (contract, _) = self.send_with_receipt().await?;
         Ok(contract)
     }
@@ -237,9 +276,10 @@ impl<M: Middleware> Deployer<M> {
     /// and the corresponding [`TransactionReceipt`](ethers_core::types::TransactionReceipt).
     pub async fn send_with_receipt(
         self,
-    ) -> Result<(Contract<M>, TransactionReceipt), ContractError<M>> {
+    ) -> Result<(ContractInstance<B, M>, TransactionReceipt), ContractError<M>> {
         let pending_tx = self
             .client
+            .borrow()
             .send_transaction(self.tx, Some(self.block.into()))
             .await
             .map_err(ContractError::MiddlewareError)?;
@@ -252,7 +292,7 @@ impl<M: Middleware> Deployer<M> {
             .ok_or(ContractError::ContractNotDeployed)?;
         let address = receipt.contract_address.ok_or(ContractError::ContractNotDeployed)?;
 
-        let contract = Contract::new(address, self.abi.clone(), self.client);
+        let contract = ContractInstance::new(address, self.abi.clone(), self.client.clone());
         Ok((contract, receipt))
     }
 
@@ -262,8 +302,8 @@ impl<M: Middleware> Deployer<M> {
     }
 
     /// Returns a pointer to the deployer's client
-    pub fn client(&self) -> Arc<M> {
-        self.client.clone()
+    pub fn client(&self) -> &M {
+        self.client.borrow()
     }
 }
 
@@ -309,31 +349,43 @@ impl<M: Middleware> Deployer<M> {
 /// # Ok(())
 /// # }
 #[derive(Debug)]
-pub struct ContractFactory<M> {
-    client: Arc<M>,
+pub struct DeploymentTxFactory<B, M> {
+    client: B,
     abi: Abi,
     bytecode: Bytes,
+    _m: PhantomData<M>,
 }
 
-impl<M> Clone for ContractFactory<M> {
+impl<B, M> Clone for DeploymentTxFactory<B, M>
+where
+    B: Clone,
+{
     fn clone(&self) -> Self {
-        ContractFactory {
+        DeploymentTxFactory {
             client: self.client.clone(),
             abi: self.abi.clone(),
             bytecode: self.bytecode.clone(),
+            _m: PhantomData,
         }
     }
 }
 
-impl<M: Middleware> ContractFactory<M> {
+impl<B, M> DeploymentTxFactory<B, M>
+where
+    B: Borrow<M> + Clone,
+    M: Middleware,
+{
     /// Creates a factory for deployment of the Contract with bytecode, and the
     /// constructor defined in the abi. The client will be used to send any deployment
     /// transaction.
-    pub fn new(abi: Abi, bytecode: Bytes, client: Arc<M>) -> Self {
-        Self { client, abi, bytecode }
+    pub fn new(abi: Abi, bytecode: Bytes, client: B) -> Self {
+        Self { client, abi, bytecode, _m: PhantomData }
     }
 
-    pub fn deploy_tokens(self, params: Vec<Token>) -> Result<Deployer<M>, ContractError<M>> {
+    pub fn deploy_tokens(self, params: Vec<Token>) -> Result<Deployer<B, M>, ContractError<M>>
+    where
+        B: Clone,
+    {
         // Encode the constructor args & concatenate with the bytecode if necessary
         let data: Bytes = match (self.abi.constructor(), params.is_empty()) {
             (None, false) => return Err(ContractError::ConstructorError),
@@ -353,11 +405,12 @@ impl<M: Middleware> ContractFactory<M> {
         let tx = tx.into();
 
         Ok(Deployer {
-            client: Arc::clone(&self.client), // cheap clone behind the arc
+            client: self.client.clone(),
             abi: self.abi,
             tx,
             confs: 1,
             block: BlockNumber::Latest,
+            _m: PhantomData,
         })
     }
 
@@ -369,7 +422,10 @@ impl<M: Middleware> ContractFactory<M> {
     /// 1. If there are no constructor arguments, you should pass `()` as the argument.
     /// 1. The default poll duration is 7 seconds.
     /// 1. The default number of confirmations is 1 block.
-    pub fn deploy<T: Tokenize>(self, constructor_args: T) -> Result<Deployer<M>, ContractError<M>> {
+    pub fn deploy<T: Tokenize>(
+        self,
+        constructor_args: T,
+    ) -> Result<Deployer<B, M>, ContractError<M>> {
         self.deploy_tokens(constructor_args.into_tokens())
     }
 }
diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs
index 2d4fc645..4a14760b 100644
--- a/ethers-contract/src/lib.rs
+++ b/ethers-contract/src/lib.rs
@@ -15,7 +15,7 @@ mod error;
 pub use error::EthError;
 
 mod factory;
-pub use factory::{ContractDeployer, ContractFactory};
+pub use factory::{ContractDeployer, ContractDeploymentTx, ContractFactory, DeploymentTxFactory};
 
 mod event;
 pub use event::{EthEvent, Event};
diff --git a/ethers-core/src/types/trace/geth.rs b/ethers-core/src/types/trace/geth.rs
index 6c4ca3ac..630810db 100644
--- a/ethers-core/src/types/trace/geth.rs
+++ b/ethers-core/src/types/trace/geth.rs
@@ -1,5 +1,16 @@
+mod call;
+mod four_byte;
+mod noop;
+mod pre_state;
+
+pub use self::{
+    call::{CallConfig, CallFrame},
+    four_byte::FourByteFrame,
+    noop::NoopFrame,
+    pre_state::{PreStateConfig, PreStateFrame},
+};
 use crate::{
-    types::{Address, Bytes, NameOrAddress, H256, U256},
+    types::{Bytes, H256, U256},
     utils::from_int_or_hex,
 };
 use serde::{Deserialize, Serialize};
@@ -40,34 +51,14 @@ pub struct StructLog {
     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,
-    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),
+    NoopTracer(NoopFrame),
+    FourByteTracer(FourByteFrame),
     CallTracer(CallFrame),
+    PreStateTracer(PreStateFrame),
 }
 
 impl From<DefaultFrame> for GethTraceFrame {
@@ -76,12 +67,30 @@ impl From<DefaultFrame> for GethTraceFrame {
     }
 }
 
+impl From<FourByteFrame> for GethTraceFrame {
+    fn from(value: FourByteFrame) -> Self {
+        GethTraceFrame::FourByteTracer(value)
+    }
+}
+
 impl From<CallFrame> for GethTraceFrame {
     fn from(value: CallFrame) -> Self {
         GethTraceFrame::CallTracer(value)
     }
 }
 
+impl From<PreStateFrame> for GethTraceFrame {
+    fn from(value: PreStateFrame) -> Self {
+        GethTraceFrame::PreStateTracer(value)
+    }
+}
+
+impl From<NoopFrame> for GethTraceFrame {
+    fn from(value: NoopFrame) -> Self {
+        GethTraceFrame::NoopTracer(value)
+    }
+}
+
 #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
 #[serde(untagged)]
 pub enum GethTrace {
@@ -106,8 +115,21 @@ impl From<Value> for GethTrace {
 /// See <https://geth.ethereum.org/docs/developers/evm-tracing/built-in-tracers>
 #[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
 pub enum GethDebugBuiltInTracerType {
+    #[serde(rename = "4byteTracer")]
+    FourByteTracer,
     #[serde(rename = "callTracer")]
     CallTracer,
+    #[serde(rename = "prestateTracer")]
+    PreStateTracer,
+    #[serde(rename = "noopTracer")]
+    NoopTracer,
+}
+
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+#[serde(untagged)]
+pub enum GethDebugBuiltInTracerConfig {
+    CallTracer(CallConfig),
+    PreStateTracer(PreStateConfig),
 }
 
 /// Available tracers
@@ -123,6 +145,16 @@ pub enum GethDebugTracerType {
     JsTracer(String),
 }
 
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+#[serde(untagged)]
+pub enum GethDebugTracerConfig {
+    /// built-in tracer
+    BuiltInTracer(GethDebugBuiltInTracerConfig),
+
+    /// custom JS tracer
+    JsTracer(Value),
+}
+
 /// Bindings for additional `debug_traceTransaction` options
 ///
 /// See <https://geth.ethereum.org/docs/rpc/ns-debug#debug_tracetransaction>
@@ -139,6 +171,10 @@ pub struct GethDebugTracingOptions {
     pub enable_return_data: Option<bool>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub tracer: Option<GethDebugTracerType>,
+    /// tracerConfig is slated for Geth v1.11.0
+    /// See <https://github.com/ethereum/go-ethereum/issues/26513>
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub tracer_config: Option<GethDebugTracerConfig>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub timeout: Option<String>,
 }
diff --git a/ethers-core/src/types/trace/geth/call.rs b/ethers-core/src/types/trace/geth/call.rs
new file mode 100644
index 00000000..f6c97b89
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/call.rs
@@ -0,0 +1,87 @@
+use crate::{
+    types::{Address, Bytes, NameOrAddress, H256, U256},
+    utils::from_int_or_hex,
+};
+use serde::{Deserialize, Serialize};
+
+// https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/call.go#L44
+#[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<NameOrAddress>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub value: Option<U256>,
+    #[serde(default, deserialize_with = "from_int_or_hex")]
+    pub gas: U256,
+    #[serde(default, 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>>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub logs: Option<Vec<CallLogFrame>>,
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct CallLogFrame {
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    address: Option<Address>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    topics: Option<Vec<H256>>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    data: Option<Bytes>,
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CallConfig {
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub only_top_call: Option<bool>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub with_log: Option<bool>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::types::*;
+
+    // See <https://github.com/ethereum/go-ethereum/tree/master/eth/tracers/internal/tracetest/testdata>
+    const DEFAULT: &str = include_str!("./test_data/call_tracer/default.json");
+    const LEGACY: &str = include_str!("./test_data/call_tracer/legacy.json");
+    const ONLY_TOP_CALL: &str = include_str!("./test_data/call_tracer/only_top_call.json");
+    const WITH_LOG: &str = include_str!("./test_data/call_tracer/with_log.json");
+
+    #[test]
+    fn test_serialize_call_trace() {
+        let mut opts = GethDebugTracingCallOptions::default();
+        opts.tracing_options.disable_storage = Some(false);
+        opts.tracing_options.tracer =
+            Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::CallTracer));
+        opts.tracing_options.tracer_config =
+            Some(GethDebugTracerConfig::BuiltInTracer(GethDebugBuiltInTracerConfig::CallTracer(
+                CallConfig { only_top_call: Some(true), with_log: Some(true) },
+            )));
+
+        assert_eq!(
+            serde_json::to_string(&opts).unwrap(),
+            r#"{"disableStorage":false,"tracer":"callTracer","tracerConfig":{"onlyTopCall":true,"withLog":true}}"#
+        );
+    }
+
+    #[test]
+    fn test_deserialize_call_trace() {
+        let _trace: CallFrame = serde_json::from_str(DEFAULT).unwrap();
+        let _trace: CallFrame = serde_json::from_str(LEGACY).unwrap();
+        let _trace: CallFrame = serde_json::from_str(ONLY_TOP_CALL).unwrap();
+        let trace: CallFrame = serde_json::from_str(WITH_LOG).unwrap();
+        let _logs = trace.logs.unwrap();
+    }
+}
diff --git a/ethers-core/src/types/trace/geth/four_byte.rs b/ethers-core/src/types/trace/geth/four_byte.rs
new file mode 100644
index 00000000..10f3ef38
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/four_byte.rs
@@ -0,0 +1,34 @@
+use serde::{Deserialize, Serialize};
+use std::collections::BTreeMap;
+
+// https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/4byte.go#L48
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct FourByteFrame(pub BTreeMap<String, u64>);
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::types::*;
+
+    const DEFAULT: &str = r#"{
+        "0x27dc297e-128": 1,
+        "0x38cc4831-0": 2,
+        "0x524f3889-96": 1,
+        "0xadf59f99-288": 1,
+        "0xc281d19e-0": 1
+    }"#;
+
+    #[test]
+    fn test_serialize_four_byte_trace() {
+        let mut opts = GethDebugTracingCallOptions::default();
+        opts.tracing_options.tracer =
+            Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::FourByteTracer));
+
+        assert_eq!(serde_json::to_string(&opts).unwrap(), r#"{"tracer":"4byteTracer"}"#);
+    }
+
+    #[test]
+    fn test_deserialize_four_byte_trace() {
+        let _trace: FourByteFrame = serde_json::from_str(DEFAULT).unwrap();
+    }
+}
diff --git a/ethers-core/src/types/trace/geth/noop.rs b/ethers-core/src/types/trace/geth/noop.rs
new file mode 100644
index 00000000..6c7554fd
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/noop.rs
@@ -0,0 +1,30 @@
+use serde::{Deserialize, Serialize};
+use std::collections::BTreeMap;
+
+// https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/noop.go#L34
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct NoopFrame(BTreeMap<Null, Null>);
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
+struct Null;
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::types::*;
+
+    const DEFAULT: &str = r#"{}"#;
+
+    #[test]
+    fn test_serialize_noop_trace() {
+        let mut opts = GethDebugTracingCallOptions::default();
+        opts.tracing_options.tracer =
+            Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::NoopTracer));
+
+        assert_eq!(serde_json::to_string(&opts).unwrap(), r#"{"tracer":"noopTracer"}"#);
+    }
+
+    #[test]
+    fn test_deserialize_noop_trace() {
+        let _trace: NoopFrame = serde_json::from_str(DEFAULT).unwrap();
+    }
+}
diff --git a/ethers-core/src/types/trace/geth/pre_state.rs b/ethers-core/src/types/trace/geth/pre_state.rs
new file mode 100644
index 00000000..b87dbbd0
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/pre_state.rs
@@ -0,0 +1,92 @@
+use crate::{
+    types::{Address, H256, U256},
+    utils::from_int_or_hex_opt,
+};
+use serde::{Deserialize, Serialize};
+use std::collections::BTreeMap;
+
+// https://github.com/ethereum/go-ethereum/blob/91cb6f863a965481e51d5d9c0e5ccd54796fd967/eth/tracers/native/prestate.go#L38
+#[derive(Debug, PartialEq, Eq, Clone, Deserialize, Serialize)]
+#[serde(untagged)]
+pub enum PreStateFrame {
+    Default(PreStateMode),
+    Diff(DiffMode),
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct PreStateMode(pub BTreeMap<Address, AccountState>);
+
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct DiffMode {
+    pub pre: BTreeMap<Address, AccountState>,
+    pub post: BTreeMap<Address, AccountState>,
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+pub struct AccountState {
+    #[serde(
+        default,
+        deserialize_with = "from_int_or_hex_opt",
+        skip_serializing_if = "Option::is_none"
+    )]
+    pub balance: Option<U256>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub code: Option<String>,
+    #[serde(
+        default,
+        deserialize_with = "from_int_or_hex_opt",
+        skip_serializing_if = "Option::is_none"
+    )]
+    pub nonce: Option<U256>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub storage: Option<BTreeMap<H256, H256>>,
+}
+
+#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PreStateConfig {
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub diff_mode: Option<bool>,
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::types::*;
+
+    // See <https://github.com/ethereum/go-ethereum/tree/master/eth/tracers/internal/tracetest/testdata>
+    const DEFAULT: &str = include_str!("./test_data/pre_state_tracer/default.json");
+    const LEGACY: &str = include_str!("./test_data/pre_state_tracer/legacy.json");
+    const DIFF_MODE: &str = include_str!("./test_data/pre_state_tracer/diff_mode.json");
+
+    #[test]
+    fn test_serialize_pre_state_trace() {
+        let mut opts = GethDebugTracingCallOptions::default();
+        opts.tracing_options.disable_storage = Some(false);
+        opts.tracing_options.tracer =
+            Some(GethDebugTracerType::BuiltInTracer(GethDebugBuiltInTracerType::PreStateTracer));
+        opts.tracing_options.tracer_config = Some(GethDebugTracerConfig::BuiltInTracer(
+            GethDebugBuiltInTracerConfig::PreStateTracer(PreStateConfig { diff_mode: Some(true) }),
+        ));
+
+        assert_eq!(
+            serde_json::to_string(&opts).unwrap(),
+            r#"{"disableStorage":false,"tracer":"prestateTracer","tracerConfig":{"diffMode":true}}"#
+        );
+    }
+
+    #[test]
+    fn test_deserialize_pre_state_trace() {
+        let trace: PreStateFrame = serde_json::from_str(DEFAULT).unwrap();
+        match trace {
+            PreStateFrame::Default(PreStateMode(_)) => {}
+            _ => unreachable!(),
+        }
+        let _trace: PreStateFrame = serde_json::from_str(LEGACY).unwrap();
+        let trace: PreStateFrame = serde_json::from_str(DIFF_MODE).unwrap();
+        match trace {
+            PreStateFrame::Diff(DiffMode { pre: _pre, post: _post }) => {}
+            _ => unreachable!(),
+        }
+    }
+}


Test patch:
diff --git a/ethers-core/src/types/trace/geth/test_data/call_tracer/default.json b/ethers-core/src/types/trace/geth/test_data/call_tracer/default.json
new file mode 100644
index 00000000..553b2a39
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/test_data/call_tracer/default.json
@@ -0,0 +1,21 @@
+{
+    "calls": [
+        {
+            "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
+            "gas": "0x6d05",
+            "gasUsed": "0x0",
+            "input": "0x",
+            "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
+            "type": "CALL",
+            "value": "0x6f05b59d3b20000"
+        }
+    ],
+    "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb",
+    "gas": "0x10738",
+    "gasUsed": "0x9751",
+    "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
+    "output": "0x0000000000000000000000000000000000000000000000000000000000000001",
+    "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
+    "type": "CALL",
+    "value": "0x0"
+}
diff --git a/ethers-core/src/types/trace/geth/test_data/call_tracer/legacy.json b/ethers-core/src/types/trace/geth/test_data/call_tracer/legacy.json
new file mode 100644
index 00000000..b89e7ae8
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/test_data/call_tracer/legacy.json
@@ -0,0 +1,19 @@
+{
+    "calls": [
+        {
+            "from": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
+            "input": "0x",
+            "to": "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
+            "type": "CALL",
+            "value": "0x6f05b59d3b20000"
+        }
+    ],
+    "from": "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb",
+    "gas": "0x10738",
+    "gasUsed": "0x9751",
+    "input": "0x63e4bff40000000000000000000000000024f658a46fbb89d8ac105e98d7ac7cbbaf27c5",
+    "output": "0x0000000000000000000000000000000000000000000000000000000000000001",
+    "to": "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe",
+    "type": "CALL",
+    "value": "0x0"
+}
diff --git a/ethers-core/src/types/trace/geth/test_data/call_tracer/only_top_call.json b/ethers-core/src/types/trace/geth/test_data/call_tracer/only_top_call.json
new file mode 100644
index 00000000..327bb427
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/test_data/call_tracer/only_top_call.json
@@ -0,0 +1,10 @@
+{
+    "from": "0x4f5777744b500616697cb655dcb02ee6cd51deb5",
+    "gas": "0x2dced",
+    "gasUsed": "0x1a9e5",
+    "to": "0x200edd17f30485a8735878661960cd7a9a95733f",
+    "input": "0xba51a6df0000000000000000000000000000000000000000000000000000000000000000",
+    "output": "0xba51a6df00000000000000000000000000000000000000000000000000000000",
+    "value": "0x8ac7230489e80000",
+    "type": "CALL"
+}
diff --git a/ethers-core/src/types/trace/geth/test_data/call_tracer/with_log.json b/ethers-core/src/types/trace/geth/test_data/call_tracer/with_log.json
new file mode 100644
index 00000000..2528bbc0
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/test_data/call_tracer/with_log.json
@@ -0,0 +1,20 @@
+{
+    "from": "0xd1220a0cf47c7b9be7a2e6ba89f429762e7b9adb",
+    "gas": "0x1f36d",
+    "gasUsed": "0xc6a5",
+    "to": "0xf4eced2f682ce333f96f2d8966c613ded8fc95dd",
+    "input": "0xa9059cbb000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb0000000000000000000000000000000000000000000000000000000000989680",
+    "logs": [
+        {
+            "address": "0xf4eced2f682ce333f96f2d8966c613ded8fc95dd",
+            "topics": [
+            "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
+            "0x000000000000000000000000d1220a0cf47c7b9be7a2e6ba89f429762e7b9adb",
+            "0x000000000000000000000000dbf03b407c01e7cd3cbea99509d93f8dddc8c6fb"
+            ],
+            "data": "0x0000000000000000000000000000000000000000000000000000000000989680"
+        }
+    ],
+    "value": "0x0",
+    "type": "CALL"
+}
diff --git a/ethers-core/src/types/trace/geth/test_data/pre_state_tracer/default.json b/ethers-core/src/types/trace/geth/test_data/pre_state_tracer/default.json
new file mode 100644
index 00000000..43e69b11
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/test_data/pre_state_tracer/default.json
@@ -0,0 +1,20 @@
+{
+    "0x082d4cdf07f386ffa9258f52a5c49db4ac321ec6": {
+        "balance": "0xc820f93200f4000",
+        "nonce": 94
+    },
+    "0x332b656504f4eabb44c8617a42af37461a34e9dc": {
+        "balance": "0x11faea4f35e5af80000",
+        "storage": {
+            "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000"
+        }
+    },
+    "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5": {
+        "balance": "0xbf681825be002ac452",
+        "nonce": 28922
+    },
+    "0x82effbaaaf28614e55b2ba440fb198e0e5789b0f": {
+        "balance": "0xb3d0ac5cb94df6f6b0",
+        "nonce": 1
+    }
+}
diff --git a/ethers-core/src/types/trace/geth/test_data/pre_state_tracer/diff_mode.json b/ethers-core/src/types/trace/geth/test_data/pre_state_tracer/diff_mode.json
new file mode 100644
index 00000000..0654d26f
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/test_data/pre_state_tracer/diff_mode.json
@@ -0,0 +1,41 @@
+{
+    "pre": {
+        "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": {
+            "balance": "0x0",
+            "nonce": 22
+        },
+        "0x1585936b53834b021f68cc13eeefdec2efc8e724": {
+            "balance": "0x0"
+        },
+        "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": {
+            "balance": "0x4d87094125a369d9bd5",
+            "nonce": 1,
+            "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029",
+            "storage": {
+                "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834"
+            }
+        },
+        "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": {
+            "balance": "0x1780d77678137ac1b775",
+            "nonce": 29072
+        }
+    },
+    "post": {
+        "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": {
+            "balance": "0x6f05b59d3b20000"
+        },
+        "0x1585936b53834b021f68cc13eeefdec2efc8e724": {
+            "balance": "0x420eed1bd6c00"
+        },
+        "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": {
+            "balance": "0x4d869a3b70062eb9bd5",
+            "storage": {
+                "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b95e"
+            }
+        },
+        "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": {
+            "balance": "0x1780d7725724a9044b75",
+            "nonce": 29073
+        }
+    }
+}
diff --git a/ethers-core/src/types/trace/geth/test_data/pre_state_tracer/legacy.json b/ethers-core/src/types/trace/geth/test_data/pre_state_tracer/legacy.json
new file mode 100644
index 00000000..dbefb198
--- /dev/null
+++ b/ethers-core/src/types/trace/geth/test_data/pre_state_tracer/legacy.json
@@ -0,0 +1,25 @@
+{
+    "0x0024f658a46fbb89d8ac105e98d7ac7cbbaf27c5": {
+        "balance": "0x0",
+        "code": "0x",
+        "nonce": 22,
+        "storage": {}
+    },
+    "0x3b873a919aa0512d5a0f09e6dcceaa4a6727fafe": {
+        "balance": "0x4d87094125a369d9bd5",
+        "code": "0x606060405236156100935763ffffffff60e060020a60003504166311ee8382811461009c57806313af4035146100be5780631f5e8f4c146100ee57806324daddc5146101125780634921a91a1461013b57806363e4bff414610157578063764978f91461017f578063893d20e8146101a1578063ba40aaa1146101cd578063cebc9a82146101f4578063e177246e14610216575b61009a5b5b565b005b34156100a457fe5b6100ac61023d565b60408051918252519081900360200190f35b34156100c657fe5b6100da600160a060020a0360043516610244565b604080519115158252519081900360200190f35b34156100f657fe5b6100da610307565b604080519115158252519081900360200190f35b341561011a57fe5b6100da6004351515610318565b604080519115158252519081900360200190f35b6100da6103d6565b604080519115158252519081900360200190f35b6100da600160a060020a0360043516610420565b604080519115158252519081900360200190f35b341561018757fe5b6100ac61046c565b60408051918252519081900360200190f35b34156101a957fe5b6101b1610473565b60408051600160a060020a039092168252519081900360200190f35b34156101d557fe5b6100da600435610483565b604080519115158252519081900360200190f35b34156101fc57fe5b6100ac61050d565b60408051918252519081900360200190f35b341561021e57fe5b6100da600435610514565b604080519115158252519081900360200190f35b6003545b90565b60006000610250610473565b600160a060020a031633600160a060020a03161415156102705760006000fd5b600160a060020a03831615156102865760006000fd5b50600054600160a060020a0390811690831681146102fb57604051600160a060020a0380851691908316907ffcf23a92150d56e85e3a3d33b357493246e55783095eb6a733eb8439ffc752c890600090a360008054600160a060020a031916600160a060020a03851617905560019150610300565b600091505b5b50919050565b60005460a060020a900460ff165b90565b60006000610324610473565b600160a060020a031633600160a060020a03161415156103445760006000fd5b5060005460a060020a900460ff16801515831515146102fb576000546040805160a060020a90920460ff1615158252841515602083015280517fe6cd46a119083b86efc6884b970bfa30c1708f53ba57b86716f15b2f4551a9539281900390910190a16000805460a060020a60ff02191660a060020a8515150217905560019150610300565b600091505b5b50919050565b60006103e0610307565b801561040557506103ef610473565b600160a060020a031633600160a060020a031614155b156104105760006000fd5b610419336105a0565b90505b5b90565b600061042a610307565b801561044f5750610439610473565b600160a060020a031633600160a060020a031614155b1561045a5760006000fd5b610463826105a0565b90505b5b919050565b6001545b90565b600054600160a060020a03165b90565b6000600061048f610473565b600160a060020a031633600160a060020a03161415156104af5760006000fd5b506001548281146102fb57604080518281526020810185905281517f79a3746dde45672c9e8ab3644b8bb9c399a103da2dc94b56ba09777330a83509929181900390910190a160018381559150610300565b600091505b5b50919050565b6002545b90565b60006000610520610473565b600160a060020a031633600160a060020a03161415156105405760006000fd5b506002548281146102fb57604080518281526020810185905281517ff6991a728965fedd6e927fdf16bdad42d8995970b4b31b8a2bf88767516e2494929181900390910190a1600283905560019150610300565b600091505b5b50919050565b60006000426105ad61023d565b116102fb576105c46105bd61050d565b4201610652565b6105cc61046c565b604051909150600160a060020a038416908290600081818185876187965a03f1925050501561063d57604080518281529051600160a060020a038516917f9bca65ce52fdef8a470977b51f247a2295123a4807dfa9e502edf0d30722da3b919081900360200190a260019150610300565b6102fb42610652565b5b600091505b50919050565b60038190555b505600a165627a7a72305820f3c973c8b7ed1f62000b6701bd5b708469e19d0f1d73fde378a56c07fd0b19090029",
+        "nonce": 1,
+        "storage": {
+            "0x0000000000000000000000000000000000000000000000000000000000000000": "0x000000000000000000000001b436ba50d378d4bbc8660d312a13df6af6e89dfb",
+            "0x0000000000000000000000000000000000000000000000000000000000000001": "0x00000000000000000000000000000000000000000000000006f05b59d3b20000",
+            "0x0000000000000000000000000000000000000000000000000000000000000002": "0x000000000000000000000000000000000000000000000000000000000000003c",
+            "0x0000000000000000000000000000000000000000000000000000000000000003": "0x000000000000000000000000000000000000000000000000000000005a37b834"
+        }
+    },
+    "0xb436ba50d378d4bbc8660d312a13df6af6e89dfb": {
+        "balance": "0x1780d77678137ac1b775",
+        "code": "0x",
+        "nonce": 29072,
+        "storage": {}
+    }
+}