Repository: gakonst/ethers-rs
Pull request #1813 (2022-10-28 15:19:55)

ci: add doc CI
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation

<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

ref https://github.com/gakonst/ethers-rs/pull/1808#issuecomment-1291091964

blocked by #1808 for docs CI, #1812 for lints CI

## Solution

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

-   [ ] Added Tests
-   [ ] Added Documentation
-   [ ] Updated the changelog



Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index bb2a143a..91d0133d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -20,7 +20,7 @@ jobs:
         runs-on: ubuntu-latest
         steps:
             - name: Checkout sources
-              uses: actions/checkout@v2
+              uses: actions/checkout@v3
             - name: Install Anvil
               uses: foundry-rs/foundry-toolchain@v1
               with:
@@ -63,7 +63,7 @@ jobs:
         runs-on: ubuntu-latest
         steps:
             - name: Checkout sources
-              uses: actions/checkout@v2
+              uses: actions/checkout@v3
             # TODO: can we combine these shared steps in github actions?
             - name: Install Anvil
               uses: foundry-rs/foundry-toolchain@v1
@@ -107,7 +107,7 @@ jobs:
         runs-on: ubuntu-latest
         steps:
             - name: Checkout sources
-              uses: actions/checkout@v2
+              uses: actions/checkout@v3
             - name: Install toolchain
               uses: actions-rs/toolchain@v1
               with:
@@ -123,13 +123,33 @@ jobs:
             - name: cargo clippy
               run: cargo +nightly clippy --all-features -- -D warnings
 
+    docs:
+        name: docs
+        runs-on: ubuntu-latest
+        steps:
+            - name: Checkout sources
+              uses: actions/checkout@v3
+            - name: Install toolchain
+              uses: actions-rs/toolchain@v1
+              with:
+                  toolchain: nightly
+                  override: true
+            - uses: Swatinem/rust-cache@v1
+              with:
+                  cache-on-failure: true
+            - name: cargo doc
+              run: cargo +nightly doc --lib --all-features
+              env:
+                  RUSTFLAGS: --cfg docsrs
+                  RUSTDOCFLAGS: --cfg docsrs -Dwarnings
+
     wasm:
         name: WASM
         runs-on: ubuntu-latest
 
         steps:
             - name: Checkout sources
-              uses: actions/checkout@v2
+              uses: actions/checkout@v3
             - name: Install rust
               uses: actions-rs/toolchain@v1
               with:
@@ -176,7 +196,7 @@ jobs:
         runs-on: ubuntu-latest
         steps:
             - name: Checkout sources
-              uses: actions/checkout@v2
+              uses: actions/checkout@v3
 
             - name: Install geth (for state overrides example)
               run: |
@@ -261,7 +281,7 @@ jobs:
 
         steps:
             - name: checkout
-              uses: actions/checkout@v2
+              uses: actions/checkout@v3
 
               # Run build
             - name: Install Rustup using win.rustup.rs
diff --git a/CHANGELOG.md b/CHANGELOG.md
index fffa6f22..06169b9e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,8 @@
 
 ### Unreleased
 
+-   Remove rust_decimals dependency for ethers-core
+-   Add support for numbers greater than 2^96 for `ethers_core::utils::parse_units` [#1822](https://github.com/gakonst/ethers-rs/issues/1822)
 -   Add comment about safety of u8 -> u64 cast in `ethers_core::types::Signature`
 -   Stop defaulting to the `"latest"` block in `eth_estimateGas` params [#1657](https://github.com/gakonst/ethers-rs/pull/1657)
 -   Fix geth trace types for debug_traceTransaction rpc
@@ -91,6 +93,7 @@
 -   [#1632](https://github.com/gakonst/ethers-rs/pull/1632) Re-export `H32` from `ethabi`.
 -   [#1634](https://github.com/gakonst/ethers-rs/pull/1634) Derive missing `Clone`, `Copy` and `Debug` impls in ethers-etherscan.
 -   Bytes debug format now displays hex literals [#1658](https://github.com/gakonst/ethers-rs/pull/1658)
+-   Fix `get_logs_paginated` trying to fetch beyond the latest block
 
 ## ethers-contract-abigen
 
diff --git a/Cargo.lock b/Cargo.lock
index 83356b62..6d26c424 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -186,9 +186,9 @@ checksum = "3441f0f7b02788e948e47f457ca01f1d7e6d92c693bc132c22b087d3141c03ff"
 
 [[package]]
 name = "base64"
-version = "0.13.0"
+version = "0.13.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
 
 [[package]]
 name = "base64ct"
@@ -1316,7 +1316,6 @@ dependencies = [
  "rand 0.8.5",
  "rlp",
  "rlp-derive",
- "rust_decimal",
  "serde",
  "serde_json",
  "strum",
@@ -1393,7 +1392,7 @@ version = "1.0.0"
 dependencies = [
  "async-trait",
  "auto_impl 1.0.1",
- "base64 0.13.0",
+ "base64 0.13.1",
  "bytes",
  "ethers-core",
  "futures-channel",
@@ -2388,9 +2387,9 @@ dependencies = [
 
 [[package]]
 name = "num_cpus"
-version = "1.13.1"
+version = "1.14.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5"
 dependencies = [
  "hermit-abi",
  "libc",
@@ -3095,7 +3094,7 @@ version = "0.11.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "431949c384f4e2ae07605ccaa56d1d9d2ecdb5cadd4f9577ccfab29f2e5149fc"
 dependencies = [
- "base64 0.13.0",
+ "base64 0.13.1",
  "bytes",
  "encoding_rs",
  "futures-core",
@@ -3168,9 +3167,9 @@ dependencies = [
 
 [[package]]
 name = "rlp"
-version = "0.5.1"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5"
+checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec"
 dependencies = [
  "bytes",
  "rustc-hex",
@@ -3204,7 +3203,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1db30db44ea73551326269adcf7a2169428a054f14faf9e1768f2163494f2fa2"
 dependencies = [
  "async-trait",
- "base64 0.13.0",
+ "base64 0.13.1",
  "bytes",
  "crc32fast",
  "futures",
@@ -3260,7 +3259,7 @@ version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a5ae95491c8b4847931e291b151127eccd6ff8ca13f33603eb3d0035ecb05272"
 dependencies = [
- "base64 0.13.0",
+ "base64 0.13.1",
  "bytes",
  "chrono",
  "digest 0.9.0",
@@ -3280,17 +3279,6 @@ dependencies = [
  "tokio",
 ]
 
-[[package]]
-name = "rust_decimal"
-version = "1.26.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee9164faf726e4f3ece4978b25ca877ddc6802fa77f38cdccb32c7f805ecd70c"
-dependencies = [
- "arrayvec 0.7.2",
- "num-traits",
- "serde",
-]
-
 [[package]]
 name = "rustc-hex"
 version = "2.1.0"
@@ -3324,7 +3312,7 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9"
 dependencies = [
- "base64 0.13.0",
+ "base64 0.13.1",
 ]
 
 [[package]]
@@ -4224,7 +4212,7 @@ version = "0.17.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e27992fd6a8c29ee7eef28fc78349aa244134e10ad447ce3b9f0ac0ed0fa4ce0"
 dependencies = [
- "base64 0.13.0",
+ "base64 0.13.1",
  "byteorder",
  "bytes",
  "http",
diff --git a/README.md b/README.md
index 7171cf9c..a13e15e0 100644
--- a/README.md
+++ b/README.md
@@ -175,7 +175,7 @@ issues itself: `cargo +nightly clippy --fix -Z unstable-options`
 
 ## Related Projects
 
-This library would not have been possibly without the great work done in:
+This library would not have been possible without the great work done in:
 
 -   [`ethers.js`](https://github.com/ethers-io/ethers.js/)
 -   [`rust-web3`](https://github.com/tomusdrw/rust-web3/)
diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs
index d8b05b92..1d3bf00f 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract.rs
@@ -337,7 +337,7 @@ where
     if not_aliased.len() > 1 {
         let mut overloaded_aliases = Vec::new();
         for (idx, (sig, name)) in not_aliased.into_iter().enumerate() {
-            let unique_name = format!("{}{}", name, idx + 1);
+            let unique_name = format!("{name}{}", idx + 1);
             overloaded_aliases.push((sig, get_ident(&unique_name)));
         }
         aliases.extend(overloaded_aliases);
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
index bd9e1bfe..5868f19d 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
@@ -64,7 +64,7 @@ where
 }
 
 pub(crate) fn imports(name: &str) -> TokenStream {
-    let doc = util::expand_doc(&format!("{} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs", name));
+    let doc = util::expand_doc(&format!("{name} was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs"));
 
     let ethers_core = ethers_core_crate();
     let ethers_providers = ethers_providers_crate();
@@ -116,7 +116,7 @@ pub(crate) fn struct_declaration(cx: &Context) -> TokenStream {
 
     let bytecode = if let Some(ref bytecode) = cx.contract_bytecode {
         let bytecode_name = cx.inline_bytecode_ident();
-        let hex_bytecode = format!("{}", bytecode);
+        let hex_bytecode = format!("{bytecode}");
         quote! {
             /// Bytecode of the #name contract
             pub static #bytecode_name: #ethers_contract::Lazy<#ethers_core::types::Bytes> = #ethers_contract::Lazy::new(|| #hex_bytecode.parse()
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index 69f59ce9..cfda0b00 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -34,7 +34,7 @@ impl Context {
             .map(|function| {
                 let signature = function.abi_signature();
                 self.expand_function(function, aliases.get(&signature).cloned())
-                    .with_context(|| format!("error expanding function '{}'", signature))
+                    .with_context(|| format!("error expanding function '{signature}'"))
             })
             .collect::<Result<Vec<_>>>()?;
 
@@ -593,7 +593,7 @@ impl Context {
                             name_conflicts(*idx, &diffs)
                         {
                             needs_alias_for_first_fun_using_idx = true;
-                            format!("{}{}", overloaded_fun.name.to_snake_case(), idx)
+                            format!("{}{idx}", overloaded_fun.name.to_snake_case())
                         } else {
                             format!(
                                 "{}_with_{}",
@@ -608,7 +608,7 @@ impl Context {
                             name_conflicts(*idx, &diffs)
                         {
                             needs_alias_for_first_fun_using_idx = true;
-                            format!("{}{}", overloaded_fun.name.to_snake_case(), idx)
+                            format!("{}{idx}", overloaded_fun.name.to_snake_case())
                         } else {
                             // 1 + n additional input params
                             let and = diff
@@ -632,7 +632,7 @@ impl Context {
 
             if needs_alias_for_first_fun_using_idx {
                 // insert an alias for the root duplicated call
-                let prev_alias = format!("{}{}", first_fun.name.to_snake_case(), first_fun_idx);
+                let prev_alias = format!("{}{first_fun_idx}", first_fun.name.to_snake_case());
 
                 let alias = MethodAlias::new(&prev_alias);
 
@@ -698,9 +698,9 @@ fn expand_struct_name_postfix(
     postfix: &str,
 ) -> Ident {
     let name = if let Some(alias) = alias {
-        format!("{}{}", alias.struct_name, postfix)
+        format!("{}{postfix}", alias.struct_name)
     } else {
-        format!("{}{}", util::safe_pascal_case(&function.name), postfix)
+        format!("{}{postfix}", util::safe_pascal_case(&function.name))
     };
     util::ident(&name)
 }
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
index 850f65e8..0f6ef3a1 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
@@ -129,9 +129,9 @@ impl Context {
             "".to_string()
         };
 
-        let abi_signature = format!("{}({})", name, sig,);
+        let abi_signature = format!("{name}({sig})",);
 
-        let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
+        let abi_signature_doc = util::expand_doc(&format!("`{abi_signature}`"));
 
         // use the same derives as for events
         let derives = util::expand_derives(&self.event_derives);
@@ -184,7 +184,7 @@ impl Context {
             param_types.iter().map(|kind| kind.to_string()).collect::<Vec<_>>().join(","),
         );
 
-        let abi_signature_doc = util::expand_doc(&format!("`{}`", abi_signature));
+        let abi_signature_doc = util::expand_doc(&format!("`{abi_signature}`"));
 
         let name = util::ident(name);
 
@@ -225,7 +225,7 @@ pub struct InternalStructs {
     /// from ethabi.
     pub(crate) function_params: HashMap<(String, String), String>,
 
-    /// (function name) -> Vec<structs> all structs the function returns
+    /// (function name) -> `Vec<structs>` all structs the function returns
     pub(crate) outputs: HashMap<String, Vec<String>>,
 
     /// (event name, idx) -> struct which are the identifying properties we get the name
@@ -392,12 +392,12 @@ fn insert_rust_type_name(
         let mut other_name = name.clone();
         // name collision `A.name` `B.name`, rename to `AName`, `BName`
         if !other_projections.is_empty() {
-            other_name = format!("{}{}", other_projections.remove(0).to_pascal_case(), other_name);
+            other_name = format!("{}{other_name}", other_projections.remove(0).to_pascal_case());
         }
         insert_rust_type_name(type_names, other_name, other_projections, other_id);
 
         if !projections.is_empty() {
-            name = format!("{}{}", projections.remove(0).to_pascal_case(), name);
+            name = format!("{}{name}", projections.remove(0).to_pascal_case());
         }
         insert_rust_type_name(type_names, name, projections, id);
     } else {
diff --git a/ethers-contract/ethers-contract-abigen/src/filter.rs b/ethers-contract/ethers-contract-abigen/src/filter.rs
index c5023d7e..d071ac87 100644
--- a/ethers-contract/ethers-contract-abigen/src/filter.rs
+++ b/ethers-contract/ethers-contract-abigen/src/filter.rs
@@ -1,4 +1,4 @@
-//! Filtering support for contracts used in [`Abigen`]
+//! Filtering support for contracts used in [`Abigen`][crate::Abigen]
 
 use regex::Regex;
 use std::collections::HashSet;
diff --git a/ethers-contract/ethers-contract-abigen/src/multi.rs b/ethers-contract/ethers-contract-abigen/src/multi.rs
index 9c687eba..b09a7019 100644
--- a/ethers-contract/ethers-contract-abigen/src/multi.rs
+++ b/ethers-contract/ethers-contract-abigen/src/multi.rs
@@ -99,6 +99,7 @@ impl MultiAbigen {
     ///    let gen = MultiAbigen::from_json_files("./abi").unwrap().with_filter(
     ///        SelectContracts::default().add_name("MyContract").add_name("MyOtherContract"),
     ///    );
+    /// # }
     /// ```
     ///
     /// Exclude all contracts that end with test
@@ -109,6 +110,7 @@ impl MultiAbigen {
     ///    let gen = MultiAbigen::from_json_files("./abi").unwrap().with_filter(
     ///        ExcludeContracts::default().add_pattern(".*Test"),
     ///    );
+    /// # }
     /// ```
     #[must_use]
     pub fn with_filter(mut self, filter: impl Into<ContractFilter>) -> Self {
@@ -598,8 +600,8 @@ ethers = {{ git = "https://github.com/gakonst/ethers-rs", default-features = fal
             mod_names.insert(shared.name.to_snake_case());
         }
 
-        for module in mod_names.into_iter().map(|name| format!("pub mod {};", name)) {
-            writeln!(buf, "{}", module)?;
+        for module in mod_names.into_iter().map(|name| format!("pub mod {name};")) {
+            writeln!(buf, "{module}")?;
         }
 
         Ok(())
diff --git a/ethers-contract/ethers-contract-abigen/src/source.rs b/ethers-contract/ethers-contract-abigen/src/source.rs
index 346a4b94..751c1e41 100644
--- a/ethers-contract/ethers-contract-abigen/src/source.rs
+++ b/ethers-contract/ethers-contract-abigen/src/source.rs
@@ -248,7 +248,7 @@ fn get_local_contract(path: impl AsRef<str>) -> Result<String> {
 #[cfg(not(target_arch = "wasm32"))]
 fn get_http_contract(url: &Url) -> Result<String> {
     let json = util::http_get(url.as_str())
-        .with_context(|| format!("failed to retrieve JSON from {}", url))?;
+        .with_context(|| format!("failed to retrieve JSON from {url}"))?;
     Ok(json)
 }
 
@@ -266,15 +266,14 @@ fn get_etherscan_contract(address: Address, domain: &str) -> Result<String> {
             "snowtrace.io" => env::var("SNOWTRACE_API_KEY").ok(),
             _ => None,
         };
-        key_res.map(|key| format!("&apikey={}", key)).unwrap_or_default()
+        key_res.map(|key| format!("&apikey={key}")).unwrap_or_default()
     };
 
     let abi_url = format!(
         "http://api.{}/api?module=contract&action=getabi&address={:?}&format=raw{}",
         domain, address, api_key,
     );
-    let abi =
-        util::http_get(&abi_url).context(format!("failed to retrieve ABI from {}", domain))?;
+    let abi = util::http_get(&abi_url).context(format!("failed to retrieve ABI from {domain}"))?;
 
     if abi.starts_with("Contract source code not verified") {
         eyre::bail!("Contract source code not verified: {:?}", address);
@@ -292,9 +291,9 @@ fn get_etherscan_contract(address: Address, domain: &str) -> Result<String> {
 /// Retrieves a Truffle artifact or ABI from an npm package through `unpkg.io`.
 #[cfg(not(target_arch = "wasm32"))]
 fn get_npm_contract(package: &str) -> Result<String> {
-    let unpkg_url = format!("https://unpkg.io/{}", package);
+    let unpkg_url = format!("https://unpkg.io/{package}");
     let json = util::http_get(&unpkg_url)
-        .with_context(|| format!("failed to retrieve JSON from for npm package {}", package))?;
+        .with_context(|| format!("failed to retrieve JSON from for npm package {package}"))?;
 
     Ok(json)
 }
diff --git a/ethers-contract/ethers-contract-abigen/src/util.rs b/ethers-contract/ethers-contract-abigen/src/util.rs
index 5a2f775b..74105744 100644
--- a/ethers-contract/ethers-contract-abigen/src/util.rs
+++ b/ethers-contract/ethers-contract-abigen/src/util.rs
@@ -19,7 +19,7 @@ pub fn ident(name: &str) -> Ident {
 ///
 /// Parsing keywords like `self` can fail, in this case we add an underscore.
 pub fn safe_ident(name: &str) -> Ident {
-    syn::parse_str::<SynIdent>(name).unwrap_or_else(|_| ident(&format!("{}_", name)))
+    syn::parse_str::<SynIdent>(name).unwrap_or_else(|_| ident(&format!("{name}_")))
 }
 
 ///  Converts a `&str` to `snake_case` `String` while respecting identifier rules
@@ -35,7 +35,7 @@ pub fn safe_pascal_case(ident: &str) -> String {
 /// respects identifier rules, such as, an identifier must not start with a numeric char
 fn safe_identifier_name(name: String) -> String {
     if name.starts_with(|c: char| c.is_numeric()) {
-        format!("_{}", name)
+        format!("_{name}")
     } else {
         name
     }
@@ -76,7 +76,7 @@ pub fn preserve_underscore_delim(ident: &str, alias: &str) -> String {
 /// identifiers that are reserved keywords get `_` appended to them.
 pub fn expand_input_name(index: usize, name: &str) -> TokenStream {
     let name_str = match name {
-        "" => format!("p{}", index),
+        "" => format!("p{index}"),
         n => n.to_snake_case(),
     };
     let name = safe_ident(&name_str);
diff --git a/ethers-contract/ethers-contract-derive/src/abigen.rs b/ethers-contract/ethers-contract-derive/src/abigen.rs
index a91334ed..8c803dbe 100644
--- a/ethers-contract/ethers-contract-derive/src/abigen.rs
+++ b/ethers-contract/ethers-contract-derive/src/abigen.rs
@@ -184,7 +184,7 @@ impl Parse for Parameter {
             _ => {
                 return Err(ParseError::new(
                     name.span(),
-                    format!("unexpected named parameter `{}`", name),
+                    format!("unexpected named parameter `{name}`"),
                 ))
             }
         };
diff --git a/ethers-contract/ethers-contract-derive/src/call.rs b/ethers-contract/ethers-contract-derive/src/call.rs
index cf8c244e..ac89afb8 100644
--- a/ethers-contract/ethers-contract-derive/src/call.rs
+++ b/ethers-contract/ethers-contract-derive/src/call.rs
@@ -33,7 +33,7 @@ pub(crate) fn derive_eth_call_impl(input: DeriveInput) -> TokenStream {
             ) {
                 Ok(derived) => derived,
                 Err(err) => {
-                    Error::new(span, format!("Unable to determine ABI for `{}` : {}", src, err))
+                    Error::new(span, format!("Unable to determine ABI for `{src}` : {err}"))
                         .to_compile_error()
                 }
             }
diff --git a/ethers-contract/ethers-contract-derive/src/calllike.rs b/ethers-contract/ethers-contract-derive/src/calllike.rs
index 21743758..4f54057f 100644
--- a/ethers-contract/ethers-contract-derive/src/calllike.rs
+++ b/ethers-contract/ethers-contract-derive/src/calllike.rs
@@ -32,14 +32,14 @@ pub fn parse_calllike_attributes(
                                 Meta::Path(path) => {
                                     return Err(Error::new(
                                         path.span(),
-                                        format!("unrecognized {} parameter", attr_name),
+                                        format!("unrecognized {attr_name} parameter"),
                                     )
                                     .to_compile_error())
                                 }
                                 Meta::List(meta) => {
                                     return Err(Error::new(
                                         meta.path.span(),
-                                        format!("unrecognized {} parameter", attr_name),
+                                        format!("unrecognized {attr_name} parameter"),
                                     )
                                     .to_compile_error())
                                 }
@@ -85,7 +85,7 @@ pub fn parse_calllike_attributes(
                                     } else {
                                         return Err(Error::new(
                                             meta.span(),
-                                            format!("unrecognized {} parameter", attr_name),
+                                            format!("unrecognized {attr_name} parameter"),
                                         )
                                         .to_compile_error())
                                     }
diff --git a/ethers-contract/ethers-contract-derive/src/error.rs b/ethers-contract/ethers-contract-derive/src/error.rs
index c0914dc1..74ac968b 100644
--- a/ethers-contract/ethers-contract-derive/src/error.rs
+++ b/ethers-contract/ethers-contract-derive/src/error.rs
@@ -32,7 +32,7 @@ pub(crate) fn derive_eth_error_impl(input: DeriveInput) -> TokenStream {
             ) {
                 Ok(derived) => derived,
                 Err(err) => {
-                    Error::new(span, format!("Unable to determine ABI for `{}` : {}", src, err))
+                    Error::new(span, format!("Unable to determine ABI for `{src}` : {err}"))
                         .to_compile_error()
                 }
             }
diff --git a/ethers-contract/ethers-contract-derive/src/utils.rs b/ethers-contract/ethers-contract-derive/src/utils.rs
index b8691552..f1b1fd38 100644
--- a/ethers-contract/ethers-contract-derive/src/utils.rs
+++ b/ethers-contract/ethers-contract-derive/src/utils.rs
@@ -168,20 +168,20 @@ pub fn derive_abi_inputs_from_fields(
             Fields::Unit => {
                 return Err(Error::new(
                     input.span(),
-                    format!("{} cannot be derived for empty structs and unit", trait_name),
+                    format!("{trait_name} cannot be derived for empty structs and unit"),
                 ))
             }
         },
         Data::Enum(_) => {
             return Err(Error::new(
                 input.span(),
-                format!("{} cannot be derived for enums", trait_name),
+                format!("{trait_name} cannot be derived for enums"),
             ))
         }
         Data::Union(_) => {
             return Err(Error::new(
                 input.span(),
-                format!("{} cannot be derived for unions", trait_name),
+                format!("{trait_name} cannot be derived for unions"),
             ))
         }
     };
@@ -258,20 +258,20 @@ pub fn derive_abi_parameters_array(
             Fields::Unit => {
                 return Err(Error::new(
                     input.span(),
-                    format!("{} cannot be derived for empty structs and unit", trait_name),
+                    format!("{trait_name} cannot be derived for empty structs and unit"),
                 ))
             }
         },
         Data::Enum(_) => {
             return Err(Error::new(
                 input.span(),
-                format!("{} cannot be derived for enums", trait_name),
+                format!("{trait_name} cannot be derived for enums"),
             ))
         }
         Data::Union(_) => {
             return Err(Error::new(
                 input.span(),
-                format!("{} cannot be derived for unions", trait_name),
+                format!("{trait_name} cannot be derived for unions"),
             ))
         }
     };
diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs
index 1f753414..c9a8ab63 100644
--- a/ethers-contract/src/call.rs
+++ b/ethers-contract/src/call.rs
@@ -14,7 +14,14 @@ use ethers_providers::{
     Middleware, PendingTransaction, ProviderError,
 };
 
-use std::{borrow::Cow, fmt::Debug, future::Future, marker::PhantomData, sync::Arc};
+use std::{
+    borrow::Cow,
+    fmt::Debug,
+    future::{Future, IntoFuture},
+    marker::PhantomData,
+    pin::Pin,
+    sync::Arc,
+};
 
 use thiserror::Error as ThisError;
 
@@ -211,3 +218,19 @@ where
             .map_err(ContractError::MiddlewareError)
     }
 }
+
+/// [`ContractCall`] can be turned into [`Future`] automatically with `.await`.
+/// Defaults to calling [`ContractCall::call`].
+impl<M, D> IntoFuture for ContractCall<M, D>
+where
+    Self: 'static,
+    M: Middleware,
+    D: Detokenize,
+{
+    type Output = Result<D, ContractError<M>>;
+    type IntoFuture = Pin<Box<dyn Future<Output = Self::Output>>>;
+
+    fn into_future(self) -> Self::IntoFuture {
+        Box::pin(async move { self.call().await })
+    }
+}
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index 11ec62a3..2a4e2c8c 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -147,7 +147,7 @@ use std::{fmt::Debug, marker::PhantomData, sync::Arc};
 /// _Disclaimer: these above docs have been adapted from the corresponding [ethers.js page](https://docs.ethers.io/ethers.js/html/api-contract.html)_
 ///
 /// [`abigen`]: macro.abigen.html
-/// [`Abigen` builder]: crate::Abigen
+/// [`Abigen` builder]: struct.Abigen.html
 /// [`event`]: method@crate::Contract::event
 /// [`method`]: method@crate::Contract::method
 #[derive(Debug)]
diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs
index be81bc2d..a8d05145 100644
--- a/ethers-contract/src/multicall/mod.rs
+++ b/ethers-contract/src/multicall/mod.rs
@@ -29,7 +29,7 @@ pub const MULTICALL_ADDRESS: Address = H160([
 
 /// The chain IDs that [`MULTICALL_ADDRESS`] has been deployed to.
 /// Taken from: https://github.com/mds1/multicall#multicall3-contract-addresses
-pub static MULTICALL_SUPPORTED_CHAIN_IDS: Lazy<[U256; 47]> = Lazy::new(|| {
+pub static MULTICALL_SUPPORTED_CHAIN_IDS: Lazy<[U256; 48]> = Lazy::new(|| {
     use Chain::*;
     [
         U256::from(Mainnet),                  // Mainnet
@@ -47,6 +47,7 @@ pub static MULTICALL_SUPPORTED_CHAIN_IDS: Lazy<[U256; 47]> = Lazy::new(|| {
         U256::from(Polygon),                  // Polygon
         U256::from(PolygonMumbai),            // PolygonMumbai
         U256::from(XDai),                     // XDai
+        U256::from(Chiado),                   // ChiadoTestnet
         U256::from(Avalanche),                // Avalanche
         U256::from(AvalancheFuji),            // AvalancheFuji
         U256::from(FantomTestnet),            // FantomTestnet
@@ -139,7 +140,7 @@ impl TryFrom<u8> for MulticallVersion {
             1 => Ok(MulticallVersion::Multicall),
             2 => Ok(MulticallVersion::Multicall2),
             3 => Ok(MulticallVersion::Multicall3),
-            _ => Err(format!("Invalid Multicall version: {}. Accepted values: 1, 2, 3.", v)),
+            _ => Err(format!("Invalid Multicall version: {v}. Accepted values: 1, 2, 3.")),
         }
     }
 }
diff --git a/ethers-contract/src/multicall/multicall_contract.rs b/ethers-contract/src/multicall/multicall_contract.rs
index 7154f287..661ac998 100644
--- a/ethers-contract/src/multicall/multicall_contract.rs
+++ b/ethers-contract/src/multicall/multicall_contract.rs
@@ -4,15 +4,26 @@ pub mod multicall_3 {
     #![allow(dead_code)]
     #![allow(clippy::type_complexity)]
     #![allow(unused_imports)]
-    // Some macros like EthAbiType and EthAbiCodec expand into using "ethers_contract" which is not
-    // defined here
+    /// Some macros may expand into using `ethers_contract` instead of `crate`.
     mod ethers_contract {
         pub use crate::*;
     }
+    use self::ethers_contract::{
+        builders::{ContractCall, Event},
+        Contract, Lazy,
+    };
+    use ethers_core::{
+        abi::{Abi, Detokenize, InvalidOutputType, Token, Tokenizable},
+        types::*,
+    };
+    use ethers_providers::Middleware;
     #[doc = "Multicall3 was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs"]
+    use std::sync::Arc;
+    # [rustfmt :: skip] const __ABI : & str = "[{\"type\":\"function\",\"name\":\"aggregate\",\"inputs\":[{\"internalType\":\"struct Multicall3.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes[]\",\"name\":\"returnData\",\"type\":\"bytes[]\"}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"aggregate3\",\"inputs\":[{\"internalType\":\"struct Multicall3.Call3[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"struct Multicall3.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"aggregate3Value\",\"inputs\":[{\"internalType\":\"struct Multicall3.Call3Value[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bool\"},{\"type\":\"uint256\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"struct Multicall3.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"blockAndAggregate\",\"inputs\":[{\"internalType\":\"struct Multicall3.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"internalType\":\"struct Multicall3.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"getBasefee\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"basefee\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBlockHash\",\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getBlockNumber\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getChainId\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"chainid\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getCurrentBlockCoinbase\",\"inputs\":[],\"outputs\":[{\"internalType\":\"address\",\"name\":\"coinbase\",\"type\":\"address\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getCurrentBlockDifficulty\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"difficulty\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getCurrentBlockGasLimit\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"gaslimit\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getCurrentBlockTimestamp\",\"inputs\":[],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"timestamp\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getEthBalance\",\"inputs\":[{\"internalType\":\"address\",\"name\":\"addr\",\"type\":\"address\"}],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"getLastBlockHash\",\"inputs\":[],\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\"},{\"type\":\"function\",\"name\":\"tryAggregate\",\"inputs\":[{\"internalType\":\"bool\",\"name\":\"requireSuccess\",\"type\":\"bool\"},{\"internalType\":\"struct Multicall3.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"struct Multicall3.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"stateMutability\":\"payable\"},{\"type\":\"function\",\"name\":\"tryBlockAndAggregate\",\"inputs\":[{\"internalType\":\"bool\",\"name\":\"requireSuccess\",\"type\":\"bool\"},{\"internalType\":\"struct Multicall3.Call[]\",\"name\":\"calls\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"address\"},{\"type\":\"bytes\"}]}],\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"blockNumber\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"blockHash\",\"type\":\"bytes32\"},{\"internalType\":\"struct Multicall3.Result[]\",\"name\":\"returnData\",\"type\":\"tuple[]\",\"components\":[{\"type\":\"bool\"},{\"type\":\"bytes\"}]}],\"stateMutability\":\"payable\"}]" ;
+    #[doc = r" The parsed JSON-ABI of the contract."]
     pub static MULTICALL3_ABI: ethers_contract::Lazy<ethers_core::abi::Abi> =
         ethers_contract::Lazy::new(|| {
-            ethers_core :: utils :: __serde_json :: from_str ("[\n    {\n        \"inputs\": [\n            {\n                \"components\": [\n                    { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n                    { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Call[]\",\n                \"name\": \"calls\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"name\": \"aggregate\",\n        \"outputs\": [\n            { \"internalType\": \"uint256\", \"name\": \"blockNumber\", \"type\": \"uint256\" },\n            { \"internalType\": \"bytes[]\", \"name\": \"returnData\", \"type\": \"bytes[]\" }\n        ],\n        \"stateMutability\": \"payable\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [\n            {\n                \"components\": [\n                    { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n                    { \"internalType\": \"bool\", \"name\": \"allowFailure\", \"type\": \"bool\" },\n                    { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Call3[]\",\n                \"name\": \"calls\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"name\": \"aggregate3\",\n        \"outputs\": [\n            {\n                \"components\": [\n                    { \"internalType\": \"bool\", \"name\": \"success\", \"type\": \"bool\" },\n                    { \"internalType\": \"bytes\", \"name\": \"returnData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Result[]\",\n                \"name\": \"returnData\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"stateMutability\": \"payable\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [\n            {\n                \"components\": [\n                    { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n                    { \"internalType\": \"bool\", \"name\": \"allowFailure\", \"type\": \"bool\" },\n                    { \"internalType\": \"uint256\", \"name\": \"value\", \"type\": \"uint256\" },\n                    { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Call3Value[]\",\n                \"name\": \"calls\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"name\": \"aggregate3Value\",\n        \"outputs\": [\n            {\n                \"components\": [\n                    { \"internalType\": \"bool\", \"name\": \"success\", \"type\": \"bool\" },\n                    { \"internalType\": \"bytes\", \"name\": \"returnData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Result[]\",\n                \"name\": \"returnData\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"stateMutability\": \"payable\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [\n            {\n                \"components\": [\n                    { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n                    { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Call[]\",\n                \"name\": \"calls\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"name\": \"blockAndAggregate\",\n        \"outputs\": [\n            { \"internalType\": \"uint256\", \"name\": \"blockNumber\", \"type\": \"uint256\" },\n            { \"internalType\": \"bytes32\", \"name\": \"blockHash\", \"type\": \"bytes32\" },\n            {\n                \"components\": [\n                    { \"internalType\": \"bool\", \"name\": \"success\", \"type\": \"bool\" },\n                    { \"internalType\": \"bytes\", \"name\": \"returnData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Result[]\",\n                \"name\": \"returnData\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"stateMutability\": \"payable\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [],\n        \"name\": \"getBasefee\",\n        \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"basefee\", \"type\": \"uint256\" }],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [{ \"internalType\": \"uint256\", \"name\": \"blockNumber\", \"type\": \"uint256\" }],\n        \"name\": \"getBlockHash\",\n        \"outputs\": [{ \"internalType\": \"bytes32\", \"name\": \"blockHash\", \"type\": \"bytes32\" }],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [],\n        \"name\": \"getBlockNumber\",\n        \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"blockNumber\", \"type\": \"uint256\" }],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [],\n        \"name\": \"getChainId\",\n        \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"chainid\", \"type\": \"uint256\" }],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [],\n        \"name\": \"getCurrentBlockCoinbase\",\n        \"outputs\": [{ \"internalType\": \"address\", \"name\": \"coinbase\", \"type\": \"address\" }],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [],\n        \"name\": \"getCurrentBlockDifficulty\",\n        \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"difficulty\", \"type\": \"uint256\" }],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [],\n        \"name\": \"getCurrentBlockGasLimit\",\n        \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"gaslimit\", \"type\": \"uint256\" }],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [],\n        \"name\": \"getCurrentBlockTimestamp\",\n        \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"timestamp\", \"type\": \"uint256\" }],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [{ \"internalType\": \"address\", \"name\": \"addr\", \"type\": \"address\" }],\n        \"name\": \"getEthBalance\",\n        \"outputs\": [{ \"internalType\": \"uint256\", \"name\": \"balance\", \"type\": \"uint256\" }],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [],\n        \"name\": \"getLastBlockHash\",\n        \"outputs\": [{ \"internalType\": \"bytes32\", \"name\": \"blockHash\", \"type\": \"bytes32\" }],\n        \"stateMutability\": \"view\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [\n            { \"internalType\": \"bool\", \"name\": \"requireSuccess\", \"type\": \"bool\" },\n            {\n                \"components\": [\n                    { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n                    { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Call[]\",\n                \"name\": \"calls\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"name\": \"tryAggregate\",\n        \"outputs\": [\n            {\n                \"components\": [\n                    { \"internalType\": \"bool\", \"name\": \"success\", \"type\": \"bool\" },\n                    { \"internalType\": \"bytes\", \"name\": \"returnData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Result[]\",\n                \"name\": \"returnData\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"stateMutability\": \"payable\",\n        \"type\": \"function\"\n    },\n    {\n        \"inputs\": [\n            { \"internalType\": \"bool\", \"name\": \"requireSuccess\", \"type\": \"bool\" },\n            {\n                \"components\": [\n                    { \"internalType\": \"address\", \"name\": \"target\", \"type\": \"address\" },\n                    { \"internalType\": \"bytes\", \"name\": \"callData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Call[]\",\n                \"name\": \"calls\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"name\": \"tryBlockAndAggregate\",\n        \"outputs\": [\n            { \"internalType\": \"uint256\", \"name\": \"blockNumber\", \"type\": \"uint256\" },\n            { \"internalType\": \"bytes32\", \"name\": \"blockHash\", \"type\": \"bytes32\" },\n            {\n                \"components\": [\n                    { \"internalType\": \"bool\", \"name\": \"success\", \"type\": \"bool\" },\n                    { \"internalType\": \"bytes\", \"name\": \"returnData\", \"type\": \"bytes\" }\n                ],\n                \"internalType\": \"struct Multicall3.Result[]\",\n                \"name\": \"returnData\",\n                \"type\": \"tuple[]\"\n            }\n        ],\n        \"stateMutability\": \"payable\",\n        \"type\": \"function\"\n    }\n]\n") . expect ("invalid abi")
+            ethers_core::utils::__serde_json::from_str(__ABI).expect("invalid abi")
         });
     pub struct Multicall3<M>(ethers_contract::Contract<M>);
     impl<M> Clone for Multicall3<M> {
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index 281bc00c..1b94812c 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -13,7 +13,7 @@ keywords = ["ethereum", "web3", "celo", "ethers"]
 [dependencies]
 # Pin FastRLP to 0.1.2 when it was still Apache.
 open-fastrlp = { version = "=0.1.2", features = ["std", "derive", "ethereum-types"] }
-rlp = { version = "0.5.0", default-features = false, features = ["std"] }
+rlp = { version = "0.5.2", default-features = false, features = ["std"] }
 ethabi = { version = "17.2.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 }
@@ -43,7 +43,6 @@ cargo_metadata = { version = "0.15.0", optional = true }
 convert_case = { version = "0.6.0", optional = true }
 syn = { version = "1.0.103", optional = true }
 proc-macro2 = { version = "1.0.47", optional = true }
-rust_decimal = { version = "1.26.1", features = ["maths"] }
 
 [dev-dependencies]
 serde_json = { version = "1.0.64", default-features = false }
diff --git a/ethers-core/src/abi/human_readable/lexer.rs b/ethers-core/src/abi/human_readable/lexer.rs
index 6347565a..08ee0020 100644
--- a/ethers-core/src/abi/human_readable/lexer.rs
+++ b/ethers-core/src/abi/human_readable/lexer.rs
@@ -94,12 +94,12 @@ impl<'input> Token<'input> {
 impl<'input> fmt::Display for Token<'input> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            Token::Identifier(id) => write!(f, "{}", id),
-            Token::Number(num) => write!(f, "{}", num),
-            Token::HexNumber(num) => write!(f, "0x{}", num),
-            Token::Uint(w) => write!(f, "uint{}", w),
-            Token::Int(w) => write!(f, "int{}", w),
-            Token::Bytes(w) => write!(f, "bytes{}", w),
+            Token::Identifier(id) => write!(f, "{id}"),
+            Token::Number(num) => write!(f, "{num}"),
+            Token::HexNumber(num) => write!(f, "0x{num}"),
+            Token::Uint(w) => write!(f, "uint{w}"),
+            Token::Int(w) => write!(f, "int{w}"),
+            Token::Bytes(w) => write!(f, "bytes{w}"),
             Token::Byte => write!(f, "byte"),
             Token::DynamicBytes => write!(f, "bytes"),
             Token::Semicolon => write!(f, ";"),
diff --git a/ethers-core/src/abi/human_readable/mod.rs b/ethers-core/src/abi/human_readable/mod.rs
index 192fa42c..db5d97ac 100644
--- a/ethers-core/src/abi/human_readable/mod.rs
+++ b/ethers-core/src/abi/human_readable/mod.rs
@@ -23,7 +23,7 @@ pub struct AbiParser {
     ///
     /// Note: we need to map the index of the event here because events can contain nameless inputs
     pub event_params: HashMap<(String, usize), String>,
-    /// (function name) -> Vec<structs> all structs the function returns
+    /// (function name) -> `Vec<structs>` all structs the function returns
     pub outputs: HashMap<String, Vec<String>>,
 }
 
diff --git a/ethers-core/src/abi/mod.rs b/ethers-core/src/abi/mod.rs
index 547433f2..e9cdbf55 100644
--- a/ethers-core/src/abi/mod.rs
+++ b/ethers-core/src/abi/mod.rs
@@ -95,7 +95,7 @@ impl ErrorExt for ethabi::AbiError {
             return format!("{}()", self.name)
         }
         let inputs = self.inputs.iter().map(|p| p.kind.to_string()).collect::<Vec<_>>().join(",");
-        format!("{}({})", self.name, inputs)
+        format!("{}({inputs})", self.name)
     }
 
     fn selector(&self) -> Selector {
diff --git a/ethers-core/src/abi/raw.rs b/ethers-core/src/abi/raw.rs
index 98c4ef7f..7aaa915a 100644
--- a/ethers-core/src/abi/raw.rs
+++ b/ethers-core/src/abi/raw.rs
@@ -257,10 +257,10 @@ mod tests {
         assert!(matches!(abi, JsonAbi::Array(_)));
 
         let code = "0x608060405234801561001057600080fd5b50610242806100206000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c80635581701b14610030575b600080fd5b61004a60048036038101906100459190610199565b610060565b60405161005791906101f1565b60405180910390f35b610068610070565b819050919050565b60405180602001604052806000151581525090565b6000604051905090565b600080fd5b600080fd5b6000601f19601f8301169050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6100e282610099565b810181811067ffffffffffffffff82111715610101576101006100aa565b5b80604052505050565b6000610114610085565b905061012082826100d9565b919050565b60008115159050919050565b61013a81610125565b811461014557600080fd5b50565b60008135905061015781610131565b92915050565b60006020828403121561017357610172610094565b5b61017d602061010a565b9050600061018d84828501610148565b60008301525092915050565b6000602082840312156101af576101ae61008f565b5b60006101bd8482850161015d565b91505092915050565b6101cf81610125565b82525050565b6020820160008201516101eb60008501826101c6565b50505050565b600060208201905061020660008301846101d5565b9291505056fea2646970667358221220890202b0964477379a457ab3725a21d7c14581e4596552e32a54e23f1c6564e064736f6c634300080c0033";
-        let s = format!(r#"{{"abi": {}, "bin" : "{}" }}"#, abi_str, code);
+        let s = format!(r#"{{"abi": {abi_str}, "bin" : "{code}" }}"#);
         assert_has_bytecode(&s);
 
-        let s = format!(r#"{{"abi": {}, "bytecode" : {{ "object": "{}" }} }}"#, abi_str, code);
+        let s = format!(r#"{{"abi": {abi_str}, "bytecode" : {{ "object": "{code}" }} }}"#);
         assert_has_bytecode(&s);
 
         let hh_artifact = include_str!(
@@ -286,7 +286,7 @@ mod tests {
     #[test]
     fn ignores_empty_bytecode() {
         let abi_str = r#"[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint64","name":"number","type":"uint64"}],"name":"MyEvent","type":"event"},{"inputs":[],"name":"greet","outputs":[],"stateMutability":"nonpayable","type":"function"}]"#;
-        let s = format!(r#"{{"abi": {}, "bin" : "0x" }}"#, abi_str);
+        let s = format!(r#"{{"abi": {abi_str}, "bin" : "0x" }}"#);
 
         match serde_json::from_str::<JsonAbi>(&s).unwrap() {
             JsonAbi::Object(abi) => {
@@ -297,7 +297,7 @@ mod tests {
             }
         }
 
-        let s = format!(r#"{{"abi": {}, "bytecode" : {{ "object": "0x" }} }}"#, abi_str);
+        let s = format!(r#"{{"abi": {abi_str}, "bytecode" : {{ "object": "0x" }} }}"#);
 
         match serde_json::from_str::<JsonAbi>(&s).unwrap() {
             JsonAbi::Object(abi) => {
diff --git a/ethers-core/src/abi/struct_def.rs b/ethers-core/src/abi/struct_def.rs
index ff8945a0..2d9380ef 100644
--- a/ethers-core/src/abi/struct_def.rs
+++ b/ethers-core/src/abi/struct_def.rs
@@ -127,7 +127,7 @@ impl StructFieldType {
         if path.is_empty() {
             name.to_string()
         } else {
-            format!("{}.{}", path, name)
+            format!("{path}.{name}")
         }
     }
 
diff --git a/ethers-core/src/macros/ethers_crate.rs b/ethers-core/src/macros/ethers_crate.rs
index bdc3be12..78bc38d8 100644
--- a/ethers-core/src/macros/ethers_crate.rs
+++ b/ethers-core/src/macros/ethers_crate.rs
@@ -59,11 +59,11 @@ pub fn determine_ethers_crates() -> (&'static str, &'static str, &'static str) {
     };
 
     // check if the lock file exists, if it's missing we need to clean up afterward
-    let lock_file = format!("{}/Cargo.lock", manifest_dir);
+    let lock_file = format!("{manifest_dir}/Cargo.lock");
     let needs_lock_file_cleanup = !std::path::Path::new(&lock_file).exists();
 
     let res = MetadataCommand::new()
-        .manifest_path(&format!("{}/Cargo.toml", manifest_dir))
+        .manifest_path(&format!("{manifest_dir}/Cargo.toml"))
         .exec()
         .ok()
         .and_then(|metadata| {
diff --git a/ethers-core/src/types/block.rs b/ethers-core/src/types/block.rs
index e091f298..768507d6 100644
--- a/ethers-core/src/types/block.rs
+++ b/ethers-core/src/types/block.rs
@@ -14,7 +14,7 @@ use thiserror::Error;
 
 /// The block type returned from RPC calls.
 /// This is generic over a `TX` type which will be either the hash or the full transaction,
-/// i.e. `Block<TxHash>` or Block<Transaction>`.
+/// i.e. `Block<TxHash>` or `Block<Transaction>`.
 #[derive(Debug, Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
 pub struct Block<TX> {
     /// Hash of the block
@@ -461,7 +461,7 @@ impl Serialize for BlockId {
         match *self {
             BlockId::Hash(ref x) => {
                 let mut s = serializer.serialize_struct("BlockIdEip1898", 1)?;
-                s.serialize_field("blockHash", &format!("{:?}", x))?;
+                s.serialize_field("blockHash", &format!("{x:?}"))?;
                 s.end()
             }
             BlockId::Number(ref num) => num.serialize(serializer),
@@ -599,7 +599,7 @@ impl Serialize for BlockNumber {
         S: Serializer,
     {
         match *self {
-            BlockNumber::Number(ref x) => serializer.serialize_str(&format!("0x{:x}", x)),
+            BlockNumber::Number(ref x) => serializer.serialize_str(&format!("0x{x:x}")),
             BlockNumber::Latest => serializer.serialize_str("latest"),
             BlockNumber::Finalized => serializer.serialize_str("finalized"),
             BlockNumber::Safe => serializer.serialize_str("safe"),
@@ -638,7 +638,7 @@ impl FromStr for BlockNumber {
 impl fmt::Display for BlockNumber {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            BlockNumber::Number(ref x) => format!("0x{:x}", x).fmt(f),
+            BlockNumber::Number(ref x) => format!("0x{x:x}").fmt(f),
             BlockNumber::Latest => f.write_str("latest"),
             BlockNumber::Finalized => f.write_str("finalized"),
             BlockNumber::Safe => f.write_str("safe"),
diff --git a/ethers-core/src/types/bytes.rs b/ethers-core/src/types/bytes.rs
index bf1c4450..b6d8833c 100644
--- a/ethers-core/src/types/bytes.rs
+++ b/ethers-core/src/types/bytes.rs
@@ -166,7 +166,7 @@ impl FromStr for Bytes {
             hex::decode(value)
         }
         .map(Into::into)
-        .map_err(|e| ParseBytesError(format!("Invalid hex: {}", e)))
+        .map_err(|e| ParseBytesError(format!("Invalid hex: {e}")))
     }
 }
 
@@ -200,8 +200,8 @@ mod tests {
     fn hex_formatting() {
         let b = Bytes::from(vec![1, 35, 69, 103, 137, 171, 205, 239]);
         let expected = String::from("0x0123456789abcdef");
-        assert_eq!(format!("{:x}", b), expected);
-        assert_eq!(format!("{}", b), expected);
+        assert_eq!(format!("{b:x}"), expected);
+        assert_eq!(format!("{b}"), expected);
     }
 
     #[test]
@@ -219,7 +219,7 @@ mod tests {
     #[test]
     fn test_debug_formatting() {
         let b = Bytes::from(vec![1, 35, 69, 103, 137, 171, 205, 239]);
-        assert_eq!(format!("{:?}", b), "Bytes(0x0123456789abcdef)");
-        assert_eq!(format!("{:#?}", b), "Bytes(0x0123456789abcdef)");
+        assert_eq!(format!("{b:?}"), "Bytes(0x0123456789abcdef)");
+        assert_eq!(format!("{b:#?}"), "Bytes(0x0123456789abcdef)");
     }
 }
diff --git a/ethers-core/src/types/chain.rs b/ethers-core/src/types/chain.rs
index 4645b890..f5cab58b 100644
--- a/ethers-core/src/types/chain.rs
+++ b/ethers-core/src/types/chain.rs
@@ -30,8 +30,9 @@ pub enum Chain {
     Rinkeby = 4,
     Goerli = 5,
     Kovan = 42,
-    #[strum(serialize = "xdai")]
+    #[strum(serialize = "gnosis")]
     XDai = 100,
+    Chiado = 10200,
     Polygon = 137,
     Fantom = 250,
     Dev = 1337,
@@ -103,6 +104,7 @@ impl Chain {
             Chain::Goerli |
             Chain::Kovan |
             Chain::XDai |
+            Chain::Chiado |
             Chain::Sepolia |
             Chain::Moonbase |
             Chain::MoonbeamDev |
@@ -186,6 +188,9 @@ impl Chain {
             Chain::XDai => {
                 ("https://blockscout.com/xdai/mainnet/api", "https://blockscout.com/xdai/mainnet")
             }
+            Chain::Chiado => {
+                ("https://blockscout.chiadochain.net/api", "https://blockscout.chiadochain.net")
+            }
             Chain::Sokol => {
                 ("https://blockscout.com/poa/sokol/api", "https://blockscout.com/poa/sokol")
             }
@@ -229,7 +234,8 @@ impl fmt::Display for Chain {
             Chain::Rinkeby => "rinkeby",
             Chain::Goerli => "goerli",
             Chain::Kovan => "kovan",
-            Chain::XDai => "xdai",
+            Chain::XDai => "gnosis",
+            Chain::Chiado => "chiado",
             Chain::Polygon => "polygon",
             Chain::PolygonMumbai => "mumbai",
             Chain::Avalanche => "avalanche",
@@ -265,7 +271,7 @@ impl fmt::Display for Chain {
             Chain::AuroraTestnet => "aurora-testnet",
         };
 
-        write!(formatter, "{}", chain)
+        write!(formatter, "{chain}")
     }
 }
 
@@ -299,6 +305,7 @@ impl TryFrom<u64> for Chain {
             5 => Chain::Goerli,
             42 => Chain::Kovan,
             100 => Chain::XDai,
+            10200 => Chain::Chiado,
             137 => Chain::Polygon,
             1337 => Chain::Dev,
             31337 => Chain::AnvilHardhat,
@@ -358,7 +365,8 @@ impl FromStr for Chain {
             "rinkeby" => Chain::Rinkeby,
             "goerli" => Chain::Goerli,
             "kovan" => Chain::Kovan,
-            "xdai" => Chain::XDai,
+            "xdai" | "gnosis" | "gnosis-chain" => Chain::XDai,
+            "chiado" => Chain::Chiado,
             "polygon" => Chain::Polygon,
             "mumbai" | "polygon-mumbai" => Chain::PolygonMumbai,
             "avalanche" => Chain::Avalanche,
diff --git a/ethers-core/src/types/filter.rs b/ethers-core/src/types/filter.rs
index 04f750da..605c14be 100644
--- a/ethers-core/src/types/filter.rs
+++ b/ethers-core/src/types/filter.rs
@@ -626,7 +626,7 @@ where
         }
 
         match serde_json::from_value::<Variadic<T>>(value).map_err(|err| {
-            serde::de::Error::custom(format!("Invalid variadic value or array type: {}", 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)),
diff --git a/ethers-core/src/types/i256.rs b/ethers-core/src/types/i256.rs
index a8a94e87..b2db60bc 100644
--- a/ethers-core/src/types/i256.rs
+++ b/ethers-core/src/types/i256.rs
@@ -1046,7 +1046,7 @@ impl fmt::Display for I256 {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let (sign, abs) = self.into_sign_and_abs();
         sign.fmt(f)?;
-        write!(f, "{}", abs)
+        write!(f, "{abs}")
     }
 }
 
@@ -1054,7 +1054,7 @@ impl fmt::LowerHex for I256 {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let (sign, abs) = self.into_sign_and_abs();
         fmt::Display::fmt(&sign, f)?;
-        write!(f, "{:x}", abs)
+        write!(f, "{abs:x}")
     }
 }
 
@@ -1064,9 +1064,9 @@ impl fmt::UpperHex for I256 {
         fmt::Display::fmt(&sign, f)?;
 
         // NOTE: Work around `U256: !UpperHex`.
-        let mut buffer = format!("{:x}", abs);
+        let mut buffer = format!("{abs:x}");
         buffer.make_ascii_uppercase();
-        write!(f, "{}", buffer)
+        write!(f, "{buffer}")
     }
 }
 
@@ -1385,13 +1385,13 @@ mod tests {
     fn parse_dec_str() {
         let unsigned = U256::from_dec_str("314159265358979323846264338327950288419716").unwrap();
 
-        let value = I256::from_dec_str(&format!("-{}", unsigned)).unwrap();
+        let value = I256::from_dec_str(&format!("-{unsigned}")).unwrap();
         assert_eq!(value.into_sign_and_abs(), (Sign::Negative, unsigned));
 
-        let value = I256::from_dec_str(&format!("{}", unsigned)).unwrap();
+        let value = I256::from_dec_str(&format!("{unsigned}")).unwrap();
         assert_eq!(value.into_sign_and_abs(), (Sign::Positive, unsigned));
 
-        let value = I256::from_dec_str(&format!("+{}", unsigned)).unwrap();
+        let value = I256::from_dec_str(&format!("+{unsigned}")).unwrap();
         assert_eq!(value.into_sign_and_abs(), (Sign::Positive, unsigned));
 
         let err = I256::from_dec_str("invalid string").unwrap_err();
@@ -1414,13 +1414,13 @@ mod tests {
     fn parse_hex_str() {
         let unsigned = U256::from_dec_str("314159265358979323846264338327950288419716").unwrap();
 
-        let value = I256::from_hex_str(&format!("-{:x}", unsigned)).unwrap();
+        let value = I256::from_hex_str(&format!("-{unsigned:x}")).unwrap();
         assert_eq!(value.into_sign_and_abs(), (Sign::Negative, unsigned));
 
-        let value = I256::from_hex_str(&format!("{:x}", unsigned)).unwrap();
+        let value = I256::from_hex_str(&format!("{unsigned:x}")).unwrap();
         assert_eq!(value.into_sign_and_abs(), (Sign::Positive, unsigned));
 
-        let value = I256::from_hex_str(&format!("+{:x}", unsigned)).unwrap();
+        let value = I256::from_hex_str(&format!("+{unsigned:x}")).unwrap();
         assert_eq!(value.into_sign_and_abs(), (Sign::Positive, unsigned));
 
         let err = I256::from_hex_str("invalid string").unwrap_err();
@@ -1445,20 +1445,20 @@ mod tests {
         let positive = I256::try_from(unsigned).unwrap();
         let negative = -positive;
 
-        assert_eq!(format!("{}", positive), format!("{}", unsigned));
-        assert_eq!(format!("{}", negative), format!("-{}", unsigned));
-        assert_eq!(format!("{:+}", positive), format!("+{}", unsigned));
-        assert_eq!(format!("{:+}", negative), format!("-{}", unsigned));
+        assert_eq!(format!("{positive}"), format!("{unsigned}"));
+        assert_eq!(format!("{negative}"), format!("-{unsigned}"));
+        assert_eq!(format!("{positive:+}"), format!("+{unsigned}"));
+        assert_eq!(format!("{negative:+}"), format!("-{unsigned}"));
 
-        assert_eq!(format!("{:x}", positive), format!("{:x}", unsigned));
-        assert_eq!(format!("{:x}", negative), format!("-{:x}", unsigned));
-        assert_eq!(format!("{:+x}", positive), format!("+{:x}", unsigned));
-        assert_eq!(format!("{:+x}", negative), format!("-{:x}", unsigned));
+        assert_eq!(format!("{positive:x}"), format!("{unsigned:x}"));
+        assert_eq!(format!("{negative:x}"), format!("-{unsigned:x}"));
+        assert_eq!(format!("{positive:+x}"), format!("+{unsigned:x}"));
+        assert_eq!(format!("{negative:+x}"), format!("-{unsigned:x}"));
 
-        assert_eq!(format!("{:X}", positive), format!("{:x}", unsigned).to_uppercase());
-        assert_eq!(format!("{:X}", negative), format!("-{:x}", unsigned).to_uppercase());
-        assert_eq!(format!("{:+X}", positive), format!("+{:x}", unsigned).to_uppercase());
-        assert_eq!(format!("{:+X}", negative), format!("-{:x}", unsigned).to_uppercase());
+        assert_eq!(format!("{positive:X}"), format!("{unsigned:x}").to_uppercase());
+        assert_eq!(format!("{negative:X}"), format!("-{unsigned:x}").to_uppercase());
+        assert_eq!(format!("{positive:+X}"), format!("+{unsigned:x}").to_uppercase());
+        assert_eq!(format!("{negative:+X}"), format!("-{unsigned:x}").to_uppercase());
     }
 
     #[test]
diff --git a/ethers-core/src/types/mod.rs b/ethers-core/src/types/mod.rs
index 68643dc2..3c2aff0e 100644
--- a/ethers-core/src/types/mod.rs
+++ b/ethers-core/src/types/mod.rs
@@ -6,7 +6,7 @@ pub type Selector = [u8; 4];
 pub use ethabi::ethereum_types::H256 as TxHash;
 
 pub use ethabi::ethereum_types::{
-    Address, BigEndianHash, Bloom, H128, H160, H256, H32, H512, H64, U128, U256, U64,
+    Address, BigEndianHash, Bloom, H128, H160, H256, H32, H512, H64, U128, U256, U64, U512,
 };
 
 pub mod transaction;
diff --git a/ethers-core/src/types/serde_helpers.rs b/ethers-core/src/types/serde_helpers.rs
index 87c64e51..689c9e7f 100644
--- a/ethers-core/src/types/serde_helpers.rs
+++ b/ethers-core/src/types/serde_helpers.rs
@@ -231,7 +231,7 @@ impl From<LenientBlockNumber> for BlockNumber {
 /// > blockNumber: QUANTITY - a block number
 /// > blockHash: DATA - a block hash
 ///
-/// https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md
+/// <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md>
 ///
 /// EIP-1898 does not all calls that use `BlockNumber` like `eth_getBlockByNumber` and doesn't list
 /// raw integers as supported.
diff --git a/ethers-core/src/types/transaction/eip712.rs b/ethers-core/src/types/transaction/eip712.rs
index acf155db..b608652a 100644
--- a/ethers-core/src/types/transaction/eip712.rs
+++ b/ethers-core/src/types/transaction/eip712.rs
@@ -490,7 +490,7 @@ pub struct TypedData {
 
 /// According to the MetaMask implementation,
 /// the message parameter may be JSON stringified in versions later than V1
-/// See https://github.com/MetaMask/metamask-extension/blob/0dfdd44ae7728ed02cbf32c564c75b74f37acf77/app/scripts/metamask-controller.js#L1736
+/// See <https://github.com/MetaMask/metamask-extension/blob/0dfdd44ae7728ed02cbf32c564c75b74f37acf77/app/scripts/metamask-controller.js#L1736>
 /// In fact, ethers.js JSON stringifies the message at the time of writing.
 impl<'de> Deserialize<'de> for TypedData {
     fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
@@ -741,7 +741,7 @@ pub fn encode_field(
                             // uints are commonly stringified due to how ethers-js encodes
                             let val: StringifiedNumeric = serde_json::from_value(value.clone())?;
                             let val = val.try_into().map_err(|err| {
-                                Eip712Error::Message(format!("Failed to parse uint {}", err))
+                                Eip712Error::Message(format!("Failed to parse uint {err}"))
                             })?;
 
                             Token::Uint(val)
@@ -808,7 +808,7 @@ pub fn find_parameter_type(ty: &Type) -> Result<ParamType, TokenStream> {
                     s => parse_int_param_type(s).ok_or_else(|| {
                         Error::new(
                             ty.span(),
-                            format!("Failed to derive proper ABI from field: {})", s),
+                            format!("Failed to derive proper ABI from field: {s})"),
                         )
                         .to_compile_error()
                     }),
@@ -906,9 +906,9 @@ pub fn parse_fields(ast: &DeriveInput) -> Result<Vec<(String, ParamType)>, Token
 /// Convert hash map of field names and types into a type hash corresponding to enc types;
 pub fn make_type_hash(primary_type: String, fields: &[(String, ParamType)]) -> [u8; 32] {
     let parameters =
-        fields.iter().map(|(k, v)| format!("{} {}", v, k)).collect::<Vec<String>>().join(",");
+        fields.iter().map(|(k, v)| format!("{v} {k}")).collect::<Vec<String>>().join(",");
 
-    let sig = format!("{}({})", primary_type, parameters);
+    let sig = format!("{primary_type}({parameters})");
 
     keccak256(sig)
 }
diff --git a/ethers-core/src/types/uint8.rs b/ethers-core/src/types/uint8.rs
index 3c4ca0e7..5803961b 100644
--- a/ethers-core/src/types/uint8.rs
+++ b/ethers-core/src/types/uint8.rs
@@ -83,7 +83,7 @@ impl Tokenizable for Uint8 {
                 }
                 Ok(Uint8(data.low_u32() as u8))
             }
-            other => Err(InvalidOutputType(format!("Expected `uint8`, got {:?}", other))),
+            other => Err(InvalidOutputType(format!("Expected `uint8`, got {other:?}"))),
         }
     }
     fn into_token(self) -> Token {
diff --git a/ethers-core/src/utils/hash.rs b/ethers-core/src/utils/hash.rs
index 9c13eb6a..2ddb4c43 100644
--- a/ethers-core/src/utils/hash.rs
+++ b/ethers-core/src/utils/hash.rs
@@ -15,7 +15,7 @@ where
 {
     let message = message.as_ref();
 
-    let mut eth_message = format!("{}{}", PREFIX, message.len()).into_bytes();
+    let mut eth_message = format!("{PREFIX}{}", message.len()).into_bytes();
     eth_message.extend_from_slice(message);
 
     keccak256(&eth_message).into()
diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs
index 5e3b1c2d..9aaefc43 100644
--- a/ethers-core/src/utils/mod.rs
+++ b/ethers-core/src/utils/mod.rs
@@ -56,8 +56,8 @@ pub enum ConversionError {
     InvalidFloat(#[from] std::num::ParseFloatError),
     #[error(transparent)]
     FromDecStrError(#[from] FromDecStrErr),
-    #[error(transparent)]
-    DecimalError(#[from] rust_decimal::Error),
+    #[error("Overflow parsing string")]
+    ParseOverflow,
 }
 
 /// 1 Ether = 1e18 Wei == 0x0de0b6b3a7640000 Wei
@@ -155,19 +155,27 @@ where
     S: ToString,
     K: TryInto<Units, Error = ConversionError> + Copy,
 {
-    use rust_decimal::{Decimal, MathematicalOps};
-
-    let num: Decimal = amount.to_string().parse()?;
-    let exponent = units.try_into()?.as_num();
-
-    let multiplier = Decimal::TEN
-        .checked_powu(exponent.into())
-        .ok_or(rust_decimal::Error::ExceedsMaximumPossibleValue)?;
+    let exponent: u32 = units.try_into()?.as_num();
+    let mut amount_str = amount.to_string().replace("_", "");
+    let dec_len = if let Some(di) = amount_str.find('.') {
+        amount_str.remove(di);
+        amount_str[di..].len() as u32
+    } else {
+        0
+    };
 
-    let val =
-        num.checked_mul(multiplier).ok_or(rust_decimal::Error::ExceedsMaximumPossibleValue)?;
-    let u256_n: U256 = U256::from_dec_str(&val.round().to_string())?;
-    Ok(u256_n)
+    if dec_len > exponent {
+        // Truncate the decimal part if it is longer than the exponent
+        let amount_str = &amount_str[..(amount_str.len() - (dec_len - exponent) as usize)];
+        let a_uint = U256::from_dec_str(amount_str)?;
+        Ok(a_uint)
+    } else {
+        let mut a_uint = U256::from_dec_str(&amount_str)?;
+        a_uint *= U256::from(10)
+            .checked_pow(U256::from(exponent - dec_len))
+            .ok_or(ConversionError::ParseOverflow)?;
+        Ok(a_uint)
+    }
 }
 /// The address for an Ethereum contract is deterministically computed from the
 /// address of its creator (sender) and how many transactions the creator has
@@ -257,7 +265,7 @@ pub fn get_create2_address_from_hash(
         [&[0xff], from.into().as_bytes(), salt.into().as_ref(), init_code_hash.into().as_ref()]
             .concat();
 
-    let hash = keccak256(&bytes);
+    let hash = keccak256(bytes);
 
     let mut bytes = [0u8; 20];
     bytes.copy_from_slice(&hash[12..]);
@@ -278,10 +286,10 @@ pub fn secret_key_to_address(secret_key: &SigningKey) -> Address {
 /// Ref: <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md>
 pub fn to_checksum(addr: &Address, chain_id: Option<u8>) -> String {
     let prefixed_addr = match chain_id {
-        Some(chain_id) => format!("{}0x{:x}", chain_id, addr),
-        None => format!("{:x}", addr),
+        Some(chain_id) => format!("{chain_id}0x{addr:x}"),
+        None => format!("{addr:x}"),
     };
-    let hash = hex::encode(keccak256(&prefixed_addr));
+    let hash = hex::encode(keccak256(prefixed_addr));
     let hash = hash.as_bytes();
 
     let addr_hex = hex::encode(addr.as_bytes());
@@ -474,6 +482,22 @@ mod tests {
 
         let val = parse_units("2.3", "ether").unwrap();
         assert_eq!(val, U256::from_dec_str("2300000000000000000").unwrap());
+
+        assert_eq!(parse_units(".2", 2).unwrap(), U256::from(20), "leading dot");
+        assert_eq!(parse_units("333.21", 2).unwrap(), U256::from(33321), "trailing dot");
+        assert_eq!(
+            parse_units("98766", 16).unwrap(),
+            U256::from_dec_str("987660000000000000000").unwrap(),
+            "no dot"
+        );
+        assert_eq!(parse_units("3_3_0", 3).unwrap(), U256::from(330000), "underscore");
+        assert_eq!(parse_units("330", 0).unwrap(), U256::from(330), "zero decimals");
+        assert_eq!(parse_units(".1234", 3).unwrap(), U256::from(123), "truncate too many decimals");
+        assert_eq!(parse_units("1", 80).is_err(), true, "overflow");
+        assert_eq!(parse_units("1", -1).is_err(), true, "neg units");
+        let two_e30 = U256::from(2) * U256([0x4674edea40000000, 0xc9f2c9cd0, 0x0, 0x0]);
+        assert_eq!(parse_units("2", 30).unwrap(), two_e30, "2e30");
+        assert_eq!(parse_units(".33_319_2", 0).unwrap(), U256::zero(), "mix");
     }
 
     #[test]
diff --git a/ethers-etherscan/src/account.rs b/ethers-etherscan/src/account.rs
index 71c53ecd..60f6c047 100644
--- a/ethers-etherscan/src/account.rs
+++ b/ethers-etherscan/src/account.rs
@@ -448,16 +448,16 @@ impl TokenQueryOption {
         let mut params: HashMap<&'static str, String> = list_params.into();
         match self {
             TokenQueryOption::ByAddress(address) => {
-                params.insert("address", format!("{:?}", address));
+                params.insert("address", format!("{address:?}"));
                 params
             }
             TokenQueryOption::ByContract(contract) => {
-                params.insert("contractaddress", format!("{:?}", contract));
+                params.insert("contractaddress", format!("{contract:?}"));
                 params
             }
             TokenQueryOption::ByAddressAndContract(address, contract) => {
-                params.insert("address", format!("{:?}", address));
-                params.insert("contractaddress", format!("{:?}", contract));
+                params.insert("address", format!("{address:?}"));
+                params.insert("contractaddress", format!("{contract:?}"));
                 params
             }
         }
@@ -507,7 +507,7 @@ impl Client {
         tag: Option<Tag>,
     ) -> Result<AccountBalance> {
         let tag_str = tag.unwrap_or_default().to_string();
-        let addr_str = format!("{:?}", address);
+        let addr_str = format!("{address:?}");
         let query = self.create_query(
             "account",
             "balance",
@@ -542,7 +542,7 @@ impl Client {
         tag: Option<Tag>,
     ) -> Result<Vec<AccountBalance>> {
         let tag_str = tag.unwrap_or_default().to_string();
-        let addrs = addresses.iter().map(|x| format!("{:?}", x)).collect::<Vec<String>>().join(",");
+        let addrs = addresses.iter().map(|x| format!("{x:?}")).collect::<Vec<String>>().join(",");
         let query: Query<HashMap<&str, &str>> = self.create_query(
             "account",
             "balancemulti",
@@ -577,7 +577,7 @@ impl Client {
         params: Option<TxListParams>,
     ) -> Result<Vec<NormalTransaction>> {
         let mut tx_params: HashMap<&str, String> = params.unwrap_or_default().into();
-        tx_params.insert("address", format!("{:?}", address));
+        tx_params.insert("address", format!("{address:?}"));
         let query = self.create_query("account", "txlist", tx_params);
         let response: Response<Vec<NormalTransaction>> = self.get_json(&query).await?;
 
@@ -608,10 +608,10 @@ impl Client {
         let mut tx_params: HashMap<&str, String> = params.unwrap_or_default().into();
         match tx_query_option {
             InternalTxQueryOption::ByAddress(address) => {
-                tx_params.insert("address", format!("{:?}", address));
+                tx_params.insert("address", format!("{address:?}"));
             }
             InternalTxQueryOption::ByTransactionHash(tx_hash) => {
-                tx_params.insert("txhash", format!("{:?}", tx_hash));
+                tx_params.insert("txhash", format!("{tx_hash:?}"));
             }
             _ => {}
         }
@@ -730,7 +730,7 @@ impl Client {
         page_and_offset: Option<(u64, u64)>,
     ) -> Result<Vec<MinedBlock>> {
         let mut params = HashMap::new();
-        params.insert("address", format!("{:?}", address));
+        params.insert("address", format!("{address:?}"));
         params.insert("blocktype", block_type.unwrap_or_default().to_string());
         if let Some((page, offset)) = page_and_offset {
             params.insert("page", page.to_string());
diff --git a/ethers-etherscan/src/contract.rs b/ethers-etherscan/src/contract.rs
index 86aedda2..ae19abf6 100644
--- a/ethers-etherscan/src/contract.rs
+++ b/ethers-etherscan/src/contract.rs
@@ -132,8 +132,8 @@ pub struct Metadata {
     /// The constructor arguments the contract was deployed with.
     pub constructor_arguments: Bytes,
 
-    /// The version of the EVM the contract was deployed in. Can be either a variant of
-    /// [EvmVersion] or "Default" which indicates the compiler's default.
+    /// The version of the EVM the contract was deployed in. Can be either a variant of EvmVersion
+    /// or "Default" which indicates the compiler's default.
     #[serde(rename = "EVMVersion")]
     pub evm_version: String,
 
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index 4df3ebf2..ce511ad9 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -103,6 +103,7 @@ impl Client {
                 std::env::var("FTMSCAN_API_KEY").or_else(|_| std::env::var("FANTOMSCAN_API_KEY"))?
             }
             Chain::XDai |
+            Chain::Chiado |
             Chain::Sepolia |
             Chain::Rsk |
             Chain::Sokol |
@@ -138,22 +139,22 @@ impl Client {
 
     /// Return the URL for the given block number
     pub fn block_url(&self, block: u64) -> String {
-        format!("{}block/{}", self.etherscan_url, block)
+        format!("{}block/{block}", self.etherscan_url)
     }
 
     /// Return the URL for the given address
     pub fn address_url(&self, address: Address) -> String {
-        format!("{}address/{:?}", self.etherscan_url, address)
+        format!("{}address/{address:?}", self.etherscan_url)
     }
 
     /// Return the URL for the given transaction hash
     pub fn transaction_url(&self, tx_hash: H256) -> String {
-        format!("{}tx/{:?}", self.etherscan_url, tx_hash)
+        format!("{}tx/{tx_hash:?}", self.etherscan_url)
     }
 
     /// Return the URL for the given token hash
     pub fn token_url(&self, token_hash: Address) -> String {
-        format!("{}token/{:?}", self.etherscan_url, token_hash)
+        format!("{}token/{token_hash:?}", self.etherscan_url)
     }
 
     /// Execute an GET request with parameters.
@@ -372,7 +373,7 @@ impl Cache {
     }
 
     fn set<T: Serialize>(&self, prefix: &str, address: Address, item: T) {
-        let path = self.root.join(prefix).join(format!("{:?}.json", address));
+        let path = self.root.join(prefix).join(format!("{address:?}.json"));
         let writer = std::fs::File::create(path).ok().map(std::io::BufWriter::new);
         if let Some(mut writer) = writer {
             let _ = serde_json::to_writer(
@@ -392,7 +393,7 @@ impl Cache {
     }
 
     fn get<T: DeserializeOwned>(&self, prefix: &str, address: Address) -> Option<T> {
-        let path = self.root.join(prefix).join(format!("{:?}.json", address));
+        let path = self.root.join(prefix).join(format!("{address:?}.json"));
         let reader = std::io::BufReader::new(std::fs::File::open(path).ok()?);
         if let Ok(inner) = serde_json::from_reader::<_, CacheEnvelope<T>>(reader) {
             // If this does not return None then we have passed the expiry
@@ -458,7 +459,7 @@ mod tests {
         let etherscan = Client::new_from_env(Chain::Mainnet).unwrap();
         let block: u64 = 1;
         let block_url: String = etherscan.block_url(block);
-        assert_eq!(block_url, format!("https://etherscan.io/block/{}", block));
+        assert_eq!(block_url, format!("https://etherscan.io/block/{block}"));
     }
 
     #[test]
@@ -466,7 +467,7 @@ mod tests {
         let etherscan = Client::new_from_env(Chain::Mainnet).unwrap();
         let addr: Address = Address::zero();
         let address_url: String = etherscan.address_url(addr);
-        assert_eq!(address_url, format!("https://etherscan.io/address/{:?}", addr));
+        assert_eq!(address_url, format!("https://etherscan.io/address/{addr:?}"));
     }
 
     #[test]
@@ -474,7 +475,7 @@ mod tests {
         let etherscan = Client::new_from_env(Chain::Mainnet).unwrap();
         let tx_hash = H256::zero();
         let tx_url: String = etherscan.transaction_url(tx_hash);
-        assert_eq!(tx_url, format!("https://etherscan.io/tx/{:?}", tx_hash));
+        assert_eq!(tx_url, format!("https://etherscan.io/tx/{tx_hash:?}"));
     }
 
     #[test]
@@ -482,7 +483,7 @@ mod tests {
         let etherscan = Client::new_from_env(Chain::Mainnet).unwrap();
         let token_hash = Address::zero();
         let token_url: String = etherscan.token_url(token_hash);
-        assert_eq!(token_url, format!("https://etherscan.io/token/{:?}", token_hash));
+        assert_eq!(token_url, format!("https://etherscan.io/token/{token_hash:?}"));
     }
 
     #[test]
diff --git a/ethers-etherscan/src/verify.rs b/ethers-etherscan/src/verify.rs
index 9b63d6bd..ebe15255 100644
--- a/ethers-etherscan/src/verify.rs
+++ b/ethers-etherscan/src/verify.rs
@@ -57,7 +57,7 @@ impl VerifyContract {
 
     #[must_use]
     pub fn runs(mut self, runs: u32) -> Self {
-        self.runs = Some(format!("{}", runs));
+        self.runs = Some(format!("{runs}"));
         self
     }
 
diff --git a/ethers-middleware/src/signer.rs b/ethers-middleware/src/signer.rs
index b911a660..a6c79e15 100644
--- a/ethers-middleware/src/signer.rs
+++ b/ethers-middleware/src/signer.rs
@@ -1,9 +1,10 @@
 use ethers_core::types::{
     transaction::{eip2718::TypedTransaction, eip2930::AccessListWithGasUsed},
-    Address, BlockId, Bytes, Signature, U256,
+    Address, BlockId, Bytes, Chain, Signature, TransactionRequest, U256,
 };
 use ethers_providers::{maybe, FromErr, Middleware, PendingTransaction};
 use ethers_signers::Signer;
+use std::convert::TryFrom;
 
 use async_trait::async_trait;
 use thiserror::Error;
@@ -251,6 +252,18 @@ where
             tx.set_chain_id(chain_id);
         }
 
+        // If a chain_id is matched to a known chain that doesn't support EIP-1559, automatically
+        // change transaction to be Legacy type.
+        if let Some(chain_id) = tx.chain_id() {
+            let chain = Chain::try_from(chain_id.as_u64());
+            if chain.unwrap_or_default().is_legacy() {
+                if let TypedTransaction::Eip1559(inner) = tx {
+                    let tx_req: TransactionRequest = inner.clone().into();
+                    *tx = TypedTransaction::Legacy(tx_req);
+                }
+            }
+        }
+
         let nonce = maybe(tx.nonce().cloned(), self.get_transaction_count(from, block)).await?;
         tx.set_nonce(nonce);
         self.inner()
@@ -338,7 +351,7 @@ where
 mod tests {
     use super::*;
     use ethers_core::{
-        types::TransactionRequest,
+        types::{Eip1559TransactionRequest, TransactionRequest},
         utils::{self, keccak256, Anvil},
     };
     use ethers_providers::Provider;
@@ -503,4 +516,72 @@ mod tests {
         let tx = client.get_transaction(hash).await.unwrap().unwrap();
         assert_eq!(tx.from, acc);
     }
+
+    #[tokio::test]
+    async fn converts_tx_to_legacy_to_match_chain() {
+        let eip1559 = Eip1559TransactionRequest {
+            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()),
+            access_list: Default::default(),
+            max_priority_fee_per_gas: None,
+            data: None,
+            chain_id: None,
+            max_fee_per_gas: None,
+        };
+        let mut tx = TypedTransaction::Eip1559(eip1559);
+
+        let chain_id = 10u64; // optimism does not support EIP-1559
+
+        // 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);
+        client.fill_transaction(&mut tx, None).await.unwrap();
+
+        assert!(tx.as_eip1559_ref().is_none());
+        assert_eq!(tx, TypedTransaction::Legacy(tx.as_legacy_ref().unwrap().clone()));
+    }
+
+    #[tokio::test]
+    async fn does_not_convert_to_legacy_for_eip1559_chain() {
+        let eip1559 = Eip1559TransactionRequest {
+            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()),
+            access_list: Default::default(),
+            max_priority_fee_per_gas: None,
+            data: None,
+            chain_id: None,
+            max_fee_per_gas: None,
+        };
+        let mut tx = TypedTransaction::Eip1559(eip1559);
+
+        let chain_id = 1u64; // eth main supports EIP-1559
+
+        // 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);
+        client.fill_transaction(&mut tx, None).await.unwrap();
+
+        assert!(tx.as_legacy_ref().is_none());
+        assert_eq!(tx, TypedTransaction::Eip1559(tx.as_eip1559_ref().unwrap().clone()));
+    }
 }
diff --git a/ethers-middleware/src/transformer/ds_proxy/factory.rs b/ethers-middleware/src/transformer/ds_proxy/factory.rs
index c6f0561d..11fb1380 100644
--- a/ethers-middleware/src/transformer/ds_proxy/factory.rs
+++ b/ethers-middleware/src/transformer/ds_proxy/factory.rs
@@ -150,7 +150,7 @@ mod dsproxyfactory_mod {
                     cache: Tokenizable::from_token(iter.next().unwrap())?,
                 })
             } else {
-                Err(InvalidOutputType(format!("Expected Tuple, got {:?}", token)))
+                Err(InvalidOutputType(format!("Expected Tuple, got {token:?}")))
             }
         }
         fn into_token(self) -> Token {
diff --git a/ethers-providers/src/ens.rs b/ethers-providers/src/ens.rs
index 886783e6..f3b0e9de 100644
--- a/ethers-providers/src/ens.rs
+++ b/ethers-providers/src/ens.rs
@@ -72,7 +72,7 @@ pub fn resolve<T: Into<NameOrAddress>>(
 
 /// Returns the reverse-registrar name of an address.
 pub fn reverse_address(addr: Address) -> String {
-    format!("{:?}.{}", addr, ENS_REVERSE_REGISTRAR_DOMAIN)[2..].to_string()
+    format!("{addr:?}.{ENS_REVERSE_REGISTRAR_DOMAIN}")[2..].to_string()
 }
 
 /// Returns the ENS namehash as specified in [EIP-137](https://eips.ethereum.org/EIPS/eip-137)
@@ -83,7 +83,7 @@ pub fn namehash(name: &str) -> H256 {
 
     // iterate in reverse
     name.rsplit('.')
-        .fold([0u8; 32], |node, label| keccak256(&[node, keccak256(label.as_bytes())].concat()))
+        .fold([0u8; 32], |node, label| keccak256([node, keccak256(label.as_bytes())].concat()))
         .into()
 }
 
diff --git a/ethers-providers/src/erc.rs b/ethers-providers/src/erc.rs
index b139f860..ac99b4f4 100644
--- a/ethers-providers/src/erc.rs
+++ b/ethers-providers/src/erc.rs
@@ -38,12 +38,12 @@ impl FromStr for ERCNFT {
         let token_split: Vec<&str> = inner_path.split('/').collect();
         let (contract_addr, token_id) = if token_split.len() == 2 {
             let token_id = U256::from_dec_str(token_split[1])
-                .map_err(|e| format!("Unsupported token id type: {} {}", token_split[1], e))?;
+                .map_err(|e| format!("Unsupported token id type: {} {e}", token_split[1]))?;
             let mut token_id_bytes = [0x0; 32];
             token_id.to_big_endian(&mut token_id_bytes);
             (
                 Address::from_str(token_split[0].trim_start_matches("0x"))
-                    .map_err(|e| format!("Invalid contract address: {} {}", token_split[0], e))?,
+                    .map_err(|e| format!("Invalid contract address: {} {e}", token_split[0]))?,
                 token_id_bytes,
             )
         } else {
diff --git a/ethers-providers/src/log_query.rs b/ethers-providers/src/log_query.rs
index 43cf7086..ea8c145a 100644
--- a/ethers-providers/src/log_query.rs
+++ b/ethers-providers/src/log_query.rs
@@ -93,7 +93,7 @@ where
                         // this is okay because we will only enter this state when the filter is
                         // paginatable i.e. from block is set
                         let from_block = self.filter.get_from_block().unwrap();
-                        let to_block = from_block + self.page_size;
+                        let to_block = std::cmp::min(from_block + self.page_size, last_block);
                         self.from_block = Some(to_block + 1);
 
                         let filter = self.filter.clone().from_block(from_block).to_block(to_block);
@@ -122,7 +122,7 @@ where
                         // load new logs if there are still more pages to go through
                         // can safely assume this will always be set in this state
                         let from_block = self.from_block.unwrap();
-                        let to_block = from_block + self.page_size;
+                        let to_block = std::cmp::min(from_block + self.page_size, self.last_block.unwrap());
 
                         // no more pages to load, and everything is consumed
                         // can safely assume this will always be set in this state
diff --git a/ethers-providers/src/pending_escalator.rs b/ethers-providers/src/pending_escalator.rs
index b156c970..5b77b60d 100644
--- a/ethers-providers/src/pending_escalator.rs
+++ b/ethers-providers/src/pending_escalator.rs
@@ -130,7 +130,7 @@ macro_rules! completed {
 
 /// Tests Provider error for nonce too low issue through debug contents
 fn is_nonce_too_low(e: &ProviderError) -> bool {
-    let debug_str = format!("{:?}", e);
+    let debug_str = format!("{e:?}");
 
     debug_str.contains("nonce too low") // Geth, Arbitrum, Optimism
             || debug_str.contains("nonce is too low") // Parity
diff --git a/ethers-providers/src/pending_transaction.rs b/ethers-providers/src/pending_transaction.rs
index c6ffea54..92a89be8 100644
--- a/ethers-providers/src/pending_transaction.rs
+++ b/ethers-providers/src/pending_transaction.rs
@@ -137,7 +137,7 @@ impl<'a, P> PendingTransaction<'a, P> {
 
     /// Logs the pending transaction hash along with a custom message before it.
     pub fn log_msg<S: std::fmt::Display>(self, msg: S) -> Self {
-        self.inspect(|s| println!("{}: {:?}", msg, **s))
+        self.inspect(|s| println!("{msg}: {:?}", **s))
     }
 
     /// Logs the pending transaction's hash
diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs
index 0b26489e..e38464da 100644
--- a/ethers-providers/src/provider.rs
+++ b/ethers-providers/src/provider.rs
@@ -939,7 +939,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
         };
         let data = self.call(&tx.into(), None).await?;
         let mut metadata_url = Url::parse(&decode_bytes::<String>(ParamType::String, data))
-            .map_err(|e| ProviderError::CustomError(format!("Invalid metadata url: {}", e)))?;
+            .map_err(|e| ProviderError::CustomError(format!("Invalid metadata url: {e}")))?;
 
         if token.type_ == erc::ERCNFTType::ERC1155 {
             metadata_url.set_path(&metadata_url.path().replace("%7Bid%7D", &hex::encode(token.id)));
@@ -1832,7 +1832,7 @@ mod tests {
             ("cdixon.eth", "https://ipfs.io/ipfs/QmYA6ZpEARgHvRHZQdFPynMMX8NtdL2JCadvyuyG2oA88u"),
             ("0age.eth", "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c3ZnIHN0eWxlPSJiYWNrZ3JvdW5kLWNvbG9yOmJsYWNrIiB2aWV3Qm94PSIwIDAgNTAwIDUwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB4PSIxNTUiIHk9IjYwIiB3aWR0aD0iMTkwIiBoZWlnaHQ9IjM5MCIgZmlsbD0iIzY5ZmYzNyIvPjwvc3ZnPg==")
         ] {
-        println!("Resolving: {}", ens_name);
+        println!("Resolving: {ens_name}");
         assert_eq!(provider.resolve_avatar(ens_name).await.unwrap(), Url::parse(res).unwrap());
     }
     }
diff --git a/ethers-providers/src/transports/common.rs b/ethers-providers/src/transports/common.rs
index ad8fa665..45494e33 100644
--- a/ethers-providers/src/transports/common.rs
+++ b/ethers-providers/src/transports/common.rs
@@ -204,8 +204,8 @@ impl Authorization {
 impl fmt::Display for Authorization {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            Authorization::Basic(auth_secret) => write!(f, "Basic {}", auth_secret),
-            Authorization::Bearer(token) => write!(f, "Bearer {}", token),
+            Authorization::Basic(auth_secret) => write!(f, "Basic {auth_secret}"),
+            Authorization::Bearer(token) => write!(f, "Bearer {token}"),
         }
     }
 }
diff --git a/ethers-providers/src/transports/ipc.rs b/ethers-providers/src/transports/ipc.rs
index 61ba86c9..acd111cf 100644
--- a/ethers-providers/src/transports/ipc.rs
+++ b/ethers-providers/src/transports/ipc.rs
@@ -205,7 +205,7 @@ impl Shared {
             match msg {
                 Request { id, request, sender } => {
                     let prev = self.pending.borrow_mut().insert(id, sender);
-                    assert!(prev.is_none(), "replaced pending IPC request (id={})", id);
+                    assert!(prev.is_none(), "{}", "replaced pending IPC request (id={id})");
 
                     if let Err(err) = writer.write_all(&request).await {
                         tracing::error!("IPC connection error: {:?}", err);
diff --git a/ethers-providers/src/transports/retry.rs b/ethers-providers/src/transports/retry.rs
index b871d679..8112e508 100644
--- a/ethers-providers/src/transports/retry.rs
+++ b/ethers-providers/src/transports/retry.rs
@@ -210,7 +210,7 @@ pub enum RetryClientError {
 
 impl std::fmt::Display for RetryClientError {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{:?}", self)
+        write!(f, "{self:?}")
     }
 }
 
diff --git a/ethers-providers/src/transports/ws.rs b/ethers-providers/src/transports/ws.rs
index e43d5ed8..d05970ec 100644
--- a/ethers-providers/src/transports/ws.rs
+++ b/ethers-providers/src/transports/ws.rs
@@ -419,7 +419,7 @@ where
 
 // TrySendError is private :(
 fn to_client_error<T: Debug>(err: T) -> ClientError {
-    ClientError::ChannelError(format!("{:?}", err))
+    ClientError::ChannelError(format!("{err:?}"))
 }
 
 #[derive(Error, Debug)]
diff --git a/ethers-signers/src/ledger/types.rs b/ethers-signers/src/ledger/types.rs
index fd85e59a..73ed6663 100644
--- a/ethers-signers/src/ledger/types.rs
+++ b/ethers-signers/src/ledger/types.rs
@@ -21,8 +21,8 @@ impl fmt::Display for DerivationType {
             f,
             "{}",
             match self {
-                DerivationType::Legacy(index) => format!("m/44'/60'/0'/{}", index),
-                DerivationType::LedgerLive(index) => format!("m/44'/60'/{}'/0/0", index),
+                DerivationType::Legacy(index) => format!("m/44'/60'/0'/{index}"),
+                DerivationType::LedgerLive(index) => format!("m/44'/60'/{index}'/0/0"),
                 DerivationType::Other(inner) => inner.to_owned(),
             }
         )
diff --git a/ethers-signers/src/trezor/types.rs b/ethers-signers/src/trezor/types.rs
index 05b272fc..5cb629ab 100644
--- a/ethers-signers/src/trezor/types.rs
+++ b/ethers-signers/src/trezor/types.rs
@@ -23,7 +23,7 @@ impl fmt::Display for DerivationType {
             f,
             "{}",
             match self {
-                DerivationType::TrezorLive(index) => format!("m/44'/60'/{}'/0/0", index),
+                DerivationType::TrezorLive(index) => format!("m/44'/60'/{index}'/0/0"),
                 DerivationType::Other(inner) => inner.to_owned(),
             }
         )
diff --git a/ethers-signers/src/wallet/private_key.rs b/ethers-signers/src/wallet/private_key.rs
index 4ac06ebd..cdddf00a 100644
--- a/ethers-signers/src/wallet/private_key.rs
+++ b/ethers-signers/src/wallet/private_key.rs
@@ -152,7 +152,7 @@ mod tests {
         // read from the encrypted JSON keystore and decrypt it, while validating that the
         // signatures produced by both the keys should match
         let path = Path::new(dir.path()).join(uuid);
-        let key2 = Wallet::<SigningKey>::decrypt_keystore(&path.clone(), "randpsswd").unwrap();
+        let key2 = Wallet::<SigningKey>::decrypt_keystore(path.clone(), "randpsswd").unwrap();
         let signature2 = key2.sign_message(message).await.unwrap();
         assert_eq!(signature, signature2);
         std::fs::remove_file(&path).unwrap();
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index f275cd72..1d247d58 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -31,7 +31,7 @@ hex = "0.4.3"
 yansi = "0.5.1"
 glob = "0.3.0"
 tracing = "0.1.37"
-num_cpus = "1.13.1"
+num_cpus = "1.14.0"
 tiny-keccak = { version = "2.0.2", default-features = false }
 tempfile = { version = "3.3.0", optional = true }
 fs_extra = { version = "1.2.0", optional = true }
diff --git a/ethers-solc/benches/read_all.rs b/ethers-solc/benches/read_all.rs
index 3baccbb0..1184bcad 100644
--- a/ethers-solc/benches/read_all.rs
+++ b/ethers-solc/benches/read_all.rs
@@ -33,7 +33,7 @@ fn read_all_benchmark(c: &mut Criterion) {
 fn prepare_contracts(root: &Path, num: usize) -> Vec<PathBuf> {
     let mut files = Vec::with_capacity(num);
     for _ in 0..num {
-        let path = root.join(format!("file{}.sol", num));
+        let path = root.join(format!("file{num}.sol"));
         let f = File::create(&path).unwrap();
         let mut writer = BufWriter::new(f);
 
diff --git a/ethers-solc/src/artifact_output/mod.rs b/ethers-solc/src/artifact_output/mod.rs
index 960b43bd..e1ec2854 100644
--- a/ethers-solc/src/artifact_output/mod.rs
+++ b/ethers-solc/src/artifact_output/mod.rs
@@ -62,7 +62,7 @@ impl ArtifactId {
         self
     }
 
-    /// Returns a <filename>:<name> slug that identifies an artifact
+    /// Returns a `<filename>:<name>` slug that identifies an artifact
     ///
     /// Note: This identifier is not necessarily unique. If two contracts have the same name, they
     /// will share the same slug. For a unique identifier see [ArtifactId::identifier].
@@ -70,12 +70,12 @@ impl ArtifactId {
         format!("{}.json:{}", self.path.file_stem().unwrap().to_string_lossy(), self.name)
     }
 
-    /// Returns a <source path>:<name> slug that uniquely identifies an artifact
+    /// Returns a `<source path>:<name>` slug that uniquely identifies an artifact
     pub fn identifier(&self) -> String {
         format!("{}:{}", self.source.to_string_lossy(), self.name)
     }
 
-    /// Returns a <filename><version>:<name> slug that identifies an artifact
+    /// Returns a `<filename><version>:<name>` slug that identifies an artifact
     pub fn slug_versioned(&self) -> String {
         format!(
             "{}.{}.{}.{}.json:{}",
@@ -1133,7 +1133,7 @@ mod tests {
 
         let alternative = ConfigurableArtifacts::conflict_free_output_file(
             &already_taken,
-            conflict.clone(),
+            conflict,
             file,
             "/Users/carter/dev/goldfinch/mono/packages/protocol/artifacts",
         );
diff --git a/ethers-solc/src/artifacts/ast.rs b/ethers-solc/src/artifacts/ast.rs
index 4544e582..122054f8 100644
--- a/ethers-solc/src/artifacts/ast.rs
+++ b/ethers-solc/src/artifacts/ast.rs
@@ -73,7 +73,7 @@ impl FromStr for SourceLocation {
     type Err = String;
 
     fn from_str(s: &str) -> Result<Self, Self::Err> {
-        let invalid_location = move || format!("{} invalid source location", s);
+        let invalid_location = move || format!("{s} invalid source location");
 
         let mut split = s.split(':');
         let start = split
diff --git a/ethers-solc/src/artifacts/bytecode.rs b/ethers-solc/src/artifacts/bytecode.rs
index 0cdac2d8..635aba3c 100644
--- a/ethers-solc/src/artifacts/bytecode.rs
+++ b/ethers-solc/src/artifacts/bytecode.rs
@@ -305,8 +305,8 @@ impl BytecodeObject {
             let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name);
 
             *unlinked = unlinked
-                .replace(&format!("__{}__", fully_qualified_placeholder), &hex_addr)
-                .replace(&format!("__{}__", place_holder), &hex_addr)
+                .replace(&format!("__{fully_qualified_placeholder}__"), &hex_addr)
+                .replace(&format!("__{place_holder}__"), &hex_addr)
         }
         self
     }
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index a53555d7..b4ae1ff8 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -522,14 +522,14 @@ impl Libraries {
         for lib in libs {
             let mut items = lib.split(':');
             let file = items.next().ok_or_else(|| {
-                SolcError::msg(format!("failed to parse path to library file: {}", lib))
+                SolcError::msg(format!("failed to parse path to library file: {lib}"))
             })?;
             let lib = items
                 .next()
-                .ok_or_else(|| SolcError::msg(format!("failed to parse library name: {}", lib)))?;
-            let addr = items.next().ok_or_else(|| {
-                SolcError::msg(format!("failed to parse library address: {}", lib))
-            })?;
+                .ok_or_else(|| SolcError::msg(format!("failed to parse library name: {lib}")))?;
+            let addr = items
+                .next()
+                .ok_or_else(|| SolcError::msg(format!("failed to parse library address: {lib}")))?;
             if items.next().is_some() {
                 return Err(SolcError::msg(format!(
                     "failed to parse, too many arguments passed: {}",
@@ -735,7 +735,7 @@ impl fmt::Display for EvmVersion {
             EvmVersion::London => "london",
             EvmVersion::Byzantium => "byzantium",
         };
-        write!(f, "{}", string)
+        write!(f, "{string}")
     }
 }
 
@@ -753,7 +753,7 @@ impl FromStr for EvmVersion {
             "berlin" => Ok(EvmVersion::Berlin),
             "london" => Ok(EvmVersion::London),
             "byzantium" => Ok(EvmVersion::Byzantium),
-            s => Err(format!("Unknown evm version: {}", s)),
+            s => Err(format!("Unknown evm version: {s}")),
         }
     }
 }
@@ -807,7 +807,7 @@ impl fmt::Display for RevertStrings {
             RevertStrings::Debug => "debug",
             RevertStrings::VerboseDebug => "verboseDebug",
         };
-        write!(f, "{}", string)
+        write!(f, "{string}")
     }
 }
 
@@ -820,7 +820,7 @@ impl FromStr for RevertStrings {
             "strip" => Ok(RevertStrings::Strip),
             "debug" => Ok(RevertStrings::Debug),
             "verboseDebug" | "verbosedebug" => Ok(RevertStrings::VerboseDebug),
-            s => Err(format!("Unknown evm version: {}", s)),
+            s => Err(format!("Unknown evm version: {s}")),
         }
     }
 }
@@ -887,7 +887,7 @@ impl FromStr for BytecodeHash {
             "none" => Ok(BytecodeHash::None),
             "ipfs" => Ok(BytecodeHash::Ipfs),
             "bzzr1" => Ok(BytecodeHash::Bzzr1),
-            s => Err(format!("Unknown bytecode hash: {}", s)),
+            s => Err(format!("Unknown bytecode hash: {s}")),
         }
     }
 }
@@ -1050,7 +1050,7 @@ impl fmt::Display for ModelCheckerEngine {
             ModelCheckerEngine::BMC => "bmc",
             ModelCheckerEngine::CHC => "chc",
         };
-        write!(f, "{}", string)
+        write!(f, "{string}")
     }
 }
 
@@ -1063,7 +1063,7 @@ impl FromStr for ModelCheckerEngine {
             "all" => Ok(ModelCheckerEngine::All),
             "bmc" => Ok(ModelCheckerEngine::BMC),
             "chc" => Ok(ModelCheckerEngine::CHC),
-            s => Err(format!("Unknown model checker engine: {}", s)),
+            s => Err(format!("Unknown model checker engine: {s}")),
         }
     }
 }
@@ -1100,7 +1100,7 @@ impl fmt::Display for ModelCheckerTarget {
             ModelCheckerTarget::OutOfBounds => "outOfBounds",
             ModelCheckerTarget::Balance => "balance",
         };
-        write!(f, "{}", string)
+        write!(f, "{string}")
     }
 }
 
@@ -1117,7 +1117,7 @@ impl FromStr for ModelCheckerTarget {
             "popEmptyArray" => Ok(ModelCheckerTarget::PopEmptyArray),
             "outOfBounds" => Ok(ModelCheckerTarget::OutOfBounds),
             "balance" => Ok(ModelCheckerTarget::Balance),
-            s => Err(format!("Unknown model checker target: {}", s)),
+            s => Err(format!("Unknown model checker target: {s}")),
         }
     }
 }
@@ -1729,13 +1729,13 @@ impl fmt::Display for Error {
             match self.severity {
                 Severity::Error => {
                     if let Some(code) = self.error_code {
-                        Paint::red(format!("error[{}]: ", code)).fmt(f)?;
+                        Paint::red(format!("error[{code}]: ")).fmt(f)?;
                     }
                     Paint::red(msg).fmt(f)
                 }
                 Severity::Warning | Severity::Info => {
                     if let Some(code) = self.error_code {
-                        Paint::yellow(format!("warning[{}]: ", code)).fmt(f)?;
+                        Paint::yellow(format!("warning[{code}]: ")).fmt(f)?;
                     }
                     Paint::yellow(msg).fmt(f)
                 }
@@ -1786,7 +1786,7 @@ impl FromStr for Severity {
             "error" => Ok(Severity::Error),
             "warning" => Ok(Severity::Warning),
             "info" => Ok(Severity::Info),
-            s => Err(format!("Invalid severity: {}", s)),
+            s => Err(format!("Invalid severity: {s}")),
         }
     }
 }
diff --git a/ethers-solc/src/artifacts/output_selection.rs b/ethers-solc/src/artifacts/output_selection.rs
index a016b6ef..b6017d91 100644
--- a/ethers-solc/src/artifacts/output_selection.rs
+++ b/ethers-solc/src/artifacts/output_selection.rs
@@ -256,7 +256,7 @@ impl FromStr for ContractOutputSelection {
             s => EvmOutputSelection::from_str(s)
                 .map(ContractOutputSelection::Evm)
                 .or_else(|_| EwasmOutputSelection::from_str(s).map(ContractOutputSelection::Ewasm))
-                .map_err(|_| format!("Invalid contract output selection: {}", s)),
+                .map_err(|_| format!("Invalid contract output selection: {s}")),
         }
     }
 }
@@ -347,7 +347,7 @@ impl FromStr for EvmOutputSelection {
                     DeployedBytecodeOutputSelection::from_str(s)
                         .map(EvmOutputSelection::DeployedByteCode)
                 })
-                .map_err(|_| format!("Invalid evm selection: {}", s)),
+                .map_err(|_| format!("Invalid evm selection: {s}")),
         }
     }
 }
@@ -412,7 +412,7 @@ impl FromStr for BytecodeOutputSelection {
             "evm.bytecode.sourceMap" => Ok(BytecodeOutputSelection::SourceMap),
             "evm.bytecode.linkReferences" => Ok(BytecodeOutputSelection::LinkReferences),
             "evm.bytecode.generatedSources" => Ok(BytecodeOutputSelection::GeneratedSources),
-            s => Err(format!("Invalid bytecode selection: {}", s)),
+            s => Err(format!("Invalid bytecode selection: {s}")),
         }
     }
 }
@@ -494,7 +494,7 @@ impl FromStr for DeployedBytecodeOutputSelection {
             "evm.deployedBytecode.immutableReferences" => {
                 Ok(DeployedBytecodeOutputSelection::ImmutableReferences)
             }
-            s => Err(format!("Invalid deployedBytecode selection: {}", s)),
+            s => Err(format!("Invalid deployedBytecode selection: {s}")),
         }
     }
 }
@@ -543,7 +543,7 @@ impl FromStr for EwasmOutputSelection {
             "ewasm" => Ok(EwasmOutputSelection::All),
             "ewasm.wast" => Ok(EwasmOutputSelection::Wast),
             "ewasm.wasm" => Ok(EwasmOutputSelection::Wasm),
-            s => Err(format!("Invalid ewasm selection: {}", s)),
+            s => Err(format!("Invalid ewasm selection: {s}")),
         }
     }
 }
diff --git a/ethers-solc/src/artifacts/serde_helpers.rs b/ethers-solc/src/artifacts/serde_helpers.rs
index 75e87ee5..c24c263b 100644
--- a/ethers-solc/src/artifacts/serde_helpers.rs
+++ b/ethers-solc/src/artifacts/serde_helpers.rs
@@ -120,7 +120,7 @@ pub mod string_bytes {
         if value.starts_with("0x") {
             serializer.serialize_str(value.as_str())
         } else {
-            serializer.serialize_str(&format!("0x{}", value))
+            serializer.serialize_str(&format!("0x{value}"))
         }
     }
 
diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs
index 6d44f3ce..fe40cfb9 100644
--- a/ethers-solc/src/compile/mod.rs
+++ b/ethers-solc/src/compile/mod.rs
@@ -149,7 +149,7 @@ impl Default for Solc {
         #[cfg(not(target_arch = "wasm32"))]
         {
             if let Some(solc) = Solc::svm_global_version()
-                .and_then(|vers| Solc::find_svm_installed_version(&vers.to_string()).ok())
+                .and_then(|vers| Solc::find_svm_installed_version(vers.to_string()).ok())
                 .flatten()
             {
                 return solc
@@ -279,7 +279,7 @@ impl Solc {
         let solc = Self::svm_home()
             .ok_or_else(|| SolcError::solc("svm home dir not found"))?
             .join(version)
-            .join(format!("solc-{}", version));
+            .join(format!("solc-{version}"));
 
         if !solc.is_file() {
             return Ok(None)
@@ -688,7 +688,7 @@ fn version_from_output(output: Output) -> Result<Version> {
             .lines()
             .last()
             .ok_or_else(|| SolcError::solc("version not found in solc output"))?
-            .map_err(|err| SolcError::msg(format!("Failed to read output: {}", err)))?;
+            .map_err(|err| SolcError::msg(format!("Failed to read output: {err}")))?;
         // NOTE: semver doesn't like `+` in g++ in build metadata which is invalid semver
         Ok(Version::from_str(&version.trim_start_matches("Version: ").replace(".g++", ".gcc"))?)
     } else {
@@ -858,8 +858,8 @@ mod tests {
         {
             Solc::blocking_install(&version).unwrap();
         }
-        let res = Solc::find_svm_installed_version(&version.to_string()).unwrap().unwrap();
-        let expected = svm::SVM_HOME.join(ver).join(format!("solc-{}", ver));
+        let res = Solc::find_svm_installed_version(version.to_string()).unwrap().unwrap();
+        let expected = svm::SVM_HOME.join(ver).join(format!("solc-{ver}"));
         assert_eq!(res.solc, expected);
     }
 
@@ -876,7 +876,7 @@ mod tests {
     fn does_not_find_not_installed_version() {
         let ver = "1.1.1";
         let version = Version::from_str(ver).unwrap();
-        let res = Solc::find_svm_installed_version(&version.to_string()).unwrap();
+        let res = Solc::find_svm_installed_version(version.to_string()).unwrap();
         assert!(res.is_none());
     }
 
@@ -908,6 +908,6 @@ mod tests {
     ///// helpers
 
     fn source(version: &str) -> Source {
-        Source { content: format!("pragma solidity {};\n", version) }
+        Source { content: format!("pragma solidity {version};\n") }
     }
 }
diff --git a/ethers-solc/src/compile/output/info.rs b/ethers-solc/src/compile/output/info.rs
index 3094bd9a..1d2be6d6 100644
--- a/ethers-solc/src/compile/output/info.rs
+++ b/ethers-solc/src/compile/output/info.rs
@@ -41,7 +41,7 @@ impl ContractInfo {
 impl fmt::Display for ContractInfo {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         if let Some(ref path) = self.path {
-            write!(f, "{}:{}", path, self.name)
+            write!(f, "{path}:{}", self.name)
         } else {
             write!(f, "{}", self.name)
         }
diff --git a/ethers-solc/src/compile/output/mod.rs b/ethers-solc/src/compile/output/mod.rs
index 57198cb0..294c4e48 100644
--- a/ethers-solc/src/compile/output/mod.rs
+++ b/ethers-solc/src/compile/output/mod.rs
@@ -752,10 +752,10 @@ impl<'a> fmt::Display for OutputDiagnostics<'a> {
                 });
 
                 if !is_ignored {
-                    writeln!(f, "\n{}", err)?;
+                    writeln!(f, "\n{err}")?;
                 }
             } else {
-                writeln!(f, "\n{}", err)?;
+                writeln!(f, "\n{err}")?;
             }
         }
         Ok(())
diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs
index 735afeaf..390c4a18 100644
--- a/ethers-solc/src/config.rs
+++ b/ethers-solc/src/config.rs
@@ -230,7 +230,7 @@ impl ProjectPathsConfig {
             // if the import is relative we assume it's already part of the processed input
             // file set
             utils::canonicalize(cwd.join(import)).map_err(|err| {
-                SolcError::msg(format!("failed to resolve relative import \"{:?}\"", err))
+                SolcError::msg(format!("failed to resolve relative import \"{err:?}\""))
             })
         } else {
             // resolve library file
@@ -477,7 +477,7 @@ impl ProjectPathsConfig {
         }
 
         let result = String::from_utf8(content).map_err(|err| {
-            SolcError::msg(format!("failed to convert extended bytes to string: {}", err))
+            SolcError::msg(format!("failed to convert extended bytes to string: {err}"))
         })?;
 
         Ok(result)
@@ -497,7 +497,7 @@ impl fmt::Display for ProjectPathsConfig {
         }
         writeln!(f, "remappings:")?;
         for remapping in &self.remappings {
-            writeln!(f, "    {}", remapping)?;
+            writeln!(f, "    {remapping}")?;
         }
         Ok(())
     }
@@ -588,7 +588,7 @@ impl PathStyle {
                 .artifacts(root.join("out"))
                 .build_infos(root.join("out").join("build-info"))
                 .lib(root.join("lib"))
-                .remappings(Remapping::find_many(&root.join("lib")))
+                .remappings(Remapping::find_many(root.join("lib")))
                 .root(root)
                 .build()?,
             PathStyle::HardHat => ProjectPathsConfig::builder()
@@ -883,7 +883,7 @@ impl fmt::Display for AllowedLibPaths {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         let lib_paths =
             self.paths().map(|path| format!("{}", path.display())).collect::<Vec<_>>().join(",");
-        write!(f, "{}", lib_paths)
+        write!(f, "{lib_paths}")
     }
 }
 
diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs
index 935a4c33..94805ca6 100644
--- a/ethers-solc/src/lib.rs
+++ b/ethers-solc/src/lib.rs
@@ -981,9 +981,9 @@ mod tests {
             .lib(root.join("lib1"))
             .lib(root.join("lib2"))
             .remappings(
-                Remapping::find_many(&root.join("lib1"))
+                Remapping::find_many(root.join("lib1"))
                     .into_iter()
-                    .chain(Remapping::find_many(&root.join("lib2"))),
+                    .chain(Remapping::find_many(root.join("lib2"))),
             )
             .build()
             .unwrap();
@@ -1009,7 +1009,7 @@ mod tests {
             .root(&root)
             .sources(root.join("src"))
             .lib(root.join("lib"))
-            .remappings(Remapping::find_many(&root.join("lib")))
+            .remappings(Remapping::find_many(root.join("lib")))
             .build()
             .unwrap();
         let project = Project::builder().no_artifacts().paths(paths).ephemeral().build().unwrap();
diff --git a/ethers-solc/src/project_util/mock.rs b/ethers-solc/src/project_util/mock.rs
index 25744cb4..8a467aea 100644
--- a/ethers-solc/src/project_util/mock.rs
+++ b/ethers-solc/src/project_util/mock.rs
@@ -395,15 +395,15 @@ pub struct SimpleNamingStrategy {
 
 impl NamingStrategy for SimpleNamingStrategy {
     fn new_source_file_name(&mut self, id: usize) -> String {
-        format!("SourceFile{}", id)
+        format!("SourceFile{id}")
     }
 
     fn new_lib_file_name(&mut self, id: usize) -> String {
-        format!("LibFile{}", id)
+        format!("LibFile{id}")
     }
 
     fn new_lib_name(&mut self, id: usize) -> String {
-        format!("Lib{}", id)
+        format!("Lib{id}")
     }
 }
 
diff --git a/ethers-solc/src/project_util/mod.rs b/ethers-solc/src/project_util/mod.rs
index b5a35e5d..a7653731 100644
--- a/ethers-solc/src/project_util/mod.rs
+++ b/ethers-solc/src/project_util/mod.rs
@@ -388,7 +388,7 @@ fn contract_file_name(name: impl AsRef<str>) -> String {
     if name.ends_with(".sol") {
         name.to_string()
     } else {
-        format!("{}.sol", name)
+        format!("{name}.sol")
     }
 }
 
@@ -419,7 +419,7 @@ impl TempProject<ConfigurableArtifacts> {
     pub fn dapptools_init() -> Result<Self> {
         let mut project = Self::dapptools()?;
         let orig_root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample");
-        copy_dir(&orig_root, project.root())?;
+        copy_dir(orig_root, project.root())?;
         project.project_mut().paths.remappings = Remapping::find_many(project.root());
 
         Ok(project)
diff --git a/ethers-solc/src/remappings.rs b/ethers-solc/src/remappings.rs
index 0120e452..0006d603 100644
--- a/ethers-solc/src/remappings.rs
+++ b/ethers-solc/src/remappings.rs
@@ -217,7 +217,7 @@ impl Remapping {
                 if let Some(name) = candidate.window_start.file_name().and_then(|s| s.to_str()) {
                     insert_prioritized(
                         &mut all_remappings,
-                        format!("{}/", name),
+                        format!("{name}/"),
                         candidate.source_dir,
                     );
                 }
@@ -778,7 +778,7 @@ mod tests {
                 touch(&path).unwrap();
             } else {
                 let path = tmp.join(path);
-                std::fs::create_dir_all(&path).unwrap();
+                std::fs::create_dir_all(path).unwrap();
             }
         }
     }
@@ -801,7 +801,7 @@ mod tests {
         assert_eq!(remappings.len(), 1);
 
         assert_eq!(remappings[0].name, "repo1/");
-        assert_eq!(remappings[0].path, format!("{}/src/", path));
+        assert_eq!(remappings[0].path, format!("{path}/src/"));
     }
 
     #[test]
@@ -1015,7 +1015,7 @@ mod tests {
             "node_modules/@openzeppelin/contracts/token/ERC20/IERC20.sol",
         ];
         mkdir_or_touch(tmp_dir.path(), &paths[..]);
-        let remappings = Remapping::find_many(&tmp_dir_node_modules);
+        let remappings = Remapping::find_many(tmp_dir_node_modules);
         let mut paths = ProjectPathsConfig::hardhat(tmp_dir.path()).unwrap();
         paths.remappings = remappings;
 
@@ -1054,7 +1054,7 @@ mod tests {
         mkdir_or_touch(tmp_dir_path, &paths[..]);
 
         let path = tmp_dir_path.display().to_string();
-        let mut remappings = Remapping::find_many(&path);
+        let mut remappings = Remapping::find_many(path);
         remappings.sort_unstable();
 
         let mut expected = vec![
@@ -1115,7 +1115,7 @@ mod tests {
         touch(&contract2).unwrap();
 
         let path = tmp_dir_path.display().to_string();
-        let mut remappings = Remapping::find_many(&path);
+        let mut remappings = Remapping::find_many(path);
         remappings.sort_unstable();
         let mut expected = vec![
             Remapping {
@@ -1151,7 +1151,7 @@ mod tests {
         mkdir_or_touch(tmp_dir_path, &paths[..]);
 
         let path = tmp_dir_path.display().to_string();
-        let mut remappings = Remapping::find_many(&path);
+        let mut remappings = Remapping::find_many(path);
         remappings.sort_unstable();
 
         let mut expected = vec![
diff --git a/ethers-solc/src/report/compiler.rs b/ethers-solc/src/report/compiler.rs
index b1e74edd..c39dbb72 100644
--- a/ethers-solc/src/report/compiler.rs
+++ b/ethers-solc/src/report/compiler.rs
@@ -163,7 +163,7 @@ pub struct BadName {
 fn get_file_name(path: impl Into<PathBuf>, v: &Version) -> PathBuf {
     let mut path = path.into();
     if let Some(stem) = path.file_stem().and_then(|s| s.to_str().map(|s| s.to_string())) {
-        path.set_file_name(format!("{}.{}.{}.{}.json", stem, v.major, v.minor, v.patch));
+        path.set_file_name(format!("{stem}.{}.{}.{}.json", v.major, v.minor, v.patch));
     }
     path
 }
diff --git a/ethers-solc/src/report/mod.rs b/ethers-solc/src/report/mod.rs
index f664afca..99cd60f9 100644
--- a/ethers-solc/src/report/mod.rs
+++ b/ethers-solc/src/report/mod.rs
@@ -370,23 +370,23 @@ impl Reporter for BasicStdoutReporter {
     ) {
         self.solc_io_report.log_compiler_output(output, version);
         println!(
-            "Solc {}.{}.{} finished in {:.2?}",
-            version.major, version.minor, version.patch, duration
+            "Solc {}.{}.{} finished in {duration:.2?}",
+            version.major, version.minor, version.patch
         );
     }
 
     /// Invoked before a new [`Solc`] bin is installed
     fn on_solc_installation_start(&self, version: &Version) {
-        println!("installing solc version \"{}\"", version);
+        println!("installing solc version \"{version}\"");
     }
 
     /// Invoked before a new [`Solc`] bin was successfully installed
     fn on_solc_installation_success(&self, version: &Version) {
-        println!("Successfully installed solc {}", version);
+        println!("Successfully installed solc {version}");
     }
 
     fn on_solc_installation_error(&self, version: &Version, error: &str) {
-        eprintln!("Failed to install solc {}: {}", version, error);
+        eprintln!("Failed to install solc {version}: {error}");
     }
 
     fn on_unresolved_imports(&self, imports: &[(&Path, &Path)], remappings: &[Remapping]) {
diff --git a/ethers-solc/src/resolver/mod.rs b/ethers-solc/src/resolver/mod.rs
index 252af2ca..985f686d 100644
--- a/ethers-solc/src/resolver/mod.rs
+++ b/ethers-solc/src/resolver/mod.rs
@@ -787,7 +787,7 @@ impl VersionedSources {
             } else {
                 // find installed svm
                 Solc::find_svm_installed_version(version.to_string())?.ok_or_else(|| {
-                    SolcError::msg(format!("solc \"{}\" should have been installed", version))
+                    SolcError::msg(format!("solc \"{version}\" should have been installed"))
                 })?
             };
 
diff --git a/ethers-solc/src/resolver/tree.rs b/ethers-solc/src/resolver/tree.rs
index 9716613d..829dc0c0 100644
--- a/ethers-solc/src/resolver/tree.rs
+++ b/ethers-solc/src/resolver/tree.rs
@@ -27,7 +27,7 @@ impl FromStr for Charset {
         match s {
             "utf8" => Ok(Charset::Utf8),
             "ascii" => Ok(Charset::Ascii),
-            s => Err(format!("invalid charset: {}", s)),
+            s => Err(format!("invalid charset: {s}")),
         }
     }
 }
@@ -103,7 +103,7 @@ fn print_node(
     if let Some((last_continues, rest)) = levels_continue.split_last() {
         for continues in rest {
             let c = if *continues { symbols.down } else { " " };
-            write!(out, "{}   ", c)?;
+            write!(out, "{c}   ")?;
         }
 
         let c = if *last_continues { symbols.tee } else { symbols.ell };
@@ -117,7 +117,7 @@ fn print_node(
     let has_deps = graph.has_outgoing_edges(node_index);
     let star = if (new_node && !in_cycle) || !has_deps { "" } else { " (*)" };
 
-    writeln!(out, "{}{}", graph.display_node(node_index), star)?;
+    writeln!(out, "{}{star}", graph.display_node(node_index))?;
 
     if !new_node || in_cycle {
         return Ok(())
diff --git a/ethers-solc/src/sourcemap.rs b/ethers-solc/src/sourcemap.rs
index bc77f62e..5e722368 100644
--- a/ethers-solc/src/sourcemap.rs
+++ b/ethers-solc/src/sourcemap.rs
@@ -38,7 +38,7 @@ enum Token<'a> {
 impl<'a> fmt::Debug for Token<'a> {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
         match self {
-            Token::Number(s) => write!(f, "NUMBER({:?})", s),
+            Token::Number(s) => write!(f, "NUMBER({s:?})"),
             Token::Semicolon => write!(f, "SEMICOLON"),
             Token::Colon => write!(f, "COLON"),
             Token::In => write!(f, "JMP(i)"),
@@ -291,7 +291,7 @@ impl SourceElementBuilder {
 
     fn set_jmp(&mut self, jmp: Jump, i: usize) -> Option<SyntaxError> {
         if self.jump.is_some() {
-            return Some(SyntaxError::new(format!("Jump already set: {}", i)))
+            return Some(SyntaxError::new(format!("Jump already set: {i}")))
         }
         self.jump = Some(jmp);
         None
@@ -299,7 +299,7 @@ impl SourceElementBuilder {
 
     fn set_offset(&mut self, offset: usize, i: usize) -> Option<SyntaxError> {
         if self.offset.is_some() {
-            return Some(SyntaxError::new(format!("Offset already set: {}", i)))
+            return Some(SyntaxError::new(format!("Offset already set: {i}")))
         }
         self.offset = Some(offset);
         None
@@ -307,7 +307,7 @@ impl SourceElementBuilder {
 
     fn set_length(&mut self, length: usize, i: usize) -> Option<SyntaxError> {
         if self.length.is_some() {
-            return Some(SyntaxError::new(format!("Length already set: {}", i)))
+            return Some(SyntaxError::new(format!("Length already set: {i}")))
         }
         self.length = Some(length);
         None
@@ -315,7 +315,7 @@ impl SourceElementBuilder {
 
     fn set_index(&mut self, index: Option<u32>, i: usize) -> Option<SyntaxError> {
         if self.index.is_some() {
-            return Some(SyntaxError::new(format!("Index already set: {}", i)))
+            return Some(SyntaxError::new(format!("Index already set: {i}")))
         }
         self.index = Some(index);
         None
@@ -323,7 +323,7 @@ impl SourceElementBuilder {
 
     fn set_modifier(&mut self, modifier_depth: usize, i: usize) -> Option<SyntaxError> {
         if self.modifier_depth.is_some() {
-            return Some(SyntaxError::new(format!("Modifier depth already set: {}", i)))
+            return Some(SyntaxError::new(format!("Modifier depth already set: {i}")))
         }
         self.modifier_depth = Some(modifier_depth);
         None
@@ -486,7 +486,7 @@ impl State {
             State::Length => *self = State::Index,
             State::Index => *self = State::Jmp,
             State::Jmp => *self = State::Modifier,
-            State::Modifier => return Some(SyntaxError::new(format!("unexpected colon at {}", i))),
+            State::Modifier => return Some(SyntaxError::new(format!("unexpected colon at {i}"))),
         }
         None
     }
diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs
index 0e23b010..b39053ce 100644
--- a/ethers-solc/src/utils.rs
+++ b/ethers-solc/src/utils.rs
@@ -43,7 +43,7 @@ pub static RE_THREE_OR_MORE_NEWLINES: Lazy<Regex> = Lazy::new(|| Regex::new("\n{
 
 /// Create a regex that matches any library or contract name inside a file
 pub fn create_contract_or_lib_name_regex(name: &str) -> Regex {
-    Regex::new(&format!(r#"(?:using\s+(?P<n1>{name})\s+|is\s+(?:\w+\s*,\s*)*(?P<n2>{name})(?:\s*,\s*\w+)*|(?:(?P<ignore>(?:function|error|as)\s+|\n[^\n]*(?:"([^"\n]|\\")*|'([^'\n]|\\')*))|\W+)(?P<n3>{name})(?:\.|\(| ))"#, name = name)).unwrap()
+    Regex::new(&format!(r#"(?:using\s+(?P<n1>{name})\s+|is\s+(?:\w+\s*,\s*)*(?P<n2>{name})(?:\s*,\s*\w+)*|(?:(?P<ignore>(?:function|error|as)\s+|\n[^\n]*(?:"([^"\n]|\\")*|'([^'\n]|\\')*))|\W+)(?P<n3>{name})name.|\(| ))"#)).unwrap()
 }
 
 /// Move a range by a specified offset
@@ -285,7 +285,7 @@ pub fn library_fully_qualified_placeholder(name: impl AsRef<str>) -> String {
 pub fn library_hash_placeholder(name: impl AsRef<[u8]>) -> String {
     let hash = library_hash(name);
     let placeholder = hex::encode(hash);
-    format!("${}$", placeholder)
+    format!("${placeholder}$")
 }
 
 /// Returns the library placeholder for the given name
diff --git a/examples/abigen.rs b/examples/abigen.rs
index 33dd69d3..e0886687 100644
--- a/examples/abigen.rs
+++ b/examples/abigen.rs
@@ -7,7 +7,7 @@ fn main() -> eyre::Result<()> {
     let contract_name = args.next().unwrap_or_else(|| "SimpleStorage".to_owned());
     let contract: String = args.next().unwrap_or_else(|| "examples/contract.sol".to_owned());
 
-    println!("Generating bindings for {}\n", contract);
+    println!("Generating bindings for {contract}\n");
 
     // compile it
     let abi = if contract.ends_with(".sol") {
@@ -22,7 +22,7 @@ fn main() -> eyre::Result<()> {
 
     // print to stdout if no output arg is given
     if let Some(output_path) = args.next() {
-        bindings.write_to_file(&output_path)?;
+        bindings.write_to_file(output_path)?;
     } else {
         bindings.write(std::io::stdout())?;
     }
diff --git a/examples/contract_human_readable.rs b/examples/contract_human_readable.rs
index 3148dc58..d0ca18c7 100644
--- a/examples/contract_human_readable.rs
+++ b/examples/contract_human_readable.rs
@@ -67,7 +67,7 @@ async fn main() -> Result<()> {
     // 11. get the new value
     let value = contract.get_value().call().await?;
 
-    println!("Value: {}. Logs: {}", value, serde_json::to_string(&logs)?);
+    println!("Value: {value}. Logs: {}", serde_json::to_string(&logs)?);
 
     Ok(())
 }
diff --git a/examples/contract_with_abi.rs b/examples/contract_with_abi.rs
index 35425777..c77d094f 100644
--- a/examples/contract_with_abi.rs
+++ b/examples/contract_with_abi.rs
@@ -56,7 +56,7 @@ async fn main() -> Result<()> {
     // 11. get the new value
     let value = contract.get_value().call().await?;
 
-    println!("Value: {}. Logs: {}", value, serde_json::to_string(&logs)?);
+    println!("Value: {value}. Logs: {}", serde_json::to_string(&logs)?);
 
     Ok(())
 }
diff --git a/examples/decode_tx_input.rs b/examples/decode_tx_input.rs
index c52f1c36..f11b6f1b 100644
--- a/examples/decode_tx_input.rs
+++ b/examples/decode_tx_input.rs
@@ -18,8 +18,8 @@ fn main() -> Result<()> {
     let from = path.next().unwrap();
     let to = path.next().unwrap();
     println!(
-        "Swapped {} of token {} for {} of token {}",
-        decoded.amount_in, from, decoded.amount_out_min, to
+        "Swapped {} of token {from} for {} of token {to}",
+        decoded.amount_in, decoded.amount_out_min
     );
 
     Ok(())
diff --git a/examples/ethers-wasm/src/lib.rs b/examples/ethers-wasm/src/lib.rs
index b3b960d7..2e18ad03 100644
--- a/examples/ethers-wasm/src/lib.rs
+++ b/examples/ethers-wasm/src/lib.rs
@@ -66,7 +66,7 @@ pub async fn deploy() {
     let value = contract.get_value().call().await.unwrap();
 
     console::log_2(
-        &format!("Value: `{}`. Logs: ", value).into(),
+        &format!("Value: `{value}`. Logs: ").into(),
         &serde_wasm_bindgen::to_value(&logs).unwrap(),
     );
 }
diff --git a/examples/geth_trace_transaction.rs b/examples/geth_trace_transaction.rs
index dac3c62f..ef20f738 100644
--- a/examples/geth_trace_transaction.rs
+++ b/examples/geth_trace_transaction.rs
@@ -12,7 +12,7 @@ async fn main() -> Result<()> {
         let h: H256 = H256::from_str(tx_hash)?;
         let options: GethDebugTracingOptions = GethDebugTracingOptions::default();
         let traces = client.debug_trace_transaction(h, options).await?;
-        println!("{:?}", traces);
+        println!("{traces:?}");
     }
 
     Ok(())
diff --git a/examples/ipc.rs b/examples/ipc.rs
index a4c9e4d7..048f0f3e 100644
--- a/examples/ipc.rs
+++ b/examples/ipc.rs
@@ -7,7 +7,7 @@ async fn main() -> eyre::Result<()> {
         .await?
         .interval(std::time::Duration::from_millis(2000));
     let block = provider.get_block_number().await?;
-    println!("Current block: {}", block);
+    println!("Current block: {block}");
     let mut stream = provider.watch_blocks().await?.stream();
     while let Some(block) = stream.next().await {
         dbg!(block);
diff --git a/examples/moonbeam_with_abi.rs b/examples/moonbeam_with_abi.rs
index 3b63af58..e17500bc 100644
--- a/examples/moonbeam_with_abi.rs
+++ b/examples/moonbeam_with_abi.rs
@@ -65,7 +65,7 @@ async fn main() -> eyre::Result<()> {
     // 11. get the new value
     let value = contract.get_value().call().await?;
 
-    println!("Value: {}. Logs: {}", value, serde_json::to_string(&logs)?);
+    println!("Value: {value}. Logs: {}", serde_json::to_string(&logs)?);
 
     Ok(())
 }
diff --git a/examples/paginated_logs.rs b/examples/paginated_logs.rs
index 689d80ad..9dbe8ae9 100644
--- a/examples/paginated_logs.rs
+++ b/examples/paginated_logs.rs
@@ -10,7 +10,7 @@ async fn main() -> Result<()> {
     let client = Arc::new(client);
 
     let last_block = client.get_block(BlockNumber::Latest).await?.unwrap().number.unwrap();
-    println!("last_block: {}", last_block);
+    println!("last_block: {last_block}");
 
     let erc20_transfer_filter = Filter::new()
         .from_block(last_block - 10000)
diff --git a/examples/remove_liquidity.rs b/examples/remove_liquidity.rs
index 1694ddf5..0d3e889d 100644
--- a/examples/remove_liquidity.rs
+++ b/examples/remove_liquidity.rs
@@ -49,12 +49,12 @@ async fn example() -> Result<()> {
 
     let (reserve0, reserve1, _) = pair.get_reserves().call().await?;
 
-    println!("Reserves (token A, Token B): ({}, {})", reserve0, reserve1);
+    println!("Reserves (token A, Token B): ({reserve0}, {reserve1})");
 
     let price =
         if reserve0 > reserve1 { 1000 * reserve0 / reserve1 } else { 1000 * reserve1 / reserve0 } /
             1000;
-    println!("token0 / token1 price = {}", price);
+    println!("token0 / token1 price = {price}");
 
     let liquidity = 100.into();
 
@@ -62,9 +62,9 @@ async fn example() -> Result<()> {
     let receipt =
         pair.approve(router.address(), liquidity).send().await?.await?.expect("no receipt found");
     println!("contract approved succesfully!");
-    println!("{:?}", receipt);
+    println!("{receipt:?}");
 
-    println!("Removing {} liquidity!", liquidity);
+    println!("Removing {liquidity} liquidity!");
 
     let token0 = pair.token_0().call().await?;
     let token1 = pair.token_1().call().await?;
@@ -84,7 +84,7 @@ async fn example() -> Result<()> {
         .await?
         .expect("no receipt for remove_liquidity");
     println!("liquidity removed succesfully!");
-    println!("{:?}", receipt);
+    println!("{receipt:?}");
 
     Ok(())
 }
diff --git a/examples/sign.rs b/examples/sign.rs
index a3c2d79d..6f6b592e 100644
--- a/examples/sign.rs
+++ b/examples/sign.rs
@@ -16,7 +16,7 @@ async fn main() -> Result<()> {
 
     // sign message from your wallet and print out signature produced.
     let signature = wallet.sign_message(message).await?;
-    println!("Produced signature {}", signature);
+    println!("Produced signature {signature}");
 
     // verify the signature produced from your wallet.
     signature.verify(message, wallet.address()).unwrap();
diff --git a/examples/subscribe_contract_events.rs b/examples/subscribe_contract_events.rs
new file mode 100644
index 00000000..a6f769f9
--- /dev/null
+++ b/examples/subscribe_contract_events.rs
@@ -0,0 +1,38 @@
+use ethers::prelude::*;
+use eyre::Result;
+use std::sync::Arc;
+
+// Generate the type-safe contract bindings by providing the ABI
+// definition in human readable format
+abigen!(
+    ERC20,
+    r#"[
+        event  Transfer(address indexed src, address indexed dst, uint wad)
+    ]"#,
+);
+
+// In order to run this example you need to include Ws and TLS features
+// Run this example with
+// `cargo run -p ethers --example subscribe_contract_events --features="ws","rustls"`
+#[tokio::main]
+async fn main() -> Result<()> {
+    let client =
+        Provider::<Ws>::connect("wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27")
+            .await?;
+
+    let client = Arc::new(client);
+
+    // WETH Token
+    let address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse::<Address>()?;
+    let weth = ERC20::new(address, Arc::clone(&client));
+
+    // Subscribe Transfer events
+    let events = weth.events();
+    let mut stream = events.stream().await?;
+
+    while let Some(Ok(event)) = stream.next().await {
+        println!("src: {:?}, dst: {:?}, wad: {:?}", event.src, event.dst, event.wad);
+    }
+
+    Ok(())
+}
diff --git a/examples/subscribe_contract_events_with_meta.rs b/examples/subscribe_contract_events_with_meta.rs
new file mode 100644
index 00000000..c81b4da8
--- /dev/null
+++ b/examples/subscribe_contract_events_with_meta.rs
@@ -0,0 +1,53 @@
+use ethers::prelude::*;
+use eyre::Result;
+use std::sync::Arc;
+
+// Generate the type-safe contract bindings by providing the ABI
+// definition in human readable format
+abigen!(
+    ERC20,
+    r#"[
+        event  Transfer(address indexed src, address indexed dst, uint wad)
+    ]"#,
+);
+
+// In order to run this example you need to include Ws and TLS features
+// Run this example with
+// `cargo run -p ethers --example subscribe_contract_events_with_meta --features="ws","rustls"`
+#[tokio::main]
+async fn main() -> Result<()> {
+    let client =
+        Provider::<Ws>::connect("wss://mainnet.infura.io/ws/v3/c60b0bb42f8a4c6481ecd229eddaca27")
+            .await?;
+
+    let client = Arc::new(client);
+
+    // WETH Token
+    let address = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2".parse::<Address>()?;
+    let weth = ERC20::new(address, Arc::clone(&client));
+
+    // Subscribe Transfer events
+    let events = weth.events();
+    let mut stream = events.stream().await?.with_meta();
+    while let Some(Ok((event, meta))) = stream.next().await {
+        println!("src: {:?}, dst: {:?}, wad: {:?}", event.src, event.dst, event.wad);
+
+        println!(
+            r#"address: {:?}, 
+               block_number: {:?}, 
+               block_hash: {:?}, 
+               transaction_hash: {:?}, 
+               transaction_index: {:?}, 
+               log_index: {:?}
+            "#,
+            meta.address,
+            meta.block_number,
+            meta.block_hash,
+            meta.transaction_hash,
+            meta.transaction_index,
+            meta.log_index
+        );
+    }
+
+    Ok(())
+}
diff --git a/examples/subscribe_logs.rs b/examples/subscribe_logs.rs
index 18216db9..02788a9c 100644
--- a/examples/subscribe_logs.rs
+++ b/examples/subscribe_logs.rs
@@ -2,6 +2,8 @@ use ethers::{abi::AbiDecode, prelude::*, utils::keccak256};
 use eyre::Result;
 use std::sync::Arc;
 
+// In order to run this example you need to include Ws and TLS features
+// Run this example with `cargo run -p ethers --example subscribe_logs --features="ws","rustls"`
 #[tokio::main]
 async fn main() -> Result<()> {
     let client =
@@ -10,7 +12,7 @@ async fn main() -> Result<()> {
     let client = Arc::new(client);
 
     let last_block = client.get_block(BlockNumber::Latest).await?.unwrap().number.unwrap();
-    println!("last_block: {}", last_block);
+    println!("last_block: {last_block}");
 
     let erc20_transfer_filter = Filter::new()
         .from_block(last_block - 25)
diff --git a/examples/transfer_eth.rs b/examples/transfer_eth.rs
index 5326dd99..3be81cc9 100644
--- a/examples/transfer_eth.rs
+++ b/examples/transfer_eth.rs
@@ -30,8 +30,8 @@ async fn main() -> Result<()> {
     let balance_after = provider.get_balance(from, None).await?;
     assert!(balance_after < balance_before);
 
-    println!("Balance before {}", balance_before);
-    println!("Balance after {}", balance_after);
+    println!("Balance before {balance_before}");
+    println!("Balance after {balance_after}");
 
     Ok(())
 }
diff --git a/examples/uniswapv2.rs b/examples/uniswapv2.rs
index c2c51655..8b956112 100644
--- a/examples/uniswapv2.rs
+++ b/examples/uniswapv2.rs
@@ -24,9 +24,9 @@ async fn main() -> Result<()> {
 
     // getReserves -> get_reserves
     let (reserve0, reserve1, _timestamp) = pair.get_reserves().call().await?;
-    println!("Reserves (ETH, USDT): ({}, {})", reserve0, reserve1);
+    println!("Reserves (ETH, USDT): ({reserve0}, {reserve1})");
 
     let mid_price = f64::powi(10.0, 18 - 6) * reserve1 as f64 / reserve0 as f64;
-    println!("ETH/USDT price: {:.2}", mid_price);
+    println!("ETH/USDT price: {mid_price:.2}");
     Ok(())
 }
diff --git a/src/lib.rs b/src/lib.rs
index ce0e9515..151575b4 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,5 +1,5 @@
 #![warn(missing_debug_implementations, missing_docs, rust_2018_idioms, unreachable_pub)]
-#![deny(broken_intra_doc_links)]
+#![deny(rustdoc::broken_intra_doc_links)]
 #![doc(test(
     no_crate_inject,
     attr(deny(warnings, rust_2018_idioms), allow(dead_code, unused_variables))


Test patch:
diff --git a/ethers-contract/tests/it/abigen.rs b/ethers-contract/tests/it/abigen.rs
index dbcaa1df..f38f9538 100644
--- a/ethers-contract/tests/it/abigen.rs
+++ b/ethers-contract/tests/it/abigen.rs
@@ -85,7 +85,7 @@ fn can_gen_structs_readable() {
     assert_codec::<Value>();
     assert_codec::<Addresses>();
     let encoded = addr.clone().encode();
-    let other = Addresses::decode(&encoded).unwrap();
+    let other = Addresses::decode(encoded).unwrap();
     assert_eq!(addr, other);
 }
 
@@ -178,7 +178,7 @@ fn can_gen_return_struct() {
         binding: T,
     ) {
         let encoded = binding.clone().encode();
-        let decoded = T::decode(&encoded).unwrap();
+        let decoded = T::decode(encoded).unwrap();
         assert_eq!(binding, decoded);
     }
 
diff --git a/ethers-contract/tests/it/common/derive.rs b/ethers-contract/tests/it/common/derive.rs
index aeaff216..477ee04f 100644
--- a/ethers-contract/tests/it/common/derive.rs
+++ b/ethers-contract/tests/it/common/derive.rs
@@ -394,7 +394,7 @@ fn eth_display_works() {
         hex::encode(&item.v),
     );
 
-    assert_eq!(val, format!("{}", item));
+    assert_eq!(val, format!("{item}"));
 }
 
 #[test]
@@ -408,9 +408,9 @@ fn eth_display_works_for_human_readable() {
     );
 
     let log = LogFilter("abc".to_string());
-    assert_eq!("abc".to_string(), format!("{}", log));
+    assert_eq!("abc".to_string(), format!("{log}"));
     let log = Log2Filter { x: "abc".to_string() };
-    assert_eq!("abc".to_string(), format!("{}", log));
+    assert_eq!("abc".to_string(), format!("{log}"));
 }
 
 #[test]
@@ -493,7 +493,7 @@ fn can_derive_abi_codec() {
     let val = SomeType { inner: Default::default(), msg: "hello".to_string() };
 
     let encoded = val.clone().encode();
-    let other = SomeType::decode(&encoded).unwrap();
+    let other = SomeType::decode(encoded).unwrap();
     assert_eq!(val, other);
 }
 
@@ -603,7 +603,7 @@ fn eth_display_works_on_ethers_bytes() {
     }
     let call = LogBytesCall { p_0: hex::decode(b"aaaaaa").unwrap().into() };
 
-    let s = format!("{}", call);
+    let s = format!("{call}");
     assert_eq!(s, "0xaaaaaa");
 }
 
diff --git a/ethers-contract/tests/it/common/mod.rs b/ethers-contract/tests/it/common/mod.rs
index 7ed4be56..91be1686 100644
--- a/ethers-contract/tests/it/common/mod.rs
+++ b/ethers-contract/tests/it/common/mod.rs
@@ -31,7 +31,7 @@ pub struct ValueChanged {
 /// compiles the given contract and returns the ABI and Bytecode
 #[track_caller]
 pub fn compile_contract(name: &str, filename: &str) -> (Abi, Bytes) {
-    let path = format!("./tests/solidity-contracts/{}", filename);
+    let path = format!("./tests/solidity-contracts/{filename}");
     let compiled = Solc::default().compile_source(&path).unwrap();
     let contract = compiled.get(&path, name).expect("could not find contract");
     let (abi, bin, _) = contract.into_parts_or_default();
diff --git a/ethers-middleware/tests/signer.rs b/ethers-middleware/tests/signer.rs
index d941f289..df00babe 100644
--- a/ethers-middleware/tests/signer.rs
+++ b/ethers-middleware/tests/signer.rs
@@ -245,7 +245,7 @@ async fn deploy_and_call_contract() {
 
     // compiles the given contract and returns the ABI and Bytecode
     fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
-        let path = format!("./tests/solidity-contracts/{}", path);
+        let path = format!("./tests/solidity-contracts/{path}");
         let compiled = Solc::default().compile_source(&path).unwrap();
         let contract = compiled.get(&path, name).expect("could not find contract");
         let (abi, bin, _) = contract.into_parts_or_default();
@@ -308,7 +308,7 @@ impl TestWallets {
         let mut nonce = provider.get_transaction_count(addr, None).await.unwrap();
         let mut pending_txs = Vec::new();
         for addr in addrs {
-            println!("Funding wallet {:?}", addr);
+            println!("Funding wallet {addr:?}");
             let tx = TransactionRequest::new()
                 .nonce(nonce)
                 .to(addr)
diff --git a/ethers-middleware/tests/transformer.rs b/ethers-middleware/tests/transformer.rs
index 8f74d224..f27ef7f0 100644
--- a/ethers-middleware/tests/transformer.rs
+++ b/ethers-middleware/tests/transformer.rs
@@ -16,7 +16,7 @@ type HttpWallet = SignerMiddleware<Provider<Http>, LocalWallet>;
 
 // compiles the given contract and returns the ABI and Bytecode
 fn compile_contract(path: &str, name: &str) -> (Abi, Bytes) {
-    let path = format!("./tests/solidity-contracts/{}", path);
+    let path = format!("./tests/solidity-contracts/{path}");
     let compiled = Solc::default().compile_source(&path).unwrap();
     let contract = compiled.get(&path, name).expect("could not find contract");
     let (abi, bin, _) = contract.into_parts_or_default();
diff --git a/ethers-providers/tests/log_query.rs b/ethers-providers/tests/log_query.rs
new file mode 100644
index 00000000..641272c6
--- /dev/null
+++ b/ethers-providers/tests/log_query.rs
@@ -0,0 +1,21 @@
+#![cfg(not(target_arch = "wasm32"))]
+use ethers_core::{
+    types::{Filter, BlockNumber},
+    utils::Anvil,
+};
+use ethers_providers::{Http, Middleware, Provider, StreamExt};
+use std::convert::TryFrom;
+
+#[tokio::test]
+async fn get_logs_paginated() {
+    let geth = Anvil::new().block_time(20u64).spawn();
+    let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
+
+    let filter = Filter::new().from_block(BlockNumber::Latest);
+    // try to get beyond the number of blocks available
+    let mut stream = provider.get_logs_paginated(&filter, 10);
+    let res = stream.next().await;
+    assert!(res.is_some());
+    let log = res.unwrap();
+    assert!(log.is_ok());
+}
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index fa7de6d9..1814610f 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -421,7 +421,7 @@ fn can_compile_dapp_sample_with_cache() {
     );
 
     // deleted artifact is not taken from the cache
-    std::fs::remove_file(&project.paths.sources.join("Dapp.sol")).unwrap();
+    std::fs::remove_file(project.paths.sources.join("Dapp.sol")).unwrap();
     let compiled: ProjectCompileOutput<_> = project.compile().unwrap();
     assert!(compiled.find_first("Dapp").is_none());
 }
@@ -636,7 +636,7 @@ contract FooBar {}
 fn can_flatten_on_solang_failure() {
     let root =
         PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/test-flatten-solang-failure");
-    let paths = ProjectPathsConfig::builder().sources(&root.join("contracts"));
+    let paths = ProjectPathsConfig::builder().sources(root.join("contracts"));
     let project = TempProject::<ConfigurableArtifacts>::new(paths).unwrap();
 
     let target = root.join("contracts/Contract.sol");
@@ -1231,7 +1231,7 @@ fn can_recompile_with_changes() {
     assert!(compiled.is_unchanged());
 
     // modify A.sol
-    tmp.add_source("A", format!("{}\n", content)).unwrap();
+    tmp.add_source("A", format!("{content}\n")).unwrap();
     let compiled = tmp.compile().unwrap();
     assert!(!compiled.has_compiler_errors());
     assert!(!compiled.is_unchanged());
@@ -1286,7 +1286,7 @@ fn can_recompile_with_lowercase_names() {
     assert!(compiled.is_unchanged());
 
     // modify upgradeProxy.sol
-    tmp.add_source("upgradeProxy.sol", format!("{}\n", upgrade)).unwrap();
+    tmp.add_source("upgradeProxy.sol", format!("{upgrade}\n")).unwrap();
     let compiled = tmp.compile().unwrap();
     assert!(!compiled.has_compiler_errors());
     assert!(!compiled.is_unchanged());
@@ -1339,7 +1339,7 @@ fn can_recompile_unchanged_with_empty_files() {
     assert!(compiled.is_unchanged());
 
     // modify C.sol
-    tmp.add_source("C", format!("{}\n", c)).unwrap();
+    tmp.add_source("C", format!("{c}\n")).unwrap();
     let compiled = tmp.compile().unwrap();
     assert!(!compiled.has_compiler_errors());
     assert!(!compiled.is_unchanged());