Repository: gakonst/ethers-rs
Pull request #2426 (2023-05-22 22:02:00)

fix(`verify`): make verification result an option to handle blockscout's format
<!--
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

I've been investigating verification issues due to https://github.com/foundry-rs/foundry/issues/4909 — and it turns out that blockscout, unlike etherscan, likes to return `null` on the `result` field sometimes, instead of an empty string on `message` and the actual message on the `result` field, like etherscan. This in turn introduced a bug when detecting if a contract was already verified as it expects that the blockscout format is identical to etherscan's. 

To be a bit more specifc, this format is returned by blockscout when a contract is not verified, and this is _not_ what etherscan returns (see the `null` in result):

```
{
  "message": "Contract source code not verified",
  "result": null,
  "status": "0"
}
```

While making a blockscout verification client might be the "best" fix, this is a slight difference in formats and I opted for this solution instead. We can handle separating blockscout & etherscan verification later.

## Solution

Parses the `result` field from the API response as an Option instead of just a string, to handle cases where result is `null`.



Patch:
diff --git a/book/providers/advanced_usage.md b/book/providers/advanced_usage.md
index 47a7b1dc..a648ee92 100644
--- a/book/providers/advanced_usage.md
+++ b/book/providers/advanced_usage.md
@@ -8,7 +8,7 @@ Lets take a quick look at how to use the `CallBuilder`.
 
 ```rust
 use ethers::{
-    providers::{ Http, Provider},
+    providers::{Http, Provider},
     types::{TransactionRequest, H160},
     utils::parse_ether,
 };
@@ -75,7 +75,7 @@ Let's look at how to use the state override set. In short, the state override se
 ```rust
 use ethers::{
     providers::{
-        call_raw::{spoof::State, RawCall},
+        call_raw::RawCall,
         Http, Provider,
     },
     types::{TransactionRequest, H160, U256, U64},
@@ -98,7 +98,7 @@ async fn main() -> eyre::Result<()> {
         .value(val)
         .into();
 
-    let mut state = State::default();
+    let mut state = spoof::State::default();
 
     // Set the account balance to max u256
     state.account(from_adr).balance(U256::MAX);
diff --git a/ethers-core/src/types/trace/geth.rs b/ethers-core/src/types/trace/geth.rs
index 358dd9dc..1aa89dac 100644
--- a/ethers-core/src/types/trace/geth.rs
+++ b/ethers-core/src/types/trace/geth.rs
@@ -9,7 +9,9 @@ pub use self::{
     noop::NoopFrame,
     pre_state::{AccountState, DiffMode, PreStateConfig, PreStateFrame, PreStateMode},
 };
-use crate::types::{serde_helpers::deserialize_stringified_numeric, Bytes, H256, U256};
+use crate::types::{
+    serde_helpers::deserialize_stringified_numeric, Address, Bytes, H256, U256, U64,
+};
 use serde::{Deserialize, Serialize};
 use serde_json::Value;
 use std::collections::BTreeMap;
@@ -205,5 +207,265 @@ pub struct GethDebugTracingOptions {
 pub struct GethDebugTracingCallOptions {
     #[serde(flatten)]
     pub tracing_options: GethDebugTracingOptions,
-    // TODO: Add stateoverrides and blockoverrides options
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub state_overrides: Option<spoof::State>,
+    // TODO: Add blockoverrides options
+}
+
+/// Provides types and methods for constructing an `eth_call`
+/// [state override set](https://geth.ethereum.org/docs/rpc/ns-eth#3-object---state-override-set)
+pub mod spoof {
+    use super::*;
+    use std::collections::HashMap;
+
+    /// The state elements to override for a particular account.
+    #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
+    pub struct Account {
+        /// Account nonce
+        #[serde(skip_serializing_if = "Option::is_none")]
+        pub nonce: Option<U64>,
+        /// Account balance
+        #[serde(skip_serializing_if = "Option::is_none")]
+        pub balance: Option<U256>,
+        /// Account code
+        #[serde(skip_serializing_if = "Option::is_none")]
+        pub code: Option<Bytes>,
+        /// Account storage
+        #[serde(flatten, skip_serializing_if = "Option::is_none")]
+        pub storage: Option<Storage>,
+    }
+
+    impl Account {
+        /// Override the account nonce
+        pub fn nonce(&mut self, nonce: U64) -> &mut Self {
+            self.nonce = Some(nonce);
+            self
+        }
+        /// Override the account balance
+        pub fn balance(&mut self, bal: U256) -> &mut Self {
+            self.balance = Some(bal);
+            self
+        }
+        /// Override the code at the account
+        pub fn code(&mut self, code: Bytes) -> &mut Self {
+            self.code = Some(code);
+            self
+        }
+        /// Override the value of the account storage at the given storage `key`
+        pub fn store(&mut self, key: H256, val: H256) -> &mut Self {
+            self.storage.get_or_insert_with(Default::default).insert(key, val);
+            self
+        }
+    }
+
+    /// Wraps a map from storage slot to the overriden value.
+    ///
+    /// Storage overrides can either replace the existing state of an account or they can be treated
+    /// as a diff on the existing state.
+    #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+    pub enum Storage {
+        /// State Diff
+        #[serde(rename = "stateDiff")]
+        Diff(HashMap<H256, H256>),
+        /// State override
+        #[serde(rename = "state")]
+        Replace(HashMap<H256, H256>),
+    }
+
+    /// The default storage override is a diff on the existing state of the account.
+    impl Default for Storage {
+        fn default() -> Self {
+            Self::Diff(Default::default())
+        }
+    }
+    impl std::ops::Deref for Storage {
+        type Target = HashMap<H256, H256>;
+        fn deref(&self) -> &Self::Target {
+            match self {
+                Self::Diff(map) => map,
+                Self::Replace(map) => map,
+            }
+        }
+    }
+    impl std::ops::DerefMut for Storage {
+        fn deref_mut(&mut self) -> &mut Self::Target {
+            match self {
+                Self::Diff(map) => map,
+                Self::Replace(map) => map,
+            }
+        }
+    }
+
+    /// A wrapper type that holds a complete state override set.
+    #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
+    #[serde(transparent)]
+    pub struct State(#[serde(skip_serializing_if = "HashMap::is_empty")] HashMap<Address, Account>);
+
+    impl State {
+        /// Returns a mutable reference to the [`Account`] in the map.
+        pub fn account(&mut self, adr: Address) -> &mut Account {
+            self.0.entry(adr).or_default()
+        }
+    }
+
+    /// Returns an empty state override set.
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// # use ethers_core::{
+    /// #     types::{Address, TransactionRequest, H256, spoof},
+    /// #     utils::{parse_ether, Geth},
+    /// # };
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::RawCall};
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let geth = Geth::new().spawn();
+    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+    ///
+    /// let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
+    /// let adr2: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
+    /// let key = H256::from_low_u64_be(1);
+    /// let val = H256::from_low_u64_be(17);
+    ///
+    /// let tx = TransactionRequest::default().to(adr2).from(adr1).into();
+    ///
+    /// // override the storage at `adr2`
+    /// let mut state = spoof::state();
+    /// state.account(adr2).store(key, val);
+    ///
+    /// // override the nonce at `adr1`
+    /// state.account(adr1).nonce(2.into());
+    ///
+    /// provider.call_raw(&tx).state(&state).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn state() -> State {
+        Default::default()
+    }
+
+    /// Returns a state override set with a single element setting the balance of the address.
+    ///
+    /// # Example
+    /// ```no_run
+    /// # use ethers_core::{
+    /// #     types::{Address, TransactionRequest, H256},
+    /// #     utils::{parse_ether, Geth},
+    /// # };
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let geth = Geth::new().spawn();
+    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+    ///
+    /// let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
+    /// let adr2: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
+    /// let pay_amt = parse_ether(1u64)?;
+    ///
+    /// // Not enough ether to pay for the transaction
+    /// let tx = TransactionRequest::pay(adr2, pay_amt).from(adr1).into();
+    ///
+    /// // override the sender's balance for the call
+    /// let state = spoof::balance(adr1, pay_amt * 2);
+    /// provider.call_raw(&tx).state(&state).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn balance(adr: Address, bal: U256) -> State {
+        let mut state = State::default();
+        state.account(adr).balance(bal);
+        state
+    }
+
+    /// Returns a state override set with a single element setting the nonce of the address.
+    ///
+    /// # Example
+    /// ```no_run
+    /// # use ethers_core::{
+    /// #     types::{Address, TransactionRequest, H256},
+    /// #     utils::{parse_ether, Geth},
+    /// # };
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let geth = Geth::new().spawn();
+    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+    ///
+    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
+    /// let pay_amt = parse_ether(1u64)?;
+    ///
+    /// let tx = TransactionRequest::default().from(adr).into();
+    ///
+    /// // override the sender's nonce for the call
+    /// let state = spoof::nonce(adr, 72.into());
+    /// provider.call_raw(&tx).state(&state).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn nonce(adr: Address, nonce: U64) -> State {
+        let mut state = State::default();
+        state.account(adr).nonce(nonce);
+        state
+    }
+
+    /// Returns a state override set with a single element setting the code at the address.
+    ///
+    /// # Example
+    /// ```no_run
+    /// # use ethers_core::{
+    /// #     types::{Address, TransactionRequest, H256},
+    /// #     utils::{parse_ether, Geth},
+    /// # };
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let geth = Geth::new().spawn();
+    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+    ///
+    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
+    /// let pay_amt = parse_ether(1u64)?;
+    ///
+    /// let tx = TransactionRequest::default().to(adr).into();
+    ///
+    /// // override the code at the target address
+    /// let state = spoof::code(adr, "0x00".parse()?);
+    /// provider.call_raw(&tx).state(&state).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn code(adr: Address, code: Bytes) -> State {
+        let mut state = State::default();
+        state.account(adr).code(code);
+        state
+    }
+
+    /// Returns a state override set with a single element setting the storage at the given address
+    /// and key.
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// # use ethers_core::{
+    /// #     types::{Address, TransactionRequest, H256},
+    /// #     utils::{parse_ether, Geth},
+    /// # };
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
+    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
+    /// let geth = Geth::new().spawn();
+    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+    ///
+    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
+    /// let key = H256::from_low_u64_be(1);
+    /// let val = H256::from_low_u64_be(17);
+    ///
+    /// let tx = TransactionRequest::default().to(adr).into();
+    ///
+    /// // override the storage slot `key` at `adr`
+    /// let state = spoof::storage(adr, key, val);
+    /// provider.call_raw(&tx).state(&state).await?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn storage(adr: Address, key: H256, val: H256) -> State {
+        let mut state = State::default();
+        state.account(adr).store(key, val);
+        state
+    }
 }
diff --git a/ethers-etherscan/src/blocks.rs b/ethers-etherscan/src/blocks.rs
new file mode 100644
index 00000000..f009e32b
--- /dev/null
+++ b/ethers-etherscan/src/blocks.rs
@@ -0,0 +1,47 @@
+use crate::{Client, EtherscanError, Response, Result};
+use ethers_core::types::BlockNumber;
+use serde::{Deserialize, Serialize};
+use std::collections::HashMap;
+
+#[derive(Clone, Debug, Deserialize, Serialize)]
+pub struct BlockNumberByTimestamp {
+    pub timestamp: u64,
+    pub block_number: BlockNumber,
+}
+
+impl Client {
+    /// Returns either (1) the oldest block since a particular timestamp occurred or (2) the newest
+    /// block that occurred prior to that timestamp
+    ///
+    /// # Examples
+    ///
+    /// ```no_run
+    /// # async fn foo(client: ethers_etherscan::Client) -> Result<(), Box<dyn std::error::Error>> {
+    /// // The newest block that occurred prior to 1 January 2020
+    /// let block_number_before = client.get_block_by_timestamp(1577836800, "before");
+    /// // The oldest block that occurred after 1 January 2020
+    /// let block_number_after = client.get_block_by_timestamp(1577836800, "after");
+    /// # Ok(()) }
+    /// ```
+    pub async fn get_block_by_timestamp(
+        &self,
+        timestamp: u64,
+        closest: &str,
+    ) -> Result<BlockNumberByTimestamp> {
+        let query = self.create_query(
+            "block",
+            "getblocknobytime",
+            HashMap::from([("timestamp", timestamp.to_string()), ("closest", closest.to_string())]),
+        );
+        let response: Response<String> = self.get_json(&query).await?;
+
+        match response.status.as_str() {
+            "0" => Err(EtherscanError::BlockNumberByTimestampFailed),
+            "1" => Ok(BlockNumberByTimestamp {
+                timestamp,
+                block_number: response.result.parse::<BlockNumber>().unwrap(),
+            }),
+            err => Err(EtherscanError::BadStatusCode(err.to_string())),
+        }
+    }
+}
diff --git a/ethers-etherscan/src/contract.rs b/ethers-etherscan/src/contract.rs
index 2942afa6..0cfff5cd 100644
--- a/ethers-etherscan/src/contract.rs
+++ b/ethers-etherscan/src/contract.rs
@@ -331,17 +331,31 @@ impl Client {
         }
 
         let query = self.create_query("contract", "getabi", HashMap::from([("address", address)]));
-        let resp: Response<String> = self.get_json(&query).await?;
-        if resp.result.starts_with("Max rate limit reached") {
+        let resp: Response<Option<String>> = self.get_json(&query).await?;
+
+        let result = match resp.result {
+            Some(result) => result,
+            None => {
+                return Err(EtherscanError::EmptyResult {
+                    message: resp.message,
+                    status: resp.status,
+                })
+            }
+        };
+
+        if result.starts_with("Max rate limit reached") {
             return Err(EtherscanError::RateLimitExceeded)
         }
-        if resp.result.starts_with("Contract source code not verified") {
+
+        if result.starts_with("Contract source code not verified") ||
+            resp.message.starts_with("Contract source code not verified")
+        {
             if let Some(ref cache) = self.cache {
                 cache.set_abi(address, None);
             }
             return Err(EtherscanError::ContractCodeNotVerified(address))
         }
-        let abi = serde_json::from_str(&resp.result)?;
+        let abi = serde_json::from_str(&result)?;
 
         if let Some(ref cache) = self.cache {
             cache.set_abi(address, Some(&abi));
diff --git a/ethers-etherscan/src/errors.rs b/ethers-etherscan/src/errors.rs
index 5f71f52d..794d62e5 100644
--- a/ethers-etherscan/src/errors.rs
+++ b/ethers-etherscan/src/errors.rs
@@ -9,6 +9,8 @@ pub enum EtherscanError {
     ExecutionFailed(String),
     #[error("Balance failed")]
     BalanceFailed,
+    #[error("Block by timestamp failed")]
+    BlockNumberByTimestampFailed,
     #[error("Transaction receipt failed")]
     TransactionReceiptFailed,
     #[error("Gas estimation failed")]
@@ -23,6 +25,8 @@ pub enum EtherscanError {
     Serde(#[from] serde_json::Error),
     #[error("Contract source code not verified: {0}")]
     ContractCodeNotVerified(Address),
+    #[error("Response result is unexpectedly empty: status={status}, message={message}")]
+    EmptyResult { status: String, message: String },
     #[error("Rate limit exceeded")]
     RateLimitExceeded,
     #[error(transparent)]
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index c1ea855e..12827955 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -20,6 +20,7 @@ use std::{
 use tracing::{error, trace};
 
 pub mod account;
+pub mod blocks;
 pub mod contract;
 pub mod errors;
 pub mod gas;
diff --git a/ethers-providers/src/rpc/provider.rs b/ethers-providers/src/rpc/provider.rs
index 522073a1..c91c02ec 100644
--- a/ethers-providers/src/rpc/provider.rs
+++ b/ethers-providers/src/rpc/provider.rs
@@ -205,20 +205,21 @@ impl<P: JsonRpcClient> Provider<P> {
     /// Analogous to [`Middleware::call`], but returns a [`CallBuilder`] that can either be
     /// `.await`d or used to override the parameters sent to `eth_call`.
     ///
-    /// See the [`call_raw::spoof`] for functions to construct state override parameters.
+    /// See the [`ethers_core::types::spoof`] for functions to construct state override
+    /// parameters.
     ///
     /// Note: this method _does not_ send a transaction from your account
     ///
-    /// [`call_raw::spoof`]: crate::call_raw::spoof
+    /// [`ethers_core::types::spoof`]: ethers_core::types::spoof
     ///
     /// # Example
     ///
     /// ```no_run
     /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
+    /// #     types::{Address, TransactionRequest, H256, spoof},
     /// #     utils::{parse_ether, Geth},
     /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
+    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::RawCall};
     /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
     /// let geth = Geth::new().spawn();
     /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
diff --git a/ethers-providers/src/rpc/transports/ws/manager.rs b/ethers-providers/src/rpc/transports/ws/manager.rs
index ff38b53d..c685da4c 100644
--- a/ethers-providers/src/rpc/transports/ws/manager.rs
+++ b/ethers-providers/src/rpc/transports/ws/manager.rs
@@ -351,25 +351,29 @@ impl RequestManager {
         old_backend.shutdown();
 
         tracing::debug!(count = self.subs.count(), "Re-starting active subscriptions");
+        let req_cnt = self.reqs.len();
 
-        // reissue subscriptionps
+        // reissue subscriptions
         for (id, sub) in self.subs.to_reissue() {
-            self.backend
-                .dispatcher
-                .unbounded_send(sub.serialize_raw(*id)?)
-                .map_err(|_| WsClientError::DeadChannel)?;
+            let (tx, _rx) = oneshot::channel();
+            let in_flight = InFlight {
+                method: "eth_subscribe".to_string(),
+                params: sub.params.clone(),
+                channel: tx,
+            };
+            // Need an entry in reqs to ensure response with new server sub ID is processed
+            self.reqs.insert(*id, in_flight);
         }
 
-        tracing::debug!(count = self.reqs.len(), "Re-issuing pending requests");
-        // reissue requests. We filter these to prevent in-flight requests for
-        // subscriptions to be re-issued twice (once in above loop, once in this loop).
-        for (id, req) in self.reqs.iter().filter(|(id, _)| !self.subs.has(**id)) {
+        tracing::debug!(count = req_cnt, "Re-issuing pending requests");
+        // reissue requests, including the re-subscription requests we just added above
+        for (id, req) in self.reqs.iter() {
             self.backend
                 .dispatcher
                 .unbounded_send(req.serialize_raw(*id)?)
                 .map_err(|_| WsClientError::DeadChannel)?;
         }
-        tracing::info!(subs = self.subs.count(), reqs = self.reqs.len(), "Re-connection complete");
+        tracing::info!(subs = self.subs.count(), reqs = req_cnt, "Re-connection complete");
 
         Ok(())
     }
diff --git a/ethers-providers/src/toolbox/call_raw.rs b/ethers-providers/src/toolbox/call_raw.rs
index 55d783c1..293f5760 100644
--- a/ethers-providers/src/toolbox/call_raw.rs
+++ b/ethers-providers/src/toolbox/call_raw.rs
@@ -2,14 +2,11 @@
 
 use crate::{utils::PinBoxFut, JsonRpcClient, Provider, ProviderError};
 use ethers_core::{
-    types::{
-        transaction::eip2718::TypedTransaction, Address, BlockId, BlockNumber, Bytes, H256, U256,
-        U64,
-    },
+    types::{transaction::eip2718::TypedTransaction, BlockId, BlockNumber, Bytes},
     utils,
 };
 use pin_project::pin_project;
-use serde::{ser::SerializeTuple, Deserialize, Serialize};
+use serde::{ser::SerializeTuple, Serialize};
 use std::{
     fmt,
     future::Future,
@@ -17,7 +14,7 @@ use std::{
     task::{Context, Poll},
 };
 
-pub use spoof::{balance, code, nonce, state, storage};
+pub use ethers_core::types::spoof;
 
 /// Provides methods for overriding parameters to the `eth_call` rpc method
 pub trait RawCall<'a> {
@@ -230,272 +227,15 @@ where
     }
 }
 
-/// Provides types and methods for constructing an `eth_call`
-/// [state override set](https://geth.ethereum.org/docs/rpc/ns-eth#3-object---state-override-set)
-pub mod spoof {
-    use super::*;
-    use std::collections::HashMap;
-
-    /// The state elements to override for a particular account.
-    #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
-    pub struct Account {
-        /// Account nonce
-        #[serde(skip_serializing_if = "Option::is_none")]
-        pub nonce: Option<U64>,
-        /// Account balance
-        #[serde(skip_serializing_if = "Option::is_none")]
-        pub balance: Option<U256>,
-        /// Account code
-        #[serde(skip_serializing_if = "Option::is_none")]
-        pub code: Option<Bytes>,
-        /// Account storage
-        #[serde(flatten, skip_serializing_if = "Option::is_none")]
-        pub storage: Option<Storage>,
-    }
-
-    impl Account {
-        /// Override the account nonce
-        pub fn nonce(&mut self, nonce: U64) -> &mut Self {
-            self.nonce = Some(nonce);
-            self
-        }
-        /// Override the account balance
-        pub fn balance(&mut self, bal: U256) -> &mut Self {
-            self.balance = Some(bal);
-            self
-        }
-        /// Override the code at the account
-        pub fn code(&mut self, code: Bytes) -> &mut Self {
-            self.code = Some(code);
-            self
-        }
-        /// Override the value of the account storage at the given storage `key`
-        pub fn store(&mut self, key: H256, val: H256) -> &mut Self {
-            self.storage.get_or_insert_with(Default::default).insert(key, val);
-            self
-        }
-    }
-
-    /// Wraps a map from storage slot to the overriden value.
-    ///
-    /// Storage overrides can either replace the existing state of an account or they can be treated
-    /// as a diff on the existing state.
-    #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
-    pub enum Storage {
-        /// State Diff
-        #[serde(rename = "stateDiff")]
-        Diff(HashMap<H256, H256>),
-        /// State override
-        #[serde(rename = "state")]
-        Replace(HashMap<H256, H256>),
-    }
-
-    /// The default storage override is a diff on the existing state of the account.
-    impl Default for Storage {
-        fn default() -> Self {
-            Self::Diff(Default::default())
-        }
-    }
-    impl std::ops::Deref for Storage {
-        type Target = HashMap<H256, H256>;
-        fn deref(&self) -> &Self::Target {
-            match self {
-                Self::Diff(map) => map,
-                Self::Replace(map) => map,
-            }
-        }
-    }
-    impl std::ops::DerefMut for Storage {
-        fn deref_mut(&mut self) -> &mut Self::Target {
-            match self {
-                Self::Diff(map) => map,
-                Self::Replace(map) => map,
-            }
-        }
-    }
-
-    /// A wrapper type that holds a complete state override set.
-    #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
-    #[serde(transparent)]
-    pub struct State(#[serde(skip_serializing_if = "HashMap::is_empty")] HashMap<Address, Account>);
-
-    impl State {
-        /// Returns a mutable reference to the [`Account`] in the map.
-        pub fn account(&mut self, adr: Address) -> &mut Account {
-            self.0.entry(adr).or_default()
-        }
-    }
-
-    /// Returns an empty state override set.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
-    /// #     utils::{parse_ether, Geth},
-    /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{spoof, RawCall}};
-    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let geth = Geth::new().spawn();
-    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
-    ///
-    /// let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
-    /// let adr2: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
-    /// let key = H256::from_low_u64_be(1);
-    /// let val = H256::from_low_u64_be(17);
-    ///
-    /// let tx = TransactionRequest::default().to(adr2).from(adr1).into();
-    ///
-    /// // override the storage at `adr2`
-    /// let mut state = spoof::state();
-    /// state.account(adr2).store(key, val);
-    ///
-    /// // override the nonce at `adr1`
-    /// state.account(adr1).nonce(2.into());
-    ///
-    /// provider.call_raw(&tx).state(&state).await?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    pub fn state() -> State {
-        Default::default()
-    }
-
-    /// Returns a state override set with a single element setting the balance of the address.
-    ///
-    /// # Example
-    /// ```no_run
-    /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
-    /// #     utils::{parse_ether, Geth},
-    /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
-    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let geth = Geth::new().spawn();
-    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
-    ///
-    /// let adr1: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
-    /// let adr2: Address = "0x295a70b2de5e3953354a6a8344e616ed314d7251".parse()?;
-    /// let pay_amt = parse_ether(1u64)?;
-    ///
-    /// // Not enough ether to pay for the transaction
-    /// let tx = TransactionRequest::pay(adr2, pay_amt).from(adr1).into();
-    ///
-    /// // override the sender's balance for the call
-    /// let state = spoof::balance(adr1, pay_amt * 2);
-    /// provider.call_raw(&tx).state(&state).await?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    pub fn balance(adr: Address, bal: U256) -> State {
-        let mut state = State::default();
-        state.account(adr).balance(bal);
-        state
-    }
-
-    /// Returns a state override set with a single element setting the nonce of the address.
-    ///
-    /// # Example
-    /// ```no_run
-    /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
-    /// #     utils::{parse_ether, Geth},
-    /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
-    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let geth = Geth::new().spawn();
-    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
-    ///
-    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
-    /// let pay_amt = parse_ether(1u64)?;
-    ///
-    /// let tx = TransactionRequest::default().from(adr).into();
-    ///
-    /// // override the sender's nonce for the call
-    /// let state = spoof::nonce(adr, 72.into());
-    /// provider.call_raw(&tx).state(&state).await?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    pub fn nonce(adr: Address, nonce: U64) -> State {
-        let mut state = State::default();
-        state.account(adr).nonce(nonce);
-        state
-    }
-
-    /// Returns a state override set with a single element setting the code at the address.
-    ///
-    /// # Example
-    /// ```no_run
-    /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
-    /// #     utils::{parse_ether, Geth},
-    /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
-    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let geth = Geth::new().spawn();
-    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
-    ///
-    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
-    /// let pay_amt = parse_ether(1u64)?;
-    ///
-    /// let tx = TransactionRequest::default().to(adr).into();
-    ///
-    /// // override the code at the target address
-    /// let state = spoof::code(adr, "0x00".parse()?);
-    /// provider.call_raw(&tx).state(&state).await?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    pub fn code(adr: Address, code: Bytes) -> State {
-        let mut state = State::default();
-        state.account(adr).code(code);
-        state
-    }
-
-    /// Returns a state override set with a single element setting the storage at the given address
-    /// and key.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// # use ethers_core::{
-    /// #     types::{Address, TransactionRequest, H256},
-    /// #     utils::{parse_ether, Geth},
-    /// # };
-    /// # use ethers_providers::{Provider, Http, Middleware, call_raw::{RawCall, spoof}};
-    /// # async fn foo() -> Result<(), Box<dyn std::error::Error>> {
-    /// let geth = Geth::new().spawn();
-    /// let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
-    ///
-    /// let adr: Address = "0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse()?;
-    /// let key = H256::from_low_u64_be(1);
-    /// let val = H256::from_low_u64_be(17);
-    ///
-    /// let tx = TransactionRequest::default().to(adr).into();
-    ///
-    /// // override the storage slot `key` at `adr`
-    /// let state = spoof::storage(adr, key, val);
-    /// provider.call_raw(&tx).state(&state).await?;
-    /// # Ok(())
-    /// # }
-    /// ```
-    pub fn storage(adr: Address, key: H256, val: H256) -> State {
-        let mut state = State::default();
-        state.account(adr).store(key, val);
-        state
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
     use crate::{Http, Provider};
     use ethers_core::{
-        types::TransactionRequest,
+        types::{Address, TransactionRequest, H256, U256},
         utils::{get_contract_address, keccak256, parse_ether, Geth},
     };
+    use serde::Deserialize;
     use std::convert::TryFrom;
 
     // Deserializes eth_call parameters as owned data for testing serialization
diff --git a/examples/anvil/Cargo.toml b/examples/anvil/Cargo.toml
index 3d8b0971..26120922 100644
--- a/examples/anvil/Cargo.toml
+++ b/examples/anvil/Cargo.toml
@@ -10,5 +10,5 @@ edition.workspace = true
 
 [dev-dependencies]
 ethers.workspace = true
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 eyre.workspace = true
diff --git a/examples/contracts/Cargo.toml b/examples/contracts/Cargo.toml
index 6db33211..4246eb7e 100644
--- a/examples/contracts/Cargo.toml
+++ b/examples/contracts/Cargo.toml
@@ -15,7 +15,7 @@ legacy = ["ethers/legacy"]
 [dev-dependencies]
 ethers = { workspace = true, features = ["abigen", "rustls", "ws", "ethers-solc"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/events/Cargo.toml b/examples/events/Cargo.toml
index 8dbaea5a..6c3fb842 100644
--- a/examples/events/Cargo.toml
+++ b/examples/events/Cargo.toml
@@ -11,7 +11,7 @@ edition.workspace = true
 [dev-dependencies]
 ethers = { workspace = true, features = ["rustls"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/geth/Cargo.toml b/examples/geth/Cargo.toml
index eef9e3a1..c552a413 100644
--- a/examples/geth/Cargo.toml
+++ b/examples/geth/Cargo.toml
@@ -11,7 +11,7 @@ edition.workspace = true
 [dev-dependencies]
 ethers.workspace = true
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/middleware/Cargo.toml b/examples/middleware/Cargo.toml
index 924a0162..6ca79c64 100644
--- a/examples/middleware/Cargo.toml
+++ b/examples/middleware/Cargo.toml
@@ -12,7 +12,7 @@ edition.workspace = true
 ethers = { workspace = true, features = ["rustls"] }
 serde.workspace = true
 serde_json.workspace = true
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 async-trait.workspace = true
 thiserror.workspace = true
 eyre.workspace = true
diff --git a/examples/providers/Cargo.toml b/examples/providers/Cargo.toml
index 3a5b37cb..2f65de88 100644
--- a/examples/providers/Cargo.toml
+++ b/examples/providers/Cargo.toml
@@ -11,7 +11,7 @@ edition.workspace = true
 [dev-dependencies]
 ethers = { workspace = true, features = ["abigen", "ipc", "rustls", "ws"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 async-trait.workspace = true
 reqwest.workspace = true
 url.workspace = true
diff --git a/examples/queries/Cargo.toml b/examples/queries/Cargo.toml
index 315de0e9..ff678531 100644
--- a/examples/queries/Cargo.toml
+++ b/examples/queries/Cargo.toml
@@ -11,7 +11,7 @@ edition.workspace = true
 [dev-dependencies]
 ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/subscriptions/Cargo.toml b/examples/subscriptions/Cargo.toml
index 0d6d276a..a6a51cf7 100644
--- a/examples/subscriptions/Cargo.toml
+++ b/examples/subscriptions/Cargo.toml
@@ -11,7 +11,7 @@ edition.workspace = true
 [dev-dependencies]
 ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/transactions/Cargo.toml b/examples/transactions/Cargo.toml
index ad065baf..cb7f78ee 100644
--- a/examples/transactions/Cargo.toml
+++ b/examples/transactions/Cargo.toml
@@ -9,9 +9,9 @@ rust-version.workspace = true
 edition.workspace = true
 
 [dev-dependencies]
-ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
+ethers = { workspace = true, features = ["abigen", "ws", "rustls", "ethers-solc"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true
diff --git a/examples/transactions/examples/call_override.rs b/examples/transactions/examples/call_override.rs
index 4a124fce..70c6466b 100644
--- a/examples/transactions/examples/call_override.rs
+++ b/examples/transactions/examples/call_override.rs
@@ -1,13 +1,10 @@
 use ethers::{
     contract::abigen,
     core::{
-        types::{Address, TransactionRequest, H256},
+        types::{spoof, Address, TransactionRequest, H256},
         utils::{parse_ether, Geth},
     },
-    providers::{
-        call_raw::{self, RawCall},
-        Http, Provider,
-    },
+    providers::{call_raw::RawCall, Http, Provider},
 };
 use eyre::Result;
 use std::{
@@ -42,7 +39,7 @@ async fn main() -> Result<()> {
     // Override the sender's balance for the call
     let pay_amt = parse_ether(1u64)?;
     let tx = TransactionRequest::pay(target, pay_amt).from(from);
-    let state = call_raw::balance(from, pay_amt * 2);
+    let state = spoof::balance(from, pay_amt * 2);
 
     // The call succeeds as if the sender had sufficient balance
     client.call_raw(&tx.into()).state(&state).await.expect("balance override");
@@ -57,7 +54,7 @@ async fn main() -> Result<()> {
 
     // Override the target account's code, simulating a call to Greeter.greet()
     // as if the Greeter contract was deployed at the target address
-    let state = call_raw::code(target, runtime_bytecode.clone());
+    let state = spoof::code(target, runtime_bytecode.clone());
     let res = greeter.greet().call_raw().state(&state).await?;
 
     // greet() returns the empty string, because the target account's storage is empty
@@ -69,7 +66,7 @@ async fn main() -> Result<()> {
     let greet_val = encode_string_for_storage(greeting);
 
     // Override the target account's code and storage
-    let mut state = call_raw::state();
+    let mut state = spoof::state();
     state.account(target).code(runtime_bytecode.clone()).store(greet_slot, greet_val);
     let res = greeter.greet().call_raw().state(&state).await?;
 
diff --git a/examples/transactions/examples/trace_call.rs b/examples/transactions/examples/trace_call.rs
index 67551d6e..849f564b 100644
--- a/examples/transactions/examples/trace_call.rs
+++ b/examples/transactions/examples/trace_call.rs
@@ -26,6 +26,7 @@ async fn main() -> Result<()> {
                 )),
                 ..Default::default()
             },
+            state_overrides: None,
         };
         let traces = client.debug_trace_call(tx, Some(block), options).await?;
         println!("{traces:?}");
diff --git a/examples/wallets/Cargo.toml b/examples/wallets/Cargo.toml
index 8c82e345..7667e840 100644
--- a/examples/wallets/Cargo.toml
+++ b/examples/wallets/Cargo.toml
@@ -17,7 +17,7 @@ yubi = ["ethers/yubi"]
 [dev-dependencies]
 ethers = { workspace = true, features = ["abigen", "ws", "rustls"] }
 
-tokio = { workspace = true, features = ["macros"] }
+tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
 
 serde.workspace = true
 serde_json.workspace = true


Test patch:
diff --git a/ethers-etherscan/tests/it/blocks.rs b/ethers-etherscan/tests/it/blocks.rs
new file mode 100644
index 00000000..9c7ee6a2
--- /dev/null
+++ b/ethers-etherscan/tests/it/blocks.rs
@@ -0,0 +1,28 @@
+use crate::*;
+use ethers_core::types::BlockNumber;
+use serial_test::serial;
+
+#[tokio::test]
+#[serial]
+async fn check_get_block_by_timestamp_before() {
+    run_with_client(Chain::Mainnet, |client| async move {
+        let block_no = client.get_block_by_timestamp(1577836800, "before").await;
+        assert!(block_no.is_ok());
+
+        let block_no = block_no.unwrap().block_number;
+        assert_eq!(block_no, "9193265".parse::<BlockNumber>().unwrap());
+    })
+    .await
+}
+
+#[tokio::test]
+#[serial]
+async fn check_get_block_by_timestamp_after() {
+    run_with_client(Chain::Mainnet, |client| async move {
+        let block_no = client.get_block_by_timestamp(1577836800, "after").await;
+
+        let block_no = block_no.unwrap().block_number;
+        assert_eq!(block_no, "9193266".parse::<BlockNumber>().unwrap());
+    })
+    .await
+}
diff --git a/ethers-etherscan/tests/it/main.rs b/ethers-etherscan/tests/it/main.rs
index e0548705..0eac9aa1 100644
--- a/ethers-etherscan/tests/it/main.rs
+++ b/ethers-etherscan/tests/it/main.rs
@@ -10,6 +10,7 @@ use std::{
 };
 
 mod account;
+mod blocks;
 mod contract;
 mod gas;
 mod transaction;
diff --git a/ethers-providers/tests/it/ws_errors.rs b/ethers-providers/tests/it/ws_errors.rs
index f4eced17..168b24e9 100644
--- a/ethers-providers/tests/it/ws_errors.rs
+++ b/ethers-providers/tests/it/ws_errors.rs
@@ -1,4 +1,4 @@
-use ethers_core::types::Filter;
+use ethers_core::{types::Filter, utils::Anvil};
 use ethers_providers::{Middleware, Provider, StreamExt};
 use futures_util::SinkExt;
 use std::time::Duration;
@@ -64,3 +64,26 @@ async fn graceful_disconnect_on_ws_errors() {
 
     assert!(stream.next().await.is_none());
 }
+
+#[tokio::test]
+async fn resubscribe_on_ws_reconnect() {
+    let anvil = Anvil::new().block_time(1u64).spawn();
+    let port = anvil.port();
+    let provider = Provider::connect_with_reconnects(anvil.ws_endpoint(), 1).await.unwrap();
+
+    // Attempt to ensure a different server-side subscription id after reconnect by making
+    // the subscription we care about be the second one after initial startup, but the first
+    // (and only) one after reconnection.
+    let ignored_sub = provider.subscribe_blocks().await.unwrap();
+    let mut blocks = provider.subscribe_blocks().await.unwrap();
+    ignored_sub.unsubscribe().await.expect("unsubscribe failed");
+
+    blocks.next().await.expect("no block notice before reconnect");
+
+    // Kill & restart using the same port so we end up with the same endpoint url:
+    drop(anvil);
+    let _anvil = Anvil::new().port(port).block_time(1u64).spawn();
+
+    // Wait for the next block on existing subscription. Will fail w/o resubscription:
+    blocks.next().await.expect("no block notice after reconnect");
+}