Repository: gakonst/ethers-rs
Pull request #704 (2021-12-18 11:34:39)

feat(abigen): add EthAbiCodec proc macro
<!--
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
Add AbiEncode + AbiDecode implementation for custom structs.
Currently, those get derived for EthCall only.

<!--
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.
-->

## Solution
Add `EthAbiCodec` as a complementary proc macro to `EthAbiType` that derives encode/decode.
The reason why this needs to be separate and not included in `EthAbiType` is because in EthCall we have an additional leading 4byte selector, so `EthCall` needs to implement the codec traits but requires all the tokenized support that `EthAbiType` provides.
So we simply add an additional `EthAbiCodec` derive when we generate AbiEncoderV2 structs within abigen!
<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [x] Added Documentation
- [x] Updated the changelog


mattsse: Fixed clippy lints on #705
sveitser: Awesome. It works well on the struct I was testing with.

I noticed something that seems a bit strange to me. In order to `abi.decode` in the contract I am wrapping the struct in a one-element tuple then tokenize and encode.

If I use the new decode method on that I do get and extra `32` in front  (compared to doing `into_tokens` followed by `encode`).

Tried to illustrate this below
```rust
    abigen!(
        Example,
        r#"[
        struct Foo { uint256[] a; }
        function readFoo(bytes b)
        ]"#,
        event_derives(serde::Deserialize, serde::Serialize);
    );

    fn disp(b: &[u8]) {
        hex::encode(b)
            .chars()
            .collect::<Vec<char>>()
            .chunks(64)
            .for_each(|x| println!("{}", x.iter().collect::<String>()));
        println!("");
    }

    let encoded = foo.clone().encode();
    disp(&encoded);
    // 0000000000000000000000000000000000000000000000000000000000000020
    // 0000000000000000000000000000000000000000000000000000000000000001
    // 00000000000000000000000000000000000000000000000000000000000000ff

    // same as encode above
    let from_tokens = abi::encode(&Tokenize::into_tokens(foo.clone()));
    disp(&from_tokens);
    // 0000000000000000000000000000000000000000000000000000000000000020
    // 0000000000000000000000000000000000000000000000000000000000000001
    // 00000000000000000000000000000000000000000000000000000000000000ff

    let encoded_wrapped = (foo.clone(),).encode();
    disp(&encoded_wrapped);
    // 0000000000000000000000000000000000000000000000000000000000000020  X
    // 0000000000000000000000000000000000000000000000000000000000000020  X
    // 0000000000000000000000000000000000000000000000000000000000000020
    // 0000000000000000000000000000000000000000000000000000000000000001
    // 00000000000000000000000000000000000000000000000000000000000000ff

    // this works with abi.decode(b, (Foo)) in a contract
    let from_tokens_wrapped = abi::encode(&Tokenize::into_tokens((foo.clone(),)));
    disp(&from_tokens_wrapped);
    // 0000000000000000000000000000000000000000000000000000000000000020  X
    // 0000000000000000000000000000000000000000000000000000000000000020
    // 0000000000000000000000000000000000000000000000000000000000000001
    // 00000000000000000000000000000000000000000000000000000000000000ff
```
mattsse: nice catch!
I think I fixed tuple codec now, could you try again please @sveitser?
sveitser: Yes. I'm getting matching outputs now for the tuple :partying_face: 

```
    let encoded_wrapped = (foo.clone(),).encode();
    disp(&encoded_wrapped);
    // 0000000000000000000000000000000000000000000000000000000000000020  X
    // 0000000000000000000000000000000000000000000000000000000000000020
    // 0000000000000000000000000000000000000000000000000000000000000001
    // 00000000000000000000000000000000000000000000000000000000000000ff
```

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8aa17a08..8271eb7c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -30,6 +30,8 @@
 
 ### Unreleased
 
+- `Remapping::find_many` does not return a `Result` anymore
+  [#707](https://github.com/gakonst/ethers-rs/pull/707)
 - Add support for hardhat artifacts
   [#677](https://github.com/gakonst/ethers-rs/pull/677)
 - Add more utility functions to the `Artifact` trait
@@ -41,6 +43,8 @@
 
 ### 0.6.0
 
+- add `EthAbiCodec` proc macro to derive `AbiEncode` `AbiDecode` implementation
+  [#704](https://github.com/gakonst/ethers-rs/pull/704)
 - move `AbiEncode` `AbiDecode` trait to ethers-core and implement for core types
   [#531](https://github.com/gakonst/ethers-rs/pull/531)
 - Add EIP-712 `sign_typed_data` signer method; add ethers-core type `Eip712`
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
index 64a90bcd..ec5fa69a 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
@@ -129,7 +129,7 @@ impl Context {
         let ethers_contract = ethers_contract_crate();
         Ok(quote! {
             #abi_signature_doc
-            #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #derives)]
+            #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
             pub struct #name {
                 #( #fields ),*
             }
@@ -191,7 +191,7 @@ impl Context {
 
         Ok(quote! {
             #abi_signature_doc
-            #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #derives)]
+            #[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthAbiType, #ethers_contract::EthAbiCodec, #derives)]
             pub struct #name {
                 #( #fields ),*
             }
diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs
index 238d78d1..ef243408 100644
--- a/ethers-contract/ethers-contract-abigen/src/lib.rs
+++ b/ethers-contract/ethers-contract-abigen/src/lib.rs
@@ -81,6 +81,7 @@ impl Abigen {
 
     /// Manually adds a solidity event alias to specify what the event struct
     /// and function name will be in Rust.
+    #[must_use]
     pub fn add_event_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
     where
         S1: Into<String>,
@@ -93,6 +94,7 @@ impl Abigen {
     /// Manually adds a solidity method alias to specify what the method name
     /// will be in Rust. For solidity methods without an alias, the snake cased
     /// method name will be used.
+    #[must_use]
     pub fn add_method_alias<S1, S2>(mut self, signature: S1, alias: S2) -> Self
     where
         S1: Into<String>,
@@ -107,6 +109,7 @@ impl Abigen {
     ///
     /// Note that in case `rustfmt` does not exist or produces an error, the
     /// unformatted code will be used.
+    #[must_use]
     pub fn rustfmt(mut self, rustfmt: bool) -> Self {
         self.rustfmt = rustfmt;
         self
@@ -116,6 +119,7 @@ impl Abigen {
     ///
     /// This makes it possible to for example derive serde::Serialize and
     /// serde::Deserialize for events.
+    #[must_use]
     pub fn add_event_derive<S>(mut self, derive: S) -> Self
     where
         S: Into<String>,
diff --git a/ethers-contract/ethers-contract-derive/src/codec.rs b/ethers-contract/ethers-contract-derive/src/codec.rs
new file mode 100644
index 00000000..54a577f2
--- /dev/null
+++ b/ethers-contract/ethers-contract-derive/src/codec.rs
@@ -0,0 +1,33 @@
+//! Helper functions for deriving `EthAbiType`
+
+use ethers_core::macros::ethers_core_crate;
+
+use quote::quote;
+use syn::DeriveInput;
+
+/// Generates the `AbiEncode` + `AbiDecode` implementation
+pub fn derive_codec_impl(input: &DeriveInput) -> proc_macro2::TokenStream {
+    let name = &input.ident;
+    let core_crate = ethers_core_crate();
+
+    quote! {
+        impl  #core_crate::abi::AbiDecode for #name {
+            fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, #core_crate::abi::AbiError> {
+                if let #core_crate::abi::ParamType::Tuple(params) = <Self as #core_crate::abi::AbiType>::param_type() {
+                  let tokens = #core_crate::abi::decode(&params, bytes.as_ref())?;
+                  Ok(<Self as #core_crate::abi::Tokenizable>::from_token(#core_crate::abi::Token::Tuple(tokens))?)
+                } else {
+                    Err(
+                        #core_crate::abi::InvalidOutputType("Expected tuple".to_string()).into()
+                    )
+                }
+            }
+        }
+        impl #core_crate::abi::AbiEncode for #name {
+            fn encode(self) -> ::std::vec::Vec<u8> {
+                let tokens =  #core_crate::abi::Tokenize::into_tokens(self);
+                #core_crate::abi::encode(&tokens)
+            }
+        }
+    }
+}
diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs
index 1ab7bec0..5bb89d56 100644
--- a/ethers-contract/ethers-contract-derive/src/lib.rs
+++ b/ethers-contract/ethers-contract-derive/src/lib.rs
@@ -10,6 +10,7 @@ use abigen::Contracts;
 pub(crate) mod abi_ty;
 mod abigen;
 mod call;
+mod codec;
 mod display;
 mod event;
 mod spanned;
@@ -43,7 +44,7 @@ pub(crate) mod utils;
 /// `ETHERSCAN_API_KEY` environment variable can be set. If it is, it will use
 /// that API key when retrieving the contract ABI.
 ///
-/// Currently the proc macro accepts additional parameters to configure some
+/// Currently, the proc macro accepts additional parameters to configure some
 /// aspects of the code generation. Specifically it accepts:
 /// - `methods`: A list of mappings from method signatures to method names allowing methods names to
 ///   be explicitely set for contract methods. This also provides a workaround for generating code
@@ -94,7 +95,7 @@ pub fn abigen(input: TokenStream) -> TokenStream {
     contracts.expand().unwrap_or_else(|err| err.to_compile_error()).into()
 }
 
-/// Derives the `Tokenizable` trait for the labeled type.
+/// Derives the `AbiType` and all `Tokenizable` traits for the labeled type.
 ///
 /// This derive macro automatically adds a type bound `field: Tokenizable` for
 /// each field type.
@@ -104,6 +105,36 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
     TokenStream::from(abi_ty::derive_tokenizeable_impl(&input))
 }
 
+/// Derives the `AbiEncode`, `AbiDecode` and traits for the labeled type.
+///
+/// This is an addition to `EthAbiType` that lacks the `AbiEncode`, `AbiDecode` implementation.
+///
+/// The reason why this is a separate macro is the `AbiEncode` / `AbiDecode` are `ethers`
+/// generalized codec traits used for types, calls, etc. However, encoding/decoding a call differs
+/// from the basic encoding/decoding, (`[selector + encode(self)]`)
+///
+/// # Example
+///
+/// ```ignore
+/// use ethers_contract::{EthAbiCodec, EthAbiType};
+/// use ethers_core::types::*;
+///
+/// #[derive(Debug, Clone, EthAbiType, EthAbiCodec)]
+/// struct MyStruct {
+///     addr: Address,
+///     old_value: String,
+///     new_value: String,
+/// }
+/// let val = MyStruct {..};
+/// let bytes = val.encode();
+/// let val = MyStruct::decode(&bytes).unwrap();
+/// ```
+#[proc_macro_derive(EthAbiCodec)]
+pub fn derive_abi_codec(input: TokenStream) -> TokenStream {
+    let input = parse_macro_input!(input as DeriveInput);
+    TokenStream::from(codec::derive_codec_impl(&input))
+}
+
 /// Derives `fmt::Display` trait and generates a convenient format for all the
 /// underlying primitive types/tokens.
 ///
@@ -113,7 +144,7 @@ pub fn derive_abi_type(input: TokenStream) -> TokenStream {
 /// # Example
 ///
 /// ```ignore
-/// use ethers_contract::EthDisplay;
+/// use ethers_contract::{EthDisplay, EthAbiType};
 /// use ethers_core::types::*;
 ///
 /// #[derive(Debug, Clone, EthAbiType, EthDisplay)]
diff --git a/ethers-contract/src/call.rs b/ethers-contract/src/call.rs
index 22228369..0abdc23e 100644
--- a/ethers-contract/src/call.rs
+++ b/ethers-contract/src/call.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::return_self_not_must_use)]
+
 use super::base::{decode_function_data, AbiError};
 use ethers_core::{
     abi::{AbiDecode, AbiEncode, Detokenize, Function, InvalidOutputType, Tokenizable},
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index 43f60c5f..de909145 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -247,6 +247,7 @@ impl<M: Middleware> Contract<M> {
     /// Returns a new contract instance at `address`.
     ///
     /// Clones `self` internally
+    #[must_use]
     pub fn at<T: Into<Address>>(&self, address: T) -> Self
     where
         M: Clone,
@@ -259,6 +260,7 @@ impl<M: Middleware> Contract<M> {
     /// Returns a new contract instance using the provided client
     ///
     /// Clones `self` internally
+    #[must_use]
     pub fn connect(&self, client: Arc<M>) -> Self
     where
         M: Clone,
diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs
index dc88d78a..370bcd70 100644
--- a/ethers-contract/src/event.rs
+++ b/ethers-contract/src/event.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::return_self_not_must_use)]
+
 use crate::{log::LogMeta, stream::EventStream, ContractError, EthLogDecode};
 
 use ethers_core::{
diff --git a/ethers-contract/src/factory.rs b/ethers-contract/src/factory.rs
index bca08aea..eee777bc 100644
--- a/ethers-contract/src/factory.rs
+++ b/ethers-contract/src/factory.rs
@@ -24,17 +24,20 @@ pub struct Deployer<M> {
 
 impl<M: Middleware> Deployer<M> {
     /// Sets the number of confirmations to wait for the contract deployment transaction
+    #[must_use]
     pub fn confirmations<T: Into<usize>>(mut self, confirmations: T) -> Self {
         self.confs = confirmations.into();
         self
     }
 
+    #[must_use]
     pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
         self.block = block.into();
         self
     }
 
     /// Uses a Legacy transaction instead of an EIP-1559 one to do the deployment
+    #[must_use]
     pub fn legacy(mut self) -> Self {
         self.tx = match self.tx {
             TypedTransaction::Eip1559(inner) => {
diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs
index 2af9a18c..984fef7d 100644
--- a/ethers-contract/src/lib.rs
+++ b/ethers-contract/src/lib.rs
@@ -35,7 +35,7 @@ pub use ethers_contract_abigen::Abigen;
 
 #[cfg(any(test, feature = "abigen"))]
 #[cfg_attr(docsrs, doc(cfg(feature = "abigen")))]
-pub use ethers_contract_derive::{abigen, EthAbiType, EthCall, EthDisplay, EthEvent};
+pub use ethers_contract_derive::{abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthEvent};
 
 // Hide the Lazy re-export, it's just for convenience
 #[doc(hidden)]
diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs
index ebc4225d..61ca2701 100644
--- a/ethers-contract/src/multicall/mod.rs
+++ b/ethers-contract/src/multicall/mod.rs
@@ -173,12 +173,14 @@ impl<M: Middleware> Multicall<M> {
     }
 
     /// Makes a legacy transaction instead of an EIP-1559 one
+    #[must_use]
     pub fn legacy(mut self) -> Self {
         self.legacy = true;
         self
     }
 
     /// Sets the `block` field for the multicall aggregate call
+    #[must_use]
     pub fn block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
         self.block = Some(block.into());
         self
diff --git a/ethers-core/src/abi/codec.rs b/ethers-core/src/abi/codec.rs
index ba8cdce1..f4de400c 100644
--- a/ethers-core/src/abi/codec.rs
+++ b/ethers-core/src/abi/codec.rs
@@ -1,5 +1,7 @@
 use crate::{
-    abi::{AbiArrayType, AbiError, AbiType, Detokenize, Tokenizable, TokenizableItem},
+    abi::{
+        AbiArrayType, AbiError, AbiType, Detokenize, Token, Tokenizable, TokenizableItem, Tokenize,
+    },
     types::{Address, H256, U128, U256},
 };
 
@@ -109,8 +111,7 @@ macro_rules! impl_abi_codec_tuple {
             )+
         {
             fn encode(self) -> Vec<u8> {
-                let token = self.into_token();
-                crate::abi::encode(&[token]).into()
+                crate::abi::encode(&self.into_tokens()).into()
             }
         }
 
@@ -119,10 +120,14 @@ macro_rules! impl_abi_codec_tuple {
                 $ty: AbiType +  Tokenizable,
             )+ {
                 fn decode(bytes: impl AsRef<[u8]>) -> Result<Self, AbiError> {
-                    let tokens = crate::abi::decode(
-                    &[Self::param_type()], bytes.as_ref()
-                    )?;
-                    Ok(<Self as Detokenize>::from_tokens(tokens)?)
+                    if let crate::abi::ParamType::Tuple(params) = <Self as AbiType>::param_type() {
+                      let tokens = crate::abi::decode(&params, bytes.as_ref())?;
+                      Ok(<Self as Tokenizable>::from_token(Token::Tuple(tokens))?)
+                    } else {
+                        Err(
+                            crate::abi::InvalidOutputType("Expected tuple".to_string()).into()
+                        )
+                    }
                 }
         }
     }
diff --git a/ethers-core/src/types/i256.rs b/ethers-core/src/types/i256.rs
index 03fdd84b..9a115545 100644
--- a/ethers-core/src/types/i256.rs
+++ b/ethers-core/src/types/i256.rs
@@ -329,6 +329,7 @@ impl I256 {
     }
 
     /// Returns the sign of the number.
+    #[must_use]
     pub fn signum(self) -> Self {
         self.signum64().into()
     }
@@ -370,6 +371,7 @@ impl I256 {
     /// # Panics
     ///
     /// In debug mode, will panic if it overflows.
+    #[must_use]
     pub fn abs(self) -> Self {
         handle_overflow(self.overflowing_abs())
     }
@@ -402,12 +404,14 @@ impl I256 {
 
     /// Saturating absolute value. Computes `self.abs()`, returning `MAX` if
     /// `self == MIN` instead of overflowing.
+    #[must_use]
     pub fn saturating_abs(self) -> Self {
         self.checked_abs().unwrap_or(I256::MAX)
     }
 
     /// Wrapping absolute value. Computes `self.abs()`, wrapping around at the
     /// boundary of the type.
+    #[must_use]
     pub fn wrapping_abs(self) -> Self {
         let (result, _) = self.overflowing_abs();
         result
@@ -446,12 +450,14 @@ impl I256 {
 
     /// Saturating negation. Computes `self.neg()`, returning `MAX` if
     /// `self == MIN` instead of overflowing.
+    #[must_use]
     pub fn saturating_neg(self) -> Self {
         self.checked_neg().unwrap_or(I256::MAX)
     }
 
     /// Wrapping negation. Computes `self.neg()`, returning `MIN` if
     /// `self == MIN` instead of overflowing.
+    #[must_use]
     pub fn wrapping_neg(self) -> Self {
         let (result, _) = self.overflowing_neg();
         result
@@ -567,6 +573,7 @@ impl I256 {
     }
 
     /// Addition which saturates at the maximum value (Self::max_value()).
+    #[must_use]
     pub fn saturating_add(self, other: Self) -> Self {
         let (result, overflow) = self.overflowing_add(other);
         if overflow {
@@ -580,6 +587,7 @@ impl I256 {
     }
 
     /// Wrapping addition.
+    #[must_use]
     pub fn wrapping_add(self, other: Self) -> Self {
         let (result, _) = self.overflowing_add(other);
         result
@@ -620,6 +628,7 @@ impl I256 {
     }
 
     /// Subtraction which saturates at zero.
+    #[must_use]
     pub fn saturating_sub(self, other: Self) -> Self {
         let (result, overflow) = self.overflowing_sub(other);
         if overflow {
@@ -633,6 +642,7 @@ impl I256 {
     }
 
     /// Wrapping subtraction.
+    #[must_use]
     pub fn wrapping_sub(self, other: Self) -> Self {
         let (result, _) = self.overflowing_sub(other);
         result
@@ -662,6 +672,7 @@ impl I256 {
     }
 
     /// Multiplication which saturates at the maximum value..
+    #[must_use]
     pub fn saturating_mul(self, rhs: Self) -> Self {
         self.checked_mul(rhs).unwrap_or_else(|| {
             match Sign::from_signum64(self.signum64() * rhs.signum64()) {
@@ -672,6 +683,7 @@ impl I256 {
     }
 
     /// Wrapping multiplication.
+    #[must_use]
     pub fn wrapping_mul(self, rhs: Self) -> Self {
         let (result, _) = self.overflowing_mul(rhs);
         result
@@ -702,12 +714,14 @@ impl I256 {
     }
 
     /// Division which saturates at the maximum value.
+    #[must_use]
     pub fn saturating_div(self, rhs: Self) -> Self {
         // There is only one overflow (I256::MIN / -1 = I256::MAX)
         self.checked_div(rhs).unwrap_or(I256::MAX)
     }
 
     /// Wrapping division.
+    #[must_use]
     pub fn wrapping_div(self, rhs: Self) -> Self {
         self.overflowing_div(rhs).0
     }
@@ -737,6 +751,7 @@ impl I256 {
 
     /// Wrapping remainder. Returns the result of the operation %
     /// regardless of whether or not the division overflowed.
+    #[must_use]
     pub fn wrapping_rem(self, rhs: Self) -> Self {
         self.overflowing_rem(rhs).0
     }
@@ -749,6 +764,7 @@ impl I256 {
     /// rhs`:
     /// * If `self > 0`, this is equal to round towards zero (the default in Rust);
     /// * If `self < 0`, this is equal to round towards +/- infinity.
+    #[must_use]
     pub fn div_euclid(self, rhs: Self) -> Self {
         let q = self / rhs;
         if (self % rhs).is_negative() {
@@ -761,6 +777,7 @@ impl I256 {
     /// This is done as if by the _Euclidean division algorithm_
     /// given `r = self.rem_euclid(rhs)`, `self = rhs * self.div_euclid(rhs) + r, and 0 <= r <
     /// abs(rhs)`.
+    #[must_use]
     pub fn rem_euclid(self, rhs: Self) -> Self {
         let r = self % rhs;
         if r < Self::zero() {
@@ -801,6 +818,7 @@ impl I256 {
     /// (where `MIN` is the negative minimal value for the type).
     /// This is equivalent to `-MIN`, a positive value that is too large to represent in the type.
     /// In this case, this method returns `MIN` itself.
+    #[must_use]
     pub fn wrapping_div_euclid(self, rhs: Self) -> Self {
         self.overflowing_div_euclid(rhs).0
     }
@@ -823,6 +841,7 @@ impl I256 {
     /// (where `MIN` is the negative minimal value for the type).
     /// In this case, this method returns `0`.
     /// Panics when `rhs == 0`
+    #[must_use]
     pub fn wrapping_rem_euclid(self, rhs: Self) -> Self {
         self.overflowing_rem_euclid(rhs).0
     }
@@ -867,6 +886,7 @@ impl I256 {
     /// # Panics
     ///
     /// Panics if the result overflows the type in debug mode.
+    #[must_use]
     pub fn pow(self, exp: u32) -> Self {
         handle_overflow(self.overflowing_pow(exp))
     }
@@ -895,6 +915,7 @@ impl I256 {
 
     /// Raises self to the power of `exp`, saturating at the numeric bounds
     /// instead of overflowing.
+    #[must_use]
     pub fn saturating_pow(self, exp: u32) -> Self {
         let (result, overflow) = self.overflowing_pow(exp);
         if overflow {
@@ -909,6 +930,7 @@ impl I256 {
 
     /// Wrapping powolute value. Computes `self.pow()`, wrapping around at the
     /// boundary of the type.
+    #[must_use]
     pub fn wrapping_pow(self, exp: u32) -> Self {
         let (result, _) = self.overflowing_pow(exp);
         result
@@ -1635,7 +1657,7 @@ mod tests {
     #[test]
     #[cfg_attr(debug_assertions, should_panic)]
     fn div_euclid_overflow() {
-        I256::MIN.div_euclid(-I256::one());
+        let _ = I256::MIN.div_euclid(-I256::one());
     }
 
     #[test]
diff --git a/ethers-core/src/types/log.rs b/ethers-core/src/types/log.rs
index 43552ca4..374765ab 100644
--- a/ethers-core/src/types/log.rs
+++ b/ethers-core/src/types/log.rs
@@ -123,6 +123,7 @@ impl Default for FilterBlockOption {
 }
 
 impl FilterBlockOption {
+    #[must_use]
     pub fn set_from_block(&self, block: BlockNumber) -> Self {
         let to_block =
             if let FilterBlockOption::Range { to_block, .. } = self { *to_block } else { None };
@@ -130,6 +131,7 @@ impl FilterBlockOption {
         FilterBlockOption::Range { from_block: Some(block), to_block }
     }
 
+    #[must_use]
     pub fn set_to_block(&self, block: BlockNumber) -> Self {
         let from_block =
             if let FilterBlockOption::Range { from_block, .. } = self { *from_block } else { None };
@@ -137,6 +139,7 @@ impl FilterBlockOption {
         FilterBlockOption::Range { from_block, to_block: Some(block) }
     }
 
+    #[must_use]
     pub fn set_hash(&self, hash: H256) -> Self {
         FilterBlockOption::AtBlockHash(hash)
     }
@@ -273,64 +276,75 @@ impl Filter {
     /// let filter = Filter::new().select(..1337u64);
     /// # }
     /// ```
+    #[must_use]
     pub fn select(mut self, filter: impl Into<FilterBlockOption>) -> Self {
         self.block_option = filter.into();
         self
     }
 
     #[allow(clippy::wrong_self_convention)]
+    #[must_use]
     pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
         self.block_option = self.block_option.set_from_block(block.into());
         self
     }
 
     #[allow(clippy::wrong_self_convention)]
+    #[must_use]
     pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
         self.block_option = self.block_option.set_to_block(block.into());
         self
     }
 
     #[allow(clippy::wrong_self_convention)]
+    #[must_use]
     pub fn at_block_hash<T: Into<H256>>(mut self, hash: T) -> Self {
         self.block_option = self.block_option.set_hash(hash.into());
         self
     }
 
+    #[must_use]
     pub fn address<T: Into<ValueOrArray<Address>>>(mut self, address: T) -> Self {
         self.address = Some(address.into());
         self
     }
 
     /// given the event in string form, it hashes it and adds it to the topics to monitor
+    #[must_use]
     pub fn event(self, event_name: &str) -> Self {
         let hash = H256::from(keccak256(event_name.as_bytes()));
         self.topic0(hash)
     }
 
     /// Sets topic0 (the event name for non-anonymous events)
+    #[must_use]
     pub fn topic0<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
         self.topics[0] = Some(topic.into());
         self
     }
 
     /// Sets the 1st indexed topic
+    #[must_use]
     pub fn topic1<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
         self.topics[1] = Some(topic.into());
         self
     }
 
     /// Sets the 2nd indexed topic
+    #[must_use]
     pub fn topic2<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
         self.topics[2] = Some(topic.into());
         self
     }
 
     /// Sets the 3rd indexed topic
+    #[must_use]
     pub fn topic3<T: Into<ValueOrArray<H256>>>(mut self, topic: T) -> Self {
         self.topics[3] = Some(topic.into());
         self
     }
 
+    #[must_use]
     pub fn limit(mut self, limit: usize) -> Self {
         self.limit = Some(limit);
         self
diff --git a/ethers-core/src/types/trace/filter.rs b/ethers-core/src/types/trace/filter.rs
index 7424e74d..c69cb2cd 100644
--- a/ethers-core/src/types/trace/filter.rs
+++ b/ethers-core/src/types/trace/filter.rs
@@ -29,6 +29,7 @@ pub struct TraceFilter {
 impl TraceFilter {
     /// Sets From block
     #[allow(clippy::wrong_self_convention)]
+    #[must_use]
     pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
         self.from_block = Some(block.into());
         self
@@ -36,6 +37,7 @@ impl TraceFilter {
 
     /// Sets to block
     #[allow(clippy::wrong_self_convention)]
+    #[must_use]
     pub fn to_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
         self.to_block = Some(block.into());
         self
@@ -43,6 +45,7 @@ impl TraceFilter {
 
     /// Sets to address
     #[allow(clippy::wrong_self_convention)]
+    #[must_use]
     pub fn to_address(mut self, address: Vec<H160>) -> Self {
         self.to_address = Some(address);
         self
@@ -50,18 +53,21 @@ impl TraceFilter {
 
     /// Sets from address
     #[allow(clippy::wrong_self_convention)]
+    #[must_use]
     pub fn from_address(mut self, address: Vec<H160>) -> Self {
         self.from_address = Some(address);
         self
     }
 
     /// Sets after offset
+    #[must_use]
     pub fn after(mut self, after: usize) -> Self {
         self.after = Some(after);
         self
     }
 
     /// Sets amount of traces to display
+    #[must_use]
     pub fn count(mut self, count: usize) -> Self {
         self.count = Some(count);
         self
diff --git a/ethers-core/src/types/transaction/eip1559.rs b/ethers-core/src/types/transaction/eip1559.rs
index ced1e1c3..2868be31 100644
--- a/ethers-core/src/types/transaction/eip1559.rs
+++ b/ethers-core/src/types/transaction/eip1559.rs
@@ -68,54 +68,63 @@ impl Eip1559TransactionRequest {
     // Builder pattern helpers
 
     /// Sets the `from` field in the transaction to the provided value
+    #[must_use]
     pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
         self.from = Some(from.into());
         self
     }
 
     /// Sets the `to` field in the transaction to the provided value
+    #[must_use]
     pub fn to<T: Into<NameOrAddress>>(mut self, to: T) -> Self {
         self.to = Some(to.into());
         self
     }
 
     /// Sets the `gas` field in the transaction to the provided value
+    #[must_use]
     pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
         self.gas = Some(gas.into());
         self
     }
 
     /// Sets the `max_priority_fee_per_gas` field in the transaction to the provided value
+    #[must_use]
     pub fn max_priority_fee_per_gas<T: Into<U256>>(mut self, max_priority_fee_per_gas: T) -> Self {
         self.max_priority_fee_per_gas = Some(max_priority_fee_per_gas.into());
         self
     }
 
     /// Sets the `max_fee_per_gas` field in the transaction to the provided value
+    #[must_use]
     pub fn max_fee_per_gas<T: Into<U256>>(mut self, max_fee_per_gas: T) -> Self {
         self.max_fee_per_gas = Some(max_fee_per_gas.into());
         self
     }
 
     /// Sets the `value` field in the transaction to the provided value
+    #[must_use]
     pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
         self.value = Some(value.into());
         self
     }
 
     /// Sets the `data` field in the transaction to the provided value
+    #[must_use]
     pub fn data<T: Into<Bytes>>(mut self, data: T) -> Self {
         self.data = Some(data.into());
         self
     }
 
     /// Sets the `access_list` field in the transaction to the provided value
+    #[must_use]
     pub fn access_list<T: Into<AccessList>>(mut self, access_list: T) -> Self {
         self.access_list = access_list.into();
         self
     }
 
     /// Sets the `nonce` field in the transaction to the provided value
+    #[must_use]
     pub fn nonce<T: Into<U256>>(mut self, nonce: T) -> Self {
         self.nonce = Some(nonce.into());
         self
diff --git a/ethers-core/src/types/transaction/eip2930.rs b/ethers-core/src/types/transaction/eip2930.rs
index b40670e0..c9b6fb4e 100644
--- a/ethers-core/src/types/transaction/eip2930.rs
+++ b/ethers-core/src/types/transaction/eip2930.rs
@@ -119,7 +119,7 @@ mod tests {
 
         let enc = rlp::encode(&tx.rlp_signed(1, &sig).as_ref());
         let expected = "b86601f8630103018261a894b94f5374fce5edbc8e2a8697c15331677e6ebf0b0a825544c001a0c9519f4f2b30335884581971573fadf60c6204f59a911df35ee8a540456b2660a032f1e8e2c5dd761f9e4f88f41c8310aeaba26a8bfcdacfedfa12ec3862d37521";
-        assert_eq!(hex::encode(enc.to_vec()), expected);
+        assert_eq!(hex::encode(&enc), expected);
     }
 
     #[test]
diff --git a/ethers-core/src/types/transaction/eip712.rs b/ethers-core/src/types/transaction/eip712.rs
index d7cd3a44..61a6b9a1 100644
--- a/ethers-core/src/types/transaction/eip712.rs
+++ b/ethers-core/src/types/transaction/eip712.rs
@@ -166,6 +166,7 @@ impl<T: Eip712 + Clone> EIP712WithDomain<T> {
         Ok(Self { domain, inner })
     }
 
+    #[must_use]
     pub fn set_domain(self, domain: EIP712Domain) -> Self {
         Self { domain, inner: self.inner }
     }
diff --git a/ethers-core/src/types/transaction/request.rs b/ethers-core/src/types/transaction/request.rs
index 065eccf9..371b120f 100644
--- a/ethers-core/src/types/transaction/request.rs
+++ b/ethers-core/src/types/transaction/request.rs
@@ -75,42 +75,49 @@ impl TransactionRequest {
     // Builder pattern helpers
 
     /// Sets the `from` field in the transaction to the provided value
+    #[must_use]
     pub fn from<T: Into<Address>>(mut self, from: T) -> Self {
         self.from = Some(from.into());
         self
     }
 
     /// Sets the `to` field in the transaction to the provided value
+    #[must_use]
     pub fn to<T: Into<NameOrAddress>>(mut self, to: T) -> Self {
         self.to = Some(to.into());
         self
     }
 
     /// Sets the `gas` field in the transaction to the provided value
+    #[must_use]
     pub fn gas<T: Into<U256>>(mut self, gas: T) -> Self {
         self.gas = Some(gas.into());
         self
     }
 
     /// Sets the `gas_price` field in the transaction to the provided value
+    #[must_use]
     pub fn gas_price<T: Into<U256>>(mut self, gas_price: T) -> Self {
         self.gas_price = Some(gas_price.into());
         self
     }
 
     /// Sets the `value` field in the transaction to the provided value
+    #[must_use]
     pub fn value<T: Into<U256>>(mut self, value: T) -> Self {
         self.value = Some(value.into());
         self
     }
 
     /// Sets the `data` field in the transaction to the provided value
+    #[must_use]
     pub fn data<T: Into<Bytes>>(mut self, data: T) -> Self {
         self.data = Some(data.into());
         self
     }
 
     /// Sets the `nonce` field in the transaction to the provided value
+    #[must_use]
     pub fn nonce<T: Into<U256>>(mut self, nonce: T) -> Self {
         self.nonce = Some(nonce.into());
         self
@@ -174,6 +181,7 @@ impl TransactionRequest {
 
     /// Sets the `fee_currency` field in the transaction to the provided value
     #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    #[must_use]
     pub fn fee_currency<T: Into<Address>>(mut self, fee_currency: T) -> Self {
         self.fee_currency = Some(fee_currency.into());
         self
@@ -181,6 +189,7 @@ impl TransactionRequest {
 
     /// Sets the `gateway_fee` field in the transaction to the provided value
     #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    #[must_use]
     pub fn gateway_fee<T: Into<U256>>(mut self, gateway_fee: T) -> Self {
         self.gateway_fee = Some(gateway_fee.into());
         self
@@ -188,6 +197,7 @@ impl TransactionRequest {
 
     /// Sets the `gateway_fee_recipient` field in the transaction to the provided value
     #[cfg_attr(docsrs, doc(cfg(feature = "celo")))]
+    #[must_use]
     pub fn gateway_fee_recipient<T: Into<Address>>(mut self, gateway_fee_recipient: T) -> Self {
         self.gateway_fee_recipient = Some(gateway_fee_recipient.into());
         self
diff --git a/ethers-core/src/utils/ganache.rs b/ethers-core/src/utils/ganache.rs
index c68c1a58..c173a742 100644
--- a/ethers-core/src/utils/ganache.rs
+++ b/ethers-core/src/utils/ganache.rs
@@ -93,18 +93,21 @@ impl Ganache {
     }
 
     /// Sets the port which will be used when the `ganache-cli` instance is launched.
+    #[must_use]
     pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
         self.port = Some(port.into());
         self
     }
 
     /// Sets the mnemonic which will be used when the `ganache-cli` instance is launched.
+    #[must_use]
     pub fn mnemonic<T: Into<String>>(mut self, mnemonic: T) -> Self {
         self.mnemonic = Some(mnemonic.into());
         self
     }
 
     /// Sets the block-time which will be used when the `ganache-cli` instance is launched.
+    #[must_use]
     pub fn block_time<T: Into<u64>>(mut self, block_time: T) -> Self {
         self.block_time = Some(block_time.into());
         self
@@ -114,18 +117,21 @@ impl Ganache {
     /// at a given block. Input should be the HTTP location and port of the other client,
     /// e.g. `http://localhost:8545`. You can optionally specify the block to fork from
     /// using an @ sign: `http://localhost:8545@1599200`
+    #[must_use]
     pub fn fork<T: Into<String>>(mut self, fork: T) -> Self {
         self.fork = Some(fork.into());
         self
     }
 
     /// Adds an argument to pass to the `ganache-cli`.
+    #[must_use]
     pub fn arg<T: Into<String>>(mut self, arg: T) -> Self {
         self.args.push(arg.into());
         self
     }
 
     /// Adds multiple arguments to pass to the `ganache-cli`.
+    #[must_use]
     pub fn args<I, S>(mut self, args: I) -> Self
     where
         I: IntoIterator<Item = S>,
diff --git a/ethers-core/src/utils/geth.rs b/ethers-core/src/utils/geth.rs
index 22ded40b..9016bc8f 100644
--- a/ethers-core/src/utils/geth.rs
+++ b/ethers-core/src/utils/geth.rs
@@ -87,18 +87,21 @@ impl Geth {
     }
 
     /// Sets the port which will be used when the `geth-cli` instance is launched.
+    #[must_use]
     pub fn port<T: Into<u16>>(mut self, port: T) -> Self {
         self.port = Some(port.into());
         self
     }
 
     /// Sets the block-time which will be used when the `geth-cli` instance is launched.
+    #[must_use]
     pub fn block_time<T: Into<u64>>(mut self, block_time: T) -> Self {
         self.block_time = Some(block_time.into());
         self
     }
 
     /// Manually sets the IPC path for the socket manually.
+    #[must_use]
     pub fn ipc_path<T: Into<PathBuf>>(mut self, path: T) -> Self {
         self.ipc_path = Some(path.into());
         self
diff --git a/ethers-etherscan/src/contract.rs b/ethers-etherscan/src/contract.rs
index 87ea0b03..45eab419 100644
--- a/ethers-etherscan/src/contract.rs
+++ b/ethers-etherscan/src/contract.rs
@@ -49,16 +49,19 @@ impl VerifyContract {
         }
     }
 
+    #[must_use]
     pub fn contract_name(mut self, name: impl Into<String>) -> Self {
         self.contract_name = Some(name.into());
         self
     }
 
+    #[must_use]
     pub fn runs(mut self, runs: u32) -> Self {
         self.runs = Some(format!("{}", runs));
         self
     }
 
+    #[must_use]
     pub fn optimization(self, optimization: bool) -> Self {
         if optimization {
             self.optimized()
@@ -67,26 +70,31 @@ impl VerifyContract {
         }
     }
 
+    #[must_use]
     pub fn optimized(mut self) -> Self {
         self.optimization_used = Some("1".to_string());
         self
     }
 
+    #[must_use]
     pub fn not_optimized(mut self) -> Self {
         self.optimization_used = Some("0".to_string());
         self
     }
 
+    #[must_use]
     pub fn code_format(mut self, code_format: CodeFormat) -> Self {
         self.code_format = code_format;
         self
     }
 
+    #[must_use]
     pub fn evm_version(mut self, evm_version: impl Into<String>) -> Self {
         self.evm_version = Some(evm_version.into());
         self
     }
 
+    #[must_use]
     pub fn constructor_arguments(
         mut self,
         constructor_arguments: Option<impl Into<String>>,
diff --git a/ethers-middleware/src/gas_oracle/eth_gas_station.rs b/ethers-middleware/src/gas_oracle/eth_gas_station.rs
index 1c430948..6e2510c5 100644
--- a/ethers-middleware/src/gas_oracle/eth_gas_station.rs
+++ b/ethers-middleware/src/gas_oracle/eth_gas_station.rs
@@ -73,6 +73,7 @@ impl EthGasStation {
     }
 
     /// Sets the gas price category to be used when fetching the gas price.
+    #[must_use]
     pub fn category(mut self, gas_category: GasCategory) -> Self {
         self.gas_category = gas_category;
         self
diff --git a/ethers-middleware/src/gas_oracle/etherchain.rs b/ethers-middleware/src/gas_oracle/etherchain.rs
index 551269ef..f74187a8 100644
--- a/ethers-middleware/src/gas_oracle/etherchain.rs
+++ b/ethers-middleware/src/gas_oracle/etherchain.rs
@@ -44,6 +44,7 @@ impl Etherchain {
     }
 
     /// Sets the gas price category to be used when fetching the gas price.
+    #[must_use]
     pub fn category(mut self, gas_category: GasCategory) -> Self {
         self.gas_category = gas_category;
         self
diff --git a/ethers-middleware/src/gas_oracle/etherscan.rs b/ethers-middleware/src/gas_oracle/etherscan.rs
index 828f5f92..b39cebff 100644
--- a/ethers-middleware/src/gas_oracle/etherscan.rs
+++ b/ethers-middleware/src/gas_oracle/etherscan.rs
@@ -20,6 +20,7 @@ impl Etherscan {
     }
 
     /// Sets the gas price category to be used when fetching the gas price.
+    #[must_use]
     pub fn category(mut self, gas_category: GasCategory) -> Self {
         self.gas_category = gas_category;
         self
diff --git a/ethers-middleware/src/signer.rs b/ethers-middleware/src/signer.rs
index 1d7c4be4..7234e94f 100644
--- a/ethers-middleware/src/signer.rs
+++ b/ethers-middleware/src/signer.rs
@@ -130,6 +130,7 @@ where
         &self.signer
     }
 
+    #[must_use]
     pub fn with_signer(&self, signer: S) -> Self
     where
         S: Clone,
diff --git a/ethers-providers/src/pending_escalator.rs b/ethers-providers/src/pending_escalator.rs
index 511fca36..b813f986 100644
--- a/ethers-providers/src/pending_escalator.rs
+++ b/ethers-providers/src/pending_escalator.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::return_self_not_must_use)]
+
 use ethers_core::types::{Bytes, TransactionReceipt, H256};
 use futures_util::{stream::FuturesUnordered, StreamExt};
 use pin_project::pin_project;
diff --git a/ethers-providers/src/pending_transaction.rs b/ethers-providers/src/pending_transaction.rs
index fbb070e8..968f689b 100644
--- a/ethers-providers/src/pending_transaction.rs
+++ b/ethers-providers/src/pending_transaction.rs
@@ -57,12 +57,14 @@ impl<'a, P: JsonRpcClient> PendingTransaction<'a, P> {
 
     /// Sets the number of confirmations for the pending transaction to resolve
     /// to a receipt
+    #[must_use]
     pub fn confirmations(mut self, confs: usize) -> Self {
         self.confirmations = confs;
         self
     }
 
     /// Sets the polling interval
+    #[must_use]
     pub fn interval<T: Into<Duration>>(mut self, duration: T) -> Self {
         let duration = duration.into();
 
diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs
index e0ebdeb4..8d2617cb 100644
--- a/ethers-providers/src/provider.rs
+++ b/ethers-providers/src/provider.rs
@@ -175,6 +175,7 @@ impl<P: JsonRpcClient> Provider<P> {
         }
     }
 
+    #[must_use]
     pub fn with_sender(mut self, address: impl Into<Address>) -> Self {
         self.from = Some(address.into());
         self
@@ -973,6 +974,7 @@ impl<P: JsonRpcClient> Provider<P> {
     }
 
     /// Sets the ENS Address (default: mainnet)
+    #[must_use]
     pub fn ens<T: Into<Address>>(mut self, ens: T) -> Self {
         self.ens = Some(ens.into());
         self
@@ -980,6 +982,7 @@ impl<P: JsonRpcClient> Provider<P> {
 
     /// Sets the default polling interval for event filters and pending transactions
     /// (default: 7 seconds)
+    #[must_use]
     pub fn interval<T: Into<Duration>>(mut self, interval: T) -> Self {
         self.interval = Some(interval.into());
         self
diff --git a/ethers-providers/src/stream.rs b/ethers-providers/src/stream.rs
index b8ebe181..4003b61c 100644
--- a/ethers-providers/src/stream.rs
+++ b/ethers-providers/src/stream.rs
@@ -1,3 +1,5 @@
+#![allow(clippy::return_self_not_must_use)]
+
 use crate::{JsonRpcClient, Middleware, PinBoxFut, Provider, ProviderError};
 
 use ethers_core::types::{Transaction, TxHash, U256};
diff --git a/ethers-signers/src/lib.rs b/ethers-signers/src/lib.rs
index f127fa30..86888911 100644
--- a/ethers-signers/src/lib.rs
+++ b/ethers-signers/src/lib.rs
@@ -79,5 +79,6 @@ pub trait Signer: std::fmt::Debug + Send + Sync {
     fn chain_id(&self) -> u64;
 
     /// Sets the signer's chain id
+    #[must_use]
     fn with_chain_id<T: Into<u64>>(self, chain_id: T) -> Self;
 }
diff --git a/ethers-signers/src/trezor/app.rs b/ethers-signers/src/trezor/app.rs
index 8434857e..74768090 100644
--- a/ethers-signers/src/trezor/app.rs
+++ b/ethers-signers/src/trezor/app.rs
@@ -249,7 +249,7 @@ mod tests {
         let trezor = TrezorEthereum::new(DerivationType::TrezorLive(0), 1).await.unwrap();
 
         // invalid data
-        let big_data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string()+ &"ff".repeat(1032*2) + &"aa".to_string()).unwrap();
+        let big_data = hex::decode("095ea7b30000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488dffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff".to_string()+ &"ff".repeat(1032*2) + "aa").unwrap();
         let tx_req = TransactionRequest::new()
             .to("2ed7afa17473e17ac59908f088b4371d28585476".parse::<Address>().unwrap())
             .gas(1000000)
diff --git a/ethers-signers/src/wallet/mnemonic.rs b/ethers-signers/src/wallet/mnemonic.rs
index c3377c0e..38c6a19b 100644
--- a/ethers-signers/src/wallet/mnemonic.rs
+++ b/ethers-signers/src/wallet/mnemonic.rs
@@ -82,6 +82,7 @@ impl<W: Wordlist> MnemonicBuilder<W> {
     /// # Ok(())
     /// # }
     /// ```
+    #[must_use]
     pub fn phrase<P: Into<PathOrString>>(mut self, phrase: P) -> Self {
         self.phrase = Some(phrase.into());
         self
@@ -104,6 +105,7 @@ impl<W: Wordlist> MnemonicBuilder<W> {
     /// # Ok(())
     /// # }
     /// ```
+    #[must_use]
     pub fn word_count(mut self, count: usize) -> Self {
         self.word_count = count;
         self
@@ -127,6 +129,7 @@ impl<W: Wordlist> MnemonicBuilder<W> {
     }
 
     /// Sets the password used to construct the seed from the mnemonic phrase.
+    #[must_use]
     pub fn password(mut self, password: &str) -> Self {
         self.password = Some(password.to_string());
         self
@@ -134,6 +137,7 @@ impl<W: Wordlist> MnemonicBuilder<W> {
 
     /// Sets the path to which the randomly generated phrase will be written to. This field is
     /// ignored when building a wallet from the provided mnemonic phrase.
+    #[must_use]
     pub fn write_to<P: Into<PathBuf>>(mut self, path: P) -> Self {
         self.write_to = Some(path.into());
         self
diff --git a/ethers-solc/src/artifacts.rs b/ethers-solc/src/artifacts.rs
index 20719dcc..3ba04a3b 100644
--- a/ethers-solc/src/artifacts.rs
+++ b/ethers-solc/src/artifacts.rs
@@ -40,12 +40,14 @@ impl CompilerInput {
     }
 
     /// Sets the EVM version for compilation
+    #[must_use]
     pub fn evm_version(mut self, version: EvmVersion) -> Self {
         self.settings.evm_version = Some(version);
         self
     }
 
     /// Sets the optimizer runs (default = 200)
+    #[must_use]
     pub fn optimizer(mut self, runs: usize) -> Self {
         self.settings.optimizer.runs(runs);
         self
@@ -53,6 +55,7 @@ impl CompilerInput {
 
     /// Normalizes the EVM version used in the settings to be up to the latest one
     /// supported by the provided compiler version.
+    #[must_use]
     pub fn normalize_evm_version(mut self, version: &Version) -> Self {
         if let Some(ref mut evm_version) = self.settings.evm_version {
             self.settings.evm_version = evm_version.normalize_version(version);
@@ -60,6 +63,7 @@ impl CompilerInput {
         self
     }
 
+    #[must_use]
     pub fn with_remappings(mut self, remappings: Vec<Remapping>) -> Self {
         self.settings.remappings = remappings;
         self
@@ -172,6 +176,7 @@ impl Settings {
     }
 
     /// Adds `ast` to output
+    #[must_use]
     pub fn with_ast(mut self) -> Self {
         let output = self.output_selection.entry("*".to_string()).or_insert_with(BTreeMap::default);
         output.insert("".to_string(), vec!["ast".to_string()]);
diff --git a/ethers-solc/src/cache.rs b/ethers-solc/src/cache.rs
index fcf13ad3..3ca2b26a 100644
--- a/ethers-solc/src/cache.rs
+++ b/ethers-solc/src/cache.rs
@@ -219,16 +219,19 @@ pub struct SolFilesCacheBuilder {
 }
 
 impl SolFilesCacheBuilder {
+    #[must_use]
     pub fn format(mut self, format: impl Into<String>) -> Self {
         self.format = Some(format.into());
         self
     }
 
+    #[must_use]
     pub fn solc_config(mut self, solc_config: SolcConfig) -> Self {
         self.solc_config = Some(solc_config);
         self
     }
 
+    #[must_use]
     pub fn root(mut self, root: impl Into<PathBuf>) -> Self {
         self.root = Some(root.into());
         self
diff --git a/ethers-solc/src/compile.rs b/ethers-solc/src/compile.rs
index 0521bee0..8f88d2af 100644
--- a/ethers-solc/src/compile.rs
+++ b/ethers-solc/src/compile.rs
@@ -42,6 +42,7 @@ use once_cell::sync::Lazy;
 use std::sync::Mutex;
 
 #[cfg(any(test, feature = "tests"))]
+#[allow(unused)]
 static LOCK: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
 
 #[cfg(all(feature = "svm", feature = "async"))]
@@ -67,6 +68,11 @@ pub static RELEASES: Lazy<(svm::Releases, Vec<Version>)> = Lazy::new(|| {
 /// Abstraction over `solc` command line utility
 ///
 /// Supports sync and async functions.
+///
+/// By default the solc path is configured as follows, with descending priority:
+///   1. `SOLC_PATH` environment variable
+///   2. [svm](https://github.com/roynalnaruto/svm-rs)'s  `global_version` (set via `svm use <version>`), stored at `<svm_home>/.global_version`
+///   3. `solc` otherwise
 #[derive(Debug, Clone, Eq, PartialEq, PartialOrd, Ord)]
 pub struct Solc {
     /// Path to the `solc` executable
@@ -77,7 +83,20 @@ pub struct Solc {
 
 impl Default for Solc {
     fn default() -> Self {
-        std::env::var("SOLC_PATH").map(Solc::new).unwrap_or_else(|_| Solc::new(SOLC))
+        if let Ok(solc) = std::env::var("SOLC_PATH") {
+            return Solc::new(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())
+                .flatten()
+            {
+                return solc
+            }
+        }
+
+        Solc::new(SOLC)
     }
 }
 
@@ -88,12 +107,14 @@ impl Solc {
     }
 
     /// Adds an argument to pass to the `solc` command.
+    #[must_use]
     pub fn arg<T: Into<String>>(mut self, arg: T) -> Self {
         self.args.push(arg.into());
         self
     }
 
     /// Adds multiple arguments to pass to the `solc`.
+    #[must_use]
     pub fn args<I, S>(mut self, args: I) -> Self
     where
         I: IntoIterator<Item = S>,
@@ -113,6 +134,18 @@ impl Solc {
         home::home_dir().map(|dir| dir.join(".svm"))
     }
 
+    /// Returns the `semver::Version` [svm](https://github.com/roynalnaruto/svm-rs)'s `.global_version` is currently set to.
+    ///  `global_version` is configured with (`svm use <version>`)
+    ///
+    /// This will read the version string (eg: "0.8.9") that the  `~/.svm/.global_version` file
+    /// contains
+    #[cfg(not(target_arch = "wasm32"))]
+    pub fn svm_global_version() -> Option<Version> {
+        let version =
+            std::fs::read_to_string(Self::svm_home().map(|p| p.join(".global_version"))?).ok()?;
+        Version::parse(&version).ok()
+    }
+
     /// Returns the path for a [svm](https://github.com/roynalnaruto/svm-rs) installed version.
     ///
     /// # Example
@@ -127,17 +160,15 @@ impl Solc {
     #[cfg(not(target_arch = "wasm32"))]
     pub fn find_svm_installed_version(version: impl AsRef<str>) -> Result<Option<Self>> {
         let version = version.as_ref();
-        let solc = walkdir::WalkDir::new(
-            Self::svm_home().ok_or_else(|| SolcError::solc("svm home dir not found"))?,
-        )
-        .max_depth(1)
-        .into_iter()
-        .filter_map(std::result::Result::ok)
-        .filter(|e| e.file_type().is_dir())
-        .find(|e| e.path().ends_with(version))
-        .map(|e| e.path().join(format!("solc-{}", version)))
-        .map(Solc::new);
-        Ok(solc)
+        let solc = Self::svm_home()
+            .ok_or_else(|| SolcError::solc("svm home dir not found"))?
+            .join(version)
+            .join(format!("solc-{}", version));
+
+        if !solc.is_file() {
+            return Ok(None)
+        }
+        Ok(Some(Solc::new(solc)))
     }
 
     /// Assuming the `versions` array is sorted, it returns the first element which satisfies
@@ -506,7 +537,7 @@ mod tests {
     use crate::CompilerInput;
 
     fn solc() -> Solc {
-        std::env::var("SOLC_PATH").map(Solc::new).unwrap_or_default()
+        Solc::default()
     }
 
     #[test]
diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs
index 7332529e..1e6d2089 100644
--- a/ethers-solc/src/config.rs
+++ b/ethers-solc/src/config.rs
@@ -11,7 +11,9 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use std::{
     collections::BTreeMap,
     convert::TryFrom,
-    fmt, fs, io,
+    fmt,
+    fmt::Formatter,
+    fs, io,
     path::{Path, PathBuf},
 };
 
@@ -75,6 +77,24 @@ impl ProjectPathsConfig {
     }
 }
 
+impl fmt::Display for ProjectPathsConfig {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        writeln!(f, "root: {}", self.root.display())?;
+        writeln!(f, "contracts: {}", self.sources.display())?;
+        writeln!(f, "artifacts: {}", self.artifacts.display())?;
+        writeln!(f, "tests: {}", self.tests.display())?;
+        writeln!(f, "libs:")?;
+        for lib in &self.libraries {
+            writeln!(f, "    {}", lib.display())?;
+        }
+        writeln!(f, "remappings:")?;
+        for remapping in &self.remappings {
+            writeln!(f, "    {}", remapping)?;
+        }
+        Ok(())
+    }
+}
+
 #[derive(Debug, Clone, Eq, PartialEq)]
 pub enum PathStyle {
     HardHat,
@@ -91,7 +111,7 @@ impl PathStyle {
                 .sources(root.join("src"))
                 .artifacts(root.join("out"))
                 .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()
diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs
index 21b0edcd..91c3049d 100644
--- a/ethers-solc/src/lib.rs
+++ b/ethers-solc/src/lib.rs
@@ -529,39 +529,46 @@ pub struct ProjectBuilder<Artifacts: ArtifactOutput = MinimalCombinedArtifacts>
 }
 
 impl<Artifacts: ArtifactOutput> ProjectBuilder<Artifacts> {
+    #[must_use]
     pub fn paths(mut self, paths: ProjectPathsConfig) -> Self {
         self.paths = Some(paths);
         self
     }
 
+    #[must_use]
     pub fn solc(mut self, solc: impl Into<Solc>) -> Self {
         self.solc = Some(solc.into());
         self
     }
 
+    #[must_use]
     pub fn solc_config(mut self, solc_config: SolcConfig) -> Self {
         self.solc_config = Some(solc_config);
         self
     }
 
+    #[must_use]
     pub fn ignore_error_code(mut self, code: u64) -> Self {
         self.ignored_error_codes.push(code);
         self
     }
 
     /// Disables cached builds
+    #[must_use]
     pub fn ephemeral(mut self) -> Self {
         self.cached = false;
         self
     }
 
     /// Disables writing artifacts to disk
+    #[must_use]
     pub fn no_artifacts(mut self) -> Self {
         self.no_artifacts = true;
         self
     }
 
     /// Disables automatic solc version detection
+    #[must_use]
     pub fn no_auto_detect(mut self) -> Self {
         self.auto_detect = false;
         self
@@ -572,6 +579,7 @@ impl<Artifacts: ArtifactOutput> ProjectBuilder<Artifacts> {
     /// # Panics
     ///
     /// `jobs` must be at least 1
+    #[must_use]
     pub fn solc_jobs(mut self, jobs: usize) -> Self {
         assert!(jobs > 0);
         self.solc_jobs = Some(jobs);
@@ -579,6 +587,7 @@ impl<Artifacts: ArtifactOutput> ProjectBuilder<Artifacts> {
     }
 
     /// Sets the number of parallel `solc` processes to `1`, no parallelization
+    #[must_use]
     pub fn single_solc_jobs(self) -> Self {
         self.solc_jobs(1)
     }
@@ -612,12 +621,14 @@ impl<Artifacts: ArtifactOutput> ProjectBuilder<Artifacts> {
     }
 
     /// Adds an allowed-path to the solc executable
+    #[must_use]
     pub fn allowed_path<T: Into<PathBuf>>(mut self, path: T) -> Self {
         self.allowed_paths.push(path.into());
         self
     }
 
     /// Adds multiple allowed-path to the solc executable
+    #[must_use]
     pub fn allowed_paths<I, S>(mut self, args: I) -> Self
     where
         I: IntoIterator<Item = S>,
diff --git a/ethers-solc/src/remappings.rs b/ethers-solc/src/remappings.rs
index a7d197aa..324673f4 100644
--- a/ethers-solc/src/remappings.rs
+++ b/ethers-solc/src/remappings.rs
@@ -1,8 +1,13 @@
-use crate::{error::SolcError, Result};
 use serde::{Deserialize, Serialize};
-use std::{fmt, str::FromStr};
+use std::{
+    collections::{hash_map::Entry, HashMap},
+    fmt,
+    path::{Path, PathBuf},
+    str::FromStr,
+};
 
 const DAPPTOOLS_CONTRACTS_DIR: &str = "src";
+const DAPPTOOLS_LIB_DIR: &str = "lib";
 const JS_CONTRACTS_DIR: &str = "contracts";
 
 /// The solidity compiler can only reference files that exist locally on your computer.
@@ -54,16 +59,14 @@ impl FromStr for Remapping {
     type Err = RemappingError;
 
     fn from_str(remapping: &str) -> std::result::Result<Self, Self::Err> {
-        let mut split = remapping.split('=');
-        let name = split.next().ok_or(RemappingError::NoPrefix)?.to_string();
-        if name.is_empty() {
+        let (name, path) = remapping.split_once('=').ok_or(RemappingError::NoPrefix)?;
+        if name.trim().is_empty() {
             return Err(RemappingError::NoPrefix)
         }
-        let path = split.next().ok_or(RemappingError::NoTarget)?.to_string();
-        if path.is_empty() {
+        if path.trim().is_empty() {
             return Err(RemappingError::NoTarget)
         }
-        Ok(Remapping { name, path })
+        Ok(Remapping { name: name.to_string(), path: path.to_string() })
     }
 }
 
@@ -94,88 +97,172 @@ impl fmt::Display for Remapping {
 }
 
 impl Remapping {
-    /// Detects a remapping prioritizing Dapptools-style remappings over `contracts/`-style ones.
-    fn find(root: &str) -> Result<Self> {
-        Self::find_with_type(root, DAPPTOOLS_CONTRACTS_DIR)
-            .or_else(|_| Self::find_with_type(root, JS_CONTRACTS_DIR))
+    /// Returns all formatted remappings
+    pub fn find_many_str(path: &str) -> Vec<String> {
+        Self::find_many(path).into_iter().map(|r| r.to_string()).collect()
     }
 
-    /// Given a path and the style of contracts dir, it proceeds to find
-    /// a `Remapping` for it.
-    fn find_with_type(name: &str, source: &str) -> Result<Self> {
-        let pattern = if name.contains(source) {
-            format!("{}/**/*.sol", name)
-        } else {
-            format!("{}/{}/**/*.sol", name, source)
-        };
-        let mut dapptools_contracts = glob::glob(&pattern)?;
-        let next = dapptools_contracts.next();
-        if next.is_some() {
-            let path = format!("{}/{}/", name, source);
-            let mut name = name.split('/').last().unwrap().to_string();
-            name.push('/');
-            Ok(Remapping { name, path })
-        } else {
-            Err(SolcError::NoContracts(source.to_string()))
+    /// Attempts to autodetect all remappings given a certain root path.
+    ///
+    /// This will recursively scan all subdirectories of the root path, if a subdirectory contains a
+    /// solidity file then this a candidate for a remapping. The name of the remapping will be the
+    /// folder name.
+    ///
+    /// However, there are additional rules/assumptions when it comes to determining if a candidate
+    /// should in fact be a remapping:
+    ///
+    /// All names and paths end with a trailing "/"
+    ///
+    /// The name of the remapping will be the parent folder of a solidity file, unless the folder is
+    /// named `src`, `lib` or `contracts` in which case the name of the remapping will be the parent
+    /// folder's name of `src`, `lib`, `contracts`: The remapping of `repo1/src/contract.sol` is
+    /// `name: "repo1/", path: "repo1/src/"`
+    ///
+    /// Nested remappings need to be separated by `src`, `lib` or `contracts`, The remapping of
+    /// `repo1/lib/ds-math/src/contract.sol` is `name: "ds-match/", "repo1/lib/ds-math/src/"`
+    ///
+    /// Remapping detection is primarily designed for dapptool's rules for lib folders, however, we
+    /// attempt to detect and optimize various folder structures commonly used in `node_modules`
+    /// dependencies. For those the same rules apply. In addition, we try to unify all
+    /// remappings discovered according to the rules mentioned above, so that layouts like,
+    //   @aave/
+    //   ├─ governance/
+    //   │  ├─ contracts/
+    //   ├─ protocol-v2/
+    //   │  ├─ contracts/
+    ///
+    /// which would be multiple rededications according to our rules ("governance", "protocol-v2"),
+    /// are unified into `@aave` by looking at their common ancestor, the root of this subdirectory
+    /// (`@aave`)
+    pub fn find_many(root: impl AsRef<Path>) -> Vec<Remapping> {
+        /// prioritize ("a", "1/2") over ("a", "1/2/3")
+        fn insert_higher_path(mappings: &mut HashMap<String, PathBuf>, key: String, path: PathBuf) {
+            match mappings.entry(key) {
+                Entry::Occupied(mut e) => {
+                    if e.get().components().count() > path.components().count() {
+                        e.insert(path);
+                    }
+                }
+                Entry::Vacant(e) => {
+                    e.insert(path);
+                }
+            }
         }
-    }
 
-    pub fn find_many_str(path: &str) -> Result<Vec<String>> {
-        let remappings = Self::find_many(path)?;
-        Ok(remappings.iter().map(|mapping| format!("{}={}", mapping.name, mapping.path)).collect())
-    }
-
-    /// Gets all the remappings detected
-    pub fn find_many(path: impl AsRef<std::path::Path>) -> Result<Vec<Self>> {
-        let path = path.as_ref();
-        if !path.exists() {
-            // nothing to find
-            return Ok(Vec::new())
-        }
-        let mut paths = std::fs::read_dir(path)
-            .map_err(|err| SolcError::io(err, path))?
+        let mut remappings = HashMap::new();
+        // iterate over all dirs that are children of the root
+        for dir in walkdir::WalkDir::new(root)
+            .follow_links(true)
+            .min_depth(1)
+            .max_depth(1)
             .into_iter()
-            .collect::<Vec<_>>();
-
-        let mut remappings = Vec::new();
-        while let Some(p) = paths.pop() {
-            let path = p.map_err(|err| SolcError::io(err, path))?.path();
-
-            // get all the directories inside a file if it's a valid dir
-            if let Ok(dir) = std::fs::read_dir(&path) {
-                for inner in dir {
-                    let inner = inner.map_err(|err| SolcError::io(err, &path))?;
-                    let path = inner.path().display().to_string();
-                    let path = path.rsplit('/').next().unwrap().to_string();
-                    if path != DAPPTOOLS_CONTRACTS_DIR && path != JS_CONTRACTS_DIR {
-                        paths.push(Ok(inner));
+            .filter_map(std::result::Result::ok)
+            .filter(|e| e.file_type().is_dir())
+        {
+            let dir_path = dir.path();
+
+            // in node_modules there could be following pattern:
+            //   @aave/
+            //   ├─ governance/
+            //   │  ├─ contracts/
+            //   ├─ protocol-v2/
+            //   │  ├─ contracts/
+            // in such cases, the desired remapping name would be `@aave`, but at this point we
+            // would have detected `governance` and `protocol-v2` as remapping names. so
+            // now we need to unify them by checking if they share the `dir_path` as
+            // common ancestor. We make the assumption here, that we can only unify
+            // remappings if their direct parent dir is the root, so `@aave/lib` or
+            // `@aave/src` is not mergeable as that would violate dapptools style lib paths and
+            // remappings. This means we can only unify/simplify them if there is no! `src` or `lib`
+            // path between the remappings' path and the dir_path
+            if let Some(root_name) = dir_path.file_name().and_then(|f| f.to_str()) {
+                let mut simplified = false;
+                // check all remappings in this depth 1 folder
+                'outer: for (name, path) in scan_children(dir_path) {
+                    let mut p = path.as_path();
+                    let mut first_parent = true;
+                    while let Some(parent) = p.parent() {
+                        if parent == dir_path {
+                            if !simplified {
+                                // handle trailing src,lib,contracts dir in cases like
+                                // `dir_path/@ens/contracts`
+                                let path = if first_parent { path } else { dir_path.to_path_buf() };
+                                insert_higher_path(
+                                    &mut remappings,
+                                    format!("{}/", root_name),
+                                    path,
+                                );
+                                simplified = true;
+                            }
+                            continue 'outer
+                        }
+                        if parent.ends_with(DAPPTOOLS_CONTRACTS_DIR) ||
+                            parent.ends_with(DAPPTOOLS_LIB_DIR)
+                        {
+                            // end early
+                            insert_higher_path(&mut remappings, name, path);
+                            continue 'outer
+                        }
+                        first_parent = false;
+                        p = parent;
                     }
                 }
             }
+        }
+        remappings
+            .into_iter()
+            .map(|(name, path)| Remapping { name, path: format!("{}/", path.display()) })
+            .collect()
+    }
+}
 
-            let remapping = Self::find(&path.display().to_string());
-            if let Ok(remapping) = remapping {
-                // skip remappings that exist already
-                if let Some(ref mut found) =
-                    remappings.iter_mut().find(|x: &&mut Remapping| x.name == remapping.name)
-                {
-                    // always replace with the shortest length path
-                    fn depth(path: &str, delim: char) -> usize {
-                        path.matches(delim).count()
-                    }
-                    // if the one which exists is larger, we should replace it
-                    // if not, ignore it
-                    if depth(&found.path, '/') > depth(&remapping.path, '/') {
-                        **found = remapping;
+/// Recursively scans sub folders and checks if they contain a solidity file
+fn scan_children(root: &Path) -> HashMap<String, PathBuf> {
+    // this is a marker if the current root is already a remapping
+    let mut remapping = false;
+
+    // all found remappings
+    let mut remappings = HashMap::new();
+
+    for entry in walkdir::WalkDir::new(&root)
+        .min_depth(1)
+        .max_depth(1)
+        .into_iter()
+        .filter_map(std::result::Result::ok)
+    {
+        let entry: walkdir::DirEntry = entry;
+
+        if entry.file_type().is_file() && !remapping {
+            if entry.file_name().to_str().filter(|f| f.ends_with(".sol")).is_some() {
+                // found a solidity file
+
+                // this will hold the actual root remapping if root is named `src` or `lib`
+                let actual_parent = root.parent().filter(|_| {
+                    root.ends_with(DAPPTOOLS_CONTRACTS_DIR) ||
+                        root.ends_with(DAPPTOOLS_LIB_DIR) ||
+                        root.ends_with(JS_CONTRACTS_DIR)
+                });
+
+                let parent = actual_parent.unwrap_or(root);
+                if let Some(name) = parent.file_name().and_then(|f| f.to_str()) {
+                    remappings.insert(format!("{}/", name), root.to_path_buf());
+                    remapping = true;
+                }
+            }
+        } else if entry.file_type().is_dir() {
+            let path = entry.path();
+            // we skip `tests` and nested `node_modules`
+            if !path.ends_with("tests") || !path.ends_with("node_modules") {
+                for (name, path) in scan_children(path) {
+                    if let Entry::Vacant(e) = remappings.entry(name) {
+                        e.insert(path);
                     }
-                } else {
-                    remappings.push(remapping);
                 }
             }
         }
-
-        Ok(remappings)
     }
+
+    remappings
 }
 
 #[cfg(test)]
@@ -229,17 +316,16 @@ mod tests {
         mkdir_or_touch(tmp_dir_path, &paths[..]);
 
         let path = tmp_dir_path.join("repo1").display().to_string();
-        Remapping::find_with_type(&path, JS_CONTRACTS_DIR).unwrap_err();
-        let remapping = Remapping::find_with_type(&path, DAPPTOOLS_CONTRACTS_DIR).unwrap();
-
+        let remappings = Remapping::find_many(tmp_dir_path);
         // repo1/=lib/repo1/src
-        assert_eq!(remapping.name, "repo1/");
-        assert_eq!(remapping.path, format!("{}/src/", path));
+        assert_eq!(remappings.len(), 1);
+
+        assert_eq!(remappings[0].name, "repo1/");
+        assert_eq!(remappings[0].path, format!("{}/src/", path));
     }
 
     #[test]
     fn recursive_remappings() {
-        //let tmp_dir_path = PathBuf::from("."); // tempdir::TempDir::new("lib").unwrap();
         let tmp_dir = tempdir::TempDir::new("lib").unwrap();
         let tmp_dir_path = tmp_dir.path();
         let paths = [
@@ -254,7 +340,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).unwrap();
+        let mut remappings = Remapping::find_many(&path);
         remappings.sort_unstable();
 
         let mut expected = vec![
@@ -302,7 +388,7 @@ mod tests {
         touch(&contract2).unwrap();
 
         let path = tmp_dir.path().display().to_string();
-        let mut remappings = Remapping::find_many(&path).unwrap();
+        let mut remappings = Remapping::find_many(&path);
         remappings.sort_unstable();
         let mut expected = vec![
             Remapping {
@@ -317,4 +403,36 @@ mod tests {
         expected.sort_unstable();
         assert_eq!(remappings, expected);
     }
+
+    #[test]
+    fn hardhat_remappings() {
+        let tmp_dir = tempdir::TempDir::new("node_modules").unwrap();
+        let tmp_dir_node_modules = tmp_dir.path().join("node_modules");
+        let paths = [
+            "node_modules/@aave/aave-token/contracts/token/",
+            "node_modules/@aave/aave-token/contracts/token/AaveToken.sol",
+            "node_modules/@aave/governance-v2/contracts/governance/",
+            "node_modules/@aave/governance-v2/contracts/governance/Executor.sol",
+            "node_modules/@aave/protocol-v2/contracts/protocol/lendingpool/",
+            "node_modules/@aave/protocol-v2/contracts/protocol/lendingpool/LendingPool.sol",
+            "node_modules/@ensdomains/ens/contracts/",
+            "node_modules/@ensdomains/ens/contracts/contract.sol",
+        ];
+        mkdir_or_touch(tmp_dir.path(), &paths[..]);
+        let mut remappings = Remapping::find_many(&tmp_dir_node_modules);
+        remappings.sort_unstable();
+        let mut expected = vec![
+            Remapping {
+                name: "@aave/".to_string(),
+                path: to_str(tmp_dir_node_modules.join("@aave")),
+            },
+            Remapping {
+                name: "@ensdomains/".to_string(),
+                path: to_str(tmp_dir_node_modules.join("@ensdomains")),
+            },
+        ];
+        expected.sort_unstable();
+
+        assert_eq!(remappings, expected);
+    }
 }


Test patch:
diff --git a/ethers-contract/tests/abigen.rs b/ethers-contract/tests/abigen.rs
index f3e7b4e9..a4647be7 100644
--- a/ethers-contract/tests/abigen.rs
+++ b/ethers-contract/tests/abigen.rs
@@ -9,6 +9,9 @@ use ethers_providers::Provider;
 use ethers_solc::Solc;
 use std::{convert::TryFrom, sync::Arc};
 
+fn assert_codec<T: AbiDecode + AbiEncode>() {}
+fn assert_tokenizeable<T: Tokenizable>() {}
+
 #[test]
 fn can_gen_human_readable() {
     abigen!(
@@ -54,18 +57,24 @@ fn can_gen_structs_readable() {
     ]"#,
         event_derives(serde::Deserialize, serde::Serialize)
     );
-    let value = Addresses {
+    let addr = Addresses {
         addr: vec!["eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee".parse().unwrap()],
         s: "hello".to_string(),
     };
-    let token = value.clone().into_token();
-    assert_eq!(value, Addresses::from_token(token).unwrap());
+    let token = addr.clone().into_token();
+    assert_eq!(addr, Addresses::from_token(token).unwrap());
 
     assert_eq!("ValueChanged", ValueChangedFilter::name());
     assert_eq!(
         "ValueChanged((address,string),(address,string),(address[],string))",
         ValueChangedFilter::abi_signature()
     );
+
+    assert_codec::<Value>();
+    assert_codec::<Addresses>();
+    let encoded = addr.clone().encode();
+    let other = Addresses::decode(&encoded).unwrap();
+    assert_eq!(addr, other);
 }
 
 #[test]
@@ -83,9 +92,10 @@ fn can_gen_structs_with_arrays_readable() {
         "ValueChanged((address,string),(address,string),(address[],string)[])",
         ValueChangedFilter::abi_signature()
     );
-}
 
-fn assert_tokenizeable<T: Tokenizable>() {}
+    assert_codec::<Value>();
+    assert_codec::<Addresses>();
+}
 
 #[test]
 fn can_generate_internal_structs() {
@@ -97,6 +107,10 @@ fn can_generate_internal_structs() {
     assert_tokenizeable::<VerifyingKey>();
     assert_tokenizeable::<G1Point>();
     assert_tokenizeable::<G2Point>();
+
+    assert_codec::<VerifyingKey>();
+    assert_codec::<G1Point>();
+    assert_codec::<G2Point>();
 }
 
 #[test]
@@ -119,6 +133,10 @@ fn can_generate_internal_structs_multiple() {
     assert_tokenizeable::<G1Point>();
     assert_tokenizeable::<G2Point>();
 
+    assert_codec::<VerifyingKey>();
+    assert_codec::<G1Point>();
+    assert_codec::<G2Point>();
+
     let (provider, _) = Provider::mocked();
     let client = Arc::new(provider);
 
@@ -153,6 +171,7 @@ fn can_gen_human_readable_with_structs() {
         event_derives(serde::Deserialize, serde::Serialize)
     );
     assert_tokenizeable::<Foo>();
+    assert_codec::<Foo>();
 
     let (client, _mock) = Provider::mocked();
     let contract = SimpleContract::new(Address::default(), Arc::new(client));
diff --git a/ethers-contract/tests/common/derive.rs b/ethers-contract/tests/common/derive.rs
index 3889c84e..58c0aa83 100644
--- a/ethers-contract/tests/common/derive.rs
+++ b/ethers-contract/tests/common/derive.rs
@@ -1,6 +1,8 @@
-use ethers_contract::{abigen, EthAbiType, EthCall, EthDisplay, EthEvent, EthLogDecode};
+use ethers_contract::{
+    abigen, EthAbiCodec, EthAbiType, EthCall, EthDisplay, EthEvent, EthLogDecode,
+};
 use ethers_core::{
-    abi::{RawLog, Tokenizable},
+    abi::{AbiDecode, AbiEncode, RawLog, Tokenizable},
     types::{Address, H160, H256, I256, U128, U256},
 };
 
@@ -478,3 +480,83 @@ fn can_derive_for_enum() {
     let token = ActionChoices::GoLeft.into_token();
     assert_eq!(ActionChoices::GoLeft, ActionChoices::from_token(token).unwrap());
 }
+
+#[test]
+fn can_derive_abi_codec() {
+    #[derive(Debug, Clone, PartialEq, EthAbiType, EthAbiCodec)]
+    pub struct SomeType {
+        inner: Address,
+        msg: String,
+    }
+
+    let val = SomeType { inner: Default::default(), msg: "hello".to_string() };
+
+    let encoded = val.clone().encode();
+    let other = SomeType::decode(&encoded).unwrap();
+    assert_eq!(val, other);
+}
+
+#[test]
+fn can_derive_abi_codec_single_field() {
+    #[derive(Debug, Clone, PartialEq, EthAbiType, EthAbiCodec)]
+    pub struct SomeType {
+        inner: Vec<U256>,
+    }
+
+    let val = SomeType { inner: Default::default() };
+
+    let encoded = val.clone().encode();
+    let decoded = SomeType::decode(&encoded).unwrap();
+    assert_eq!(val, decoded);
+
+    let encoded_tuple = (Vec::<U256>::default(),).encode();
+
+    assert_eq!(encoded_tuple, encoded);
+    let decoded_tuple = SomeType::decode(&encoded_tuple).unwrap();
+    assert_eq!(decoded_tuple, decoded);
+
+    let tuple = (val,);
+    let encoded = tuple.clone().encode();
+    let decoded = <(SomeType,)>::decode(&encoded).unwrap();
+    assert_eq!(tuple, decoded);
+
+    let wrapped =
+        ethers_core::abi::encode(&ethers_core::abi::Tokenize::into_tokens(tuple.clone())).to_vec();
+    assert_eq!(wrapped, encoded);
+    let decoded_wrapped = <(SomeType,)>::decode(&wrapped).unwrap();
+
+    assert_eq!(decoded_wrapped, tuple);
+}
+
+#[test]
+fn can_derive_abi_codec_two_field() {
+    #[derive(Debug, Clone, PartialEq, EthAbiType, EthAbiCodec)]
+    pub struct SomeType {
+        inner: Vec<U256>,
+        addr: Address,
+    }
+
+    let val = SomeType { inner: Default::default(), addr: Default::default() };
+
+    let encoded = val.clone().encode();
+    let decoded = SomeType::decode(&encoded).unwrap();
+    assert_eq!(val, decoded);
+
+    let encoded_tuple = (Vec::<U256>::default(), Address::default()).encode();
+
+    assert_eq!(encoded_tuple, encoded);
+    let decoded_tuple = SomeType::decode(&encoded_tuple).unwrap();
+    assert_eq!(decoded_tuple, decoded);
+
+    let tuple = (val,);
+    let encoded = tuple.clone().encode();
+    let decoded = <(SomeType,)>::decode(&encoded).unwrap();
+    assert_eq!(tuple, decoded);
+
+    let wrapped =
+        ethers_core::abi::encode(&ethers_core::abi::Tokenize::into_tokens(tuple.clone())).to_vec();
+    assert_eq!(wrapped, encoded);
+    let decoded_wrapped = <(SomeType,)>::decode(&wrapped).unwrap();
+
+    assert_eq!(decoded_wrapped, tuple);
+}