Repository: gakonst/ethers-rs
Pull request #641 (2021-12-03 11:35:01)

fix(provider::ws): ignore the websocket response when the request has been cancelled
In cases when the request is out, but cancelled locally before it gets back, then later on, the replied request should be ignored, otherwise, it hits an error and aborts the whole websocket connection.

guanqun: force pushed to fix a fmt issue, didn't notice that it was using a nightly fmt.

PS: Actually the IPC has similar issue.

Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 3e0c2324..d8f551e7 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -129,7 +129,7 @@ jobs:
       - name: cargo fmt
         run: cargo +nightly fmt --all -- --check
       - name: cargo clippy
-        run: cargo +nightly clippy --all --all-features -- -D warnings
+        run: cargo +nightly clippy --all-features -- -D warnings
 
   wasm:
     name: WASM
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c3bb4800..c9884ccb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -61,6 +61,8 @@
 - Fix `http Provider` data race when generating new request `id`s.
 - Add support for `net_version` RPC method.
   [595](https://github.com/gakonst/ethers-rs/pull/595)
+- Add support for `evm_snapshot` and `evm_revert` dev RPC methods.
+  [640](https://github.com/gakonst/ethers-rs/pull/640)
 
 ### Unreleased
 
diff --git a/Cargo.lock b/Cargo.lock
index 344c9fd1..55c0a183 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -78,9 +78,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.48"
+version = "1.0.51"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62e1f47f7dc0422027a4e370dd4548d4d66b26782e513e98dca1e689e058a80e"
+checksum = "8b26702f315f53b6071259e15dd9d64528213b44d61de1ec926eca7715d62203"
 
 [[package]]
 name = "arrayref"
@@ -196,9 +196,9 @@ checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
 
 [[package]]
 name = "base64ct"
-version = "1.2.0"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "392c772b012d685a640cdad68a5a21f4a45e696f85a2c2c907aab2fe49a91e19"
+checksum = "43a46022bae2c3bc5a17c2d45d59c1233ce0e2cca9ae9b92e92e9ce529874177"
 
 [[package]]
 name = "bech32"
@@ -437,9 +437,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "2.33.3"
+version = "2.34.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
 dependencies = [
  "bitflags",
  "textwrap",
@@ -575,6 +575,16 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "console_error_panic_hook"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc"
+dependencies = [
+ "cfg-if 1.0.0",
+ "wasm-bindgen",
+]
+
 [[package]]
 name = "const-oid"
 version = "0.6.2"
@@ -583,9 +593,9 @@ checksum = "9d6f2aa4d0537bcc1c74df8755072bd31c1ef1a3a1b85a68e8404a8c353b7b8b"
 
 [[package]]
 name = "const-oid"
-version = "0.7.0"
+version = "0.7.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d355758f44afa81c21e66e1301d47cbffbbcde4b405cbe46b8b19f213abf9f60"
+checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3"
 
 [[package]]
 name = "constant_time_eq"
@@ -626,9 +636,9 @@ dependencies = [
 
 [[package]]
 name = "crc32fast"
-version = "1.2.2"
+version = "1.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3825b1e8580894917dc4468cb634a1b4e9745fddc854edad72d9c04644c0319f"
+checksum = "738c290dfaea84fc1ca15ad9c168d083b05a714e1efddd8edaab678dc28d2836"
 dependencies = [
  "cfg-if 1.0.0",
 ]
@@ -726,9 +736,9 @@ dependencies = [
 
 [[package]]
 name = "der"
-version = "0.4.4"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28e98c534e9c8a0483aa01d6f6913bc063de254311bd267c9cf535e9b70e15b2"
+checksum = "79b71cca7d95d7681a4b3b9cdf63c8dbc3730d0584c2c74e31416d64a90493f4"
 dependencies = [
  "const-oid 0.6.2",
 ]
@@ -739,7 +749,7 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c"
 dependencies = [
- "const-oid 0.7.0",
+ "const-oid 0.7.1",
 ]
 
 [[package]]
@@ -799,7 +809,7 @@ version = "0.12.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "43ee23aa5b4f68c7a092b5c3beb25f50c406adc75e2363634f242f28ab255372"
 dependencies = [
- "der 0.4.4",
+ "der 0.4.5",
  "elliptic-curve 0.10.6",
  "hmac",
  "signature",
@@ -1179,6 +1189,7 @@ dependencies = [
  "colored",
  "ethers-core",
  "futures-util",
+ "getrandom 0.2.3",
  "glob",
  "hex",
  "home",
@@ -1197,6 +1208,23 @@ dependencies = [
  "walkdir",
 ]
 
+[[package]]
+name = "ethers-wasm"
+version = "0.1.0"
+dependencies = [
+ "console_error_panic_hook",
+ "ethers",
+ "hex",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-bindgen-test",
+ "web-sys",
+ "wee_alloc",
+]
+
 [[package]]
 name = "fake-simd"
 version = "0.1.2"
@@ -1801,6 +1829,12 @@ version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
 
+[[package]]
+name = "memory_units"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
+
 [[package]]
 name = "mime"
 version = "0.3.16"
@@ -2124,7 +2158,7 @@ version = "0.7.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ee3ef9b64d26bad0536099c816c6734379e45bbd5f14798def6809e5cc350447"
 dependencies = [
- "der 0.4.4",
+ "der 0.4.5",
  "spki",
 ]
 
@@ -2629,9 +2663,9 @@ dependencies = [
 
 [[package]]
 name = "ryu"
-version = "1.0.5"
+version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
+checksum = "3c9613b5a66ab9ba26415184cfc41156594925a9cf3a2057e57f31ff145f6568"
 
 [[package]]
 name = "salsa20"
@@ -2661,6 +2695,12 @@ dependencies = [
  "winapi",
 ]
 
+[[package]]
+name = "scoped-tls"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+
 [[package]]
 name = "scopeguard"
 version = "1.1.0"
@@ -2771,9 +2811,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.71"
+version = "1.0.72"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "063bf466a64011ac24040a49009724ee60a57da1b437617ceb32e53ad61bfb19"
+checksum = "d0ffa0837f2dfa6fb90868c2b5468cad482e175f7dad97e7421951e663f2b527"
 dependencies = [
  "itoa",
  "ryu",
@@ -2955,7 +2995,7 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5c01a0c15da1b0b0e1494112e7af814a678fec9bd157881b49beac661e9b6f32"
 dependencies = [
- "der 0.4.4",
+ "der 0.4.5",
 ]
 
 [[package]]
@@ -3507,6 +3547,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
 dependencies = [
  "cfg-if 1.0.0",
+ "serde",
+ "serde_json",
  "wasm-bindgen-macro",
 ]
 
@@ -3566,6 +3608,30 @@ version = "0.2.78"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0237232789cf037d5480773fe568aac745bfe2afbc11a863e97901780a6b47cc"
 
+[[package]]
+name = "wasm-bindgen-test"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96f1aa7971fdf61ef0f353602102dbea75a56e225ed036c1e3740564b91e6b7e"
+dependencies = [
+ "console_error_panic_hook",
+ "js-sys",
+ "scoped-tls",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "wasm-bindgen-test-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-test-macro"
+version = "0.3.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6006f79628dfeb96a86d4db51fbf1344cd7fd8408f06fc9aa3c84913a4789688"
+dependencies = [
+ "proc-macro2",
+ "quote",
+]
+
 [[package]]
 name = "wasm-timer"
 version = "0.2.5"
@@ -3620,6 +3686,18 @@ dependencies = [
  "webpki 0.21.4",
 ]
 
+[[package]]
+name = "wee_alloc"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
+dependencies = [
+ "cfg-if 0.1.10",
+ "libc",
+ "memory_units",
+ "winapi",
+]
+
 [[package]]
 name = "winapi"
 version = "0.3.9"
diff --git a/Cargo.toml b/Cargo.toml
index 7877c4cf..12434931 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -20,7 +20,8 @@ members = [
     "ethers-core",
     "ethers-middleware",
     "ethers-etherscan",
-    "ethers-solc"
+    "ethers-solc",
+    "examples/ethers-wasm",
 ]
 
 default-members = [
@@ -30,11 +31,7 @@ default-members = [
     "ethers-core",
     "ethers-middleware",
     "ethers-etherscan",
-    "ethers-solc"
-]
-
-exclude = [
-    "examples/ethers-wasm",
+    "ethers-solc",
 ]
 
 [package.metadata.docs.rs]
@@ -67,6 +64,7 @@ ws = ["ethers-providers/ws"]
 ipc = ["ethers-providers/ipc"]
 rustls = ["ethers-providers/rustls"]
 openssl = ["ethers-providers/openssl"]
+dev-rpc = ["ethers-providers/dev-rpc"]
 ## signers
 ledger = ["ethers-signers/ledger"]
 yubi = ["ethers-signers/yubi"]
@@ -101,3 +99,9 @@ serde_json = "1.0.64"
 tokio = { version = "1.5", features = ["macros", "rt-multi-thread"] }
 hex = "0.4.3"
 bytes = "1.1.0"
+
+
+# profile for the wasm example
+[profile.release.package.ethers-wasm]
+# Tell `rustc` to optimize for small code size.
+opt-level = "s"
\ No newline at end of file
diff --git a/ethers-core/src/types/chain.rs b/ethers-core/src/types/chain.rs
index 28ad23c3..7dd6fefb 100644
--- a/ethers-core/src/types/chain.rs
+++ b/ethers-core/src/types/chain.rs
@@ -2,7 +2,7 @@ use std::fmt;
 
 use crate::types::U256;
 
-#[derive(Debug)]
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
 pub enum Chain {
     Mainnet,
     Ropsten,
@@ -15,6 +15,9 @@ pub enum Chain {
     Avalanche,
     AvalancheFuji,
     Sepolia,
+    Moonbeam,
+    MoonbeamDev,
+    Moonriver,
 }
 
 impl fmt::Display for Chain {
@@ -37,6 +40,9 @@ impl From<Chain> for u32 {
             Chain::Avalanche => 43114,
             Chain::AvalancheFuji => 43113,
             Chain::Sepolia => 11155111,
+            Chain::Moonbeam => 1287,
+            Chain::MoonbeamDev => 1281,
+            Chain::Moonriver => 1285,
         }
     }
 }
@@ -46,3 +52,9 @@ impl From<Chain> for U256 {
         u32::from(chain).into()
     }
 }
+
+impl From<Chain> for u64 {
+    fn from(chain: Chain) -> Self {
+        u32::from(chain).into()
+    }
+}
diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs
index 7b16f1a5..722d0e83 100644
--- a/ethers-core/src/utils/mod.rs
+++ b/ethers-core/src/utils/mod.rs
@@ -10,6 +10,9 @@ mod geth;
 #[cfg(not(target_arch = "wasm32"))]
 pub use geth::{Geth, GethInstance};
 
+/// Moonbeam utils
+pub mod moonbeam;
+
 mod hash;
 pub use hash::{hash_message, id, keccak256, serialize};
 
diff --git a/ethers-core/src/utils/moonbeam.rs b/ethers-core/src/utils/moonbeam.rs
new file mode 100644
index 00000000..d054d8e7
--- /dev/null
+++ b/ethers-core/src/utils/moonbeam.rs
@@ -0,0 +1,146 @@
+//! Moonbeam utilities
+
+use std::collections::BTreeMap;
+
+use k256::SecretKey;
+
+/// Returns the private developer keys https://docs.moonbeam.network/snippets/code/setting-up-node/dev-accounts/
+pub fn dev_keys() -> Vec<SecretKey> {
+    MoonbeamDev::default().into_keys().collect()
+}
+
+/// Holds private developer keys with their names
+#[derive(Debug, Clone)]
+pub struct MoonbeamDev {
+    keys: BTreeMap<&'static str, SecretKey>,
+}
+
+impl MoonbeamDev {
+    pub fn keys(&self) -> impl Iterator<Item = &SecretKey> {
+        self.keys.values()
+    }
+
+    pub fn into_keys(self) -> impl Iterator<Item = SecretKey> {
+        self.keys.into_values()
+    }
+
+    /// Get a key by then, like `Alith`
+    pub fn get(&self, name: impl AsRef<str>) -> Option<&SecretKey> {
+        self.keys.get(name.as_ref())
+    }
+
+    pub fn alith(&self) -> &SecretKey {
+        self.get("Alith").unwrap()
+    }
+
+    pub fn baltathar(&self) -> &SecretKey {
+        self.get("Baltathar").unwrap()
+    }
+
+    pub fn charleth(&self) -> &SecretKey {
+        self.get("Charleth").unwrap()
+    }
+
+    pub fn ethan(&self) -> &SecretKey {
+        self.get("Ethan").unwrap()
+    }
+}
+
+impl Default for MoonbeamDev {
+    fn default() -> Self {
+        Self {
+            keys: BTreeMap::from([
+                (
+                    "Alith",
+                    SecretKey::from_bytes(
+                        hex::decode(
+                            "5fb92d6e98884f76de468fa3f6278f8807c48bebc13595d45af5bdc4da702133",
+                        )
+                        .unwrap(),
+                    )
+                    .unwrap(),
+                ),
+                (
+                    "Baltathar",
+                    SecretKey::from_bytes(
+                        hex::decode(
+                            "8075991ce870b93a8870eca0c0f91913d12f47948ca0fd25b49c6fa7cdbeee8b",
+                        )
+                        .unwrap(),
+                    )
+                    .unwrap(),
+                ),
+                (
+                    "Charleth",
+                    SecretKey::from_bytes(
+                        hex::decode(
+                            "0b6e18cafb6ed99687ec547bd28139cafdd2bffe70e6b688025de6b445aa5c5b",
+                        )
+                        .unwrap(),
+                    )
+                    .unwrap(),
+                ),
+                (
+                    "Dorothy",
+                    SecretKey::from_bytes(
+                        hex::decode(
+                            "39539ab1876910bbf3a223d84a29e28f1cb4e2e456503e7e91ed39b2e7223d68",
+                        )
+                        .unwrap(),
+                    )
+                    .unwrap(),
+                ),
+                (
+                    "Faith",
+                    SecretKey::from_bytes(
+                        hex::decode(
+                            "b9d2ea9a615f3165812e8d44de0d24da9bbd164b65c4f0573e1ce2c8dbd9c8df",
+                        )
+                        .unwrap(),
+                    )
+                    .unwrap(),
+                ),
+                (
+                    "Goliath",
+                    SecretKey::from_bytes(
+                        hex::decode(
+                            "96b8a38e12e1a31dee1eab2fffdf9d9990045f5b37e44d8cc27766ef294acf18",
+                        )
+                        .unwrap(),
+                    )
+                    .unwrap(),
+                ),
+                (
+                    "Heath",
+                    SecretKey::from_bytes(
+                        hex::decode(
+                            "0d6dcaaef49272a5411896be8ad16c01c35d6f8c18873387b71fbc734759b0ab",
+                        )
+                        .unwrap(),
+                    )
+                    .unwrap(),
+                ),
+                (
+                    "Ida",
+                    SecretKey::from_bytes(
+                        hex::decode(
+                            "4c42532034540267bf568198ccec4cb822a025da542861fcb146a5fab6433ff8",
+                        )
+                        .unwrap(),
+                    )
+                    .unwrap(),
+                ),
+                (
+                    "Judith",
+                    SecretKey::from_bytes(
+                        hex::decode(
+                            "94c49300a58d576011096bcb006aa06f5a91b34b4383891e8029c21dc39fbb8b",
+                        )
+                        .unwrap(),
+                    )
+                    .unwrap(),
+                ),
+            ]),
+        }
+    }
+}
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index 400bcf82..772e64ca 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -78,7 +78,11 @@ impl Client {
             Chain::Mainnet | Chain::Ropsten | Chain::Kovan | Chain::Rinkeby | Chain::Goerli => {
                 std::env::var("ETHERSCAN_API_KEY")?
             }
+
             Chain::XDai | Chain::Sepolia => String::default(),
+            Chain::Moonbeam | Chain::MoonbeamDev | Chain::Moonriver => {
+                std::env::var("MOONSCAN_API_KEY")?
+            }
         };
         Self::new(chain, api_key)
     }
diff --git a/ethers-providers/Cargo.toml b/ethers-providers/Cargo.toml
index d5b0618b..c2d7e164 100644
--- a/ethers-providers/Cargo.toml
+++ b/ethers-providers/Cargo.toml
@@ -65,3 +65,4 @@ ipc = ["tokio", "tokio/io-util", "tokio-util", "bytes"]
 
 openssl = ["tokio-tungstenite/native-tls", "reqwest/native-tls"]
 rustls = ["tokio-tungstenite/rustls-tls", "reqwest/rustls-tls"]
+dev-rpc = []
diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs
index 99dd869d..ba538a8a 100644
--- a/ethers-providers/src/lib.rs
+++ b/ethers-providers/src/lib.rs
@@ -32,6 +32,10 @@ use std::{error::Error, fmt::Debug, future::Future, pin::Pin, str::FromStr};
 
 pub use provider::{FilterKind, Provider, ProviderError};
 
+// feature-enabled support for dev-rpc methods
+#[cfg(feature = "dev-rpc")]
+pub use provider::dev_rpc::DevRpcMiddleware;
+
 /// A simple gas escalation policy
 pub type EscalationPolicy = Box<dyn Fn(U256, usize) -> U256 + Send + Sync>;
 
diff --git a/ethers-providers/src/pending_escalator.rs b/ethers-providers/src/pending_escalator.rs
index e1893a4f..bbc45aea 100644
--- a/ethers-providers/src/pending_escalator.rs
+++ b/ethers-providers/src/pending_escalator.rs
@@ -142,7 +142,7 @@ macro_rules! poll_broadcast_fut {
             Poll::Ready(Err(e)) => {
                 // kludge. Prevents erroring on "nonce too low" which indicates
                 // a previous escalation confirmed during this broadcast attempt
-                if format!("{}", e).contains("nonce too low") {
+                if format!("{:?}", e).contains("nonce too low") {
                     check_all_receipts!($cx, $this);
                 } else {
                     tracing::error!(
diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs
index b4b8a12b..01c9b678 100644
--- a/ethers-providers/src/provider.rs
+++ b/ethers-providers/src/provider.rs
@@ -1021,6 +1021,183 @@ impl TryFrom<String> for Provider<HttpProvider> {
     }
 }
 
+/// A middleware supporting development-specific JSON RPC methods
+///
+/// # Example
+///
+///```
+/// use ethers_providers::{Provider, Http, Middleware, DevRpcMiddleware};
+/// use ethers_core::types::TransactionRequest;
+/// use ethers_core::utils::Ganache;
+/// use std::convert::TryFrom;
+///
+/// # #[tokio::main]
+/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
+/// let ganache = Ganache::new().spawn();
+/// let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap();
+/// let client = DevRpcMiddleware::new(provider);
+///
+/// // snapshot the initial state
+/// let block0 = client.get_block_number().await.unwrap();
+/// let snap_id = client.snapshot().await.unwrap();
+///
+/// // send a transaction
+/// let accounts = client.get_accounts().await?;
+/// let from = accounts[0];
+/// let to = accounts[1];
+/// let balance_before = client.get_balance(to, None).await?;
+/// let tx = TransactionRequest::new().to(to).value(1000).from(from);
+/// client.send_transaction(tx, None).await?.await?;
+/// let balance_after = client.get_balance(to, None).await?;
+/// assert_eq!(balance_after, balance_before + 1000);
+///
+/// // revert to snapshot
+/// client.revert_to_snapshot(snap_id).await.unwrap();
+/// let balance_after_revert = client.get_balance(to, None).await?;
+/// assert_eq!(balance_after_revert, balance_before);
+/// # Ok(())
+/// # }
+/// ```
+#[cfg(feature = "dev-rpc")]
+pub mod dev_rpc {
+    use crate::{FromErr, Middleware, ProviderError};
+    use async_trait::async_trait;
+    use ethers_core::types::U256;
+    use thiserror::Error;
+
+    use std::fmt::Debug;
+
+    #[derive(Clone, Debug)]
+    pub struct DevRpcMiddleware<M>(M);
+
+    #[derive(Error, Debug)]
+    pub enum DevRpcMiddlewareError<M: Middleware> {
+        #[error("{0}")]
+        MiddlewareError(M::Error),
+
+        #[error("{0}")]
+        ProviderError(ProviderError),
+
+        #[error("Could not revert to snapshot")]
+        NoSnapshot,
+    }
+
+    #[async_trait]
+    impl<M: Middleware> Middleware for DevRpcMiddleware<M> {
+        type Error = DevRpcMiddlewareError<M>;
+        type Provider = M::Provider;
+        type Inner = M;
+
+        fn inner(&self) -> &M {
+            &self.0
+        }
+    }
+
+    impl<M: Middleware> FromErr<M::Error> for DevRpcMiddlewareError<M> {
+        fn from(src: M::Error) -> DevRpcMiddlewareError<M> {
+            DevRpcMiddlewareError::MiddlewareError(src)
+        }
+    }
+
+    impl<M> From<ProviderError> for DevRpcMiddlewareError<M>
+    where
+        M: Middleware,
+    {
+        fn from(src: ProviderError) -> Self {
+            Self::ProviderError(src)
+        }
+    }
+
+    impl<M: Middleware> DevRpcMiddleware<M> {
+        pub fn new(inner: M) -> Self {
+            Self(inner)
+        }
+
+        // both ganache and hardhat increment snapshot id even if no state has changed
+        pub async fn snapshot(&self) -> Result<U256, DevRpcMiddlewareError<M>> {
+            self.provider().request::<(), U256>("evm_snapshot", ()).await.map_err(From::from)
+        }
+
+        pub async fn revert_to_snapshot(&self, id: U256) -> Result<(), DevRpcMiddlewareError<M>> {
+            let ok = self
+                .provider()
+                .request::<[U256; 1], bool>("evm_revert", [id])
+                .await
+                .map_err(DevRpcMiddlewareError::ProviderError)?;
+            if ok {
+                Ok(())
+            } else {
+                Err(DevRpcMiddlewareError::NoSnapshot)
+            }
+        }
+    }
+    #[cfg(test)]
+    // Celo blocks can not get parsed when used with Ganache
+    #[cfg(not(feature = "celo"))]
+    mod tests {
+        use super::*;
+        use crate::{Http, Provider};
+        use ethers_core::utils::Ganache;
+        use std::convert::TryFrom;
+
+        #[tokio::test]
+        async fn test_snapshot() {
+            // launch ganache
+            let ganache = Ganache::new().spawn();
+            let provider = Provider::<Http>::try_from(ganache.endpoint()).unwrap();
+            let client = DevRpcMiddleware::new(provider);
+
+            // snapshot initial state
+            let block0 = client.get_block_number().await.unwrap();
+            let time0 = client.get_block(block0).await.unwrap().unwrap().timestamp;
+            let snap_id0 = client.snapshot().await.unwrap();
+
+            // mine a new block
+            client.provider().mine(1).await.unwrap();
+
+            // snapshot state
+            let block1 = client.get_block_number().await.unwrap();
+            let time1 = client.get_block(block1).await.unwrap().unwrap().timestamp;
+            let snap_id1 = client.snapshot().await.unwrap();
+
+            // mine some blocks
+            client.provider().mine(5).await.unwrap();
+
+            // snapshot state
+            let block2 = client.get_block_number().await.unwrap();
+            let time2 = client.get_block(block2).await.unwrap().unwrap().timestamp;
+            let snap_id2 = client.snapshot().await.unwrap();
+
+            // mine some blocks
+            client.provider().mine(5).await.unwrap();
+
+            // revert_to_snapshot should reset state to snap id
+            client.revert_to_snapshot(snap_id2).await.unwrap();
+            let block = client.get_block_number().await.unwrap();
+            let time = client.get_block(block).await.unwrap().unwrap().timestamp;
+            assert_eq!(block, block2);
+            assert_eq!(time, time2);
+
+            client.revert_to_snapshot(snap_id1).await.unwrap();
+            let block = client.get_block_number().await.unwrap();
+            let time = client.get_block(block).await.unwrap().unwrap().timestamp;
+            assert_eq!(block, block1);
+            assert_eq!(time, time1);
+
+            // revert_to_snapshot should throw given non-existent or
+            // previously used snapshot
+            let result = client.revert_to_snapshot(snap_id1).await;
+            assert!(result.is_err());
+
+            client.revert_to_snapshot(snap_id0).await.unwrap();
+            let block = client.get_block_number().await.unwrap();
+            let time = client.get_block(block).await.unwrap().unwrap().timestamp;
+            assert_eq!(block, block0);
+            assert_eq!(time, time0);
+        }
+    }
+}
+
 #[cfg(test)]
 #[cfg(not(target_arch = "wasm32"))]
 mod tests {
diff --git a/ethers-providers/src/transports/ws.rs b/ethers-providers/src/transports/ws.rs
index 834e7666..64ea06fc 100644
--- a/ethers-providers/src/transports/ws.rs
+++ b/ethers-providers/src/transports/ws.rs
@@ -313,7 +313,9 @@ where
             Err(_) => {}
             Ok(Incoming::Response(resp)) => {
                 if let Some(request) = self.pending.remove(&resp.id) {
-                    request.send(resp.data.into_result()).map_err(to_client_error)?;
+                    if !request.is_canceled() {
+                        request.send(resp.data.into_result()).map_err(to_client_error)?;
+                    }
                 }
             }
             Ok(Incoming::Notification(notification)) => {
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index 743afd9a..296b002b 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -31,12 +31,16 @@ svm = { package = "svm-rs", version = "0.2.0", optional = true }
 glob = "0.3.0"
 tracing = "0.1.29"
 
-[target.'cfg(target_arch = "wasm32")'.dependencies]
-sha2 = { version = "0.9.8" }
+[target.'cfg(not(any(target_arch = "x86", target_arch = "x86_64")))'.dependencies]
+sha2 = { version = "0.9.8", default-features = false }
 
-[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
+[target.'cfg(any(target_arch = "x86", target_arch = "x86_64"))'.dependencies]
 home = "0.5.3"
-sha2 = { version = "0.9.8", features = ["asm"] }
+sha2 = { version = "0.9.8", default-features = false, features = ["asm"] }
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+# NOTE: this enables wasm compatibility for getrandom indirectly
+getrandom = { version = "0.2", features = ["js"] }
 
 [dev-dependencies]
 tokio = { version = "1.12.0", features = ["full"] }
diff --git a/examples/ethers-wasm/Cargo.toml b/examples/ethers-wasm/Cargo.toml
index 24b2164b..34e71d86 100644
--- a/examples/ethers-wasm/Cargo.toml
+++ b/examples/ethers-wasm/Cargo.toml
@@ -43,10 +43,4 @@ hex = "0.4.3"
 web-sys = "0.3.51"
 
 [dev-dependencies]
-wasm-bindgen-test = "0.3.24"
-
-# profile for the wasm example
-[profile.release.package.ethers-wasm]
-# Tell `rustc` to optimize for small code size.
-opt-level = "s"
-
+wasm-bindgen-test = "0.3.24"
\ No newline at end of file
diff --git a/examples/ethers-wasm/src/lib.rs b/examples/ethers-wasm/src/lib.rs
index 84be0071..e4816a55 100644
--- a/examples/ethers-wasm/src/lib.rs
+++ b/examples/ethers-wasm/src/lib.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::all)]
+
 use std::sync::Arc;
 
 use wasm_bindgen::prelude::*;
@@ -51,12 +53,7 @@ pub async fn deploy() {
     let factory = ContractFactory::new(SIMPLECONTRACT_ABI.clone(), bytecode.into(), client.clone());
 
     log!("Deploying contract...");
-    let contract = factory
-        .deploy("hello WASM!".to_string())
-        .unwrap()
-        .send()
-        .await
-        .unwrap();
+    let contract = factory.deploy("hello WASM!".to_string()).unwrap().send().await.unwrap();
     let addr = contract.address();
     log!("Deployed contract with address: {:?}", addr);
 
@@ -64,25 +61,11 @@ pub async fn deploy() {
 
     let value = "bye from WASM!";
     log!("Setting value... `{}`", value);
-    let receipt = contract
-        .set_value(value.to_owned())
-        .send()
-        .await
-        .unwrap()
-        .await
-        .unwrap();
-    console::log_2(
-        &"Set value receipt: ".into(),
-        &JsValue::from_serde(&receipt).unwrap(),
-    );
+    let receipt = contract.set_value(value.to_owned()).send().await.unwrap().await.unwrap();
+    console::log_2(&"Set value receipt: ".into(), &JsValue::from_serde(&receipt).unwrap());
 
     log!("Fetching logs...");
-    let logs = contract
-        .value_changed_filter()
-        .from_block(0u64)
-        .query()
-        .await
-        .unwrap();
+    let logs = contract.value_changed_filter().from_block(0u64).query().await.unwrap();
 
     let value = contract.get_value().call().await.unwrap();
 
diff --git a/examples/ethers-wasm/src/utils.rs b/examples/ethers-wasm/src/utils.rs
index 091ca848..70bdd53e 100644
--- a/examples/ethers-wasm/src/utils.rs
+++ b/examples/ethers-wasm/src/utils.rs
@@ -1,5 +1,7 @@
-use ethers::prelude::{LocalWallet, MnemonicBuilder};
-use ethers::signers::coins_bip39::English;
+use ethers::{
+    prelude::{LocalWallet, MnemonicBuilder},
+    signers::coins_bip39::English,
+};
 
 pub fn set_panic_hook() {
     // When the `console_error_panic_hook` feature is enabled, we can call the
@@ -17,12 +19,7 @@ pub const PHRASE: &str =
     "stuff inherit faith park genre spread huge knee ecology private marble supreme";
 
 pub fn key(index: u32) -> LocalWallet {
-    MnemonicBuilder::<English>::default()
-        .phrase(PHRASE)
-        .index(index)
-        .unwrap()
-        .build()
-        .unwrap()
+    MnemonicBuilder::<English>::default().phrase(PHRASE).index(index).unwrap().build().unwrap()
 }
 
 /// Bytecode of the `SimpleContract`
diff --git a/examples/moonbeam_with_abi.rs b/examples/moonbeam_with_abi.rs
new file mode 100644
index 00000000..c455d5f3
--- /dev/null
+++ b/examples/moonbeam_with_abi.rs
@@ -0,0 +1,75 @@
+use anyhow::Result;
+use ethers::prelude::*;
+use std::{convert::TryFrom, path::Path, sync::Arc, time::Duration};
+
+abigen!(
+    SimpleContract,
+    "./examples/contract_abi.json",
+    event_derives(serde::Deserialize, serde::Serialize)
+);
+
+const MOONBEAM_DEV_ENDPOINT: &str = "http://localhost:9933";
+
+/// This requires a running moonbeam dev instance on `localhost:9933`
+/// See `https://docs.moonbeam.network/builders/get-started/moonbeam-dev/` for reference
+///
+/// This has been tested against:
+///
+/// ```bash
+///  docker run --rm --name moonbeam_development -p 9944:9944 -p 9933:9933 purestake/moonbeam:v0.14.2 --dev --ws-external --rpc-external
+/// ```
+///
+/// Also requires the `legacy` feature to send Legacy transaction instead of an EIP-1559
+#[tokio::main]
+#[cfg(feature = "legacy")]
+async fn main() -> Result<()> {
+    // set the path to the contract, `CARGO_MANIFEST_DIR` points to the directory containing the
+    // manifest of `ethers`. which will be `../` relative to this file
+    let source = Path::new(&env!("CARGO_MANIFEST_DIR")).join("examples/contract.sol");
+    let compiled = Solc::default().compile_source(source).expect("Could not compile contracts");
+    let (abi, bytecode, _runtime_bytecode) =
+        compiled.find("SimpleStorage").expect("could not find contract").into_parts_or_default();
+
+    // 1. get a moonbeam dev key
+    let key = ethers::core::utils::moonbeam::dev_keys()[0].clone();
+
+    // 2. instantiate our wallet with chain id
+    let wallet: LocalWallet = LocalWallet::from(key).with_chain_id(Chain::MoonbeamDev);
+
+    // 3. connect to the network
+    let provider =
+        Provider::<Http>::try_from(MOONBEAM_DEV_ENDPOINT)?.interval(Duration::from_millis(10u64));
+
+    // 4. instantiate the client with the wallet
+    let client = SignerMiddleware::new(provider, wallet);
+    let client = Arc::new(client);
+
+    // 5. create a factory which will be used to deploy instances of the contract
+    let factory = ContractFactory::new(abi, bytecode, client.clone());
+
+    // 6. deploy it with the constructor arguments, note the `legacy` call
+    let contract = factory.deploy("initial value".to_string())?.legacy().send().await?;
+
+    // 7. get the contract's address
+    let addr = contract.address();
+
+    // 8. instantiate the contract
+    let contract = SimpleContract::new(addr, client.clone());
+
+    // 9. call the `setValue` method
+    // (first `await` returns a PendingTransaction, second one waits for it to be mined)
+    let _receipt = contract.set_value("hi".to_owned()).legacy().send().await?.await?;
+
+    // 10. get all events
+    let logs = contract.value_changed_filter().from_block(0u64).query().await?;
+
+    // 11. get the new value
+    let value = contract.get_value().call().await?;
+
+    println!("Value: {}. Logs: {}", value, serde_json::to_string(&logs)?);
+
+    Ok(())
+}
+
+#[cfg(not(feature = "legacy"))]
+fn main() {}


Test patch:
diff --git a/examples/ethers-wasm/tests/contract_with_abi.rs b/examples/ethers-wasm/tests/contract_with_abi.rs
index 8dc61db6..678f54b2 100644
--- a/examples/ethers-wasm/tests/contract_with_abi.rs
+++ b/examples/ethers-wasm/tests/contract_with_abi.rs
@@ -6,8 +6,7 @@ use ethers::prelude::{
     abigen, ContractFactory, Http, JsonRpcClient, LocalWallet, Provider, SignerMiddleware, Ws,
 };
 
-use std::convert::TryFrom;
-use std::sync::Arc;
+use std::{convert::TryFrom, sync::Arc};
 
 wasm_bindgen_test_configure!(run_in_browser);
 
@@ -38,12 +37,8 @@ async fn deploy<T: JsonRpcClient>(provider: Provider<T>, wallet: LocalWallet) {
 
     let bytecode = hex::decode(ethers_wasm::utils::SIMPLECONTRACT_BIN).unwrap();
     let factory = ContractFactory::new(SIMPLECONTRACT_ABI.clone(), bytecode.into(), client.clone());
-    let contract = factory
-        .deploy("Hello from Contract!".to_string())
-        .unwrap()
-        .send()
-        .await
-        .unwrap();
+    let contract =
+        factory.deploy("Hello from Contract!".to_string()).unwrap().send().await.unwrap();
     let addr = contract.address();
     console_log!("deployed to {}", addr);