Repository: gakonst/ethers-rs
Pull request #1944 (2022-12-16 22:57:17)

fix(middleware): oracles, tests
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation

<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

Old / non functioning API URLs

## Solution

- Replace gas_now url (old redirected to new)
- [deprecate eth_gas_station](https://twitter.com/ETHGasStation/status/1597341610777317376) and replace it in docs and tests with gas_now
- temporarily ignore etherchain tests which is failing in builds due to 404
- clean up parsing and gas calculation since every oracle was different
- add some `Default` impls and other derives
- export internal Response structs which were exposed in the query functions but were not exported at mod level
- add common Result type alias for gas_oracle
- add missing tests for oracles
- big tests diffs are just removing indentation from removing unnecessary `mod tests`
- add missing fields in gas_now Response
- make file structure consistent with each other (imports -> definitions -> [trait impls, impls])

*minor breaking changes:*
- make blocknative API key optional, which also makes the estimatedBaseFees response field optional since I think a key is required for that
- change some base oracles' methods from `request` to `query` for consistency
- change all oracle `query` methods to return just the Response struct and move parsing in other functions

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

-   [x] Added Tests
-   [x] Added Documentation
-   [ ] Updated the changelog
-   [x] Breaking changes


DaniPopes: What do you mean by easier to merge? I think a lot of these changes will overlap with each other which would have the opposite effect - I don't think there's much point in splitting this, it's mostly just making the individual oracles and file structure consistent + fixing a few bugs and adding a few tests

I finished listing all changes and breaking changes
mattsse: > What do you mean by easier to merge? I think a lot of these changes will overlap with each other which would have the opposite effect - I don't think there's much point in splitting this

this is good to know.

I just wanted to check this out before looking at it more closely because it looks like it has a lot of valuable corrections in it.

Patch:
diff --git a/.clippy.toml b/.clippy.toml
index 6bfeada7..ad0ea4e7 100644
--- a/.clippy.toml
+++ b/.clippy.toml
@@ -1,1 +1,1 @@
-msrv = "1.62"
+msrv = "1.64"
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1b26a77..7b157dc5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -103,6 +103,7 @@
 - [#842](https://github.com/gakonst/ethers-rs/issues/842) Add support for I256 types in `parse_units` and `format_units`.
   Added `twos_complement` function for I256.
 - [#1934](https://github.com/gakonst/ethers-rs/pull/1934) Allow 16 calls in multicall.
+- [#1941](https://github.com/gakonst/ethers-rs/pull/1941) Add `add_calls` and `call_array` for `Multicall`.
 
 ## ethers-contract-abigen
 
diff --git a/Cargo.toml b/Cargo.toml
index eff02c36..3205a175 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -2,7 +2,7 @@
 name = "ethers"
 version = "1.0.2"
 edition = "2021"
-rust-version = "1.64"
+rust-version = "1.64" # must also be changed in **/Cargo.toml and .clippy.toml
 authors = ["Georgios Konstantopoulos <me@gakonst.com>"]
 license = "MIT OR Apache-2.0"
 readme = "README.md"
diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs
index 8dd24542..56dca427 100644
--- a/ethers-contract/src/multicall/mod.rs
+++ b/ethers-contract/src/multicall/mod.rs
@@ -437,6 +437,27 @@ impl<M: Middleware> Multicall<M> {
         }
     }
 
+    /// Appends multiple `call`s to the list of calls of the Multicall instance.
+    ///
+    /// Version specific details:
+    /// - 1: `allow_failure` is ignored.
+    /// - >=2: `allow_failure` specifies whether or not this call is allowed to revert in the
+    ///   multicall.
+    /// - 3: Transaction values are used when broadcasting transactions with [`send`], otherwise
+    ///   they are always ignored.
+    ///
+    /// [`send`]: #method.send
+    pub fn add_calls<D: Detokenize>(
+        &mut self,
+        allow_failure: bool,
+        calls: impl IntoIterator<Item = ContractCall<M, D>>,
+    ) -> &mut Self {
+        for call in calls {
+            self.add_call(call, allow_failure);
+        }
+        self
+    }
+
     /// Appends a `call` to the list of calls of the Multicall instance for querying the block hash
     /// of a given block number.
     ///
@@ -615,6 +636,45 @@ impl<M: Middleware> Multicall<M> {
         Ok(data)
     }
 
+    /// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract, assuming
+    /// that every call returns same data type.
+    ///
+    /// Note: this method _does not_ send a transaction from your account.
+    ///
+    /// # Errors
+    ///
+    /// Returns a [`MulticallError`] if there are any errors in the RPC call or while detokenizing
+    /// the tokens back to the expected return type.
+    ///
+    /// # Examples
+    ///
+    /// The return type must be annotated while calling this method:
+    ///
+    /// ```no_run
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// # use ethers_core::types::{U256, Address};
+    /// # use ethers_providers::{Provider, Http};
+    /// # use ethers_contract::Multicall;
+    /// # use std::convert::TryFrom;
+    /// #
+    /// # let client = Provider::<Http>::try_from("http://localhost:8545")?;
+    /// #
+    /// # let multicall = Multicall::new(client, None).await?;
+    /// // If the all Solidity function calls `returns (uint256)`:
+    /// let result: Vec<U256> = multicall.call().await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub async fn call_array<D: Detokenize>(&self) -> Result<Vec<D>, M> {
+        let tokens = self.call_raw().await?;
+        let res: std::result::Result<Vec<D>, ContractError<M>> = tokens
+            .into_iter()
+            .map(|token| D::from_tokens(vec![token]).map_err(ContractError::DetokenizationError))
+            .collect();
+
+        Ok(res?)
+    }
+
     /// Queries the Ethereum blockchain using `eth_call`, but via the Multicall contract and
     /// without detokenization.
     ///
diff --git a/ethers-core/src/types/transaction/eip1559.rs b/ethers-core/src/types/transaction/eip1559.rs
index ba080e19..ba937fe6 100644
--- a/ethers-core/src/types/transaction/eip1559.rs
+++ b/ethers-core/src/types/transaction/eip1559.rs
@@ -208,7 +208,7 @@ impl Eip1559TransactionRequest {
         *offset += 1;
         tx.gas = Some(rlp.val_at(*offset)?);
         *offset += 1;
-        tx.to = decode_to(rlp, offset)?;
+        tx.to = decode_to(rlp, offset)?.map(NameOrAddress::Address);
         tx.value = Some(rlp.val_at(*offset)?);
         *offset += 1;
         let data = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
diff --git a/ethers-core/src/types/transaction/mod.rs b/ethers-core/src/types/transaction/mod.rs
index efe70780..816f4539 100644
--- a/ethers-core/src/types/transaction/mod.rs
+++ b/ethers-core/src/types/transaction/mod.rs
@@ -75,7 +75,7 @@ fn decode_signature(
 fn decode_to(
     rlp: &rlp::Rlp,
     offset: &mut usize,
-) -> Result<Option<super::NameOrAddress>, rlp::DecoderError> {
+) -> Result<Option<super::Address>, rlp::DecoderError> {
     let to = {
         let to = rlp.at(*offset)?;
         if to.is_empty() {
diff --git a/ethers-core/src/types/transaction/request.rs b/ethers-core/src/types/transaction/request.rs
index f8be2627..d31d91bf 100644
--- a/ethers-core/src/types/transaction/request.rs
+++ b/ethers-core/src/types/transaction/request.rs
@@ -233,7 +233,7 @@ impl TransactionRequest {
             *offset += 1;
         }
 
-        txn.to = decode_to(rlp, offset)?;
+        txn.to = decode_to(rlp, offset)?.map(NameOrAddress::Address);
         txn.value = Some(rlp.at(*offset)?.as_val()?);
         *offset += 1;
 
diff --git a/ethers-core/src/types/transaction/response.rs b/ethers-core/src/types/transaction/response.rs
index 314caf0b..b29d6f99 100644
--- a/ethers-core/src/types/transaction/response.rs
+++ b/ethers-core/src/types/transaction/response.rs
@@ -1,7 +1,7 @@
 //! Transaction types
 use super::{
-    decode_signature, eip2718::TypedTransaction, eip2930::AccessList, normalize_v, rlp_opt,
-    rlp_opt_list,
+    decode_signature, decode_to, eip2718::TypedTransaction, eip2930::AccessList, normalize_v,
+    rlp_opt, rlp_opt_list,
 };
 use crate::{
     types::{
@@ -248,8 +248,7 @@ impl Transaction {
         *offset += 1;
         self.gas = rlp.val_at(*offset)?;
         *offset += 1;
-        self.to = Some(rlp.val_at(*offset)?);
-        *offset += 1;
+        self.to = decode_to(rlp, offset)?;
         self.value = rlp.val_at(*offset)?;
         *offset += 1;
         let input = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
@@ -279,8 +278,7 @@ impl Transaction {
         #[cfg(feature = "celo")]
         self.decode_celo_metadata(rlp, offset)?;
 
-        self.to = Some(rlp.val_at(*offset)?);
-        *offset += 1;
+        self.to = decode_to(rlp, offset)?;
         self.value = rlp.val_at(*offset)?;
         *offset += 1;
         let input = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
@@ -310,8 +308,7 @@ impl Transaction {
         #[cfg(feature = "celo")]
         self.decode_celo_metadata(rlp, offset)?;
 
-        self.to = Some(rlp.val_at(*offset)?);
-        *offset += 1;
+        self.to = decode_to(rlp, offset)?;
         self.value = rlp.val_at(*offset)?;
         *offset += 1;
         let input = rlp::Rlp::new(rlp.at(*offset)?.as_raw()).data()?;
@@ -473,7 +470,7 @@ impl PartialOrd<Self> for TransactionReceipt {
 #[cfg(test)]
 #[cfg(not(feature = "celo"))]
 mod tests {
-    use rlp::Encodable;
+    use rlp::{Encodable, Rlp};
 
     use crate::types::transaction::eip2930::AccessListItem;
 
@@ -1093,4 +1090,24 @@ mod tests {
         let r = rlp::Rlp::new(b.as_slice());
         Transaction::decode(&r).unwrap();
     }
+
+    #[test]
+    fn test_rlp_decoding_create_roundtrip() {
+        let tx = Transaction {
+            block_hash: None,
+            block_number: None,
+            from: Address::from_str("c26ad91f4e7a0cad84c4b9315f420ca9217e315d").unwrap(),
+            gas: U256::from_str_radix("0x10e2b", 16).unwrap(),
+            gas_price: Some(U256::from_str_radix("0x12ec276caf", 16).unwrap()),
+            hash: H256::from_str("929ff27a5c7833953df23103c4eb55ebdfb698678139d751c51932163877fada").unwrap(),
+            input: Bytes::from(
+                hex::decode("a9059cbb000000000000000000000000fdae129ecc2c27d166a3131098bc05d143fa258e0000000000000000000000000000000000000000000000000000000002faf080").unwrap()
+            ),
+            nonce: U256::zero(),
+            transaction_index: None,
+            value: U256::zero(),
+            ..Default::default()
+        };
+        Transaction::decode(&Rlp::new(&tx.rlp())).unwrap();
+    }
 }
diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs
index 8657c888..8c13d175 100644
--- a/ethers-core/src/utils/geth.rs
+++ b/ethers-core/src/utils/geth.rs
@@ -164,6 +164,7 @@ impl Default for PrivateNetOptions {
 /// ```
 #[derive(Clone, Default)]
 pub struct Geth {
+    program: Option<PathBuf>,
     port: Option<u16>,
     authrpc_port: Option<u16>,
     ipc_path: Option<PathBuf>,
@@ -180,6 +181,32 @@ impl Geth {
         Self::default()
     }
 
+    /// Creates a Geth builder which will execute `geth` at the given path.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use ethers_core::utils::Geth;
+    /// # fn a() {
+    ///  let geth = Geth::at("../go-ethereum/build/bin/geth").spawn();
+    ///
+    ///  println!("Geth running at `{}`", geth.endpoint());
+    /// # }
+    /// ```
+    pub fn at(path: impl Into<PathBuf>) -> Self {
+        Self::new().path(path)
+    }
+
+    /// Sets the `path` to the `geth` executable
+    ///
+    /// By default, it's expected that `geth` is in `$PATH`, see also
+    /// [`std::process::Command::new()`]
+    #[must_use]
+    pub fn path<T: Into<PathBuf>>(mut self, path: T) -> Self {
+        self.program = Some(path.into());
+        self
+    }
+
     /// Sets the port which will be used when the `geth-cli` instance is launched.
     #[must_use]
     pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
@@ -274,9 +301,10 @@ impl Geth {
     /// Consumes the builder and spawns `geth` with stdout redirected
     /// to /dev/null.
     pub fn spawn(self) -> GethInstance {
-        let mut cmd = Command::new(GETH);
+        let mut cmd =
+            if let Some(ref prg) = self.program { Command::new(prg) } else { Command::new(GETH) };
         // geth uses stderr for its logs
-        cmd.stderr(std::process::Stdio::piped());
+        cmd.stderr(Stdio::piped());
         let port = if let Some(port) = self.port { port } else { unused_port() };
         let authrpc_port = if let Some(port) = self.authrpc_port { port } else { unused_port() };
 
diff --git a/ethers-middleware/README.md b/ethers-middleware/README.md
index 97d37ab2..75a9b328 100644
--- a/ethers-middleware/README.md
+++ b/ethers-middleware/README.md
@@ -23,13 +23,13 @@ use ethers_providers::{Middleware, Provider, Http};
 use std::sync::Arc;
 use std::convert::TryFrom;
 use ethers_signers::{LocalWallet, Signer};
-use ethers_middleware::{*,gas_oracle::*};
+use ethers_middleware::{gas_oracle::{GasOracle, GasNow}, MiddlewareBuilder};
 
 fn builder_example() {
     let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
     let signer = key.parse::<LocalWallet>().unwrap();
     let address = signer.address();
-    let gas_oracle = EthGasStation::new(None);
+    let gas_oracle = GasNow::new();
 
     let provider = Provider::<Http>::try_from("http://localhost:8545")
         .unwrap()
@@ -58,7 +58,7 @@ fn builder_example_wrap_into() {
         .unwrap()
         .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
         .wrap_into(|p| SignerMiddleware::new(p, signer))
-        .wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
+        .wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
         .wrap_into(|p| NonceManagerMiddleware::new(p, address)); // Outermost layer
 }
 ```
@@ -72,7 +72,7 @@ use ethers_providers::{Provider, Http};
 use ethers_signers::{LocalWallet, Signer};
 use ethers_middleware::{
     gas_escalator::{GasEscalatorMiddleware, GeometricGasPrice, Frequency},
-    gas_oracle::{GasOracleMiddleware, EthGasStation, GasCategory},
+    gas_oracle::{GasOracleMiddleware, GasCategory, GasNow},
     signer::SignerMiddleware,
     nonce_manager::NonceManagerMiddleware,
 };
@@ -91,8 +91,8 @@ let signer = LocalWallet::new(&mut rand::thread_rng());
 let address = signer.address();
 let provider = SignerMiddleware::new(provider, signer);
 
-// Use EthGasStation as the gas oracle
-let gas_oracle = EthGasStation::new(None);
+// Use GasNow as the gas oracle
+let gas_oracle = GasNow::new();
 let provider = GasOracleMiddleware::new(provider, gas_oracle);
 
 // Manage nonces locally
diff --git a/ethers-middleware/src/builder.rs b/ethers-middleware/src/builder.rs
index b1827407..f824add9 100644
--- a/ethers-middleware/src/builder.rs
+++ b/ethers-middleware/src/builder.rs
@@ -16,14 +16,14 @@ use ethers_signers::Signer;
 /// use std::sync::Arc;
 /// use std::convert::TryFrom;
 /// use ethers_signers::{LocalWallet, Signer};
-/// use ethers_middleware::{*,gas_escalator::*,gas_oracle::*};
+/// use ethers_middleware::{*, gas_escalator::*, gas_oracle::*};
 ///
 /// fn builder_example() {
 ///     let key = "fdb33e2105f08abe41a8ee3b758726a31abdd57b7a443f470f23efce853af169";
 ///     let signer = key.parse::<LocalWallet>().unwrap();
 ///     let address = signer.address();
 ///     let escalator = GeometricGasPrice::new(1.125, 60_u64, None::<u64>);
-///     let gas_oracle = EthGasStation::new(None);
+///     let gas_oracle = GasNow::new();
 ///
 ///     let provider = Provider::<Http>::try_from("http://localhost:8545")
 ///         .unwrap()
@@ -43,7 +43,7 @@ use ethers_signers::Signer;
 ///         .unwrap()
 ///         .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
 ///         .wrap_into(|p| SignerMiddleware::new(p, signer))
-///         .wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
+///         .wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
 ///         .wrap_into(|p| NonceManagerMiddleware::new(p, address)); // Outermost layer
 /// }
 /// ```
diff --git a/ethers-middleware/src/gas_escalator/mod.rs b/ethers-middleware/src/gas_escalator/mod.rs
index a71adbf3..f3ab1840 100644
--- a/ethers-middleware/src/gas_escalator/mod.rs
+++ b/ethers-middleware/src/gas_escalator/mod.rs
@@ -46,7 +46,7 @@ pub enum Frequency {
 /// use ethers_providers::{Provider, Http};
 /// use ethers_middleware::{
 ///     gas_escalator::{GeometricGasPrice, Frequency, GasEscalatorMiddleware},
-///     gas_oracle::{EthGasStation, GasCategory, GasOracleMiddleware},
+///     gas_oracle::{GasNow, GasCategory, GasOracleMiddleware},
 /// };
 /// use std::{convert::TryFrom, time::Duration, sync::Arc};
 ///
@@ -60,7 +60,7 @@ pub enum Frequency {
 /// };
 ///
 /// // ... proceed to wrap it in other middleware
-/// let gas_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
+/// let gas_oracle = GasNow::new().category(GasCategory::SafeLow);
 /// let provider = GasOracleMiddleware::new(provider, gas_oracle);
 /// ```
 pub struct GasEscalatorMiddleware<M, E> {
diff --git a/ethers-middleware/src/gas_oracle/blocknative.rs b/ethers-middleware/src/gas_oracle/blocknative.rs
index 11832c5a..84b037c0 100644
--- a/ethers-middleware/src/gas_oracle/blocknative.rs
+++ b/ethers-middleware/src/gas_oracle/blocknative.rs
@@ -1,139 +1,133 @@
-use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
+use super::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
 use async_trait::async_trait;
 use ethers_core::types::U256;
 use reqwest::{header::AUTHORIZATION, Client};
 use serde::Deserialize;
-use std::{collections::HashMap, convert::TryInto};
+use std::collections::HashMap;
 use url::Url;
 
-const BLOCKNATIVE_GAS_PRICE_ENDPOINT: &str = "https://api.blocknative.com/gasprices/blockprices";
-
-fn gas_category_to_confidence(gas_category: &GasCategory) -> u64 {
-    match gas_category {
-        GasCategory::SafeLow => 80,
-        GasCategory::Standard => 90,
-        GasCategory::Fast => 95,
-        GasCategory::Fastest => 99,
-    }
-}
+const URL: &str = "https://api.blocknative.com/gasprices/blockprices";
 
 /// A client over HTTP for the [BlockNative](https://www.blocknative.com/gas-estimator) gas tracker API
-/// that implements the `GasOracle` trait
+/// that implements the `GasOracle` trait.
 #[derive(Clone, Debug)]
+#[must_use]
 pub struct BlockNative {
     client: Client,
     url: Url,
-    api_key: String,
+    api_key: Option<String>,
     gas_category: GasCategory,
 }
 
-#[derive(Debug, Deserialize, PartialEq)]
+#[derive(Clone, Debug, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct Response {
+    pub system: String,
+    pub network: String,
+    pub unit: String,
+    pub max_price: u64,
+    pub block_prices: Vec<BlockPrice>,
+    pub estimated_base_fees: Option<Vec<HashMap<String, Vec<BaseFeeEstimate>>>>,
+}
+
+#[derive(Clone, Debug, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct BlockPrice {
+    pub block_number: u64,
+    pub estimated_transaction_count: u64,
+    pub base_fee_per_gas: f64,
+    pub estimated_prices: Vec<GasEstimate>,
+}
+
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
 #[serde(rename_all = "camelCase")]
-pub struct BlockNativeGasResponse {
-    system: Option<String>,
-    network: Option<String>,
-    unit: Option<String>,
-    max_price: Option<u64>,
-    block_prices: Vec<BlockPrice>,
-    estimated_base_fees: Vec<HashMap<String, Vec<BaseFeeEstimate>>>,
+pub struct GasEstimate {
+    pub confidence: u64,
+    pub price: u64,
+    pub max_priority_fee_per_gas: f64,
+    pub max_fee_per_gas: f64,
 }
 
-impl BlockNativeGasResponse {
-    pub fn get_estimation_for(
-        &self,
-        gas_category: &GasCategory,
-    ) -> Result<EstimatedPrice, GasOracleError> {
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct BaseFeeEstimate {
+    pub confidence: u64,
+    pub base_fee: f64,
+}
+
+impl Response {
+    #[inline]
+    pub fn estimate_from_category(&self, gas_category: &GasCategory) -> Result<GasEstimate> {
         let confidence = gas_category_to_confidence(gas_category);
-        Ok(self
+        let price = self
             .block_prices
             .first()
             .ok_or(GasOracleError::InvalidResponse)?
             .estimated_prices
             .iter()
             .find(|p| p.confidence == confidence)
-            .ok_or(GasOracleError::GasCategoryNotSupported)?
-            .clone())
+            .ok_or(GasOracleError::GasCategoryNotSupported)?;
+        Ok(*price)
     }
 }
 
-#[derive(Debug, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct BlockPrice {
-    block_number: u64,
-    estimated_transaction_count: u64,
-    base_fee_per_gas: f64,
-    estimated_prices: Vec<EstimatedPrice>,
+impl Default for BlockNative {
+    fn default() -> Self {
+        Self::new(None)
+    }
 }
 
-#[derive(Clone, Debug, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct EstimatedPrice {
-    confidence: u64,
-    price: u64,
-    max_priority_fee_per_gas: f64,
-    max_fee_per_gas: f64,
-}
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+impl GasOracle for BlockNative {
+    async fn fetch(&self) -> Result<U256> {
+        let estimate = self.query().await?.estimate_from_category(&self.gas_category)?;
+        Ok(U256::from(estimate.price) * GWEI_TO_WEI_U256)
+    }
 
-#[derive(Debug, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
-pub struct BaseFeeEstimate {
-    confidence: u64,
-    base_fee: f64,
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
+        let estimate = self.query().await?.estimate_from_category(&self.gas_category)?;
+        let max = from_gwei_f64(estimate.max_fee_per_gas);
+        let prio = from_gwei_f64(estimate.max_priority_fee_per_gas);
+        Ok((max, prio))
+    }
 }
 
 impl BlockNative {
     /// Creates a new [BlockNative](https://www.blocknative.com/gas-estimator) gas oracle.
-    pub fn new(api_key: String) -> Self {
+    pub fn new(api_key: Option<String>) -> Self {
         Self::with_client(Client::new(), api_key)
     }
 
     /// Same as [`Self::new`] but with a custom [`Client`].
-    pub fn with_client(client: Client, api_key: String) -> Self {
-        Self {
-            client,
-            api_key,
-            url: BLOCKNATIVE_GAS_PRICE_ENDPOINT.try_into().unwrap(),
-            gas_category: GasCategory::Standard,
-        }
+    pub fn with_client(client: Client, api_key: Option<String>) -> Self {
+        let url = Url::parse(URL).unwrap();
+        Self { client, api_key, url, gas_category: GasCategory::Standard }
     }
 
     /// Sets the gas price category to be used when fetching the gas price.
-    #[must_use]
     pub fn category(mut self, gas_category: GasCategory) -> Self {
         self.gas_category = gas_category;
         self
     }
 
-    /// Perform request to Blocknative, decode response
-    pub async fn request(&self) -> Result<BlockNativeGasResponse, GasOracleError> {
-        self.client
-            .get(self.url.as_ref())
-            .header(AUTHORIZATION, &self.api_key)
-            .send()
-            .await?
-            .error_for_status()?
-            .json()
-            .await
-            .map_err(GasOracleError::HttpClientError)
+    /// Perform a request to the gas price API and deserialize the response.
+    pub async fn query(&self) -> Result<Response, GasOracleError> {
+        let mut request = self.client.get(self.url.clone());
+        if let Some(api_key) = self.api_key.as_ref() {
+            request = request.header(AUTHORIZATION, api_key);
+        }
+        let response = request.send().await?.error_for_status()?.json().await?;
+        Ok(response)
     }
 }
 
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-impl GasOracle for BlockNative {
-    async fn fetch(&self) -> Result<U256, GasOracleError> {
-        let prices = self.request().await?.get_estimation_for(&self.gas_category)?;
-        Ok(U256::from(prices.price * 100_u64) * U256::from(GWEI_TO_WEI) / U256::from(100))
-    }
-
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
-        let prices = self.request().await?.get_estimation_for(&self.gas_category)?;
-        let base_fee = U256::from((prices.max_fee_per_gas * 100.0) as u64) *
-            U256::from(GWEI_TO_WEI) /
-            U256::from(100);
-        let prio_fee = U256::from((prices.max_priority_fee_per_gas * 100.0) as u64) *
-            U256::from(GWEI_TO_WEI) /
-            U256::from(100);
-        Ok((base_fee, prio_fee))
+#[inline]
+fn gas_category_to_confidence(gas_category: &GasCategory) -> u64 {
+    match gas_category {
+        GasCategory::SafeLow => 80,
+        GasCategory::Standard => 90,
+        GasCategory::Fast => 95,
+        GasCategory::Fastest => 99,
     }
 }
diff --git a/ethers-middleware/src/gas_oracle/cache.rs b/ethers-middleware/src/gas_oracle/cache.rs
index 6f8b162b..d840cb0f 100644
--- a/ethers-middleware/src/gas_oracle/cache.rs
+++ b/ethers-middleware/src/gas_oracle/cache.rs
@@ -1,4 +1,4 @@
-use crate::gas_oracle::{GasOracle, GasOracleError};
+use super::{GasOracle, Result};
 use async_trait::async_trait;
 use ethers_core::types::U256;
 use futures_locks::RwLock;
@@ -19,6 +19,24 @@ pub struct Cache<T: GasOracle> {
 #[derive(Default, Debug)]
 struct Cached<T: Clone>(RwLock<Option<(Instant, T)>>);
 
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+impl<T: GasOracle> GasOracle for Cache<T> {
+    async fn fetch(&self) -> Result<U256> {
+        self.fee.get(self.validity, || self.inner.fetch()).await
+    }
+
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
+        self.eip1559.get(self.validity, || self.inner.estimate_eip1559_fees()).await
+    }
+}
+
+impl<T: GasOracle> Cache<T> {
+    pub fn new(validity: Duration, inner: T) -> Self {
+        Self { inner, validity, fee: Cached::default(), eip1559: Cached::default() }
+    }
+}
+
 impl<T: Clone> Cached<T> {
     async fn get<F, E, Fut>(&self, validity: Duration, fetch: F) -> Result<T, E>
     where
@@ -50,21 +68,3 @@ impl<T: Clone> Cached<T> {
         }
     }
 }
-
-impl<T: GasOracle> Cache<T> {
-    pub fn new(validity: Duration, inner: T) -> Self {
-        Self { inner, validity, fee: Cached::default(), eip1559: Cached::default() }
-    }
-}
-
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-impl<T: GasOracle> GasOracle for Cache<T> {
-    async fn fetch(&self) -> Result<U256, GasOracleError> {
-        self.fee.get(self.validity, || self.inner.fetch()).await
-    }
-
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
-        self.eip1559.get(self.validity, || self.inner.estimate_eip1559_fees()).await
-    }
-}
diff --git a/ethers-middleware/src/gas_oracle/eth_gas_station.rs b/ethers-middleware/src/gas_oracle/eth_gas_station.rs
index 683caf37..73f1572c 100644
--- a/ethers-middleware/src/gas_oracle/eth_gas_station.rs
+++ b/ethers-middleware/src/gas_oracle/eth_gas_station.rs
@@ -1,73 +1,111 @@
-use std::collections::HashMap;
-
-use ethers_core::types::U256;
+#![allow(deprecated)]
 
+use super::{GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
 use async_trait::async_trait;
+use ethers_core::types::U256;
 use reqwest::Client;
 use serde::Deserialize;
+use std::collections::HashMap;
 use url::Url;
 
-use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
-
-const ETH_GAS_STATION_URL_PREFIX: &str = "https://ethgasstation.info/api/ethgasAPI.json";
+const URL: &str = "https://ethgasstation.info/api/ethgasAPI.json";
 
-/// A client over HTTP for the [EthGasStation](https://ethgasstation.info/api/ethgasAPI.json) gas tracker API
-/// that implements the `GasOracle` trait
+/// A client over HTTP for the [EthGasStation](https://ethgasstation.info) gas tracker API
+/// that implements the `GasOracle` trait.
 #[derive(Clone, Debug)]
+#[deprecated = "ETHGasStation is shutting down: https://twitter.com/ETHGasStation/status/1597341610777317376"]
+#[must_use]
 pub struct EthGasStation {
     client: Client,
     url: Url,
     gas_category: GasCategory,
 }
 
-#[derive(Clone, Debug, Deserialize, PartialEq)]
-#[serde(rename_all = "camelCase")]
 /// Eth Gas Station's response for the current recommended fast, standard and
 /// safe low gas prices on the Ethereum network, along with the current block
 /// and wait times for each "speed".
-pub struct EthGasStationResponse {
-    /// Recommended safe(expected to be mined in < 30 minutes) gas price in
-    /// x10 Gwei (divide by 10 to convert it to gwei)
-    pub safe_low: f64,
-    /// Recommended average(expected to be mined in < 5 minutes) gas price in
-    /// x10 Gwei (divide by 10 to convert it to gwei)
+#[derive(Clone, Debug, Deserialize, PartialEq)]
+#[serde(rename_all = "camelCase")]
+pub struct Response {
+    /// Recommended safe (expected to be mined in < 30 minutes).
+    ///
+    /// In gwei * 10 (divide by 10 to convert it to gwei).
+    pub safe_low: u64,
+    /// Recommended average (expected to be mined in < 5 minutes).
+    ///
+    /// In gwei * 10 (divide by 10 to convert it to gwei).
     pub average: u64,
-    /// Recommended fast(expected to be mined in < 2 minutes) gas price in
-    /// x10 Gwei (divide by 10 to convert it to gwei)
+    /// Recommended fast (expected to be mined in < 2 minutes).
+    ///
+    /// In gwei * 10 (divide by 10 to convert it to gwei).
     pub fast: u64,
-    /// Recommended fastest(expected to be mined in < 30 seconds) gas price
-    /// in x10 Gwei(divide by 10 to convert it to gwei)
+    /// Recommended fastest (expected to be mined in < 30 seconds).
+    ///
+    /// In gwei * 10 (divide by 10 to convert it to gwei).
     pub fastest: u64,
 
     // post eip-1559 fields
+    /// Average time (in seconds) to mine a single block.
     #[serde(rename = "block_time")] // inconsistent json response naming...
-    /// Average time(in seconds) to mine one single block
     pub block_time: f64,
-    /// The latest block number
+    /// The latest block number.
     pub block_num: u64,
-    /// Smallest value of (gasUsed / gaslimit) from last 10 blocks
+    /// Smallest value of `gasUsed / gaslimit` from the last 10 blocks.
     pub speed: f64,
-    /// Waiting time(in minutes) for the `safe_low` gas price
+    /// Waiting time (in minutes) for the `safe_low` gas price.
     pub safe_low_wait: f64,
-    /// Waiting time(in minutes) for the `average` gas price
+    /// Waiting time (in minutes) for the `average` gas price.
     pub avg_wait: f64,
-    /// Waiting time(in minutes) for the `fast` gas price
+    /// Waiting time (in minutes) for the `fast` gas price.
     pub fast_wait: f64,
-    /// Waiting time(in minutes) for the `fastest` gas price
+    /// Waiting time (in minutes) for the `fastest` gas price.
     pub fastest_wait: f64,
     // What is this?
     pub gas_price_range: HashMap<u64, f64>,
 }
 
+impl Response {
+    #[inline]
+    pub fn gas_from_category(&self, gas_category: GasCategory) -> u64 {
+        match gas_category {
+            GasCategory::SafeLow => self.safe_low,
+            GasCategory::Standard => self.average,
+            GasCategory::Fast => self.fast,
+            GasCategory::Fastest => self.fastest,
+        }
+    }
+}
+
+impl Default for EthGasStation {
+    fn default() -> Self {
+        Self::new(None)
+    }
+}
+
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+impl GasOracle for EthGasStation {
+    async fn fetch(&self) -> Result<U256> {
+        let res = self.query().await?;
+        let gas_price = res.gas_from_category(self.gas_category);
+        // gas_price is in `gwei * 10`
+        Ok(U256::from(gas_price) * GWEI_TO_WEI_U256 / U256::from(10_u64))
+    }
+
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
+        Err(GasOracleError::Eip1559EstimationNotSupported)
+    }
+}
+
 impl EthGasStation {
-    /// Creates a new [EthGasStation](https://docs.ethgasstation.info/) gas oracle
+    /// Creates a new [EthGasStation](https://docs.ethgasstation.info/) gas oracle.
     pub fn new(api_key: Option<&str>) -> Self {
         Self::with_client(Client::new(), api_key)
     }
 
     /// Same as [`Self::new`] but with a custom [`Client`].
     pub fn with_client(client: Client, api_key: Option<&str>) -> Self {
-        let mut url = Url::parse(ETH_GAS_STATION_URL_PREFIX).expect("invalid url");
+        let mut url = Url::parse(URL).unwrap();
         if let Some(key) = api_key {
             url.query_pairs_mut().append_pair("api-key", key);
         }
@@ -75,39 +113,15 @@ impl EthGasStation {
     }
 
     /// Sets the gas price category to be used when fetching the gas price.
-    #[must_use]
     pub fn category(mut self, gas_category: GasCategory) -> Self {
         self.gas_category = gas_category;
         self
     }
 
-    pub async fn query(&self) -> Result<EthGasStationResponse, GasOracleError> {
-        Ok(self.client.get(self.url.as_ref()).send().await?.json::<EthGasStationResponse>().await?)
-    }
-}
-
-impl Default for EthGasStation {
-    fn default() -> Self {
-        Self::new(None)
-    }
-}
-
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-impl GasOracle for EthGasStation {
-    async fn fetch(&self) -> Result<U256, GasOracleError> {
-        let res = self.query().await?;
-        let gas_price = match self.gas_category {
-            GasCategory::SafeLow => U256::from((res.safe_low.ceil() as u64 * GWEI_TO_WEI) / 10),
-            GasCategory::Standard => U256::from((res.average * GWEI_TO_WEI) / 10),
-            GasCategory::Fast => U256::from((res.fast * GWEI_TO_WEI) / 10),
-            GasCategory::Fastest => U256::from((res.fastest * GWEI_TO_WEI) / 10),
-        };
-
-        Ok(gas_price)
-    }
-
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
-        Err(GasOracleError::Eip1559EstimationNotSupported)
+    /// Perform a request to the gas price API and deserialize the response.
+    pub async fn query(&self) -> Result<Response> {
+        let response =
+            self.client.get(self.url.clone()).send().await?.error_for_status()?.json().await?;
+        Ok(response)
     }
 }
diff --git a/ethers-middleware/src/gas_oracle/etherchain.rs b/ethers-middleware/src/gas_oracle/etherchain.rs
index 3fc94e6a..5d419929 100644
--- a/ethers-middleware/src/gas_oracle/etherchain.rs
+++ b/ethers-middleware/src/gas_oracle/etherchain.rs
@@ -1,32 +1,63 @@
-use ethers_core::types::U256;
-
+use super::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result};
 use async_trait::async_trait;
+use ethers_core::types::U256;
 use reqwest::Client;
 use serde::Deserialize;
 use url::Url;
 
-use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
-
-const ETHERCHAIN_URL: &str = "https://www.etherchain.org/api/gasPriceOracle";
+const URL: &str = "https://www.etherchain.org/api/gasPriceOracle";
 
 /// A client over HTTP for the [Etherchain](https://www.etherchain.org/api/gasPriceOracle) gas tracker API
-/// that implements the `GasOracle` trait
+/// that implements the `GasOracle` trait.
 #[derive(Clone, Debug)]
+#[must_use]
 pub struct Etherchain {
     client: Client,
     url: Url,
     gas_category: GasCategory,
 }
 
-#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd)]
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
 #[serde(rename_all = "camelCase")]
-pub struct EtherchainResponse {
-    pub safe_low: f32,
-    pub standard: f32,
-    pub fast: f32,
-    pub fastest: f32,
-    pub current_base_fee: f32,
-    pub recommended_base_fee: f32,
+pub struct Response {
+    pub safe_low: f64,
+    pub standard: f64,
+    pub fast: f64,
+    pub fastest: f64,
+    pub current_base_fee: f64,
+    pub recommended_base_fee: f64,
+}
+
+impl Response {
+    #[inline]
+    pub fn gas_from_category(&self, gas_category: GasCategory) -> f64 {
+        match gas_category {
+            GasCategory::SafeLow => self.safe_low,
+            GasCategory::Standard => self.standard,
+            GasCategory::Fast => self.fast,
+            GasCategory::Fastest => self.fastest,
+        }
+    }
+}
+
+impl Default for Etherchain {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+impl GasOracle for Etherchain {
+    async fn fetch(&self) -> Result<U256> {
+        let res = self.query().await?;
+        let gas_price = res.gas_from_category(self.gas_category);
+        Ok(from_gwei_f64(gas_price))
+    }
+
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
+        Err(GasOracleError::Eip1559EstimationNotSupported)
+    }
 }
 
 impl Etherchain {
@@ -37,45 +68,20 @@ impl Etherchain {
 
     /// Same as [`Self::new`] but with a custom [`Client`].
     pub fn with_client(client: Client) -> Self {
-        let url = Url::parse(ETHERCHAIN_URL).expect("invalid url");
-
+        let url = Url::parse(URL).unwrap();
         Etherchain { client, url, gas_category: GasCategory::Standard }
     }
 
     /// Sets the gas price category to be used when fetching the gas price.
-    #[must_use]
     pub fn category(mut self, gas_category: GasCategory) -> Self {
         self.gas_category = gas_category;
         self
     }
 
-    pub async fn query(&self) -> Result<EtherchainResponse, GasOracleError> {
-        Ok(self.client.get(self.url.as_ref()).send().await?.json::<EtherchainResponse>().await?)
-    }
-}
-
-impl Default for Etherchain {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-impl GasOracle for Etherchain {
-    async fn fetch(&self) -> Result<U256, GasOracleError> {
-        let res = self.query().await?;
-        let gas_price = match self.gas_category {
-            GasCategory::SafeLow => U256::from((res.safe_low as u64) * GWEI_TO_WEI),
-            GasCategory::Standard => U256::from((res.standard as u64) * GWEI_TO_WEI),
-            GasCategory::Fast => U256::from((res.fast as u64) * GWEI_TO_WEI),
-            GasCategory::Fastest => U256::from((res.fastest as u64) * GWEI_TO_WEI),
-        };
-
-        Ok(gas_price)
-    }
-
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
-        Err(GasOracleError::Eip1559EstimationNotSupported)
+    /// Perform a request to the gas price API and deserialize the response.
+    pub async fn query(&self) -> Result<Response> {
+        let response =
+            self.client.get(self.url.clone()).send().await?.error_for_status()?.json().await?;
+        Ok(response)
     }
 }
diff --git a/ethers-middleware/src/gas_oracle/etherscan.rs b/ethers-middleware/src/gas_oracle/etherscan.rs
index b39cebff..9d8da4c0 100644
--- a/ethers-middleware/src/gas_oracle/etherscan.rs
+++ b/ethers-middleware/src/gas_oracle/etherscan.rs
@@ -1,51 +1,71 @@
+use super::{GasCategory, GasOracle, GasOracleError, Result, GWEI_TO_WEI_U256};
 use async_trait::async_trait;
-
 use ethers_core::types::U256;
 use ethers_etherscan::Client;
-
-use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError, GWEI_TO_WEI};
+use std::ops::{Deref, DerefMut};
 
 /// A client over HTTP for the [Etherscan](https://api.etherscan.io/api?module=gastracker&action=gasoracle) gas tracker API
 /// that implements the `GasOracle` trait
 #[derive(Clone, Debug)]
+#[must_use]
 pub struct Etherscan {
     client: Client,
     gas_category: GasCategory,
 }
 
-impl Etherscan {
-    /// Creates a new [Etherscan](https://etherscan.io/gastracker) gas price oracle.
-    pub fn new(client: Client) -> Self {
-        Etherscan { client, gas_category: GasCategory::Standard }
+impl Deref for Etherscan {
+    type Target = Client;
+
+    fn deref(&self) -> &Self::Target {
+        &self.client
     }
+}
 
-    /// Sets the gas price category to be used when fetching the gas price.
-    #[must_use]
-    pub fn category(mut self, gas_category: GasCategory) -> Self {
-        self.gas_category = gas_category;
-        self
+impl DerefMut for Etherscan {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.client
     }
 }
 
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 impl GasOracle for Etherscan {
-    async fn fetch(&self) -> Result<U256, GasOracleError> {
-        if matches!(self.gas_category, GasCategory::Fastest) {
-            return Err(GasOracleError::GasCategoryNotSupported)
-        }
-
-        let result = self.client.gas_oracle().await?;
-
+    async fn fetch(&self) -> Result<U256> {
+        // handle unsupported gas categories before making the request
         match self.gas_category {
-            GasCategory::SafeLow => Ok(U256::from(result.safe_gas_price * GWEI_TO_WEI)),
-            GasCategory::Standard => Ok(U256::from(result.propose_gas_price * GWEI_TO_WEI)),
-            GasCategory::Fast => Ok(U256::from(result.fast_gas_price * GWEI_TO_WEI)),
-            _ => Err(GasOracleError::GasCategoryNotSupported),
+            GasCategory::SafeLow | GasCategory::Standard | GasCategory::Fast => {}
+            GasCategory::Fastest => return Err(GasOracleError::GasCategoryNotSupported),
         }
+
+        let result = self.query().await?;
+        let gas_price = match self.gas_category {
+            GasCategory::SafeLow => result.safe_gas_price,
+            GasCategory::Standard => result.propose_gas_price,
+            GasCategory::Fast => result.fast_gas_price,
+            _ => unreachable!(),
+        };
+        Ok(U256::from(gas_price) * GWEI_TO_WEI_U256)
     }
 
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
         Err(GasOracleError::Eip1559EstimationNotSupported)
     }
 }
+
+impl Etherscan {
+    /// Creates a new [Etherscan](https://etherscan.io/gastracker) gas price oracle.
+    pub fn new(client: Client) -> Self {
+        Etherscan { client, gas_category: GasCategory::Standard }
+    }
+
+    /// Sets the gas price category to be used when fetching the gas price.
+    pub fn category(mut self, gas_category: GasCategory) -> Self {
+        self.gas_category = gas_category;
+        self
+    }
+
+    /// Perform a request to the gas price API and deserialize the response.
+    pub async fn query(&self) -> Result<ethers_etherscan::gas::GasOracle> {
+        Ok(self.client.gas_oracle().await?)
+    }
+}
diff --git a/ethers-middleware/src/gas_oracle/gas_now.rs b/ethers-middleware/src/gas_oracle/gas_now.rs
index 65ddc46c..29de2c46 100644
--- a/ethers-middleware/src/gas_oracle/gas_now.rs
+++ b/ethers-middleware/src/gas_oracle/gas_now.rs
@@ -1,64 +1,54 @@
-use ethers_core::types::U256;
-
+use super::{GasCategory, GasOracle, GasOracleError, Result};
 use async_trait::async_trait;
+use ethers_core::types::U256;
 use reqwest::Client;
 use serde::Deserialize;
 use url::Url;
 
-use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError};
+const URL: &str = "https://beaconcha.in/api/v1/execution/gasnow";
 
-const GAS_NOW_URL: &str = "https://etherchain.org/api/gasnow";
-
-/// A client over HTTP for the [Etherchain GasNow](https://etherchain.org/tools/gasnow) gas tracker API
-/// that implements the `GasOracle` trait
+/// A client over HTTP for the [beaconcha.in GasNow](https://beaconcha.in/gasnow) gas tracker API
+/// that implements the `GasOracle` trait.
 #[derive(Clone, Debug)]
+#[must_use]
 pub struct GasNow {
     client: Client,
     url: Url,
     gas_category: GasCategory,
 }
 
-#[derive(Deserialize)]
-struct GasNowResponseWrapper {
-    data: GasNowResponse,
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
+pub struct Response {
+    pub code: u64,
+    pub data: ResponseData,
 }
 
-#[derive(Clone, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
-pub struct GasNowResponse {
+#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
+pub struct ResponseData {
     pub rapid: u64,
     pub fast: u64,
     pub standard: u64,
     pub slow: u64,
+    pub timestamp: u64,
+    #[serde(rename = "priceUSD")]
+    pub price_usd: f64,
 }
 
-impl GasNow {
-    /// Creates a new [Etherchain GasNow](https://etherchain.org/tools/gasnow) gas price oracle.
-    pub fn new() -> Self {
-        Self::with_client(Client::new())
-    }
-
-    /// Same as [`Self::new`] but with a custom [`Client`].
-    pub fn with_client(client: Client) -> Self {
-        let url = Url::parse(GAS_NOW_URL).expect("invalid url");
-
-        Self { client, url, gas_category: GasCategory::Standard }
-    }
-
-    /// Sets the gas price category to be used when fetching the gas price.
-    pub fn category(mut self, gas_category: GasCategory) -> Self {
-        self.gas_category = gas_category;
-        self
+impl Response {
+    #[inline]
+    pub fn gas_from_category(&self, gas_category: GasCategory) -> u64 {
+        self.data.gas_from_category(gas_category)
     }
+}
 
-    pub async fn query(&self) -> Result<GasNowResponse, GasOracleError> {
-        let res = self
-            .client
-            .get(self.url.as_ref())
-            .send()
-            .await?
-            .json::<GasNowResponseWrapper>()
-            .await?;
-        Ok(res.data)
+impl ResponseData {
+    fn gas_from_category(&self, gas_category: GasCategory) -> u64 {
+        match gas_category {
+            GasCategory::SafeLow => self.slow,
+            GasCategory::Standard => self.standard,
+            GasCategory::Fast => self.fast,
+            GasCategory::Fastest => self.rapid,
+        }
     }
 }
 
@@ -71,19 +61,39 @@ impl Default for GasNow {
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 impl GasOracle for GasNow {
-    async fn fetch(&self) -> Result<U256, GasOracleError> {
+    async fn fetch(&self) -> Result<U256> {
         let res = self.query().await?;
-        let gas_price = match self.gas_category {
-            GasCategory::SafeLow => U256::from(res.slow),
-            GasCategory::Standard => U256::from(res.standard),
-            GasCategory::Fast => U256::from(res.fast),
-            GasCategory::Fastest => U256::from(res.rapid),
-        };
-
-        Ok(gas_price)
+        let gas_price = res.gas_from_category(self.gas_category);
+        Ok(U256::from(gas_price))
     }
 
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
         Err(GasOracleError::Eip1559EstimationNotSupported)
     }
 }
+
+impl GasNow {
+    /// Creates a new [beaconcha.in GasNow](https://beaconcha.in/gasnow) gas price oracle.
+    pub fn new() -> Self {
+        Self::with_client(Client::new())
+    }
+
+    /// Same as [`Self::new`] but with a custom [`Client`].
+    pub fn with_client(client: Client) -> Self {
+        let url = Url::parse(URL).unwrap();
+        Self { client, url, gas_category: GasCategory::Standard }
+    }
+
+    /// Sets the gas price category to be used when fetching the gas price.
+    pub fn category(mut self, gas_category: GasCategory) -> Self {
+        self.gas_category = gas_category;
+        self
+    }
+
+    /// Perform a request to the gas price API and deserialize the response.
+    pub async fn query(&self) -> Result<Response> {
+        let response =
+            self.client.get(self.url.as_ref()).send().await?.error_for_status()?.json().await?;
+        Ok(response)
+    }
+}
diff --git a/ethers-middleware/src/gas_oracle/median.rs b/ethers-middleware/src/gas_oracle/median.rs
index 2149ce41..c6dd6c7e 100644
--- a/ethers-middleware/src/gas_oracle/median.rs
+++ b/ethers-middleware/src/gas_oracle/median.rs
@@ -1,4 +1,4 @@
-use crate::gas_oracle::{GasOracle, GasOracleError};
+use super::{GasOracle, GasOracleError, Result};
 use async_trait::async_trait;
 use ethers_core::types::U256;
 use futures_util::future::join_all;
@@ -28,13 +28,10 @@ impl Median {
         self.oracles.push((weight, Box::new(oracle)));
     }
 
-    pub async fn query_all<'a, Fn, Fut, O>(
-        &'a self,
-        mut f: Fn,
-    ) -> Result<Vec<(f32, O)>, GasOracleError>
+    pub async fn query_all<'a, Fn, Fut, O>(&'a self, mut f: Fn) -> Result<Vec<(f32, O)>>
     where
         Fn: FnMut(&'a dyn GasOracle) -> Fut,
-        Fut: Future<Output = Result<O, GasOracleError>>,
+        Fut: Future<Output = Result<O>>,
     {
         // Process the oracles in parallel
         let futures = self.oracles.iter().map(|(_, oracle)| f(oracle.as_ref()));
@@ -62,13 +59,13 @@ impl Median {
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 impl GasOracle for Median {
-    async fn fetch(&self) -> Result<U256, GasOracleError> {
+    async fn fetch(&self) -> Result<U256> {
         let mut values = self.query_all(|oracle| oracle.fetch()).await?;
         // `query_all` guarantees `values` is not empty
         Ok(*weighted_fractile_by_key(0.5, &mut values, |fee| fee).unwrap())
     }
 
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
         let mut values = self.query_all(|oracle| oracle.estimate_eip1559_fees()).await?;
         // `query_all` guarantees `values` is not empty
         Ok((
diff --git a/ethers-middleware/src/gas_oracle/middleware.rs b/ethers-middleware/src/gas_oracle/middleware.rs
index 272b0150..918ec4b0 100644
--- a/ethers-middleware/src/gas_oracle/middleware.rs
+++ b/ethers-middleware/src/gas_oracle/middleware.rs
@@ -4,8 +4,8 @@ use ethers_core::types::{transaction::eip2718::TypedTransaction, *};
 use ethers_providers::{FromErr, Middleware, PendingTransaction};
 use thiserror::Error;
 
+/// Middleware used for fetching gas prices over an API instead of `eth_gasPrice`.
 #[derive(Debug)]
-/// Middleware used for fetching gas prices over an API instead of `eth_gasPrice`
 pub struct GasOracleMiddleware<M, G> {
     inner: M,
     gas_oracle: G,
@@ -21,7 +21,7 @@ where
     }
 }
 
-#[derive(Error, Debug)]
+#[derive(Debug, Error)]
 pub enum MiddlewareError<M: Middleware> {
     #[error(transparent)]
     GasOracleError(#[from] GasOracleError),
diff --git a/ethers-middleware/src/gas_oracle/mod.rs b/ethers-middleware/src/gas_oracle/mod.rs
index 4a7a701c..9be17750 100644
--- a/ethers-middleware/src/gas_oracle/mod.rs
+++ b/ethers-middleware/src/gas_oracle/mod.rs
@@ -1,54 +1,58 @@
-mod blocknative;
+pub mod blocknative;
 pub use blocknative::BlockNative;
 
-mod eth_gas_station;
+pub mod eth_gas_station;
+#[allow(deprecated)]
 pub use eth_gas_station::EthGasStation;
 
-mod etherchain;
+pub mod etherchain;
 pub use etherchain::Etherchain;
 
-mod etherscan;
+pub mod etherscan;
 pub use etherscan::Etherscan;
 
-mod middleware;
+pub mod middleware;
 pub use middleware::{GasOracleMiddleware, MiddlewareError};
 
-mod median;
+pub mod median;
 pub use median::Median;
 
-mod cache;
+pub mod cache;
 pub use cache::Cache;
 
-mod polygon;
+pub mod polygon;
 pub use polygon::Polygon;
 
-mod gas_now;
+pub mod gas_now;
 pub use gas_now::GasNow;
 
-mod provider_oracle;
+pub mod provider_oracle;
 pub use provider_oracle::ProviderOracle;
 
-use ethers_core::types::U256;
-
 use async_trait::async_trait;
 use auto_impl::auto_impl;
+use ethers_core::types::U256;
 use reqwest::Error as ReqwestError;
-use std::error::Error;
+use std::{error::Error, fmt::Debug};
 use thiserror::Error;
 
-const GWEI_TO_WEI: u64 = 1000000000;
+pub(crate) const GWEI_TO_WEI: u64 = 1_000_000_000;
+pub(crate) const GWEI_TO_WEI_U256: U256 = U256([0, 0, 0, GWEI_TO_WEI]);
+
+pub type Result<T, E = GasOracleError> = std::result::Result<T, E>;
 
-/// Various gas price categories. Choose one of the available
-#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
+/// Generic [`GasOracle`] gas price categories.
+#[derive(Clone, Copy, Default, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
 pub enum GasCategory {
     SafeLow,
+    #[default]
     Standard,
     Fast,
     Fastest,
 }
 
-#[derive(Error, Debug)]
-/// Error thrown when fetching data from the `GasOracle`
+/// Error thrown by a [`GasOracle`].
+#[derive(Debug, Error)]
 pub enum GasOracleError {
     /// An internal error in the HTTP request made from the underlying
     /// gas oracle
@@ -83,48 +87,69 @@ pub enum GasOracleError {
     UnsupportedChain,
 
     /// Error thrown when the provider failed.
-    #[error("Chain is not supported by the oracle")]
+    #[error("Provider error: {0}")]
     ProviderError(#[from] Box<dyn Error + Send + Sync>),
 }
 
-/// `GasOracle` is a trait that an underlying gas oracle needs to implement.
+/// An Ethereum gas price oracle.
 ///
 /// # Example
 ///
 /// ```no_run
-/// use ethers_middleware::{
-///     gas_oracle::{EthGasStation, Etherscan, GasCategory, GasOracle},
-/// };
+/// use ethers_core::types::U256;
+/// use ethers_middleware::gas_oracle::{GasCategory, GasNow, GasOracle};
 ///
 /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-/// let eth_gas_station_oracle = EthGasStation::new(Some("my-api-key"));
-/// let etherscan_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
-///
-/// let data_1 = eth_gas_station_oracle.fetch().await?;
-/// let data_2 = etherscan_oracle.fetch().await?;
+/// let oracle = GasNow::default().category(GasCategory::SafeLow);
+/// let gas_price = oracle.fetch().await?;
+/// assert!(gas_price > U256::zero());
 /// # Ok(())
 /// # }
 /// ```
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 #[auto_impl(&, Box, Arc)]
-pub trait GasOracle: Send + Sync + std::fmt::Debug {
-    /// Makes an asynchronous HTTP query to the underlying `GasOracle`
+pub trait GasOracle: Send + Sync + Debug {
+    /// Makes an asynchronous HTTP query to the underlying [`GasOracle`] to fetch the current gas
+    /// price estimate.
     ///
     /// # Example
     ///
+    /// ```no_run
+    /// use ethers_core::types::U256;
+    /// use ethers_middleware::gas_oracle::{GasCategory, GasNow, GasOracle};
+    ///
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let oracle = GasNow::default().category(GasCategory::SafeLow);
+    /// let gas_price = oracle.fetch().await?;
+    /// assert!(gas_price > U256::zero());
+    /// # Ok(())
+    /// # }
     /// ```
-    /// use ethers_middleware::{
-    ///     gas_oracle::{Etherchain, GasCategory, GasOracle},
-    /// };
+    async fn fetch(&self) -> Result<U256>;
+
+    /// Makes an asynchronous HTTP query to the underlying [`GasOracle`] to fetch the current max
+    /// gas fee and priority gas fee estimates.
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// use ethers_core::types::U256;
+    /// use ethers_middleware::gas_oracle::{GasCategory, GasNow, GasOracle};
     ///
     /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let etherchain_oracle = Etherchain::new().category(GasCategory::Fastest);
-    /// let data = etherchain_oracle.fetch().await?;
+    /// let oracle = GasNow::default().category(GasCategory::SafeLow);
+    /// let (max_fee, priority_fee) = oracle.estimate_eip1559_fees().await?;
+    /// assert!(max_fee > U256::zero());
+    /// assert!(priority_fee > U256::zero());
     /// # Ok(())
     /// # }
     /// ```
-    async fn fetch(&self) -> Result<U256, GasOracleError>;
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)>;
+}
 
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError>;
+#[inline]
+#[doc(hidden)]
+pub(crate) fn from_gwei_f64(gwei: f64) -> U256 {
+    ethers_core::types::u256_from_f64_saturating(gwei) * GWEI_TO_WEI_U256
 }
diff --git a/ethers-middleware/src/gas_oracle/polygon.rs b/ethers-middleware/src/gas_oracle/polygon.rs
index 47a3415d..5fd63545 100644
--- a/ethers-middleware/src/gas_oracle/polygon.rs
+++ b/ethers-middleware/src/gas_oracle/polygon.rs
@@ -1,16 +1,17 @@
-use crate::gas_oracle::{GasCategory, GasOracle, GasOracleError};
+use super::{from_gwei_f64, GasCategory, GasOracle, GasOracleError, Result};
 use async_trait::async_trait;
-use ethers_core::types::{u256_from_f64_saturating, Chain, U256};
+use ethers_core::types::{Chain, U256};
 use reqwest::Client;
 use serde::Deserialize;
 use url::Url;
 
-const GAS_PRICE_ENDPOINT: &str = "https://gasstation-mainnet.matic.network/v2";
-const MUMBAI_GAS_PRICE_ENDPOINT: &str = "https://gasstation-mumbai.matic.today/v2";
+const MAINNET_URL: &str = "https://gasstation-mainnet.matic.network/v2";
+const MUMBAI_URL: &str = "https://gasstation-mumbai.matic.today/v2";
 
 /// The [Polygon](https://docs.polygon.technology/docs/develop/tools/polygon-gas-station/) gas station API
-/// Queries over HTTP and implements the `GasOracle` trait
+/// Queries over HTTP and implements the `GasOracle` trait.
 #[derive(Clone, Debug)]
+#[must_use]
 pub struct Polygon {
     client: Client,
     url: Url,
@@ -18,74 +19,87 @@ pub struct Polygon {
 }
 
 /// The response from the Polygon gas station API.
+///
 /// Gas prices are in Gwei.
 #[derive(Debug, Deserialize, PartialEq)]
 #[serde(rename_all = "camelCase")]
 pub struct Response {
-    estimated_base_fee: f64,
-    safe_low: GasEstimate,
-    standard: GasEstimate,
-    fast: GasEstimate,
+    pub estimated_base_fee: f64,
+    pub safe_low: GasEstimate,
+    pub standard: GasEstimate,
+    pub fast: GasEstimate,
 }
 
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
 #[serde(rename_all = "camelCase")]
 pub struct GasEstimate {
-    max_priority_fee: f64,
-    max_fee: f64,
+    pub max_priority_fee: f64,
+    pub max_fee: f64,
+}
+
+impl Response {
+    #[inline]
+    pub fn estimate_from_category(&self, gas_category: GasCategory) -> GasEstimate {
+        match gas_category {
+            GasCategory::SafeLow => self.safe_low,
+            GasCategory::Standard => self.standard,
+            GasCategory::Fast => self.fast,
+            GasCategory::Fastest => self.fast,
+        }
+    }
+}
+
+impl Default for Polygon {
+    fn default() -> Self {
+        Self::new(Chain::Polygon).unwrap()
+    }
+}
+
+#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
+#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
+impl GasOracle for Polygon {
+    async fn fetch(&self) -> Result<U256> {
+        let response = self.query().await?;
+        let base = response.estimated_base_fee;
+        let prio = response.estimate_from_category(self.gas_category).max_priority_fee;
+        let fee = base + prio;
+        Ok(from_gwei_f64(fee))
+    }
+
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
+        let response = self.query().await?;
+        let estimate = response.estimate_from_category(self.gas_category);
+        let max = from_gwei_f64(estimate.max_fee);
+        let prio = from_gwei_f64(estimate.max_priority_fee);
+        Ok((max, prio))
+    }
 }
 
 impl Polygon {
-    pub fn new(chain: Chain) -> Result<Self, GasOracleError> {
+    pub fn new(chain: Chain) -> Result<Self> {
         Self::with_client(Client::new(), chain)
     }
 
-    pub fn with_client(client: Client, chain: Chain) -> Result<Self, GasOracleError> {
+    pub fn with_client(client: Client, chain: Chain) -> Result<Self> {
         // TODO: Sniff chain from chain id.
         let url = match chain {
-            Chain::Polygon => Url::parse(GAS_PRICE_ENDPOINT).unwrap(),
-            Chain::PolygonMumbai => Url::parse(MUMBAI_GAS_PRICE_ENDPOINT).unwrap(),
+            Chain::Polygon => MAINNET_URL,
+            Chain::PolygonMumbai => MUMBAI_URL,
             _ => return Err(GasOracleError::UnsupportedChain),
         };
-        Ok(Self { client, url, gas_category: GasCategory::Standard })
+        Ok(Self { client, url: Url::parse(url).unwrap(), gas_category: GasCategory::Standard })
     }
 
     /// Sets the gas price category to be used when fetching the gas price.
-    #[must_use]
     pub fn category(mut self, gas_category: GasCategory) -> Self {
         self.gas_category = gas_category;
         self
     }
 
-    /// Perform request to Blocknative, decode response
-    pub async fn request(&self) -> Result<(f64, GasEstimate), GasOracleError> {
-        let response: Response =
-            self.client.get(self.url.as_ref()).send().await?.error_for_status()?.json().await?;
-        let estimate = match self.gas_category {
-            GasCategory::SafeLow => response.safe_low,
-            GasCategory::Standard => response.standard,
-            GasCategory::Fast => response.fast,
-            GasCategory::Fastest => response.fast,
-        };
-        Ok((response.estimated_base_fee, estimate))
-    }
-}
-
-#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
-#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
-impl GasOracle for Polygon {
-    async fn fetch(&self) -> Result<U256, GasOracleError> {
-        let (base_fee, estimate) = self.request().await?;
-        let fee = base_fee + estimate.max_priority_fee;
-        Ok(from_gwei(fee))
-    }
-
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
-        let (_, estimate) = self.request().await?;
-        Ok((from_gwei(estimate.max_fee), from_gwei(estimate.max_priority_fee)))
+    /// Perform a request to the gas price API and deserialize the response.
+    pub async fn query(&self) -> Result<Response> {
+        let response =
+            self.client.get(self.url.clone()).send().await?.error_for_status()?.json().await?;
+        Ok(response)
     }
 }
-
-fn from_gwei(gwei: f64) -> U256 {
-    u256_from_f64_saturating(gwei * 1.0e9_f64)
-}
diff --git a/ethers-middleware/src/gas_oracle/provider_oracle.rs b/ethers-middleware/src/gas_oracle/provider_oracle.rs
index 894bb5ab..c97c857d 100644
--- a/ethers-middleware/src/gas_oracle/provider_oracle.rs
+++ b/ethers-middleware/src/gas_oracle/provider_oracle.rs
@@ -1,4 +1,4 @@
-use crate::gas_oracle::{GasOracle, GasOracleError};
+use super::{GasOracle, GasOracleError, Result};
 use async_trait::async_trait;
 use ethers_core::types::U256;
 use ethers_providers::Middleware;
@@ -6,7 +6,8 @@ use std::fmt::Debug;
 
 /// Gas oracle from a [`Middleware`] implementation such as an
 /// Ethereum RPC provider.
-#[derive(Debug)]
+#[derive(Clone, Debug)]
+#[must_use]
 pub struct ProviderOracle<M: Middleware> {
     provider: M,
 }
@@ -21,16 +22,16 @@ impl<M: Middleware> ProviderOracle<M> {
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 impl<M: Middleware> GasOracle for ProviderOracle<M>
 where
-    <M as Middleware>::Error: 'static,
+    M::Error: 'static,
 {
-    async fn fetch(&self) -> Result<U256, GasOracleError> {
+    async fn fetch(&self) -> Result<U256> {
         self.provider
             .get_gas_price()
             .await
             .map_err(|err| GasOracleError::ProviderError(Box::new(err)))
     }
 
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
         // TODO: Allow configuring different estimation functions.
         self.provider
             .estimate_eip1559_fees(None)
diff --git a/ethers-middleware/src/transformer/ds_proxy/factory.rs b/ethers-middleware/src/transformer/ds_proxy/factory.rs
index 11fb1380..122c7bf1 100644
--- a/ethers-middleware/src/transformer/ds_proxy/factory.rs
+++ b/ethers-middleware/src/transformer/ds_proxy/factory.rs
@@ -1,22 +1,21 @@
 use ethers_contract::Lazy;
 use ethers_core::types::*;
-use std::{collections::HashMap, str::FromStr};
+use std::collections::HashMap;
 
 /// A lazily computed hash map with the Ethereum network IDs as keys and the corresponding
 /// DsProxyFactory contract addresses as values
 pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
-    let mut m = HashMap::new();
+    let mut m = HashMap::with_capacity(1);
 
     // mainnet
-    let addr =
-        Address::from_str("eefba1e63905ef1d7acba5a8513c70307c1ce441").expect("Decoding failed");
-    m.insert(U256::from(1u8), addr);
+    let addr = "eefba1e63905ef1d7acba5a8513c70307c1ce441".parse().unwrap();
+    m.insert(U256::from(1_u64), addr);
 
     m
 });
 
+/// Generated with abigen:
 ///
-/// Generated with
 /// ```ignore
 /// # use ethers_contract::abigen;
 /// abigen!(DsProxyFactory,
@@ -26,7 +25,6 @@ pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
 ///         }
 ///     );
 /// ```
-// Auto-generated type-safe bindings
 pub use dsproxyfactory_mod::*;
 #[allow(clippy::too_many_arguments)]
 mod dsproxyfactory_mod {


Test patch:
diff --git a/ethers-contract/tests/it/contract.rs b/ethers-contract/tests/it/contract.rs
index e15005aa..8fdd0469 100644
--- a/ethers-contract/tests/it/contract.rs
+++ b/ethers-contract/tests/it/contract.rs
@@ -15,7 +15,7 @@ mod eth_tests {
     use ethers_derive_eip712::*;
     use ethers_providers::{Http, Middleware, PendingTransaction, Provider, StreamExt};
     use ethers_signers::{LocalWallet, Signer};
-    use std::{convert::TryFrom, sync::Arc, time::Duration};
+    use std::{convert::TryFrom, iter::FromIterator, sync::Arc, time::Duration};
 
     #[tokio::test]
     async fn deploy_and_call_contract() {
@@ -517,10 +517,26 @@ mod eth_tests {
             .add_get_eth_balance(addrs[5], false)
             .add_get_eth_balance(addrs[6], false);
 
+        let valid_balances = [
+            U256::from(10_000_000_000_000_000_000_000u128),
+            U256::from(10_000_000_000_000_000_000_000u128),
+            U256::from(10_000_000_000_000_000_000_000u128),
+        ];
+
         let balances: (U256, U256, U256) = multicall.call().await.unwrap();
-        assert_eq!(balances.0, U256::from(10_000_000_000_000_000_000_000u128));
-        assert_eq!(balances.1, U256::from(10_000_000_000_000_000_000_000u128));
-        assert_eq!(balances.2, U256::from(10_000_000_000_000_000_000_000u128));
+        assert_eq!(balances.0, valid_balances[0]);
+        assert_eq!(balances.1, valid_balances[1]);
+        assert_eq!(balances.2, valid_balances[2]);
+
+        // call_array
+        multicall
+            .clear_calls()
+            .add_get_eth_balance(addrs[4], false)
+            .add_get_eth_balance(addrs[5], false)
+            .add_get_eth_balance(addrs[6], false);
+
+        let balances: Vec<U256> = multicall.call_array().await.unwrap();
+        assert_eq!(balances, Vec::from_iter(valid_balances.iter().copied()));
 
         // clear multicall so we can test `call_raw` w/ >16 calls
         multicall.clear_calls();
@@ -536,10 +552,11 @@ mod eth_tests {
             .unwrap();
 
         // build up a list of calls greater than the 16 max restriction
-        for i in 0..=16 {
-            let call = simple_contract.method::<_, String>("getValue", ()).unwrap();
-            multicall.add_call(call, false);
-        }
+        multicall.add_calls(
+            false,
+            std::iter::repeat(simple_contract.method::<_, String>("getValue", ()).unwrap())
+                .take(17), // .collect(),
+        );
 
         // must use `call_raw` as `.calls` > 16
         let tokens = multicall.call_raw().await.unwrap();
diff --git a/ethers-middleware/tests/builder.rs b/ethers-middleware/tests/builder.rs
index 7c988bb8..d99c603e 100644
--- a/ethers-middleware/tests/builder.rs
+++ b/ethers-middleware/tests/builder.rs
@@ -1,69 +1,67 @@
 #![cfg(not(target_arch = "wasm32"))]
-#[cfg(not(feature = "celo"))]
-mod tests {
-    use ethers_core::{rand::thread_rng, types::U64};
-    use ethers_middleware::{
-        builder::MiddlewareBuilder,
-        gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
-        gas_oracle::{EthGasStation, GasOracleMiddleware},
-        nonce_manager::NonceManagerMiddleware,
-        signer::SignerMiddleware,
-    };
-    use ethers_providers::{Middleware, Provider};
-    use ethers_signers::{LocalWallet, Signer};
 
-    #[tokio::test]
-    async fn build_raw_middleware_stack() {
-        let (provider, mock) = Provider::mocked();
+use ethers_core::{rand::thread_rng, types::U64};
+use ethers_middleware::{
+    builder::MiddlewareBuilder,
+    gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
+    gas_oracle::{GasNow, GasOracleMiddleware},
+    nonce_manager::NonceManagerMiddleware,
+    signer::SignerMiddleware,
+};
+use ethers_providers::{Middleware, Provider};
+use ethers_signers::{LocalWallet, Signer};
 
-        let signer = LocalWallet::new(&mut thread_rng());
-        let address = signer.address();
-        let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
+#[tokio::test]
+async fn build_raw_middleware_stack() {
+    let (provider, mock) = Provider::mocked();
 
-        let provider = provider
-            .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
-            .wrap_into(|p| GasOracleMiddleware::new(p, EthGasStation::new(None)))
-            .wrap_into(|p| SignerMiddleware::new(p, signer))
-            .wrap_into(|p| NonceManagerMiddleware::new(p, address));
+    let signer = LocalWallet::new(&mut thread_rng());
+    let address = signer.address();
+    let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
 
-        // push a response
-        mock.push(U64::from(12u64)).unwrap();
-        let block: U64 = provider.get_block_number().await.unwrap();
-        assert_eq!(block.as_u64(), 12);
+    let provider = provider
+        .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
+        .wrap_into(|p| GasOracleMiddleware::new(p, GasNow::new()))
+        .wrap_into(|p| SignerMiddleware::new(p, signer))
+        .wrap_into(|p| NonceManagerMiddleware::new(p, address));
 
-        provider.get_block_number().await.unwrap_err();
+    // push a response
+    mock.push(U64::from(12u64)).unwrap();
+    let block: U64 = provider.get_block_number().await.unwrap();
+    assert_eq!(block.as_u64(), 12);
 
-        // 2 calls were made
-        mock.assert_request("eth_blockNumber", ()).unwrap();
-        mock.assert_request("eth_blockNumber", ()).unwrap();
-        mock.assert_request("eth_blockNumber", ()).unwrap_err();
-    }
+    provider.get_block_number().await.unwrap_err();
 
-    #[tokio::test]
-    async fn build_declarative_middleware_stack() {
-        let (provider, mock) = Provider::mocked();
+    // 2 calls were made
+    mock.assert_request("eth_blockNumber", ()).unwrap();
+    mock.assert_request("eth_blockNumber", ()).unwrap();
+    mock.assert_request("eth_blockNumber", ()).unwrap_err();
+}
+
+#[tokio::test]
+async fn build_declarative_middleware_stack() {
+    let (provider, mock) = Provider::mocked();
 
-        let signer = LocalWallet::new(&mut thread_rng());
-        let address = signer.address();
-        let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
-        let gas_oracle = EthGasStation::new(None);
+    let signer = LocalWallet::new(&mut thread_rng());
+    let address = signer.address();
+    let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
+    let gas_oracle = GasNow::new();
 
-        let provider = provider
-            .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
-            .gas_oracle(gas_oracle)
-            .with_signer(signer)
-            .nonce_manager(address);
+    let provider = provider
+        .wrap_into(|p| GasEscalatorMiddleware::new(p, escalator, Frequency::PerBlock))
+        .gas_oracle(gas_oracle)
+        .with_signer(signer)
+        .nonce_manager(address);
 
-        // push a response
-        mock.push(U64::from(12u64)).unwrap();
-        let block: U64 = provider.get_block_number().await.unwrap();
-        assert_eq!(block.as_u64(), 12);
+    // push a response
+    mock.push(U64::from(12u64)).unwrap();
+    let block: U64 = provider.get_block_number().await.unwrap();
+    assert_eq!(block.as_u64(), 12);
 
-        provider.get_block_number().await.unwrap_err();
+    provider.get_block_number().await.unwrap_err();
 
-        // 2 calls were made
-        mock.assert_request("eth_blockNumber", ()).unwrap();
-        mock.assert_request("eth_blockNumber", ()).unwrap();
-        mock.assert_request("eth_blockNumber", ()).unwrap_err();
-    }
+    // 2 calls were made
+    mock.assert_request("eth_blockNumber", ()).unwrap();
+    mock.assert_request("eth_blockNumber", ()).unwrap();
+    mock.assert_request("eth_blockNumber", ()).unwrap_err();
 }
diff --git a/ethers-middleware/tests/gas_escalator.rs b/ethers-middleware/tests/gas_escalator.rs
index a9eff59c..c2bfbad6 100644
--- a/ethers-middleware/tests/gas_escalator.rs
+++ b/ethers-middleware/tests/gas_escalator.rs
@@ -1,4 +1,5 @@
 #![cfg(not(target_arch = "wasm32"))]
+
 use ethers_core::types::*;
 use ethers_middleware::{
     gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
diff --git a/ethers-middleware/tests/gas_oracle.rs b/ethers-middleware/tests/gas_oracle.rs
index 10db9ba0..9464fb3d 100644
--- a/ethers-middleware/tests/gas_oracle.rs
+++ b/ethers-middleware/tests/gas_oracle.rs
@@ -1,16 +1,14 @@
 #![cfg(not(target_arch = "wasm32"))]
 
-use std::convert::TryFrom;
-
 use async_trait::async_trait;
-
 use ethers_core::{types::*, utils::Anvil};
 use ethers_middleware::gas_oracle::{
-    EthGasStation, Etherchain, Etherscan, GasCategory, GasOracle, GasOracleError,
-    GasOracleMiddleware,
+    BlockNative, Etherchain, Etherscan, GasCategory, GasNow, GasOracle, GasOracleError,
+    GasOracleMiddleware, Polygon, ProviderOracle, Result,
 };
 use ethers_providers::{Http, Middleware, Provider};
 use serial_test::serial;
+use std::convert::TryFrom;
 
 #[derive(Debug)]
 struct FakeGasOracle {
@@ -20,17 +18,18 @@ struct FakeGasOracle {
 #[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
 #[cfg_attr(not(target_arch = "wasm32"), async_trait)]
 impl GasOracle for FakeGasOracle {
-    async fn fetch(&self) -> Result<U256, GasOracleError> {
+    async fn fetch(&self) -> Result<U256> {
         Ok(self.gas_price)
     }
 
-    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256), GasOracleError> {
+    async fn estimate_eip1559_fees(&self) -> Result<(U256, U256)> {
         Err(GasOracleError::Eip1559EstimationNotSupported)
     }
 }
 
 #[tokio::test]
-async fn using_gas_oracle() {
+#[serial]
+async fn provider_using_gas_oracle() {
     let anvil = Anvil::new().spawn();
 
     let from = anvil.addresses()[0];
@@ -38,11 +37,11 @@ async fn using_gas_oracle() {
     // connect to the network
     let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
 
-    // initial base fee
-    let base_fee = 1_000_000_000u64;
     // assign a gas oracle to use
-    let gas_oracle = FakeGasOracle { gas_price: (base_fee + 1337).into() };
-    let expected_gas_price = gas_oracle.fetch().await.unwrap();
+    let expected_gas_price = U256::from(1234567890_u64);
+    let gas_oracle = FakeGasOracle { gas_price: expected_gas_price };
+    let gas_price = gas_oracle.fetch().await.unwrap();
+    assert_eq!(gas_price, expected_gas_price);
 
     let provider = GasOracleMiddleware::new(provider, gas_oracle);
 
@@ -55,35 +54,70 @@ async fn using_gas_oracle() {
 }
 
 #[tokio::test]
+#[serial]
+async fn provider_oracle() {
+    // spawn anvil and connect to it
+    let anvil = Anvil::new().spawn();
+    let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
+
+    // assert that provider.get_gas_price() and oracle.fetch() return the same value
+    let expected_gas_price = provider.get_gas_price().await.unwrap();
+    let provider_oracle = ProviderOracle::new(provider);
+    let gas = provider_oracle.fetch().await.unwrap();
+    assert_eq!(gas, expected_gas_price);
+}
+
+#[tokio::test]
+async fn blocknative() {
+    let gas_now_oracle = BlockNative::default();
+    let gas_price = gas_now_oracle.fetch().await.unwrap();
+    assert!(gas_price > U256::zero());
+}
+
+#[tokio::test]
+#[ignore = "ETHGasStation is shutting down: https://twitter.com/ETHGasStation/status/1597341610777317376"]
+#[allow(deprecated)]
 async fn eth_gas_station() {
-    // initialize and fetch gas estimates from EthGasStation
-    let eth_gas_station_oracle = EthGasStation::default();
-    let data = eth_gas_station_oracle.fetch().await;
-    data.unwrap();
+    let eth_gas_station_oracle = ethers_middleware::gas_oracle::EthGasStation::default();
+    let gas_price = eth_gas_station_oracle.fetch().await.unwrap();
+    assert!(gas_price > U256::zero());
+}
+
+#[tokio::test]
+#[ignore = "Etherchain / beaconcha.in's `gasPriceOracle` API currently returns 404: https://www.etherchain.org/api/gasPriceOracle"]
+async fn etherchain() {
+    let etherchain_oracle = Etherchain::default();
+    let gas_price = etherchain_oracle.fetch().await.unwrap();
+    assert!(gas_price > U256::zero());
 }
 
 #[tokio::test]
-#[serial]
 async fn etherscan() {
     let etherscan_client = ethers_etherscan::Client::new_from_env(Chain::Mainnet).unwrap();
 
     // initialize and fetch gas estimates from Etherscan
     // since etherscan does not support `fastest` category, we expect an error
     let etherscan_oracle = Etherscan::new(etherscan_client.clone()).category(GasCategory::Fastest);
-    let data = etherscan_oracle.fetch().await;
-    data.unwrap_err();
+    let error = etherscan_oracle.fetch().await.unwrap_err();
+    assert!(matches!(error, GasOracleError::GasCategoryNotSupported));
 
     // but fetching the `standard` gas price should work fine
     let etherscan_oracle = Etherscan::new(etherscan_client).category(GasCategory::SafeLow);
 
-    let data = etherscan_oracle.fetch().await;
-    data.unwrap();
+    let gas_price = etherscan_oracle.fetch().await.unwrap();
+    assert!(gas_price > U256::zero());
 }
 
 #[tokio::test]
-async fn etherchain() {
-    // initialize and fetch gas estimates from Etherchain
-    let etherchain_oracle = Etherchain::default().category(GasCategory::Fast);
-    let data = etherchain_oracle.fetch().await;
-    data.unwrap();
+async fn gas_now() {
+    let gas_now_oracle = GasNow::default();
+    let gas_price = gas_now_oracle.fetch().await.unwrap();
+    assert!(gas_price > U256::zero());
+}
+
+#[tokio::test]
+async fn polygon() {
+    let polygon_oracle = Polygon::default();
+    let gas_price = polygon_oracle.fetch().await.unwrap();
+    assert!(gas_price > U256::zero());
 }
diff --git a/ethers-middleware/tests/nonce_manager.rs b/ethers-middleware/tests/nonce_manager.rs
index ce7bac22..a1c74915 100644
--- a/ethers-middleware/tests/nonce_manager.rs
+++ b/ethers-middleware/tests/nonce_manager.rs
@@ -1,18 +1,18 @@
-#![cfg(not(target_arch = "wasm32"))]
+#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
+
+use ethers_core::types::*;
+use ethers_middleware::{nonce_manager::NonceManagerMiddleware, signer::SignerMiddleware};
+use ethers_providers::Middleware;
+use ethers_signers::{LocalWallet, Signer};
+use std::time::Duration;
+
 #[tokio::test]
-#[cfg(not(feature = "celo"))]
 async fn nonce_manager() {
-    use ethers_core::types::*;
-    use ethers_middleware::{nonce_manager::NonceManagerMiddleware, signer::SignerMiddleware};
-    use ethers_providers::Middleware;
-    use ethers_signers::{LocalWallet, Signer};
-    use std::time::Duration;
-
     let provider = ethers_providers::GOERLI.provider().interval(Duration::from_millis(2000u64));
     let chain_id = provider.get_chainid().await.unwrap().as_u64();
 
     let wallet = std::env::var("GOERLI_PRIVATE_KEY")
-        .unwrap()
+        .expect("GOERLI_PRIVATE_KEY is not defined")
         .parse::<LocalWallet>()
         .unwrap()
         .with_chain_id(chain_id);
diff --git a/ethers-middleware/tests/signer.rs b/ethers-middleware/tests/signer.rs
index a7c16a8c..9c2176ed 100644
--- a/ethers-middleware/tests/signer.rs
+++ b/ethers-middleware/tests/signer.rs
@@ -1,14 +1,22 @@
 #![allow(unused)]
-use ethers_providers::{Http, JsonRpcClient, Middleware, Provider, GOERLI};
 
+use ethers_contract::ContractFactory;
 use ethers_core::{
-    types::{BlockNumber, TransactionRequest},
-    utils::parse_units,
+    abi::Abi,
+    types::*,
+    utils::{parse_ether, parse_units, Anvil},
 };
 use ethers_middleware::signer::SignerMiddleware;
+use ethers_providers::{Http, JsonRpcClient, Middleware, Provider, GOERLI};
 use ethers_signers::{coins_bip39::English, LocalWallet, MnemonicBuilder, Signer};
+use ethers_solc::Solc;
 use once_cell::sync::Lazy;
-use std::{convert::TryFrom, iter::Cycle, sync::atomic::AtomicU8, time::Duration};
+use std::{
+    convert::TryFrom,
+    iter::Cycle,
+    sync::{atomic::AtomicU8, Arc},
+    time::Duration,
+};
 
 static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
     TestWallets {
@@ -22,8 +30,6 @@ static WALLETS: Lazy<TestWallets> = Lazy::new(|| {
 #[tokio::test]
 #[cfg(not(feature = "celo"))]
 async fn send_eth() {
-    use ethers_core::utils::Anvil;
-
     let anvil = Anvil::new().spawn();
 
     // this private key belongs to the above mnemonic
@@ -95,10 +101,6 @@ async fn pending_txs_with_confirmations_testnet() {
     generic_pending_txs_test(provider, address).await;
 }
 
-#[cfg(not(feature = "celo"))]
-use ethers_core::types::{Address, Eip1559TransactionRequest};
-use ethers_core::utils::parse_ether;
-
 // different keys to avoid nonce errors
 #[tokio::test]
 #[cfg(not(feature = "celo"))]
@@ -195,8 +197,6 @@ async fn test_send_transaction() {
 #[tokio::test]
 #[cfg(not(feature = "celo"))]
 async fn send_transaction_handles_tx_from_field() {
-    use ethers_core::utils::Anvil;
-
     // launch anvil
     let anvil = Anvil::new().spawn();
 
@@ -240,14 +240,6 @@ async fn send_transaction_handles_tx_from_field() {
 #[tokio::test]
 #[cfg(feature = "celo")]
 async fn deploy_and_call_contract() {
-    use ethers_contract::ContractFactory;
-    use ethers_core::{
-        abi::Abi,
-        types::{BlockNumber, Bytes, H256, U256},
-    };
-    use ethers_solc::Solc;
-    use std::sync::Arc;
-
     // compiles the given contract and returns the ABI and Bytecode
     fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
         let path = format!("./tests/solidity-contracts/{path}");
diff --git a/ethers-middleware/tests/stack.rs b/ethers-middleware/tests/stack.rs
index c7cb1ea2..bf70056f 100644
--- a/ethers-middleware/tests/stack.rs
+++ b/ethers-middleware/tests/stack.rs
@@ -1,95 +1,93 @@
-#![cfg(not(target_arch = "wasm32"))]
-#[cfg(not(feature = "celo"))]
-mod tests {
-    use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Anvil};
-    use ethers_middleware::{
-        gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
-        gas_oracle::{EthGasStation, GasCategory, GasOracleMiddleware},
-        nonce_manager::NonceManagerMiddleware,
-        signer::SignerMiddleware,
-    };
-    use ethers_providers::{Http, Middleware, Provider};
-    use ethers_signers::{LocalWallet, Signer};
-    use std::convert::TryFrom;
+#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
 
-    #[tokio::test]
-    async fn mock_with_middleware() {
-        let (provider, mock) = Provider::mocked();
+use ethers_core::{rand::thread_rng, types::TransactionRequest, utils::Anvil};
+use ethers_middleware::{
+    gas_escalator::{Frequency, GasEscalatorMiddleware, GeometricGasPrice},
+    gas_oracle::{GasCategory, GasNow, GasOracleMiddleware},
+    nonce_manager::NonceManagerMiddleware,
+    signer::SignerMiddleware,
+};
+use ethers_providers::{Http, Middleware, Provider};
+use ethers_signers::{LocalWallet, Signer};
+use std::convert::TryFrom;
 
-        // add a bunch of middlewares
-        let gas_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
-        let signer = LocalWallet::new(&mut thread_rng());
-        let address = signer.address();
-        let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
-        let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
-        let provider = GasOracleMiddleware::new(provider, gas_oracle);
-        let provider = SignerMiddleware::new(provider, signer);
-        let provider = NonceManagerMiddleware::new(provider, address);
+#[tokio::test]
+async fn mock_with_middleware() {
+    let (provider, mock) = Provider::mocked();
 
-        // push a response
-        use ethers_core::types::U64;
-        mock.push(U64::from(12u64)).unwrap();
-        let blk = provider.get_block_number().await.unwrap();
-        assert_eq!(blk.as_u64(), 12);
+    // add a bunch of middlewares
+    let gas_oracle = GasNow::new().category(GasCategory::SafeLow);
+    let signer = LocalWallet::new(&mut thread_rng());
+    let address = signer.address();
+    let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
+    let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
+    let provider = GasOracleMiddleware::new(provider, gas_oracle);
+    let provider = SignerMiddleware::new(provider, signer);
+    let provider = NonceManagerMiddleware::new(provider, address);
 
-        // now that the response is gone, there's nothing left
-        // TODO: This returns:
-        // MiddlewareError(
-        // MiddlewareError(
-        // MiddlewareError(
-        // MiddlewareError(
-        // JsonRpcClientError(EmptyResponses)
-        // ))))
-        // Can we flatten it in any way? Maybe inherent to the middleware
-        // infrastructure
-        provider.get_block_number().await.unwrap_err();
+    // push a response
+    use ethers_core::types::U64;
+    mock.push(U64::from(12u64)).unwrap();
+    let blk = provider.get_block_number().await.unwrap();
+    assert_eq!(blk.as_u64(), 12);
 
-        // 2 calls were made
-        mock.assert_request("eth_blockNumber", ()).unwrap();
-        mock.assert_request("eth_blockNumber", ()).unwrap();
-        mock.assert_request("eth_blockNumber", ()).unwrap_err();
-    }
+    // now that the response is gone, there's nothing left
+    // TODO: This returns:
+    // MiddlewareError(
+    // MiddlewareError(
+    // MiddlewareError(
+    // MiddlewareError(
+    // JsonRpcClientError(EmptyResponses)
+    // ))))
+    // Can we flatten it in any way? Maybe inherent to the middleware
+    // infrastructure
+    provider.get_block_number().await.unwrap_err();
 
-    #[tokio::test]
-    async fn can_stack_middlewares() {
-        let anvil = Anvil::new().block_time(5u64).spawn();
-        let gas_oracle = EthGasStation::new(None).category(GasCategory::SafeLow);
-        let signer: LocalWallet = anvil.keys()[0].clone().into();
-        let address = signer.address();
+    // 2 calls were made
+    mock.assert_request("eth_blockNumber", ()).unwrap();
+    mock.assert_request("eth_blockNumber", ()).unwrap();
+    mock.assert_request("eth_blockNumber", ()).unwrap_err();
+}
 
-        // the base provider
-        let provider = Arc::new(Provider::<Http>::try_from(anvil.endpoint()).unwrap());
-        let chain_id = provider.get_chainid().await.unwrap().as_u64();
-        let signer = signer.with_chain_id(chain_id);
+#[tokio::test]
+async fn can_stack_middlewares() {
+    let anvil = Anvil::new().block_time(5u64).spawn();
+    let gas_oracle = GasNow::new().category(GasCategory::SafeLow);
+    let signer: LocalWallet = anvil.keys()[0].clone().into();
+    let address = signer.address();
 
-        // the Gas Price escalator middleware is the first middleware above the provider,
-        // so that it receives the transaction last, after all the other middleware
-        // have modified it accordingly
-        let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
-        let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
+    // the base provider
+    let provider = Arc::new(Provider::<Http>::try_from(anvil.endpoint()).unwrap());
+    let chain_id = provider.get_chainid().await.unwrap().as_u64();
+    let signer = signer.with_chain_id(chain_id);
 
-        // The gas price middleware MUST be below the signing middleware for things to work
-        let provider = GasOracleMiddleware::new(provider, gas_oracle);
+    // the Gas Price escalator middleware is the first middleware above the provider,
+    // so that it receives the transaction last, after all the other middleware
+    // have modified it accordingly
+    let escalator = GeometricGasPrice::new(1.125, 60u64, None::<u64>);
+    let provider = GasEscalatorMiddleware::new(provider, escalator, Frequency::PerBlock);
 
-        // The signing middleware signs txs
-        use std::sync::Arc;
-        let provider = Arc::new(SignerMiddleware::new(provider, signer));
+    // The gas price middleware MUST be below the signing middleware for things to work
+    let provider = GasOracleMiddleware::new(provider, gas_oracle);
 
-        // The nonce manager middleware MUST be above the signing middleware so that it overrides
-        // the nonce and the signer does not make any eth_getTransaction count calls
-        let provider = NonceManagerMiddleware::new(provider, address);
+    // The signing middleware signs txs
+    use std::sync::Arc;
+    let provider = Arc::new(SignerMiddleware::new(provider, signer));
 
-        let tx = TransactionRequest::new();
-        let mut pending_txs = Vec::new();
-        for _ in 0..10 {
-            let pending = provider.send_transaction(tx.clone(), None).await.unwrap();
-            let hash = *pending;
-            let gas_price = provider.get_transaction(hash).await.unwrap().unwrap().gas_price;
-            dbg!(gas_price);
-            pending_txs.push(pending);
-        }
+    // The nonce manager middleware MUST be above the signing middleware so that it overrides
+    // the nonce and the signer does not make any eth_getTransaction count calls
+    let provider = NonceManagerMiddleware::new(provider, address);
 
-        let receipts = futures_util::future::join_all(pending_txs);
-        dbg!(receipts.await);
+    let tx = TransactionRequest::new();
+    let mut pending_txs = Vec::new();
+    for _ in 0..10 {
+        let pending = provider.send_transaction(tx.clone(), None).await.unwrap();
+        let hash = *pending;
+        let gas_price = provider.get_transaction(hash).await.unwrap().unwrap().gas_price;
+        dbg!(gas_price);
+        pending_txs.push(pending);
     }
+
+    let receipts = futures_util::future::join_all(pending_txs);
+    dbg!(receipts.await);
 }
diff --git a/ethers-middleware/tests/transformer.rs b/ethers-middleware/tests/transformer.rs
index f27ef7f0..c4b37c0a 100644
--- a/ethers-middleware/tests/transformer.rs
+++ b/ethers-middleware/tests/transformer.rs
@@ -1,5 +1,5 @@
-#![cfg(not(target_arch = "wasm32"))]
-#![allow(unused)]
+#![cfg(all(not(target_arch = "wasm32"), not(feature = "celo")))]
+
 use ethers_contract::{BaseContract, ContractFactory};
 use ethers_core::{abi::Abi, types::*, utils::Anvil};
 use ethers_middleware::{
@@ -24,7 +24,6 @@ fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
 }
 
 #[tokio::test]
-#[cfg(not(feature = "celo"))]
 async fn ds_proxy_transformer() {
     // randomness
     let mut rng = rand::thread_rng();
@@ -83,7 +82,6 @@ async fn ds_proxy_transformer() {
 }
 
 #[tokio::test]
-#[cfg(not(feature = "celo"))]
 async fn ds_proxy_code() {
     // randomness
     let mut rng = rand::thread_rng();