Repository: gakonst/ethers-rs
Pull request #1349 (2022-06-06 03:18:10)

chore(deps): bump serial_test from 0.6.0 to 0.7.0
Bumps [serial_test](https://github.com/palfrey/serial_test) from 0.6.0 to 0.7.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/palfrey/serial_test/releases">serial_test's releases</a>.</em></p>
<blockquote>
<h2>v0.7.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Parallel test running support <a href="https://github-redirect.dependabot.com/palfrey/serial_test/pull/54">palfrey/serial_test#54</a></li>
<li>Upgrade MSRV to 1.51 and upgrade packages that were blocked on it <a href="https://github-redirect.dependabot.com/palfrey/serial_test/pull/53">palfrey/serial_test#53</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/palfrey/serial_test/compare/v0.6.0...v0.7.0">https://github.com/palfrey/serial_test/compare/v0.6.0...v0.7.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/palfrey/serial_test/commit/c422658ee05b194037ec454d0717cfaef9ef8f97"><code>c422658</code></a> Merge pull request <a href="https://github-redirect.dependabot.com/palfrey/serial_test/issues/54">#54</a> from palfrey/parallel</li>
<li><a href="https://github.com/palfrey/serial_test/commit/9c901839eac80db3b575b901b946f582be9b6a33"><code>9c90183</code></a> Document parallel</li>
<li><a href="https://github.com/palfrey/serial_test/commit/5be34eeebf559e29452c5a6607a8650f4047ae6f"><code>5be34ee</code></a> Condvars shouldn't need timeouts, but sometimes they do</li>
<li><a href="https://github.com/palfrey/serial_test/commit/d9c9b2282969d3a48deca13735cbbcf95a646c4e"><code>d9c9b22</code></a> Fix format</li>
<li><a href="https://github.com/palfrey/serial_test/commit/c6a6aca4ae87a2535431b1bc83383ea050c74427"><code>c6a6aca</code></a> Add condvar timeout hack</li>
<li><a href="https://github.com/palfrey/serial_test/commit/cda69935c3c8b820f71f9bb2ac87190b6713cbe6"><code>cda6993</code></a> Add debug logging</li>
<li><a href="https://github.com/palfrey/serial_test/commit/36ea2dada077fbf55e6f64dd801700a5f49bc0b6"><code>36ea2da</code></a> check_new_key now waits a short period for both locks</li>
<li><a href="https://github.com/palfrey/serial_test/commit/96457b8b30523ffb352b7413990f8c0390d8f924"><code>96457b8</code></a> Slightly better test logging</li>
<li><a href="https://github.com/palfrey/serial_test/commit/7d0a63776456eff6dda362bd9304dfd69005fbf9"><code>7d0a637</code></a> Permit await_holding_lock</li>
<li><a href="https://github.com/palfrey/serial_test/commit/856c592f304b5d1b0f5f12a7158716c02eee5619"><code>856c592</code></a> Redo check_new_key with more robust multi-writer checks</li>
<li>Additional commits viewable in <a href="https://github.com/palfrey/serial_test/compare/v0.6.0...v0.7.0">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=serial_test&package-manager=cargo&previous-version=0.6.0&new-version=0.7.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)


</details>


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2d06ca4d..861e7c0a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -174,6 +174,8 @@
 
 ### Unreleased
 
+- Return String for net version
+  [1376](https://github.com/gakonst/ethers-rs/pull/1376)
 - Stream of paginated logs that load logs in small pages
   [1285](https://github.com/gakonst/ethers-rs/pull/1285)
 - Load previous logs before subscribing to new logs in case fromBlock is set
diff --git a/Cargo.lock b/Cargo.lock
index 462bfe72..a10ee526 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1106,9 +1106,9 @@ dependencies = [
 
 [[package]]
 name = "ethabi"
-version = "17.0.0"
+version = "17.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b69517146dfab88e9238c00c724fd8e277951c3cc6f22b016d72f422a832213e"
+checksum = "f186de076b3e77b8e6d73c99d1b52edc2a229e604f4b5eb6992c06c11d79d537"
 dependencies = [
  "ethereum-types",
  "hex",
@@ -2051,9 +2051,9 @@ checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
 
 [[package]]
 name = "js-sys"
-version = "0.3.57"
+version = "0.3.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397"
+checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27"
 dependencies = [
  "wasm-bindgen",
 ]
@@ -3396,20 +3396,21 @@ dependencies = [
 
 [[package]]
 name = "serial_test"
-version = "0.6.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5bcc41d18f7a1d50525d080fd3e953be87c4f9f1a974f3c21798ca00d54ec15"
+checksum = "d19dbfb999a147cedbfe82f042eb9555f5b0fa4ef95ee4570b74349103d9c9f4"
 dependencies = [
  "lazy_static",
- "parking_lot 0.11.2",
+ "log",
+ "parking_lot 0.12.0",
  "serial_test_derive",
 ]
 
 [[package]]
 name = "serial_test_derive"
-version = "0.6.0"
+version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2881bccd7d60fb32dfa3d7b3136385312f8ad75e2674aab2852867a09790cae8"
+checksum = "cb9e2050b2be1d681f8f1c1a528bcfe4e00afa2d8995f713974f5333288659f2"
 dependencies = [
  "proc-macro-error",
  "proc-macro2",
@@ -3665,9 +3666,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
 
 [[package]]
 name = "svm-rs"
-version = "0.2.10"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "462a64764fb3e8f26469f1c84e30240405f8a641b633b6e2b3d8c83e73f6f67a"
+checksum = "1265123e4a2ed34548e5a365b9f09253c534e425665d3b6408340483951a8ce9"
 dependencies = [
  "anyhow",
  "cfg-if 1.0.0",
@@ -3695,9 +3696,9 @@ dependencies = [
 
 [[package]]
 name = "svm-rs-builds"
-version = "0.1.0"
+version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b3cbd0d97b618011e8529b8a1ec27b1be4e2fef6030ffa557e00b0a430e2dfde"
+checksum = "4c5c2ed046b7d56d071cd36ff7d80e24fd796d43c2566b38520c600ddedfd359"
 dependencies = [
  "build_const",
  "hex",
@@ -4226,9 +4227,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad"
+checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994"
 dependencies = [
  "cfg-if 1.0.0",
  "serde",
@@ -4238,9 +4239,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4"
+checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a"
 dependencies = [
  "bumpalo",
  "lazy_static",
@@ -4253,9 +4254,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.30"
+version = "0.4.31"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2"
+checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f"
 dependencies = [
  "cfg-if 1.0.0",
  "js-sys",
@@ -4265,9 +4266,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5"
+checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -4275,9 +4276,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b"
+checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -4288,9 +4289,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.80"
+version = "0.2.81"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744"
+checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
 
 [[package]]
 name = "wasm-bindgen-test"
@@ -4333,9 +4334,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.57"
+version = "0.3.58"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283"
+checksum = "2fed94beee57daf8dd7d51f2b15dc2bcde92d7a72304cdf662a4371008b71b90"
 dependencies = [
  "js-sys",
  "wasm-bindgen",
diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs
index d2b7fcf7..600855a0 100644
--- a/ethers-contract/src/event.rs
+++ b/ethers-contract/src/event.rs
@@ -1,10 +1,9 @@
 #![allow(clippy::return_self_not_must_use)]
 
 use crate::{log::LogMeta, stream::EventStream, ContractError, EthLogDecode};
-
 use ethers_core::{
     abi::{Detokenize, RawLog},
-    types::{BlockNumber, Filter, Log, ValueOrArray, H256},
+    types::{BlockNumber, Filter, Log, Topic, H256},
 };
 use ethers_providers::{FilterWatcher, Middleware, PubsubClient, SubscriptionStream};
 use std::{borrow::Cow, marker::PhantomData};
@@ -87,25 +86,25 @@ impl<M, D: EthLogDecode> Event<'_, M, D> {
     }
 
     /// Sets the filter's 0th topic (typically the event name for non-anonymous events)
-    pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
+    pub fn topic0<T: Into<Topic>>(mut self, topic: T) -> Self {
         self.filter.topics[0] = Some(topic.into());
         self
     }
 
     /// Sets the filter's 1st topic
-    pub fn topic1<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
+    pub fn topic1<T: Into<Topic>>(mut self, topic: T) -> Self {
         self.filter.topics[1] = Some(topic.into());
         self
     }
 
     /// Sets the filter's 2nd topic
-    pub fn topic2<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
+    pub fn topic2<T: Into<Topic>>(mut self, topic: T) -> Self {
         self.filter.topics[2] = Some(topic.into());
         self
     }
 
     /// Sets the filter's 3rd topic
-    pub fn topic3<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
+    pub fn topic3<T: Into<Topic>>(mut self, topic: T) -> Self {
         self.filter.topics[3] = Some(topic.into());
         self
     }
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index 41ac1584..00fa7524 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -11,7 +11,7 @@ keywords = ["ethereum", "web3", "celo", "ethers"]
 
 [dependencies]
 rlp = { version = "0.5.0", default-features = false, features = ["std"] }
-ethabi = { version = "17.0.0", default-features = false, features = ["full-serde", "rlp"] }
+ethabi = { version = "17.1.0", default-features = false, features = ["full-serde", "rlp"] }
 arrayvec = { version = "0.7.2", default-features = false }
 rlp-derive = { version = "0.1.0", default-features = false }
 
diff --git a/ethers-core/src/abi/human_readable/mod.rs b/ethers-core/src/abi/human_readable/mod.rs
index 65af6f65..6d97e730 100644
--- a/ethers-core/src/abi/human_readable/mod.rs
+++ b/ethers-core/src/abi/human_readable/mod.rs
@@ -533,8 +533,8 @@ pub(crate) fn is_likely_tuple_not_uint8(kind: &ParamType, type_str: &str) -> boo
 pub fn contains_uint8(kind: &ParamType) -> bool {
     match kind {
         ParamType::Uint(8) => true,
-        ParamType::Array(kind) => contains_uint8(&*kind),
-        ParamType::FixedArray(kind, _) => contains_uint8(&*kind),
+        ParamType::Array(kind) => contains_uint8(kind),
+        ParamType::FixedArray(kind, _) => contains_uint8(kind),
         ParamType::Tuple(tuple) => tuple.iter().any(contains_uint8),
         _ => false,
     }
diff --git a/ethers-core/src/types/filter.rs b/ethers-core/src/types/filter.rs
new file mode 100644
index 00000000..e3466c4a
--- /dev/null
+++ b/ethers-core/src/types/filter.rs
@@ -0,0 +1,1175 @@
+use crate::{
+    abi::ethereum_types::BloomInput,
+    types::{Address, BlockNumber, Bloom, Log, H160, H256, U256, U64},
+    utils::keccak256,
+};
+use serde::{
+    de::{DeserializeOwned, MapAccess, Visitor},
+    ser::SerializeStruct,
+    Deserialize, Deserializer, Serialize, Serializer,
+};
+use std::ops::{Range, RangeFrom, RangeTo};
+
+pub type BloomFilter = Vec<Option<Bloom>>;
+
+/// A single topic
+pub type Topic = ValueOrArray<Option<H256>>;
+
+/// Represents the target range of blocks for the filter
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
+pub enum FilterBlockOption {
+    Range { from_block: Option<BlockNumber>, to_block: Option<BlockNumber> },
+    AtBlockHash(H256),
+}
+
+impl FilterBlockOption {
+    pub fn get_to_block(&self) -> Option<&BlockNumber> {
+        match self {
+            FilterBlockOption::Range { to_block, .. } => to_block.as_ref(),
+            FilterBlockOption::AtBlockHash(_) => None,
+        }
+    }
+
+    pub fn get_from_block(&self) -> Option<&BlockNumber> {
+        match self {
+            FilterBlockOption::Range { from_block, .. } => from_block.as_ref(),
+            FilterBlockOption::AtBlockHash(_) => None,
+        }
+    }
+}
+
+impl From<BlockNumber> for FilterBlockOption {
+    fn from(block: BlockNumber) -> Self {
+        let block = Some(block);
+        FilterBlockOption::Range { from_block: block, to_block: block }
+    }
+}
+
+impl From<U64> for FilterBlockOption {
+    fn from(block: U64) -> Self {
+        BlockNumber::from(block).into()
+    }
+}
+
+impl From<u64> for FilterBlockOption {
+    fn from(block: u64) -> Self {
+        BlockNumber::from(block).into()
+    }
+}
+
+impl<T: Into<BlockNumber>> From<Range<T>> for FilterBlockOption {
+    fn from(r: Range<T>) -> Self {
+        let from_block = Some(r.start.into());
+        let to_block = Some(r.end.into());
+        FilterBlockOption::Range { from_block, to_block }
+    }
+}
+
+impl<T: Into<BlockNumber>> From<RangeTo<T>> for FilterBlockOption {
+    fn from(r: RangeTo<T>) -> Self {
+        let to_block = Some(r.end.into());
+        FilterBlockOption::Range { from_block: Some(BlockNumber::Earliest), to_block }
+    }
+}
+
+impl<T: Into<BlockNumber>> From<RangeFrom<T>> for FilterBlockOption {
+    fn from(r: RangeFrom<T>) -> Self {
+        let from_block = Some(r.start.into());
+        FilterBlockOption::Range { from_block, to_block: Some(BlockNumber::Latest) }
+    }
+}
+
+impl From<H256> for FilterBlockOption {
+    fn from(hash: H256) -> Self {
+        FilterBlockOption::AtBlockHash(hash)
+    }
+}
+
+impl Default for FilterBlockOption {
+    fn default() -> Self {
+        FilterBlockOption::Range { from_block: None, to_block: None }
+    }
+}
+
+impl FilterBlockOption {
+    #[must_use]
+    pub fn set_from_block(&self, block: BlockNumber) -> Self {
+        let to_block =
+            if let FilterBlockOption::Range { to_block, .. } = self { *to_block } else { None };
+
+        FilterBlockOption::Range { from_block: Some(block), to_block }
+    }
+
+    #[must_use]
+    pub fn set_to_block(&self, block: BlockNumber) -> Self {
+        let from_block =
+            if let FilterBlockOption::Range { from_block, .. } = self { *from_block } else { None };
+
+        FilterBlockOption::Range { from_block, to_block: Some(block) }
+    }
+
+    #[must_use]
+    pub fn set_hash(&self, hash: H256) -> Self {
+        FilterBlockOption::AtBlockHash(hash)
+    }
+}
+
+/// Filter for
+#[derive(Default, Debug, PartialEq, Eq, Clone, Hash)]
+pub struct Filter {
+    /// Filter block options, specifying on which blocks the filter should
+    /// match.
+    // https://eips.ethereum.org/EIPS/eip-234
+    pub block_option: FilterBlockOption,
+
+    /// Address
+    pub address: Option<ValueOrArray<Address>>,
+
+    /// Topics
+    // TODO: We could improve the low level API here by using ethabi's RawTopicFilter
+    // and/or TopicFilter
+    pub topics: [Option<Topic>; 4],
+}
+
+impl Filter {
+    pub fn new() -> Self {
+        Self::default()
+    }
+
+    /// Sets the inner filter object
+    ///
+    /// *NOTE:* ranges are always inclusive
+    ///
+    /// # Examples
+    ///
+    /// Match only a specific block
+    ///
+    /// ```rust
+    /// # use ethers_core::types::Filter;
+    /// # fn main() {
+    /// let filter = Filter::new().select(69u64);
+    /// # }
+    /// ```
+    /// This is the same as `Filter::new().from_block(1337u64).to_block(1337u64)`
+    ///
+    /// Match the latest block only
+    ///
+    /// ```rust
+    /// # use ethers_core::types::{Filter, BlockNumber};
+    /// # fn main() {
+    /// let filter = Filter::new().select(BlockNumber::Latest);
+    /// # }
+    /// ```
+    ///
+    /// Match a block by its hash
+    ///
+    /// ```rust
+    /// # use ethers_core::types::{Filter, H256};
+    /// # fn main() {
+    /// let filter = Filter::new().select(H256::zero());
+    /// # }
+    /// ```
+    /// This is the same as `at_block_hash`
+    ///
+    /// Match a range of blocks
+    ///
+    /// ```rust
+    /// # use ethers_core::types::{Filter, H256};
+    /// # fn main() {
+    /// let filter = Filter::new().select(0u64..100u64);
+    /// # }
+    /// ```
+    ///
+    /// Match all blocks in range `(1337..BlockNumber::Latest)`
+    ///
+    /// ```rust
+    /// # use ethers_core::types::{Filter, H256};
+    /// # fn main() {
+    /// let filter = Filter::new().select(1337u64..);
+    /// # }
+    /// ```
+    ///
+    /// Match all blocks in range `(BlockNumber::Earliest..1337)`
+    ///
+    /// ```rust
+    /// # use ethers_core::types::{Filter, H256};
+    /// # fn main() {
+    /// let filter = Filter::new().select(..1337u64);
+    /// # }
+    /// ```
+    #[must_use]
+    pub fn select(mut self, filter: impl Into<FilterBlockOption>) -> Self {
+        self.block_option = filter.into();
+        self
+    }
+
+    #[allow(clippy::wrong_self_convention)]
+    #[must_use]
+    pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
+        self.block_option = self.block_option.set_from_block(block.into());
+        self
+    }
+
+    #[allow(clippy::wrong_self_convention)]
+    #[must_use]
+    pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
+        self.block_option = self.block_option.set_to_block(block.into());
+        self
+    }
+
+    #[allow(clippy::wrong_self_convention)]
+    #[must_use]
+    pub fn at_block_hash<T: Into<H256>>(mut self, hash: T) -> Self {
+        self.block_option = self.block_option.set_hash(hash.into());
+        self
+    }
+    /// Sets the inner filter object
+    ///
+    /// *NOTE:* ranges are always inclusive
+    ///
+    /// # Examples
+    ///
+    /// Match only a specific address `("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF")`
+    ///
+    /// ```rust
+    /// # use ethers_core::types::{Filter, Address};
+    /// # fn main() {
+    /// let filter = Filter::new().address("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::<Address>().unwrap());
+    /// # }
+    /// ```
+    ///
+    /// Match all addresses in array `(vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF",
+    /// "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"])`
+    ///
+    /// ```rust
+    /// # use ethers_core::types::{Filter, Address, ValueOrArray};
+    /// # fn main() {
+    /// let addresses = vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::<Address>().unwrap(),"0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".parse::<Address>().unwrap()];
+    /// let filter = Filter::new().address(addresses);
+    /// # }
+    /// ```
+    #[must_use]
+    pub fn address<T: Into<ValueOrArray<Address>>>(mut self, address: T) -> Self {
+        self.address = Some(address.into());
+        self
+    }
+
+    /// given the event in string form, it hashes it and adds it to the topics to monitor
+    #[must_use]
+    pub fn event(self, event_name: &str) -> Self {
+        let hash = H256::from(keccak256(event_name.as_bytes()));
+        self.topic0(hash)
+    }
+
+    /// Sets topic0 (the event name for non-anonymous events)
+    #[must_use]
+    pub fn topic0<T: Into<Topic>>(mut self, topic: T) -> Self {
+        self.topics[0] = Some(topic.into());
+        self
+    }
+
+    /// Sets the 1st indexed topic
+    #[must_use]
+    pub fn topic1<T: Into<Topic>>(mut self, topic: T) -> Self {
+        self.topics[1] = Some(topic.into());
+        self
+    }
+
+    /// Sets the 2nd indexed topic
+    #[must_use]
+    pub fn topic2<T: Into<Topic>>(mut self, topic: T) -> Self {
+        self.topics[2] = Some(topic.into());
+        self
+    }
+
+    /// Sets the 3rd indexed topic
+    #[must_use]
+    pub fn topic3<T: Into<Topic>>(mut self, topic: T) -> Self {
+        self.topics[3] = Some(topic.into());
+        self
+    }
+
+    pub fn is_paginatable(&self) -> bool {
+        self.get_from_block().is_some()
+    }
+
+    /// Returns the numeric value of the `toBlock` field
+    pub fn get_to_block(&self) -> Option<U64> {
+        self.block_option.get_to_block().and_then(|b| b.as_number())
+    }
+
+    /// Returns the numeric value of the `fromBlock` field
+    pub fn get_from_block(&self) -> Option<U64> {
+        self.block_option.get_from_block().and_then(|b| b.as_number())
+    }
+
+    /// Returns the numeric value of the `fromBlock` field
+    pub fn get_block_hash(&self) -> Option<H256> {
+        match self.block_option {
+            FilterBlockOption::AtBlockHash(hash) => Some(hash),
+            FilterBlockOption::Range { .. } => None,
+        }
+    }
+
+    /// Flattens the topics using the cartesian product
+    fn flatten(&self) -> Vec<ValueOrArray<Option<H256>>> {
+        fn cartesian(lists: &[Vec<Option<H256>>]) -> Vec<Vec<Option<H256>>> {
+            let mut res = Vec::new();
+            let mut list_iter = lists.iter();
+            if let Some(first_list) = list_iter.next() {
+                for &i in first_list {
+                    res.push(vec![i]);
+                }
+            }
+            for l in list_iter {
+                let mut tmp = Vec::new();
+                for r in res {
+                    for &el in l {
+                        let mut tmp_el = r.clone();
+                        tmp_el.push(el);
+                        tmp.push(tmp_el);
+                    }
+                }
+                res = tmp;
+            }
+            res
+        }
+        let mut out = Vec::new();
+        let mut tmp = Vec::new();
+        for v in self.topics.iter() {
+            let v = if let Some(v) = v {
+                match v {
+                    ValueOrArray::Value(s) => {
+                        vec![*s]
+                    }
+                    ValueOrArray::Array(s) => s.clone(),
+                }
+            } else {
+                vec![None]
+            };
+            tmp.push(v);
+        }
+        for v in cartesian(&tmp) {
+            out.push(ValueOrArray::Array(v));
+        }
+        out
+    }
+
+    /// Returns an iterator over all existing topics
+    pub fn topics(&self) -> impl Iterator<Item = &Topic> + '_ {
+        self.topics.iter().flatten()
+    }
+
+    /// Returns true if at least one topic is set
+    pub fn has_topics(&self) -> bool {
+        self.topics.iter().any(|t| t.is_some())
+    }
+}
+
+impl Serialize for Filter {
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        let mut s = serializer.serialize_struct("Filter", 5)?;
+        match self.block_option {
+            FilterBlockOption::Range { from_block, to_block } => {
+                if let Some(ref from_block) = from_block {
+                    s.serialize_field("fromBlock", from_block)?;
+                }
+
+                if let Some(ref to_block) = to_block {
+                    s.serialize_field("toBlock", to_block)?;
+                }
+            }
+
+            FilterBlockOption::AtBlockHash(ref h) => s.serialize_field("blockHash", h)?,
+        }
+
+        if let Some(ref address) = self.address {
+            s.serialize_field("address", address)?;
+        }
+
+        let mut filtered_topics = Vec::new();
+        for i in 0..4 {
+            if self.topics[i].is_some() {
+                filtered_topics.push(&self.topics[i]);
+            } else {
+                // TODO: This can be optimized
+                if self.topics[i + 1..].iter().any(|x| x.is_some()) {
+                    filtered_topics.push(&None);
+                }
+            }
+        }
+        s.serialize_field("topics", &filtered_topics)?;
+
+        s.end()
+    }
+}
+
+impl<'de> Deserialize<'de> for Filter {
+    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+    where
+        D: Deserializer<'de>,
+    {
+        struct FilterVisitor;
+
+        impl<'de> Visitor<'de> for FilterVisitor {
+            type Value = Filter;
+
+            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
+                formatter.write_str("Filter object")
+            }
+
+            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
+            where
+                A: MapAccess<'de>,
+            {
+                let mut from_block: Option<Option<BlockNumber>> = None;
+                let mut to_block: Option<Option<BlockNumber>> = None;
+                let mut block_hash: Option<Option<H256>> = None;
+                let mut address: Option<Option<ValueOrArray<Address>>> = None;
+                let mut topics: Option<Vec<Option<Topic>>> = None;
+
+                while let Some(key) = map.next_key::<String>()? {
+                    match key.as_str() {
+                        "fromBlock" => {
+                            if from_block.is_some() {
+                                return Err(serde::de::Error::duplicate_field("fromBlock"))
+                            }
+                            if block_hash.is_some() {
+                                return Err(serde::de::Error::custom(
+                                    "fromBlock not allowed with blockHash",
+                                ))
+                            }
+                            from_block = Some(map.next_value()?)
+                        }
+                        "toBlock" => {
+                            if to_block.is_some() {
+                                return Err(serde::de::Error::duplicate_field("toBlock"))
+                            }
+                            if block_hash.is_some() {
+                                return Err(serde::de::Error::custom(
+                                    "toBlock not allowed with blockHash",
+                                ))
+                            }
+                            to_block = Some(map.next_value()?)
+                        }
+                        "blockHash" => {
+                            if block_hash.is_some() {
+                                return Err(serde::de::Error::duplicate_field("blockHash"))
+                            }
+                            if from_block.is_some() || to_block.is_some() {
+                                return Err(serde::de::Error::custom(
+                                    "fromBlock,toBlock not allowed with blockHash",
+                                ))
+                            }
+                            block_hash = Some(map.next_value()?)
+                        }
+                        "address" => {
+                            if address.is_some() {
+                                return Err(serde::de::Error::duplicate_field("address"))
+                            }
+                            address = Some(map.next_value()?)
+                        }
+                        "topics" => {
+                            if topics.is_some() {
+                                return Err(serde::de::Error::duplicate_field("topics"))
+                            }
+                            topics = Some(map.next_value()?)
+                        }
+
+                        key => {
+                            return Err(serde::de::Error::unknown_field(
+                                key,
+                                &["fromBlock", "toBlock", "address", "topics", "blockHash"],
+                            ))
+                        }
+                    }
+                }
+
+                let from_block = from_block.unwrap_or_default();
+                let to_block = to_block.unwrap_or_default();
+                let block_hash = block_hash.unwrap_or_default();
+                let address = address.unwrap_or_default();
+                let topics_vec = topics.unwrap_or_default();
+
+                // maximum allowed filter len
+                if topics_vec.len() > 4 {
+                    return Err(serde::de::Error::custom("exceeded maximum topics len"))
+                }
+                let mut topics: [Option<Topic>; 4] = [None, None, None, None];
+                for (idx, topic) in topics_vec.into_iter().enumerate() {
+                    topics[idx] = topic;
+                }
+
+                let block_option = if let Some(block_hash) = block_hash {
+                    FilterBlockOption::AtBlockHash(block_hash)
+                } else {
+                    FilterBlockOption::Range { from_block, to_block }
+                };
+
+                Ok(Filter { block_option, address, topics })
+            }
+        }
+
+        deserializer.deserialize_any(FilterVisitor)
+    }
+}
+
+/// Union type for representing a single value or a vector of values inside a filter
+#[derive(Debug, PartialEq, Eq, Clone, Hash)]
+pub enum ValueOrArray<T> {
+    /// A single value
+    Value(T),
+    /// A vector of values
+    Array(Vec<T>),
+}
+
+impl From<H160> for ValueOrArray<H160> {
+    fn from(src: H160) -> Self {
+        ValueOrArray::Value(src)
+    }
+}
+
+impl From<Vec<H160>> for ValueOrArray<H160> {
+    fn from(src: Vec<H160>) -> Self {
+        ValueOrArray::Array(src)
+    }
+}
+
+impl From<H256> for Topic {
+    fn from(src: H256) -> Self {
+        ValueOrArray::Value(Some(src))
+    }
+}
+
+impl From<ValueOrArray<H256>> for Topic {
+    fn from(src: ValueOrArray<H256>) -> Self {
+        match src {
+            ValueOrArray::Value(val) => ValueOrArray::Value(Some(val)),
+            ValueOrArray::Array(arr) => arr.into(),
+        }
+    }
+}
+
+impl<I: Into<H256>> From<Vec<I>> for Topic {
+    fn from(src: Vec<I>) -> Self {
+        ValueOrArray::Array(src.into_iter().map(Into::into).map(Some).collect())
+    }
+}
+
+impl From<Address> for Topic {
+    fn from(src: Address) -> Self {
+        let mut bytes = [0; 32];
+        bytes[12..32].copy_from_slice(src.as_bytes());
+        ValueOrArray::Value(Some(H256::from(bytes)))
+    }
+}
+
+impl From<U256> for Topic {
+    fn from(src: U256) -> Self {
+        let mut bytes = [0; 32];
+        src.to_big_endian(&mut bytes);
+        ValueOrArray::Value(Some(H256::from(bytes)))
+    }
+}
+
+impl<T> Serialize for ValueOrArray<T>
+where
+    T: Serialize,
+{
+    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+    where
+        S: Serializer,
+    {
+        match self {
+            ValueOrArray::Value(inner) => inner.serialize(serializer),
+            ValueOrArray::Array(inner) => inner.serialize(serializer),
+        }
+    }
+}
+
+impl<'a, T> Deserialize<'a> for ValueOrArray<T>
+where
+    T: DeserializeOwned,
+{
+    fn deserialize<D>(deserializer: D) -> Result<ValueOrArray<T>, D::Error>
+    where
+        D: Deserializer<'a>,
+    {
+        let value = serde_json::Value::deserialize(deserializer)?;
+
+        if value.is_null() {
+            return Ok(ValueOrArray::Array(Vec::new()))
+        }
+
+        #[derive(Deserialize)]
+        #[serde(untagged)]
+        enum Variadic<T> {
+            Value(T),
+            Array(Vec<T>),
+        }
+
+        match serde_json::from_value::<Variadic<T>>(value).map_err(|err| {
+            serde::de::Error::custom(format!("Invalid variadic value or array type: {}", err))
+        })? {
+            Variadic::Value(val) => Ok(ValueOrArray::Value(val)),
+            Variadic::Array(arr) => Ok(ValueOrArray::Array(arr)),
+        }
+    }
+}
+
+/// Support for matching [Filter]s
+#[derive(Debug, Default)]
+pub struct FilteredParams {
+    pub filter: Option<Filter>,
+    pub flat_topics: Vec<ValueOrArray<Option<H256>>>,
+}
+
+impl FilteredParams {
+    pub fn new(filter: Option<Filter>) -> Self {
+        if let Some(filter) = filter {
+            let flat_topics = filter.flatten();
+            FilteredParams { filter: Some(filter), flat_topics }
+        } else {
+            Default::default()
+        }
+    }
+
+    /// Returns the [BloomFilter] for the given address
+    pub fn address_filter(address: &Option<ValueOrArray<Address>>) -> BloomFilter {
+        address.as_ref().map(address_to_bloom_filter).unwrap_or_default()
+    }
+
+    /// Returns the [BloomFilter] for the given topics
+    pub fn topics_filter(topics: &Option<Vec<ValueOrArray<Option<H256>>>>) -> Vec<BloomFilter> {
+        let mut output = Vec::new();
+        if let Some(topics) = topics {
+            output.extend(topics.iter().map(topics_to_bloom_filter));
+        }
+        output
+    }
+
+    /// Returns `true` if the bloom matches the topics
+    pub fn matches_topics(bloom: Bloom, topic_filters: &[BloomFilter]) -> bool {
+        if topic_filters.is_empty() {
+            return true
+        }
+
+        // returns true if a filter matches
+        for filter in topic_filters.iter() {
+            let mut is_match = false;
+            for maybe_bloom in filter {
+                is_match = maybe_bloom.as_ref().map(|b| bloom.contains_bloom(b)).unwrap_or(true);
+                if !is_match {
+                    break
+                }
+            }
+            if is_match {
+                return true
+            }
+        }
+        false
+    }
+
+    /// Returns `true` if the bloom contains the address
+    pub fn matches_address(bloom: Bloom, address_filter: &BloomFilter) -> bool {
+        if address_filter.is_empty() {
+            return true
+        } else {
+            for maybe_bloom in address_filter {
+                if maybe_bloom.as_ref().map(|b| bloom.contains_bloom(b)).unwrap_or(true) {
+                    return true
+                }
+            }
+        }
+        false
+    }
+
+    /// Replace None values - aka wildcards - for the log input value in that position.
+    pub fn replace(&self, log: &Log, topic: Topic) -> Option<Vec<H256>> {
+        let mut out: Vec<H256> = Vec::new();
+        match topic {
+            ValueOrArray::Value(value) => {
+                if let Some(value) = value {
+                    out.push(value);
+                }
+            }
+            ValueOrArray::Array(value) => {
+                for (k, v) in value.into_iter().enumerate() {
+                    if let Some(v) = v {
+                        out.push(v);
+                    } else {
+                        out.push(log.topics[k]);
+                    }
+                }
+            }
+        };
+        if out.is_empty() {
+            return None
+        }
+        Some(out)
+    }
+
+    pub fn filter_block_range(&self, block_number: u64) -> bool {
+        if self.filter.is_none() {
+            return true
+        }
+        let filter = self.filter.as_ref().unwrap();
+        let mut res = true;
+
+        if let Some(BlockNumber::Number(num)) = filter.block_option.get_from_block() {
+            if num.as_u64() > block_number {
+                res = false;
+            }
+        }
+
+        if let Some(to) = filter.block_option.get_to_block() {
+            match to {
+                BlockNumber::Number(num) => {
+                    if num.as_u64() < block_number {
+                        res = false;
+                    }
+                }
+                BlockNumber::Earliest => {
+                    res = false;
+                }
+                _ => {}
+            }
+        }
+        res
+    }
+
+    pub fn filter_block_hash(&self, block_hash: H256) -> bool {
+        if let Some(h) = self.filter.as_ref().and_then(|f| f.get_block_hash()) {
+            if h != block_hash {
+                return false
+            }
+        }
+        true
+    }
+
+    pub fn filter_address(&self, log: &Log) -> bool {
+        if let Some(input_address) = &self.filter.as_ref().and_then(|f| f.address.clone()) {
+            match input_address {
+                ValueOrArray::Value(x) => {
+                    if log.address != *x {
+                        return false
+                    }
+                }
+                ValueOrArray::Array(x) => {
+                    if x.is_empty() {
+                        return true
+                    }
+                    if !x.contains(&log.address) {
+                        return false
+                    }
+                }
+            }
+        }
+        true
+    }
+
+    pub fn filter_topics(&self, log: &Log) -> bool {
+        let mut out: bool = true;
+        for topic in self.flat_topics.iter().cloned() {
+            match topic {
+                ValueOrArray::Value(single) => {
+                    if let Some(single) = single {
+                        if !log.topics.starts_with(&[single]) {
+                            out = false;
+                        }
+                    }
+                }
+                ValueOrArray::Array(multi) => {
+                    if multi.is_empty() {
+                        out = true;
+                        continue
+                    }
+                    // Shrink the topics until the last item is Some.
+                    let mut new_multi = multi;
+                    while new_multi.iter().last().unwrap_or(&Some(H256::default())).is_none() {
+                        new_multi.pop();
+                    }
+                    // We can discard right away any logs with lesser topics than the filter.
+                    if new_multi.len() > log.topics.len() {
+                        out = false;
+                        break
+                    }
+                    let replaced: Option<Vec<H256>> =
+                        self.replace(log, ValueOrArray::Array(new_multi));
+                    if let Some(replaced) = replaced {
+                        out = false;
+                        if log.topics.starts_with(&replaced[..]) {
+                            out = true;
+                            break
+                        }
+                    }
+                }
+            }
+        }
+        out
+    }
+}
+
+fn topics_to_bloom_filter(topics: &ValueOrArray<Option<H256>>) -> BloomFilter {
+    let mut blooms = BloomFilter::new();
+    match topics {
+        ValueOrArray::Value(topic) => {
+            if let Some(topic) = topic {
+                let bloom: Bloom = BloomInput::Raw(topic.as_ref()).into();
+                blooms.push(Some(bloom));
+            } else {
+                blooms.push(None);
+            }
+        }
+        ValueOrArray::Array(topics) => {
+            if topics.is_empty() {
+                blooms.push(None);
+            } else {
+                for topic in topics.iter() {
+                    if let Some(topic) = topic {
+                        let bloom: Bloom = BloomInput::Raw(topic.as_ref()).into();
+                        blooms.push(Some(bloom));
+                    } else {
+                        blooms.push(None);
+                    }
+                }
+            }
+        }
+    }
+    blooms
+}
+
+fn address_to_bloom_filter(address: &ValueOrArray<Address>) -> BloomFilter {
+    let mut blooms = BloomFilter::new();
+    match address {
+        ValueOrArray::Value(address) => {
+            let bloom: Bloom = BloomInput::Raw(address.as_ref()).into();
+            blooms.push(Some(bloom))
+        }
+        ValueOrArray::Array(addresses) => {
+            if addresses.is_empty() {
+                blooms.push(None);
+            } else {
+                for address in addresses.iter() {
+                    let bloom: Bloom = BloomInput::Raw(address.as_ref()).into();
+                    blooms.push(Some(bloom));
+                }
+            }
+        }
+    }
+    blooms
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::utils::serialize;
+    use serde_json::json;
+
+    #[test]
+    fn can_serde_value_or_array() {
+        #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
+        struct Item {
+            value: ValueOrArray<U256>,
+        }
+
+        let item = Item { value: ValueOrArray::Value(U256::one()) };
+        let json = serde_json::to_value(item.clone()).unwrap();
+        let deserialized: Item = serde_json::from_value(json).unwrap();
+        assert_eq!(item, deserialized);
+
+        let item = Item { value: ValueOrArray::Array(vec![U256::one(), U256::zero()]) };
+        let json = serde_json::to_value(item.clone()).unwrap();
+        let deserialized: Item = serde_json::from_value(json).unwrap();
+        assert_eq!(item, deserialized);
+    }
+
+    #[test]
+    fn filter_serialization_test() {
+        let t1 = "9729a6fbefefc8f6005933898b13dc45c3a2c8b7".parse::<Address>().unwrap();
+        let t2 = H256::from([0; 32]);
+        let t3 = U256::from(123);
+
+        let t1_padded = H256::from(t1);
+        let t3_padded = H256::from({
+            let mut x = [0; 32];
+            x[31] = 123;
+            x
+        });
+
+        let event = "ValueChanged(address,string,string)";
+        let t0 = H256::from(keccak256(event.as_bytes()));
+        let addr: Address = "f817796F60D268A36a57b8D2dF1B97B14C0D0E1d".parse().unwrap();
+        let filter = Filter::new();
+
+        let ser = serialize(&filter);
+        assert_eq!(ser, json!({ "topics": [] }));
+
+        let filter = filter.address(ValueOrArray::Value(addr));
+
+        let ser = serialize(&filter);
+        assert_eq!(ser, json!({"address" : addr, "topics": []}));
+
+        let filter = filter.event(event);
+
+        // 0
+        let ser = serialize(&filter);
+        assert_eq!(ser, json!({ "address" : addr, "topics": [t0]}));
+
+        // 1
+        let ser = serialize(&filter.clone().topic1(t1));
+        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded]}));
+
+        // 2
+        let ser = serialize(&filter.clone().topic2(t2));
+        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, t2]}));
+
+        // 3
+        let ser = serialize(&filter.clone().topic3(t3));
+        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, null, t3_padded]}));
+
+        // 1 & 2
+        let ser = serialize(&filter.clone().topic1(t1).topic2(t2));
+        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, t2]}));
+
+        // 1 & 3
+        let ser = serialize(&filter.clone().topic1(t1).topic3(t3));
+        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, null, t3_padded]}));
+
+        // 2 & 3
+        let ser = serialize(&filter.clone().topic2(t2).topic3(t3));
+        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, t2, t3_padded]}));
+
+        // 1 & 2 & 3
+        let ser = serialize(&filter.topic1(t1).topic2(t2).topic3(t3));
+        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, t2, t3_padded]}));
+    }
+
+    fn build_bloom(address: Address, topic1: H256, topic2: H256) -> Bloom {
+        let mut block_bloom = Bloom::default();
+        block_bloom.accrue(BloomInput::Raw(&address[..]));
+        block_bloom.accrue(BloomInput::Raw(&topic1[..]));
+        block_bloom.accrue(BloomInput::Raw(&topic2[..]));
+        block_bloom
+    }
+
+    fn topic_filter(
+        topic1: H256,
+        topic2: H256,
+        topic3: H256,
+    ) -> (Filter, Option<Vec<ValueOrArray<Option<H256>>>>) {
+        let filter = Filter {
+            block_option: Default::default(),
+            address: None,
+            topics: [
+                Some(ValueOrArray::Value(Some(topic1))),
+                Some(ValueOrArray::Array(vec![Some(topic2), Some(topic3)])),
+                None,
+                None,
+            ],
+        };
+        let filtered_params = FilteredParams::new(Some(filter.clone()));
+
+        (filter, Some(filtered_params.flat_topics))
+    }
+
+    #[test]
+    fn can_detect_different_topics() {
+        let topic1 = H256::random();
+        let topic2 = H256::random();
+        let topic3 = H256::random();
+
+        let (_, topics) = topic_filter(topic1, topic2, topic3);
+        let topics_bloom = FilteredParams::topics_filter(&topics);
+        assert!(!FilteredParams::matches_topics(
+            build_bloom(Address::random(), H256::random(), H256::random()),
+            &topics_bloom
+        ));
+    }
+
+    #[test]
+    fn can_match_topic() {
+        let topic1 = H256::random();
+        let topic2 = H256::random();
+        let topic3 = H256::random();
+
+        let (_, topics) = topic_filter(topic1, topic2, topic3);
+        let _topics_bloom = FilteredParams::topics_filter(&topics);
+
+        let topics_bloom = FilteredParams::topics_filter(&topics);
+        assert!(FilteredParams::matches_topics(
+            build_bloom(Address::random(), topic1, topic2),
+            &topics_bloom
+        ));
+    }
+
+    #[test]
+    fn can_match_empty_topics() {
+        let filter =
+            Filter { block_option: Default::default(), address: None, topics: Default::default() };
+
+        let filtered_params = FilteredParams::new(Some(filter));
+        let topics = Some(filtered_params.flat_topics);
+
+        let topics_bloom = FilteredParams::topics_filter(&topics);
+        assert!(FilteredParams::matches_topics(
+            build_bloom(Address::random(), H256::random(), H256::random()),
+            &topics_bloom
+        ));
+    }
+
+    #[test]
+    fn can_match_address_and_topics() {
+        let rng_address = Address::random();
+        let topic1 = H256::random();
+        let topic2 = H256::random();
+        let topic3 = H256::random();
+
+        let filter = Filter {
+            block_option: Default::default(),
+            address: Some(ValueOrArray::Value(rng_address)),
+            topics: [
+                Some(ValueOrArray::Value(Some(topic1))),
+                Some(ValueOrArray::Array(vec![Some(topic2), Some(topic3)])),
+                None,
+                None,
+            ],
+        };
+        let filtered_params = FilteredParams::new(Some(filter.clone()));
+        let topics = Some(filtered_params.flat_topics);
+        let address_filter = FilteredParams::address_filter(&filter.address);
+        let topics_filter = FilteredParams::topics_filter(&topics);
+        assert!(
+            FilteredParams::matches_address(
+                build_bloom(rng_address, topic1, topic2),
+                &address_filter
+            ) && FilteredParams::matches_topics(
+                build_bloom(rng_address, topic1, topic2),
+                &topics_filter
+            )
+        );
+    }
+
+    #[test]
+    fn can_match_topics_wildcard() {
+        let topic1 = H256::random();
+        let topic2 = H256::random();
+        let topic3 = H256::random();
+
+        let filter = Filter {
+            block_option: Default::default(),
+            address: None,
+            topics: [None, Some(ValueOrArray::Array(vec![Some(topic2), Some(topic3)])), None, None],
+        };
+        let filtered_params = FilteredParams::new(Some(filter));
+        let topics = Some(filtered_params.flat_topics);
+        let topics_bloom = FilteredParams::topics_filter(&topics);
+        assert!(FilteredParams::matches_topics(
+            build_bloom(Address::random(), topic1, topic2),
+            &topics_bloom
+        ));
+    }
+
+    #[test]
+    fn can_match_topics_wildcard_mismatch() {
+        let filter = Filter {
+            block_option: Default::default(),
+            address: None,
+            topics: [
+                None,
+                Some(ValueOrArray::Array(vec![Some(H256::random()), Some(H256::random())])),
+                None,
+                None,
+            ],
+        };
+        let filtered_params = FilteredParams::new(Some(filter));
+        let topics_input = Some(filtered_params.flat_topics);
+        let topics_bloom = FilteredParams::topics_filter(&topics_input);
+        assert!(!FilteredParams::matches_topics(
+            build_bloom(Address::random(), H256::random(), H256::random()),
+            &topics_bloom
+        ));
+    }
+
+    #[test]
+    fn can_match_address_filter() {
+        let rng_address = Address::random();
+        let filter = Filter {
+            block_option: Default::default(),
+            address: Some(ValueOrArray::Value(rng_address)),
+            topics: Default::default(),
+        };
+        let address_bloom = FilteredParams::address_filter(&filter.address);
+        assert!(FilteredParams::matches_address(
+            build_bloom(rng_address, H256::random(), H256::random(),),
+            &address_bloom
+        ));
+    }
+
+    #[test]
+    fn can_detect_different_address() {
+        let bloom_address = Address::random();
+        let rng_address = Address::random();
+        let filter = Filter {
+            block_option: Default::default(),
+            address: Some(ValueOrArray::Value(rng_address)),
+            topics: Default::default(),
+        };
+        let address_bloom = FilteredParams::address_filter(&filter.address);
+        assert!(!FilteredParams::matches_address(
+            build_bloom(bloom_address, H256::random(), H256::random(),),
+            &address_bloom
+        ));
+    }
+
+    #[test]
+    fn can_convert_to_ethers_filter() {
+        let json = json!(
+                    {
+          "fromBlock": "0x429d3b",
+          "toBlock": "0x429d3b",
+          "address": "0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907",
+          "topics": [
+          "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
+          "0x00000000000000000000000000b46c2526e227482e2ebb8f4c69e4674d262e75",
+          "0x00000000000000000000000054a2d42a40f51259dedd1978f6c118a0f0eff078"
+          ]
+        }
+            );
+
+        let filter: Filter = serde_json::from_value(json).unwrap();
+        assert_eq!(
+            filter,
+            Filter {
+                block_option: FilterBlockOption::Range {
+                    from_block: Some(4365627u64.into()),
+                    to_block: Some(4365627u64.into()),
+                },
+                address: Some(ValueOrArray::Value(
+                    "0xb59f67a8bff5d8cd03f6ac17265c550ed8f33907".parse().unwrap()
+                )),
+                topics: [
+                    Some(ValueOrArray::Value(Some(
+                        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
+                            .parse()
+                            .unwrap(),
+                    ))),
+                    Some(ValueOrArray::Value(Some(
+                        "0x00000000000000000000000000b46c2526e227482e2ebb8f4c69e4674d262e75"
+                            .parse()
+                            .unwrap(),
+                    ))),
+                    Some(ValueOrArray::Value(Some(
+                        "0x00000000000000000000000054a2d42a40f51259dedd1978f6c118a0f0eff078"
+                            .parse()
+                            .unwrap(),
+                    ))),
+                    None,
+                ],
+            }
+        );
+    }
+}
diff --git a/ethers-core/src/types/i256.rs b/ethers-core/src/types/i256.rs
index 6a1c4dba..48d8a28d 100644
--- a/ethers-core/src/types/i256.rs
+++ b/ethers-core/src/types/i256.rs
@@ -293,7 +293,7 @@ impl I256 {
 
     /// Convert from a decimal string.
     pub fn from_dec_str(value: &str) -> Result<Self, ParseI256Error> {
-        let (sign, value) = match value.as_bytes().get(0) {
+        let (sign, value) = match value.as_bytes().first() {
             Some(b'+') => (Sign::Positive, &value[1..]),
             Some(b'-') => (Sign::Negative, &value[1..]),
             _ => (Sign::Positive, value),
@@ -308,7 +308,7 @@ impl I256 {
 
     /// Convert from a hexadecimal string.
     pub fn from_hex_str(value: &str) -> Result<Self, ParseI256Error> {
-        let (sign, value) = match value.as_bytes().get(0) {
+        let (sign, value) = match value.as_bytes().first() {
             Some(b'+') => (Sign::Positive, &value[1..]),
             Some(b'-') => (Sign::Negative, &value[1..]),
             _ => (Sign::Positive, value),
diff --git a/ethers-core/src/types/log.rs b/ethers-core/src/types/log.rs
index b570e7ac..3d055d56 100644
--- a/ethers-core/src/types/log.rs
+++ b/ethers-core/src/types/log.rs
@@ -1,12 +1,6 @@
 // Adapted from https://github.com/tomusdrw/rust-web3/blob/master/src/types/log.rs
-use crate::{
-    types::{Address, BlockNumber, Bytes, H160, H256, U256, U64},
-    utils::keccak256,
-};
-use serde::{
-    de::DeserializeOwned, ser::SerializeStruct, Deserialize, Deserializer, Serialize, Serializer,
-};
-use std::ops::{Range, RangeFrom, RangeTo};
+use crate::types::{Address, Bytes, H256, U256, U64};
+use serde::{Deserialize, Serialize};
 
 /// A log produced by a transaction.
 #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
@@ -74,490 +68,4 @@ impl rlp::Encodable for Log {
     }
 }
 
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub enum FilterBlockOption {
-    Range { from_block: Option<BlockNumber>, to_block: Option<BlockNumber> },
-    AtBlockHash(H256),
-}
-
-impl From<BlockNumber> for FilterBlockOption {
-    fn from(block: BlockNumber) -> Self {
-        let block = Some(block);
-        FilterBlockOption::Range { from_block: block, to_block: block }
-    }
-}
-
-impl From<U64> for FilterBlockOption {
-    fn from(block: U64) -> Self {
-        BlockNumber::from(block).into()
-    }
-}
-
-impl From<u64> for FilterBlockOption {
-    fn from(block: u64) -> Self {
-        BlockNumber::from(block).into()
-    }
-}
-
-impl<T: Into<BlockNumber>> From<Range<T>> for FilterBlockOption {
-    fn from(r: Range<T>) -> Self {
-        let from_block = Some(r.start.into());
-        let to_block = Some(r.end.into());
-        FilterBlockOption::Range { from_block, to_block }
-    }
-}
-
-impl<T: Into<BlockNumber>> From<RangeTo<T>> for FilterBlockOption {
-    fn from(r: RangeTo<T>) -> Self {
-        let to_block = Some(r.end.into());
-        FilterBlockOption::Range { from_block: Some(BlockNumber::Earliest), to_block }
-    }
-}
-
-impl<T: Into<BlockNumber>> From<RangeFrom<T>> for FilterBlockOption {
-    fn from(r: RangeFrom<T>) -> Self {
-        let from_block = Some(r.start.into());
-        FilterBlockOption::Range { from_block, to_block: Some(BlockNumber::Latest) }
-    }
-}
-
-impl From<H256> for FilterBlockOption {
-    fn from(hash: H256) -> Self {
-        FilterBlockOption::AtBlockHash(hash)
-    }
-}
-
-impl Default for FilterBlockOption {
-    fn default() -> Self {
-        FilterBlockOption::Range { from_block: None, to_block: None }
-    }
-}
-
-impl FilterBlockOption {
-    #[must_use]
-    pub fn set_from_block(&self, block: BlockNumber) -> Self {
-        let to_block =
-            if let FilterBlockOption::Range { to_block, .. } = self { *to_block } else { None };
-
-        FilterBlockOption::Range { from_block: Some(block), to_block }
-    }
-
-    #[must_use]
-    pub fn set_to_block(&self, block: BlockNumber) -> Self {
-        let from_block =
-            if let FilterBlockOption::Range { from_block, .. } = self { *from_block } else { None };
-
-        FilterBlockOption::Range { from_block, to_block: Some(block) }
-    }
-
-    #[must_use]
-    pub fn set_hash(&self, hash: H256) -> Self {
-        FilterBlockOption::AtBlockHash(hash)
-    }
-}
-
-/// Filter for
-#[derive(Default, Debug, PartialEq, Eq, Clone)]
-pub struct Filter {
-    /// Filter block options, specifying on which blocks the filter should
-    /// match.
-    // https://eips.ethereum.org/EIPS/eip-234
-    pub block_option: FilterBlockOption,
-
-    /// Address
-    address: Option<ValueOrArray<Address>>,
-
-    /// Topics
-    // TODO: We could improve the low level API here by using ethabi's RawTopicFilter
-    // and/or TopicFilter
-    pub topics: [Option<ValueOrArray<H256>>; 4],
-}
-
-impl Serialize for Filter {
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        let mut s = serializer.serialize_struct("Filter", 5)?;
-        match self.block_option {
-            FilterBlockOption::Range { from_block, to_block } => {
-                if let Some(ref from_block) = from_block {
-                    s.serialize_field("fromBlock", from_block)?;
-                }
-
-                if let Some(ref to_block) = to_block {
-                    s.serialize_field("toBlock", to_block)?;
-                }
-            }
-
-            FilterBlockOption::AtBlockHash(ref h) => s.serialize_field("blockHash", h)?,
-        }
-
-        if let Some(ref address) = self.address {
-            s.serialize_field("address", address)?;
-        }
-
-        let mut filtered_topics = Vec::new();
-        for i in 0..4 {
-            if self.topics[i].is_some() {
-                filtered_topics.push(&self.topics[i]);
-            } else {
-                // TODO: This can be optimized
-                if self.topics[i + 1..].iter().any(|x| x.is_some()) {
-                    filtered_topics.push(&None);
-                }
-            }
-        }
-        s.serialize_field("topics", &filtered_topics)?;
-
-        s.end()
-    }
-}
-
-impl Filter {
-    pub fn new() -> Self {
-        Self::default()
-    }
-
-    /// Sets the inner filter object
-    ///
-    /// *NOTE:* ranges are always inclusive
-    ///
-    /// # Examples
-    ///
-    /// Match only a specific block
-    ///
-    /// ```rust
-    /// # use ethers_core::types::Filter;
-    /// # fn main() {
-    /// let filter = Filter::new().select(69u64);
-    /// # }
-    /// ```
-    /// This is the same as `Filter::new().from_block(1337u64).to_block(1337u64)`
-    ///
-    /// Match the latest block only
-    ///
-    /// ```rust
-    /// # use ethers_core::types::{Filter, BlockNumber};
-    /// # fn main() {
-    /// let filter = Filter::new().select(BlockNumber::Latest);
-    /// # }
-    /// ```
-    ///
-    /// Match a block by its hash
-    ///
-    /// ```rust
-    /// # use ethers_core::types::{Filter, H256};
-    /// # fn main() {
-    /// let filter = Filter::new().select(H256::zero());
-    /// # }
-    /// ```
-    /// This is the same as `at_block_hash`
-    ///
-    /// Match a range of blocks
-    ///
-    /// ```rust
-    /// # use ethers_core::types::{Filter, H256};
-    /// # fn main() {
-    /// let filter = Filter::new().select(0u64..100u64);
-    /// # }
-    /// ```
-    ///
-    /// Match all blocks in range `(1337..BlockNumber::Latest)`
-    ///
-    /// ```rust
-    /// # use ethers_core::types::{Filter, H256};
-    /// # fn main() {
-    /// let filter = Filter::new().select(1337u64..);
-    /// # }
-    /// ```
-    ///
-    /// Match all blocks in range `(BlockNumber::Earliest..1337)`
-    ///
-    /// ```rust
-    /// # use ethers_core::types::{Filter, H256};
-    /// # fn main() {
-    /// let filter = Filter::new().select(..1337u64);
-    /// # }
-    /// ```
-    #[must_use]
-    pub fn select(mut self, filter: impl Into<FilterBlockOption>) -> Self {
-        self.block_option = filter.into();
-        self
-    }
-
-    #[allow(clippy::wrong_self_convention)]
-    #[must_use]
-    pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
-        self.block_option = self.block_option.set_from_block(block.into());
-        self
-    }
-
-    #[allow(clippy::wrong_self_convention)]
-    #[must_use]
-    pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
-        self.block_option = self.block_option.set_to_block(block.into());
-        self
-    }
-
-    #[allow(clippy::wrong_self_convention)]
-    #[must_use]
-    pub fn at_block_hash<T: Into<H256>>(mut self, hash: T) -> Self {
-        self.block_option = self.block_option.set_hash(hash.into());
-        self
-    }
-    /// Sets the inner filter object
-    ///
-    /// *NOTE:* ranges are always inclusive
-    ///
-    /// # Examples
-    ///
-    /// Match only a specific address `("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF")`
-    ///
-    /// ```rust
-    /// # use ethers_core::types::{Filter, Address};
-    /// # fn main() {
-    /// let filter = Filter::new().address("0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::<Address>().unwrap());
-    /// # }
-    /// ```
-    ///
-    /// Match all addresses in array `(vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF",
-    /// "0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8"])`
-    ///
-    /// ```rust
-    /// # use ethers_core::types::{Filter, Address, ValueOrArray};
-    /// # fn main() {
-    /// let addresses = vec!["0xAc4b3DacB91461209Ae9d41EC517c2B9Cb1B7DAF".parse::<Address>().unwrap(),"0x8ad599c3A0ff1De082011EFDDc58f1908eb6e6D8".parse::<Address>().unwrap()];
-    /// let filter = Filter::new().address(addresses);
-    /// # }
-    /// ```
-    #[must_use]
-    pub fn address<T: Into<ValueOrArray<Address>>>(mut self, address: T) -> Self {
-        self.address = Some(address.into());
-        self
-    }
-
-    /// given the event in string form, it hashes it and adds it to the topics to monitor
-    #[must_use]
-    pub fn event(self, event_name: &str) -> Self {
-        let hash = H256::from(keccak256(event_name.as_bytes()));
-        self.topic0(hash)
-    }
-
-    /// Sets topic0 (the event name for non-anonymous events)
-    #[must_use]
-    pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
-        self.topics[0] = Some(topic.into());
-        self
-    }
-
-    /// Sets the 1st indexed topic
-    #[must_use]
-    pub fn topic1<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
-        self.topics[1] = Some(topic.into());
-        self
-    }
-
-    /// Sets the 2nd indexed topic
-    #[must_use]
-    pub fn topic2<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
-        self.topics[2] = Some(topic.into());
-        self
-    }
-
-    /// Sets the 3rd indexed topic
-    #[must_use]
-    pub fn topic3<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
-        self.topics[3] = Some(topic.into());
-        self
-    }
-
-    pub fn is_paginatable(&self) -> bool {
-        self.get_from_block().is_some()
-    }
-
-    pub fn get_from_block(&self) -> Option<U64> {
-        match self.block_option {
-            FilterBlockOption::AtBlockHash(_hash) => None,
-            FilterBlockOption::Range { from_block, to_block: _ } => {
-                from_block.map(|block| block.as_number()).unwrap_or(None)
-            }
-        }
-    }
-}
-
-/// Union type for representing a single value or a vector of values inside a filter
-#[derive(Debug, PartialEq, Eq, Clone, Hash)]
-pub enum ValueOrArray<T> {
-    /// A single value
-    Value(T),
-    /// A vector of values
-    Array(Vec<T>),
-}
-
 // TODO: Implement more common types - or adjust this to work with all Tokenizable items
-
-impl From<H160> for ValueOrArray<H160> {
-    fn from(src: H160) -> Self {
-        ValueOrArray::Value(src)
-    }
-}
-
-impl From<Vec<H160>> for ValueOrArray<H160> {
-    fn from(src: Vec<H160>) -> Self {
-        ValueOrArray::Array(src)
-    }
-}
-
-impl From<H256> for ValueOrArray<H256> {
-    fn from(src: H256) -> Self {
-        ValueOrArray::Value(src)
-    }
-}
-
-impl From<Address> for ValueOrArray<H256> {
-    fn from(src: Address) -> Self {
-        let mut bytes = [0; 32];
-        bytes[12..32].copy_from_slice(src.as_bytes());
-        ValueOrArray::Value(H256::from(bytes))
-    }
-}
-
-impl From<U256> for ValueOrArray<H256> {
-    fn from(src: U256) -> Self {
-        let mut bytes = [0; 32];
-        src.to_big_endian(&mut bytes);
-        ValueOrArray::Value(H256::from(bytes))
-    }
-}
-
-impl<T> Serialize for ValueOrArray<T>
-where
-    T: Serialize,
-{
-    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
-    where
-        S: Serializer,
-    {
-        match self {
-            ValueOrArray::Value(inner) => inner.serialize(serializer),
-            ValueOrArray::Array(inner) => inner.serialize(serializer),
-        }
-    }
-}
-
-impl<'a, T> Deserialize<'a> for ValueOrArray<T>
-where
-    T: DeserializeOwned,
-{
-    fn deserialize<D>(deserializer: D) -> Result<ValueOrArray<T>, D::Error>
-    where
-        D: Deserializer<'a>,
-    {
-        let value = serde_json::Value::deserialize(deserializer)?;
-
-        if value.is_null() {
-            return Ok(ValueOrArray::Array(Vec::new()))
-        }
-
-        #[derive(Deserialize)]
-        #[serde(untagged)]
-        enum Variadic<T> {
-            Value(T),
-            Array(Vec<T>),
-        }
-
-        match serde_json::from_value::<Variadic<T>>(value).map_err(|err| {
-            serde::de::Error::custom(format!("Invalid variadic value or array type: {}", err))
-        })? {
-            Variadic::Value(val) => Ok(ValueOrArray::Value(val)),
-            Variadic::Array(arr) => Ok(ValueOrArray::Array(arr)),
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::utils::serialize;
-    use serde_json::json;
-
-    #[test]
-    fn can_serde_value_or_array() {
-        #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
-        struct Item {
-            value: ValueOrArray<U256>,
-        }
-
-        let item = Item { value: ValueOrArray::Value(U256::one()) };
-        let json = serde_json::to_value(item.clone()).unwrap();
-        let deserialized: Item = serde_json::from_value(json).unwrap();
-        assert_eq!(item, deserialized);
-
-        let item = Item { value: ValueOrArray::Array(vec![U256::one(), U256::zero()]) };
-        let json = serde_json::to_value(item.clone()).unwrap();
-        let deserialized: Item = serde_json::from_value(json).unwrap();
-        assert_eq!(item, deserialized);
-    }
-
-    #[test]
-    fn filter_serialization_test() {
-        let t1 = "9729a6fbefefc8f6005933898b13dc45c3a2c8b7".parse::<Address>().unwrap();
-        let t2 = H256::from([0; 32]);
-        let t3 = U256::from(123);
-
-        let t1_padded = H256::from(t1);
-        let t3_padded = H256::from({
-            let mut x = [0; 32];
-            x[31] = 123;
-            x
-        });
-
-        let event = "ValueChanged(address,string,string)";
-        let t0 = H256::from(keccak256(event.as_bytes()));
-        let addr: Address = "f817796F60D268A36a57b8D2dF1B97B14C0D0E1d".parse().unwrap();
-        let filter = Filter::new();
-
-        let ser = serialize(&filter);
-        assert_eq!(ser, json!({ "topics": [] }));
-
-        let filter = filter.address(ValueOrArray::Value(addr));
-
-        let ser = serialize(&filter);
-        assert_eq!(ser, json!({"address" : addr, "topics": []}));
-
-        let filter = filter.event(event);
-
-        // 0
-        let ser = serialize(&filter);
-        assert_eq!(ser, json!({ "address" : addr, "topics": [t0]}));
-
-        // 1
-        let ser = serialize(&filter.clone().topic1(t1));
-        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded]}));
-
-        // 2
-        let ser = serialize(&filter.clone().topic2(t2));
-        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, t2]}));
-
-        // 3
-        let ser = serialize(&filter.clone().topic3(t3));
-        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, null, t3_padded]}));
-
-        // 1 & 2
-        let ser = serialize(&filter.clone().topic1(t1).topic2(t2));
-        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, t2]}));
-
-        // 1 & 3
-        let ser = serialize(&filter.clone().topic1(t1).topic3(t3));
-        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, null, t3_padded]}));
-
-        // 2 & 3
-        let ser = serialize(&filter.clone().topic2(t2).topic3(t3));
-        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, null, t2, t3_padded]}));
-
-        // 1 & 2 & 3
-        let ser = serialize(&filter.topic1(t1).topic2(t2).topic3(t3));
-        assert_eq!(ser, json!({ "address" : addr, "topics": [t0, t1_padded, t2, t3_padded]}));
-    }
-}
diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs
index 994cc8ed..c24747d6 100644
--- a/ethers-core/src/types/mod.rs
+++ b/ethers-core/src/types/mod.rs
@@ -37,7 +37,10 @@ pub use block::{Block, BlockId, BlockNumber, TimeError};
 pub use block::Randomness;
 
 mod log;
-pub use log::{Filter, FilterBlockOption, Log, ValueOrArray};
+pub use log::Log;
+
+mod filter;
+pub use filter::*;
 
 mod ens;
 pub use ens::NameOrAddress;
diff --git a/ethers-core/src/types/transaction/response.rs b/ethers-core/src/types/transaction/response.rs
index 24b0125c..a50aa838 100644
--- a/ethers-core/src/types/transaction/response.rs
+++ b/ethers-core/src/types/transaction/response.rs
@@ -507,6 +507,16 @@ mod tests {
         assert_eq!(tx.max_priority_fee_per_gas.unwrap().as_u64(), 0x3b9aca00);
     }
 
+    #[test]
+    fn tx_roundtrip() {
+        let json = serde_json::json!({"accessList":[{"address":"0x8ba1f109551bd432803012645ac136ddd64dba72","storageKeys":["0x0000000000000000000000000000000000000000000000000000000000000000","0x0000000000000000000000000000000000000000000000000000000000000042"]}],"blockHash":"0x55ae43d3511e327dc532855510d110676d340aa1bbba369b4b98896d86559586","blockNumber":"0xa3d322","chainId":"0x3","from":"0x541d6a0e9ca9e7a083e41e2e178eef9f22d7492e","gas":"0x6a40","gasPrice":"0x3b9aca07","hash":"0x824384376c5972498c6fcafe71fd8cad1689f64e7d5e270d025a898638c0c34d","input":"0x","maxFeePerGas":"0x3b9aca0e","maxPriorityFeePerGas":"0x3b9aca00","nonce":"0x0","r":"0xf13b5088108f783f4b6048d4be456971118aabfb88be96bb541d734b6c2b20dc","s":"0x13fb7eb25a7d5df42a176cd4c6a086e19163ed7cd8ffba015f939d24f66bc17a","to":"0x8210357f377e901f18e45294e86a2a32215cc3c9","transactionIndex":"0xd","type":"0x2","v":"0x1","value":"0x7b"});
+        let tx: Transaction = serde_json::from_value(json.clone()).unwrap();
+        assert_eq!(tx.nonce, 0u64.into());
+
+        let encoded = serde_json::to_value(tx).unwrap();
+        assert_eq!(encoded, json);
+    }
+
     #[test]
     fn rlp_london_tx() {
         let tx = Transaction {
diff --git a/ethers-etherscan/Cargo.toml b/ethers-etherscan/Cargo.toml
index a3fa57bb..4927047a 100644
--- a/ethers-etherscan/Cargo.toml
+++ b/ethers-etherscan/Cargo.toml
@@ -26,7 +26,7 @@ semver = "1.0.10"
 [dev-dependencies]
 tempfile = "3.3.0"
 tokio = { version = "1.18", features = ["macros", "rt-multi-thread", "time"] }
-serial_test = "0.6.0"
+serial_test = "0.7.0"
 tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] }
 ethers-solc = { version = "^0.13.0", path = "../ethers-solc", default-features = false }
 
diff --git a/ethers-middleware/Cargo.toml b/ethers-middleware/Cargo.toml
index dafaeb46..32870799 100644
--- a/ethers-middleware/Cargo.toml
+++ b/ethers-middleware/Cargo.toml
@@ -44,7 +44,7 @@ rand = { version = "0.8.5", default-features = false }
 ethers-providers = { version = "^0.13.0", path = "../ethers-providers", default-features = false, features = ["ws", "rustls"] }
 once_cell = "1.12.0"
 ethers-solc = { version = "^0.13.0", path = "../ethers-solc", default-features = false }
-serial_test = "0.6.0"
+serial_test = "0.7.0"
 
 [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
 tokio = { version = "1.18", default-features = false, features = ["rt", "macros", "time"] }
diff --git a/ethers-middleware/src/signer.rs b/ethers-middleware/src/signer.rs
index 8166d6b8..33cd92ff 100644
--- a/ethers-middleware/src/signer.rs
+++ b/ethers-middleware/src/signer.rs
@@ -126,7 +126,7 @@ where
     /// id.
     async fn sign_transaction(
         &self,
-        tx: TypedTransaction,
+        mut tx: TypedTransaction,
     ) -> Result<Bytes, SignerMiddlewareError<M, S>> {
         // compare chain_id and use signer's chain_id if the tranasaction's chain_id is None,
         // return an error if they are not consistent
@@ -136,7 +136,6 @@ where
                 return Err(SignerMiddlewareError::DifferentChainID)
             }
             None => {
-                let mut tx = tx.clone();
                 tx.set_chain_id(chain_id);
             }
             _ => {}
@@ -382,6 +381,42 @@ mod tests {
         assert_eq!(tx, expected_rlp);
     }
 
+    #[tokio::test]
+    async fn signs_tx_none_chainid() {
+        // retrieved test vector from:
+        // https://web3js.readthedocs.io/en/v1.2.0/web3-eth-accounts.html#eth-accounts-signtransaction
+        // the signature is different because we're testing signer middleware handling the None
+        // case for a non-mainnet chain id
+        let tx = TransactionRequest {
+            from: None,
+            to: Some("F0109fC8DF283027b6285cc889F5aA624EaC1F55".parse::<Address>().unwrap().into()),
+            value: Some(1_000_000_000.into()),
+            gas: Some(2_000_000.into()),
+            nonce: Some(U256::zero()),
+            gas_price: Some(21_000_000_000u128.into()),
+            data: None,
+            chain_id: None,
+        }
+        .into();
+        let chain_id = 1337u64;
+
+        // Signer middlewares now rely on a working provider which it can query the chain id from,
+        // so we make sure Anvil is started with the chain id that the expected tx was signed
+        // with
+        let anvil = Anvil::new().args(vec!["--chain-id".to_string(), chain_id.to_string()]).spawn();
+        let provider = Provider::try_from(anvil.endpoint()).unwrap();
+        let key = "4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318"
+            .parse::<LocalWallet>()
+            .unwrap()
+            .with_chain_id(chain_id);
+        let client = SignerMiddleware::new(provider, key);
+
+        let tx = client.sign_transaction(tx).await.unwrap();
+
+        let expected_rlp = Bytes::from(hex::decode("f86b808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca0080820a95a08290324bae25ca0490077e0d1f4098730333088f6a500793fa420243f35c6b23a06aca42876cd28fdf614a4641e64222fee586391bb3f4061ed5dfefac006be850").unwrap());
+        assert_eq!(tx.clone(), expected_rlp);
+    }
+
     #[tokio::test]
     async fn anvil_consistent_chainid() {
         let anvil = Anvil::new().spawn();
diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs
index 7743df89..a977cf6f 100644
--- a/ethers-providers/src/lib.rs
+++ b/ethers-providers/src/lib.rs
@@ -340,7 +340,7 @@ pub trait Middleware: Sync + Send + Debug {
         self.inner().get_chainid().await.map_err(FromErr::from)
     }
 
-    async fn get_net_version(&self) -> Result<U64, Self::Error> {
+    async fn get_net_version(&self) -> Result<String, Self::Error> {
         self.inner().get_net_version().await.map_err(FromErr::from)
     }
 
diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs
index a8b4f22f..95de17a3 100644
--- a/ethers-providers/src/provider.rs
+++ b/ethers-providers/src/provider.rs
@@ -583,7 +583,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
     }
 
     /// Returns the network version.
-    async fn get_net_version(&self) -> Result<U64, ProviderError> {
+    async fn get_net_version(&self) -> Result<String, ProviderError> {
         self.request("net_version", ()).await
     }
 
diff --git a/ethers-providers/src/transports/retry.rs b/ethers-providers/src/transports/retry.rs
index 17655c56..9b4f15e0 100644
--- a/ethers-providers/src/transports/retry.rs
+++ b/ethers-providers/src/transports/retry.rs
@@ -14,6 +14,7 @@ use std::{
 use async_trait::async_trait;
 use serde::{de::DeserializeOwned, Serialize};
 use thiserror::Error;
+use tracing::trace;
 
 /// [RetryPolicy] defines logic for which [JsonRpcClient::Error] instances should
 /// the client retry the request and try to recover from.
@@ -137,6 +138,7 @@ where
 
             retry_number += 1;
             if retry_number > self.max_retry {
+                trace!("request timed out after {} retries", self.max_retry);
                 return Err(RetryClientError::TimeoutError)
             }
 
@@ -147,8 +149,10 @@ where
                 // of already queued requests
                 let next_backoff =
                     self.initial_backoff * 2u64.pow(retry_number + current_queued_requests);
+                trace!("retrying and backing off for {}ms", next_backoff);
                 tokio::time::sleep(Duration::from_millis(next_backoff)).await;
             } else {
+                trace!(err = ?err, "should not retry");
                 self.requests_enqueued.fetch_sub(1, Ordering::SeqCst);
                 return Err(RetryClientError::ProviderError(err))
             }
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index 91c17142..781e96e1 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -43,8 +43,8 @@ cfg-if = "1.0.0"
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
 home = "0.5.3"
-svm = { package = "svm-rs", version = "0.2.10", default-features = false, optional = true, features = ["blocking"] }
-svm-builds = { package = "svm-rs-builds", version = "0.1.0", optional = true}
+svm = { package = "svm-rs", version = "0.2.11", default-features = false, optional = true, features = ["blocking"] }
+svm-builds = { package = "svm-rs-builds", version = "0.1.1", optional = true}
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
 # NOTE: this enables wasm compatibility for getrandom indirectly
diff --git a/ethers-solc/src/artifacts/serde_helpers.rs b/ethers-solc/src/artifacts/serde_helpers.rs
index d935e1a9..0e38ee3f 100644
--- a/ethers-solc/src/artifacts/serde_helpers.rs
+++ b/ethers-solc/src/artifacts/serde_helpers.rs
@@ -78,7 +78,7 @@ pub mod json_string_opt {
 pub mod empty_json_object_opt {
     use serde::{
         de::{self, DeserializeOwned},
-        ser, Deserialize, Deserializer, Serialize, Serializer,
+        Deserialize, Deserializer, Serialize, Serializer,
     };
 
     pub fn serialize<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
@@ -87,8 +87,7 @@ pub mod empty_json_object_opt {
         T: Serialize,
     {
         if let Some(value) = value {
-            let value = serde_json::to_string(value).map_err(ser::Error::custom)?;
-            serializer.serialize_str(&value)
+            value.serialize(serializer)
         } else {
             let empty = serde_json::Value::Object(Default::default());
             serde_json::Value::serialize(&empty, serializer)
diff --git a/ethers-solc/src/buildinfo.rs b/ethers-solc/src/buildinfo.rs
new file mode 100644
index 00000000..97507872
--- /dev/null
+++ b/ethers-solc/src/buildinfo.rs
@@ -0,0 +1,114 @@
+//! Represents an entire build
+
+use crate::{utils, CompilerInput, CompilerOutput, SolcError};
+use md5::Digest;
+use semver::Version;
+use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
+use std::{cell::RefCell, path::Path, rc::Rc};
+
+pub const ETHERS_FORMAT_VERSION: &str = "ethers-rs-sol-build-info-1";
+
+// A hardhat compatible build info representation
+#[derive(Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct BuildInfo {
+    pub id: String,
+    #[serde(rename = "_format")]
+    pub format: String,
+    pub solc_version: Version,
+    pub solc_long_version: Version,
+    pub input: CompilerInput,
+    pub output: CompilerOutput,
+}
+
+impl BuildInfo {
+    /// Deserializes the `BuildInfo` object from the given file
+    pub fn read(path: impl AsRef<Path>) -> Result<Self, SolcError> {
+        utils::read_json_file(path)
+    }
+}
+
+/// Represents `BuildInfo` object
+#[derive(Debug, Clone, Eq, PartialEq)]
+pub struct RawBuildInfo {
+    /// The hash that identifies the BuildInfo
+    pub id: String,
+    /// serialized `BuildInfo` json
+    pub build_info: String,
+}
+
+// === impl RawBuildInfo ===
+
+impl RawBuildInfo {
+    /// Serializes a `BuildInfo` object
+    pub fn new(
+        input: &CompilerInput,
+        output: &CompilerOutput,
+        version: &Version,
+    ) -> serde_json::Result<RawBuildInfo> {
+        let mut hasher = md5::Md5::new();
+        let w = BuildInfoWriter { buf: Rc::new(RefCell::new(Vec::with_capacity(128))) };
+        let mut buf = w.clone();
+        let mut serializer = serde_json::Serializer::pretty(&mut buf);
+        let mut s = serializer.serialize_struct("BuildInfo", 6)?;
+        s.serialize_field("_format", &ETHERS_FORMAT_VERSION)?;
+        let solc_short = format!("{}.{}.{}", version.major, version.minor, version.patch);
+        s.serialize_field("solcVersion", &solc_short)?;
+        s.serialize_field("solcLongVersion", &version)?;
+        s.serialize_field("input", input)?;
+
+        // create the hash for `{_format,solcVersion,solcLongVersion,input}`
+        // N.B. this is not exactly the same as hashing the json representation of these values but
+        // the must efficient one
+        hasher.update(&*w.buf.borrow());
+        let result = hasher.finalize();
+        let id = hex::encode(result);
+
+        s.serialize_field("id", &id)?;
+        s.serialize_field("output", output)?;
+        s.end()?;
+
+        drop(buf);
+
+        let build_info = unsafe {
+            // serde_json does not emit non UTF8
+            String::from_utf8_unchecked(w.buf.take())
+        };
+
+        Ok(RawBuildInfo { id, build_info })
+    }
+}
+
+#[derive(Clone)]
+struct BuildInfoWriter {
+    buf: Rc<RefCell<Vec<u8>>>,
+}
+
+impl std::io::Write for BuildInfoWriter {
+    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
+        self.buf.borrow_mut().write(buf)
+    }
+
+    fn flush(&mut self) -> std::io::Result<()> {
+        self.buf.borrow_mut().flush()
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::Source;
+    use std::{collections::BTreeMap, path::PathBuf};
+
+    #[test]
+    fn build_info_serde() {
+        let inputs = CompilerInput::with_sources(BTreeMap::from([(
+            PathBuf::from("input.sol"),
+            Source { content: "".to_string() },
+        )]));
+        let output = CompilerOutput::default();
+        let v: Version = "0.8.4+commit.c7e474f2".parse().unwrap();
+        let raw_info = RawBuildInfo::new(&inputs[0], &output, &v).unwrap();
+        let _info: BuildInfo = serde_json::from_str(&raw_info.build_info).unwrap();
+    }
+}
diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs
index 1476ecd0..070b2b2a 100644
--- a/ethers-solc/src/compile/mod.rs
+++ b/ethers-solc/src/compile/mod.rs
@@ -821,7 +821,7 @@ mod tests {
             // update this test whenever there's a new sol
             // version. that's ok! good reminder to check the
             // patch notes.
-            (">=0.5.0", "0.8.14"),
+            (">=0.5.0", "0.8.15"),
             // range
             (">=0.4.0 <0.5.0", "0.4.26"),
         ]
diff --git a/ethers-solc/src/compile/output/mod.rs b/ethers-solc/src/compile/output/mod.rs
index 508ffb0e..cce70338 100644
--- a/ethers-solc/src/compile/output/mod.rs
+++ b/ethers-solc/src/compile/output/mod.rs
@@ -5,12 +5,14 @@ use crate::{
         contract::{CompactContractBytecode, CompactContractRef, Contract},
         Error,
     },
+    buildinfo::RawBuildInfo,
     sources::{VersionedSourceFile, VersionedSourceFiles},
-    ArtifactId, ArtifactOutput, Artifacts, CompilerOutput, ConfigurableArtifacts,
+    ArtifactId, ArtifactOutput, Artifacts, CompilerOutput, ConfigurableArtifacts, SolcIoError,
 };
 use contracts::{VersionedContract, VersionedContracts};
 use semver::Version;
 use std::{collections::BTreeMap, fmt, path::Path};
+use tracing::trace;
 
 pub mod contracts;
 pub mod sources;
@@ -243,6 +245,8 @@ pub struct AggregatedCompilerOutput {
     pub sources: VersionedSourceFiles,
     /// All compiled contracts combined with the solc version used to compile them
     pub contracts: VersionedContracts,
+    // All the `BuildInfo`s of solc invocations.
+    pub build_infos: BTreeMap<Version, RawBuildInfo>,
 }
 
 impl AggregatedCompilerOutput {
@@ -302,6 +306,29 @@ impl AggregatedCompilerOutput {
         }
     }
 
+    /// Creates all `BuildInfo` files in the given `build_info_dir`
+    ///
+    /// There can be multiple `BuildInfo`, since we support multiple versions.
+    ///
+    /// The created files have the md5 hash `{_format,solcVersion,solcLongVersion,input}` as their
+    /// file name
+    pub fn write_build_infos(&self, build_info_dir: impl AsRef<Path>) -> Result<(), SolcIoError> {
+        if self.build_infos.is_empty() {
+            return Ok(())
+        }
+        let build_info_dir = build_info_dir.as_ref();
+        std::fs::create_dir_all(build_info_dir)
+            .map_err(|err| SolcIoError::new(err, build_info_dir))?;
+        for (version, build_info) in &self.build_infos {
+            trace!("writing build info file for solc {}", version);
+            let file_name = format!("{}.json", build_info.id);
+            let file = build_info_dir.join(file_name);
+            std::fs::write(&file, &build_info.build_info)
+                .map_err(|err| SolcIoError::new(err, file))?;
+        }
+        Ok(())
+    }
+
     /// Finds the _first_ contract with the given name
     ///
     /// # Example
diff --git a/ethers-solc/src/compile/project.rs b/ethers-solc/src/compile/project.rs
index 11273f99..5c6e8769 100644
--- a/ethers-solc/src/compile/project.rs
+++ b/ethers-solc/src/compile/project.rs
@@ -104,6 +104,7 @@
 use crate::{
     artifact_output::Artifacts,
     artifacts::{Settings, VersionedFilteredSources, VersionedSources},
+    buildinfo::RawBuildInfo,
     cache::ArtifactsCache,
     error::Result,
     filter::SparseOutputFilter,
@@ -240,11 +241,13 @@ impl<'a, T: ArtifactOutput> PreprocessedState<'a, T> {
     /// advance to the next state by compiling all sources
     fn compile(self) -> Result<CompiledState<'a, T>> {
         let PreprocessedState { sources, cache, sparse_output } = self;
+        let project = cache.project();
         let mut output = sources.compile(
-            &cache.project().solc_config.settings,
-            &cache.project().paths,
+            &project.solc_config.settings,
+            &project.paths,
             sparse_output,
             cache.graph(),
+            project.build_info,
         )?;
 
         // source paths get stripped before handing them over to solc, so solc never uses absolute
@@ -288,11 +291,16 @@ impl<'a, T: ArtifactOutput> CompiledState<'a, T> {
                 output.sources.len()
             );
             // this emits the artifacts via the project's artifacts handler
-            project.artifacts_handler().on_output(
+            let artifacts = project.artifacts_handler().on_output(
                 &output.contracts,
                 &output.sources,
                 &project.paths,
-            )?
+            )?;
+
+            // emits all the build infos, if they exist
+            output.write_build_infos(project.build_info_path())?;
+
+            artifacts
         };
 
         Ok(ArtifactsState { output, cache, compiled_artifacts })
@@ -391,13 +399,14 @@ impl FilteredCompilerSources {
         paths: &ProjectPathsConfig,
         sparse_output: SparseOutputFilter,
         graph: &GraphEdges,
+        create_build_info: bool,
     ) -> Result<AggregatedCompilerOutput> {
         match self {
             FilteredCompilerSources::Sequential(input) => {
-                compile_sequential(input, settings, paths, sparse_output, graph)
+                compile_sequential(input, settings, paths, sparse_output, graph, create_build_info)
             }
             FilteredCompilerSources::Parallel(input, j) => {
-                compile_parallel(input, j, settings, paths, sparse_output, graph)
+                compile_parallel(input, j, settings, paths, sparse_output, graph, create_build_info)
             }
         }
     }
@@ -419,6 +428,7 @@ fn compile_sequential(
     paths: &ProjectPathsConfig,
     sparse_output: SparseOutputFilter,
     graph: &GraphEdges,
+    create_build_info: bool,
 ) -> Result<AggregatedCompilerOutput> {
     let mut aggregated = AggregatedCompilerOutput::default();
     tracing::trace!("compiling {} jobs sequentially", input.len());
@@ -484,6 +494,13 @@ fn compile_sequential(
             report::solc_success(&solc, &version, &output, &start.elapsed());
             tracing::trace!("compiled input, output has error: {}", output.has_error());
             tracing::trace!("received compiler output: {:?}", output.contracts.keys());
+
+            // if configured also create the build info
+            if create_build_info {
+                let build_info = RawBuildInfo::new(&input, &output, &version)?;
+                aggregated.build_infos.insert(version.clone(), build_info);
+            }
+
             aggregated.extend(version.clone(), output);
         }
     }
@@ -498,6 +515,7 @@ fn compile_parallel(
     paths: &ProjectPathsConfig,
     sparse_output: SparseOutputFilter,
     graph: &GraphEdges,
+    create_build_info: bool,
 ) -> Result<AggregatedCompilerOutput> {
     debug_assert!(num_jobs > 1);
     tracing::trace!(
@@ -580,14 +598,21 @@ fn compile_parallel(
                 report::solc_spawn(&solc, &version, &input, &actually_dirty);
                 solc.compile(&input).map(move |output| {
                     report::solc_success(&solc, &version, &output, &start.elapsed());
-                    (version, output)
+                    (version, input, output)
                 })
             })
             .collect::<Result<Vec<_>>>()
     })?;
 
     let mut aggregated = AggregatedCompilerOutput::default();
-    aggregated.extend_all(outputs);
+    for (version, input, output) in outputs {
+        // if configured also create the build info
+        if create_build_info {
+            let build_info = RawBuildInfo::new(&input, &output, &version)?;
+            aggregated.build_infos.insert(version.clone(), build_info);
+        }
+        aggregated.extend(version, output);
+    }
 
     Ok(aggregated)
 }
diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs
index 58e4dd8a..e51cb441 100644
--- a/ethers-solc/src/lib.rs
+++ b/ethers-solc/src/lib.rs
@@ -6,6 +6,7 @@ pub use artifacts::{CompilerInput, CompilerOutput, EvmVersion};
 use std::collections::{BTreeMap, HashSet};
 
 mod artifact_output;
+pub mod buildinfo;
 pub mod cache;
 pub mod hh;
 pub use artifact_output::*;
@@ -59,6 +60,8 @@ pub struct Project<T: ArtifactOutput = ConfigurableArtifacts> {
     pub solc_config: SolcConfig,
     /// Whether caching is enabled
     pub cached: bool,
+    /// Whether to output build information with each solc call.
+    pub build_info: bool,
     /// Whether writing artifacts to disk is enabled
     pub no_artifacts: bool,
     /// Whether writing artifacts to disk is enabled
@@ -121,6 +124,11 @@ impl<T: ArtifactOutput> Project<T> {
         &self.paths.cache
     }
 
+    /// Returns the path to the `build-info` directory nested in the artifacts dir
+    pub fn build_info_path(&self) -> PathBuf {
+        self.paths.artifacts.join("build-info")
+    }
+
     /// Returns the root directory of the project
     pub fn root(&self) -> &PathBuf {
         &self.paths.root
@@ -513,6 +521,8 @@ pub struct ProjectBuilder<T: ArtifactOutput = ConfigurableArtifacts> {
     solc_config: Option<SolcConfig>,
     /// Whether caching is enabled, default is true.
     cached: bool,
+    /// Whether to output build information with each solc call.
+    build_info: bool,
     /// Whether writing artifacts to disk is enabled, default is true.
     no_artifacts: bool,
     /// Whether automatic solc version detection is enabled
@@ -536,6 +546,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
             solc: None,
             solc_config: None,
             cached: true,
+            build_info: false,
             no_artifacts: false,
             auto_detect: true,
             offline: false,
@@ -591,6 +602,13 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
         self
     }
 
+    /// Sets the build info value
+    #[must_use]
+    pub fn set_build_info(mut self, build_info: bool) -> Self {
+        self.build_info = build_info;
+        self
+    }
+
     /// Activates offline mode
     ///
     /// Prevents network possible access to download/check solc installs
@@ -663,6 +681,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
             allowed_paths,
             solc_jobs,
             offline,
+            build_info,
             ..
         } = self;
         ProjectBuilder {
@@ -677,6 +696,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
             ignored_error_codes,
             allowed_paths,
             solc_jobs,
+            build_info,
         }
     }
 
@@ -713,6 +733,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
             mut allowed_paths,
             solc_jobs,
             offline,
+            build_info,
         } = self;
 
         let paths = paths.map(Ok).unwrap_or_else(ProjectPathsConfig::current_hardhat)?;
@@ -730,6 +751,7 @@ impl<T: ArtifactOutput> ProjectBuilder<T> {
             solc,
             solc_config,
             cached,
+            build_info,
             no_artifacts,
             auto_detect,
             artifacts,
diff --git a/examples/ethers-wasm/Cargo.toml b/examples/ethers-wasm/Cargo.toml
index c43c503f..878eb743 100644
--- a/examples/ethers-wasm/Cargo.toml
+++ b/examples/ethers-wasm/Cargo.toml
@@ -21,9 +21,9 @@ default = ["console_error_panic_hook"]
 [dependencies]
 ethers = { path = "../..", version = "0.13.0", features = ["abigen", "legacy", "ws"] }
 serde_derive = "1.0.137"
-wasm-bindgen-futures = "0.4.30"
+wasm-bindgen-futures = "0.4.31"
 serde_json = "1.0.64"
-wasm-bindgen = { version = "0.2.80", features = ["serde-serialize"] }
+wasm-bindgen = { version = "0.2.81", features = ["serde-serialize"] }
 
 # The `console_error_panic_hook` crate provides better debugging of panics by
 # logging them with `console.error`. This is great for development, but requires
@@ -40,7 +40,7 @@ wee_alloc = { version = "0.4.5", optional = true }
 
 serde = { version = "1.0.126", features = ["derive"] }
 hex = "0.4.3"
-web-sys = "0.3.57"
+web-sys = "0.3.58"
 
 [dev-dependencies]
 wasm-bindgen-test = "0.3.30"


Test patch:
diff --git a/ethers-middleware/tests/gas_oracle.rs b/ethers-middleware/tests/gas_oracle.rs
index f8fe1f89..4e4270e8 100644
--- a/ethers-middleware/tests/gas_oracle.rs
+++ b/ethers-middleware/tests/gas_oracle.rs
@@ -38,8 +38,8 @@ async fn using_gas_oracle() {
     // connect to the network
     let provider = Provider::<Http>::try_from(anvil.endpoint()).unwrap();
 
-    // this is set because anvil now sets 875000000 as the first block's base fee
-    let base_fee = 875000000;
+    // 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();
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index 30309cc5..bb4c9ff8 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -13,6 +13,7 @@ use ethers_solc::{
         BytecodeHash, DevDoc, ErrorDoc, EventDoc, Libraries, MethodDoc, ModelCheckerEngine::CHC,
         ModelCheckerSettings, UserDoc, UserDocNotice,
     },
+    buildinfo::BuildInfo,
     cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
     project_util::*,
     remappings::Remapping,
@@ -311,6 +312,45 @@ fn can_compile_dapp_detect_changes_in_sources() {
     }
 }
 
+#[test]
+fn can_emit_build_info() {
+    let mut project = TempProject::dapptools().unwrap();
+    project.project_mut().build_info = true;
+    project
+        .add_source(
+            "A",
+            r#"
+pragma solidity ^0.8.10;
+import "./B.sol";
+contract A { }
+"#,
+        )
+        .unwrap();
+
+    project
+        .add_source(
+            "B",
+            r#"
+pragma solidity ^0.8.10;
+contract B { }
+"#,
+        )
+        .unwrap();
+
+    let compiled = project.compile().unwrap();
+    assert!(!compiled.has_compiler_errors());
+
+    let info_dir = project.project().build_info_path();
+    assert!(info_dir.exists());
+
+    let mut build_info_count = 0;
+    for entry in fs::read_dir(info_dir).unwrap() {
+        let _info = BuildInfo::read(entry.unwrap().path()).unwrap();
+        build_info_count += 1;
+    }
+    assert_eq!(build_info_count, 1);
+}
+
 #[test]
 fn can_compile_dapp_sample_with_cache() {
     let tmp_dir = tempfile::tempdir().unwrap();