Repository: gakonst/ethers-rs
Pull request #2131 (2023-02-09 03:25:34)

refactor(abigen): solidity types expansion
<!--
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

This PR aims to move all Solidity -> Rust type expansion into the types module to remove repetition and improve the logic

<!--
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

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

-   [ ] Added Tests
-   [x] Added Documentation
-   [ ] Updated the changelog
-   [ ] Breaking changes


DaniPopes: > I think this will conflict with this 1-line bug fix

Fixed with 2cbd07f78ccf0916c6a2058114dc1f9a65594566

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5510068b..975a3c51 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -114,11 +114,13 @@
   Added `twos_complement` function for I256.
 - [#1934](https://github.com/gakonst/ethers-rs/pull/1934) Allow 16 calls in multicall.
 - [#1941](https://github.com/gakonst/ethers-rs/pull/1941) Add `add_calls` and `call_array` for `Multicall`.
+- Added basic event log filtering example.
 
 ## ethers-contract-abigen
 
 ### Unreleased
 
+- Abigen now generates events with new `<B, M>` generic pattern [#2103](https://github.com/gakonst/ethers-rs/pull/2103)
 - Fix Cargo.toml generation issue that could cause dependency conflicts [#1852](https://github.com/gakonst/ethers-rs/pull/1852)
 - Use corresponding rust structs for event fields if they're solidity structs [#1674](https://github.com/gakonst/ethers-rs/pull/1674)
 - Add `ContractFilter` to filter contracts in `MultiAbigen` [#1564](https://github.com/gakonst/ethers-rs/pull/1564)
@@ -299,6 +301,8 @@
 
 ### Unreleased
 
+- (Breaking) Make `Event` objects generic over borrow & remove lifetime
+  [#2105](https://github.com/gakonst/ethers-rs/pull/2105)
 - Make `Factory` objects generic over the borrow trait, to allow non-arc mware
   [#2103](https://github.com/gakonst/ethers-rs/pull/2103)
 - Make `Contract` objects generic over the borrow trait, to allow non-arc mware
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
index b53b08d6..08178908 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
@@ -1,66 +1,8 @@
-use super::{types, util, Context};
-use ethers_core::{
-    abi::{Param, ParamType},
-    macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate},
-};
+use super::Context;
+use ethers_core::macros::{ethers_contract_crate, ethers_core_crate, ethers_providers_crate};
 use proc_macro2::{Ident, Literal, TokenStream};
 use quote::quote;
 
-/// Expands to the `name : type` pairs for the params
-pub(crate) fn expand_params<'a, F>(
-    params: &[Param],
-    resolve_tuple: F,
-) -> eyre::Result<Vec<(TokenStream, TokenStream)>>
-where
-    F: Fn(&str) -> Option<&'a str>,
-{
-    params
-        .iter()
-        .enumerate()
-        .map(|(idx, param)| {
-            let name = util::expand_input_name(idx, &param.name);
-            let ty = expand_param_type(param, &param.kind, |s| resolve_tuple(s))?;
-            Ok((name, ty))
-        })
-        .collect()
-}
-
-/// returns the Tokenstream for the corresponding rust type
-pub(crate) fn expand_param_type<'a, F>(
-    param: &Param,
-    kind: &ParamType,
-    resolve_tuple: F,
-) -> eyre::Result<TokenStream>
-where
-    F: Fn(&str) -> Option<&'a str>,
-{
-    match kind {
-        ParamType::Array(ty) => {
-            let ty = expand_param_type(param, ty, resolve_tuple)?;
-            Ok(quote! {
-                ::std::vec::Vec<#ty>
-            })
-        }
-        ParamType::FixedArray(ty, size) => {
-            let ty = expand_param_type(param, ty, resolve_tuple)?;
-            let size = *size;
-            Ok(quote! {[#ty; #size]})
-        }
-        ParamType::Tuple(_) => {
-            let ty = if let Some(rust_struct_name) =
-                param.internal_type.as_ref().and_then(|s| resolve_tuple(s.as_str()))
-            {
-                let ident = util::ident(rust_struct_name);
-                quote! {#ident}
-            } else {
-                types::expand(kind)?
-            };
-            Ok(ty)
-        }
-        _ => types::expand(kind),
-    }
-}
-
 pub(crate) fn imports(name: &str) -> TokenStream {
     let doc_str = format!("{name} was auto-generated with ethers-rs Abigen. More information at: <https://github.com/gakonst/ethers-rs>");
 
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/errors.rs b/ethers-contract/ethers-contract-abigen/src/contract/errors.rs
index 4462386e..f8fea826 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/errors.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/errors.rs
@@ -1,7 +1,9 @@
 //! derive error bindings
 
-use super::{util, Context};
-use crate::contract::common::{expand_data_struct, expand_data_tuple, expand_params};
+use super::{
+    common::{expand_data_struct, expand_data_tuple},
+    types, util, Context,
+};
 use ethers_core::{
     abi::{ethabi::AbiError, ErrorExt},
     macros::{ethers_contract_crate, ethers_core_crate},
@@ -81,7 +83,9 @@ impl Context {
 
     /// Expands to the `name : type` pairs of the function's outputs
     fn expand_error_params(&self, error: &AbiError) -> Result<Vec<(TokenStream, TokenStream)>> {
-        expand_params(&error.inputs, |s| self.internal_structs.get_struct_type(s))
+        types::expand_params(&error.inputs, |p| {
+            p.internal_type.as_deref().and_then(|s| self.internal_structs.get_struct_type(s))
+        })
     }
 
     /// The name ident of the errors enum
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/events.rs b/ethers-contract/ethers-contract-abigen/src/contract/events.rs
index f0e1d778..bef73855 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/events.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/events.rs
@@ -1,12 +1,12 @@
 use super::{types, util, Context};
 use crate::util::can_derive_defaults;
 use ethers_core::{
-    abi::{Event, EventExt, EventParam, Param, ParamType},
+    abi::{Event, EventExt, Param},
     macros::{ethers_contract_crate, ethers_core_crate},
 };
 use eyre::Result;
 use inflector::Inflector;
-use proc_macro2::{Ident, Literal, TokenStream};
+use proc_macro2::{Ident, TokenStream};
 use quote::quote;
 use std::collections::BTreeMap;
 
@@ -127,7 +127,7 @@ impl Context {
 
             quote! {
                 /// Returns an [`Event`](#ethers_contract::builders::Event) builder for all events of this contract
-                pub fn events(&self) -> #ethers_contract::builders::Event<M, #ty> {
+                pub fn events(&self) -> #ethers_contract::builders::Event<Arc<M>, M, #ty> {
                     self.0.event_with_filter(Default::default())
                 }
             }
@@ -136,85 +136,6 @@ impl Context {
         }
     }
 
-    /// Expands an event property type.
-    ///
-    /// Note that this is slightly different from expanding a Solidity type as
-    /// complex types like arrays and strings get emitted as hashes when they are
-    /// indexed.
-    /// If a complex types matches with a struct previously parsed by the internal structs,
-    /// we can replace it
-    fn expand_input_type(
-        &self,
-        event: &Event,
-        input: &EventParam,
-        idx: usize,
-    ) -> Result<TokenStream> {
-        let ethers_core = ethers_core_crate();
-        Ok(match (&input.kind, input.indexed) {
-            (ParamType::Array(_), true) => {
-                quote! { #ethers_core::types::H256 }
-            }
-            (ParamType::FixedArray(_, _), true) => {
-                quote! { #ethers_core::types::H256 }
-            }
-            (ParamType::Tuple(..), true) => {
-                quote! { #ethers_core::types::H256 }
-            }
-            (ParamType::Bytes, true) | (ParamType::String, true) => {
-                quote! { #ethers_core::types::H256 }
-            }
-            (ParamType::Tuple(_), false) => {
-                let ty = if let Some(rust_struct_name) =
-                    self.internal_structs.get_event_input_struct_type(&event.name, idx)
-                {
-                    let ident = util::ident(rust_struct_name);
-                    quote! {#ident}
-                } else {
-                    types::expand(&input.kind)?
-                };
-                ty
-            }
-            (ParamType::Array(_), _) => {
-                // represents an array of a struct
-                if let Some(rust_struct_name) =
-                    self.internal_structs.get_event_input_struct_type(&event.name, idx)
-                {
-                    let ty = util::ident(rust_struct_name);
-                    return Ok(quote! {::std::vec::Vec<#ty>})
-                }
-                types::expand(&input.kind)?
-            }
-            (ParamType::FixedArray(_, size), _) => {
-                // represents a fixed array of a struct
-                if let Some(rust_struct_name) =
-                    self.internal_structs.get_event_input_struct_type(&event.name, idx)
-                {
-                    let ty = util::ident(rust_struct_name);
-                    let size = Literal::usize_unsuffixed(*size);
-                    return Ok(quote! {[#ty; #size]})
-                }
-                types::expand(&input.kind)?
-            }
-            (kind, _) => types::expand(kind)?,
-        })
-    }
-
-    /// Expands the name-type pairs for the given inputs
-    fn expand_event_params(&self, event: &Event) -> Result<Vec<(TokenStream, TokenStream, bool)>> {
-        event
-            .inputs
-            .iter()
-            .enumerate()
-            .map(|(idx, input)| {
-                // NOTE: Events can contain nameless values.
-                let name = util::expand_input_name(idx, &input.name);
-                let ty = self.expand_input_type(event, input, idx)?;
-
-                Ok((name, ty, input.indexed))
-            })
-            .collect()
-    }
-
     /// Expands into a single method for contracting an event stream.
     fn expand_filter(&self, event: &Event) -> TokenStream {
         let name = &event.name;
@@ -235,7 +156,7 @@ impl Context {
 
         quote! {
             #[doc = #doc_str]
-            pub fn #function_name(&self) -> #ethers_contract::builders::Event<M, #struct_name> {
+            pub fn #function_name(&self) -> #ethers_contract::builders::Event<Arc<M>, M, #struct_name> {
                 self.0.event()
             }
         }
@@ -251,7 +172,7 @@ impl Context {
 
         let event_name = event_struct_name(&event.name, sig);
 
-        let params = self.expand_event_params(event)?;
+        let params = types::expand_event_inputs(event, &self.internal_structs)?;
         // expand as a tuple if all fields are anonymous
         let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
         let data_type_definition = if all_anonymous_fields {
@@ -355,6 +276,7 @@ mod tests {
     use super::*;
     use crate::Abigen;
     use ethers_core::abi::{EventParam, Hash, ParamType};
+    use proc_macro2::Literal;
 
     /// Expands a 256-bit `Hash` into a literal representation that can be used with
     /// quasi-quoting for code generation. We do this to avoid allocating at runtime
@@ -406,7 +328,7 @@ mod tests {
             #[doc = "Gets the contract's `Transfer` event"]
             pub fn transfer_event_filter(
                 &self
-            ) -> ::ethers_contract::builders::Event<M, TransferEventFilter> {
+            ) -> ::ethers_contract::builders::Event<Arc<M>, M, TransferEventFilter> {
                 self.0.event()
             }
         });
@@ -425,7 +347,9 @@ mod tests {
         let cx = test_context();
         assert_quote!(cx.expand_filter(&event), {
             #[doc = "Gets the contract's `Transfer` event"]
-            pub fn transfer_filter(&self) -> ::ethers_contract::builders::Event<M, TransferFilter> {
+            pub fn transfer_filter(
+                &self,
+            ) -> ::ethers_contract::builders::Event<Arc<M>, M, TransferFilter> {
                 self.0.event()
             }
         });
@@ -443,7 +367,7 @@ mod tests {
         };
 
         let cx = test_context();
-        let params = cx.expand_event_params(&event).unwrap();
+        let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
         let name = event_struct_name(&event.name, None);
         let definition = expand_data_struct(&name, &params);
 
@@ -467,7 +391,7 @@ mod tests {
         };
 
         let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
-        let params = cx.expand_event_params(&event).unwrap();
+        let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
         let alias = Some(util::ident("FooAliased"));
         let name = event_struct_name(&event.name, alias);
         let definition = expand_data_struct(&name, &params);
@@ -492,7 +416,7 @@ mod tests {
         };
 
         let cx = test_context();
-        let params = cx.expand_event_params(&event).unwrap();
+        let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
         let name = event_struct_name(&event.name, None);
         let definition = expand_data_tuple(&name, &params);
 
@@ -513,7 +437,7 @@ mod tests {
         };
 
         let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
-        let params = cx.expand_event_params(&event).unwrap();
+        let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
         let alias = Some(util::ident("FooAliased"));
         let name = event_struct_name(&event.name, alias);
         let definition = expand_data_tuple(&name, &params);
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
index b4d1ed21..0df43602 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/methods.rs
@@ -1,10 +1,8 @@
-use std::collections::{btree_map::Entry, BTreeMap, HashMap, HashSet};
-
-use super::{types, util, Context};
-use crate::{
-    contract::common::{expand_data_struct, expand_data_tuple, expand_param_type, expand_params},
-    util::can_derive_defaults,
+use super::{
+    common::{expand_data_struct, expand_data_tuple},
+    types, Context,
 };
+use crate::util::{self, can_derive_defaults};
 use ethers_core::{
     abi::{Function, FunctionExt, Param, ParamType},
     macros::{ethers_contract_crate, ethers_core_crate},
@@ -14,6 +12,7 @@ use eyre::{Context as _, Result};
 use inflector::Inflector;
 use proc_macro2::{Literal, TokenStream};
 use quote::quote;
+use std::collections::{btree_map::Entry, BTreeMap, HashMap, HashSet};
 use syn::Ident;
 
 /// The maximum amount of overloaded functions that are attempted to auto aliased with their param
@@ -304,129 +303,47 @@ impl Context {
 
     /// Expands to the `name : type` pairs of the function's inputs
     fn expand_input_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
-        fun.inputs
-            .iter()
-            .enumerate()
-            .map(|(idx, param)| {
-                let name = util::expand_input_name(idx, &param.name);
-                let ty = self.expand_input_param_type(fun, &param.name, &param.kind)?;
-                Ok((name, ty))
-            })
-            .collect()
+        types::expand_params(&fun.inputs, |p| {
+            self.internal_structs.get_function_input_struct_type(&fun.name, &p.name)
+        })
     }
 
-    /// Expands to the `name : type` pairs of the function's outputs
+    /// Expands to the `name: type` pairs of the function's outputs
     fn expand_output_params(&self, fun: &Function) -> Result<Vec<(TokenStream, TokenStream)>> {
-        expand_params(&fun.outputs, |s| {
-            self.internal_structs.get_function_output_struct_type(&fun.name, s)
+        types::expand_params(&fun.outputs, |p| {
+            p.internal_type
+                .as_deref()
+                .and_then(|s| self.internal_structs.get_function_output_struct_type(&fun.name, s))
         })
     }
 
-    /// Expands to the return type of a function
-    fn expand_outputs(&self, fun: &Function) -> Result<TokenStream> {
-        let mut outputs = Vec::with_capacity(fun.outputs.len());
-        for param in fun.outputs.iter() {
-            let ty = self.expand_output_param_type(fun, param, &param.kind)?;
-            outputs.push(ty);
-        }
-
-        let return_ty = match outputs.len() {
-            0 => quote! { () },
-            1 => outputs[0].clone(),
-            _ => {
-                quote! { (#( #outputs ),*) }
-            }
-        };
-        Ok(return_ty)
-    }
-
     /// Expands the arguments for the call that eventually calls the contract
-    fn expand_contract_call_args(&self, fun: &Function) -> Result<TokenStream> {
-        let mut call_args = Vec::with_capacity(fun.inputs.len());
-        for (idx, param) in fun.inputs.iter().enumerate() {
+    fn expand_contract_call_args(&self, fun: &Function) -> TokenStream {
+        let mut call_args = fun.inputs.iter().enumerate().map(|(idx, param)| {
             let name = util::expand_input_name(idx, &param.name);
-            let call_arg = match param.kind {
+            match param.kind {
                 // this is awkward edge case where the function inputs are a single struct
-                // we need to force this argument into a tuple so it gets expanded to `((#name,))`
-                // this is currently necessary because internally `flatten_tokens` is called which
-                // removes the outermost `tuple` level and since `((#name))` is not
-                // a rust tuple it doesn't get wrapped into another tuple that will be peeled off by
-                // `flatten_tokens`
+                // we need to force this argument into a tuple so it gets expanded to
+                // `((#name,))` this is currently necessary because
+                // internally `flatten_tokens` is called which removes the
+                // outermost `tuple` level and since `((#name))` is not
+                // a rust tuple it doesn't get wrapped into another tuple that will be peeled
+                // off by `flatten_tokens`
                 ParamType::Tuple(_) if fun.inputs.len() == 1 => {
                     // make sure the tuple gets converted to `Token::Tuple`
-                    quote! {(#name,)}
+                    quote!((#name,))
                 }
                 _ => name,
-            };
-            call_args.push(call_arg);
-        }
-        let call_args = match call_args.len() {
-            0 => quote! { () },
-            1 => quote! { #( #call_args )* },
-            _ => quote! { ( #(#call_args, )* ) },
-        };
-
-        Ok(call_args)
-    }
-
-    /// returns the Tokenstream for the corresponding rust type of the param
-    fn expand_input_param_type(
-        &self,
-        fun: &Function,
-        param: &str,
-        kind: &ParamType,
-    ) -> Result<TokenStream> {
-        let ethers_core = ethers_core_crate();
-        match kind {
-            ParamType::Array(ty) => {
-                let ty = self.expand_input_param_type(fun, param, ty)?;
-                Ok(quote! {
-                    ::std::vec::Vec<#ty>
-                })
             }
-            ParamType::FixedArray(ty, size) => {
-                let ty = match **ty {
-                    ParamType::Uint(size) => {
-                        if size / 8 == 1 {
-                            // this prevents type ambiguity with `FixedBytes`
-                            quote! { #ethers_core::types::Uint8}
-                        } else {
-                            self.expand_input_param_type(fun, param, ty)?
-                        }
-                    }
-                    _ => self.expand_input_param_type(fun, param, ty)?,
-                };
+        });
 
-                let size = *size;
-                Ok(quote! {[#ty; #size]})
-            }
-            ParamType::Tuple(_) => {
-                let ty = if let Some(rust_struct_name) =
-                    self.internal_structs.get_function_input_struct_type(&fun.name, param)
-                {
-                    let ident = util::ident(rust_struct_name);
-                    quote! {#ident}
-                } else {
-                    types::expand(kind)?
-                };
-                Ok(ty)
-            }
-            _ => types::expand(kind),
+        match fun.inputs.len() {
+            0 => quote!(()),
+            1 => call_args.next().unwrap(),
+            _ => quote!(( #( #call_args ),* )),
         }
     }
 
-    /// returns the TokenStream for the corresponding rust type of the output param
-    fn expand_output_param_type(
-        &self,
-        fun: &Function,
-        param: &Param,
-        kind: &ParamType,
-    ) -> Result<TokenStream> {
-        expand_param_type(param, kind, |s| {
-            self.internal_structs.get_function_output_struct_type(&fun.name, s)
-        })
-    }
-
     /// Expands a single function with the given alias
     fn expand_function(
         &self,
@@ -439,12 +356,22 @@ impl Context {
 
         let selector_tokens = expand_selector(selector);
 
-        let contract_args = self.expand_contract_call_args(function)?;
+        let contract_args = self.expand_contract_call_args(function);
         let function_params =
             self.expand_input_params(function)?.into_iter().map(|(name, ty)| quote! { #name: #ty });
         let function_params = quote! { #( , #function_params )* };
 
-        let outputs = self.expand_outputs(function)?;
+        let outputs = {
+            let mut out = self.expand_output_params(function)?;
+            match out.len() {
+                0 => quote!(()),
+                1 => out.pop().unwrap().1,
+                _ => {
+                    let iter = out.into_iter().map(|(_, ty)| ty);
+                    quote!(( #( #iter ),* ))
+                }
+            }
+        };
 
         let doc_str =
             format!("Calls the contract's `{name}` (0x{}) function", hex::encode(selector));
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
index b2b3dbeb..13dcac4f 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
@@ -93,7 +93,7 @@ impl Context {
         for field in sol_struct.fields() {
             let ty = match field.r#type() {
                 FieldType::Elementary(ty) => types::expand(ty)?,
-                FieldType::Struct(struct_ty) => expand_struct_type(struct_ty),
+                FieldType::Struct(struct_ty) => types::expand_struct_type(struct_ty),
                 FieldType::Mapping(_) => {
                     eyre::bail!("Mapping types in struct `{}` are not supported {:?}", name, field)
                 }
@@ -154,7 +154,7 @@ impl Context {
                     fields.push(quote! { pub #field_name: #ty });
                 }
                 FieldType::Struct(struct_ty) => {
-                    let ty = expand_struct_type(struct_ty);
+                    let ty = types::expand_struct_type(struct_ty);
                     fields.push(quote! { pub #field_name: #ty });
 
                     let name = struct_ty.name();
@@ -592,24 +592,6 @@ fn struct_type_projections(name: &str) -> Vec<String> {
     iter.rev().map(str::to_string).collect()
 }
 
-/// Expands to the rust struct type
-fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream {
-    match struct_ty {
-        StructFieldType::Type(ty) => {
-            let ty = util::ident(&ty.name().to_pascal_case());
-            quote! {#ty}
-        }
-        StructFieldType::Array(ty) => {
-            let ty = expand_struct_type(ty);
-            quote! {::std::vec::Vec<#ty>}
-        }
-        StructFieldType::FixedArray(ty, size) => {
-            let ty = expand_struct_type(ty);
-            quote! { [#ty; #size]}
-        }
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/types.rs b/ethers-contract/ethers-contract-abigen/src/contract/types.rs
index 609add65..f1918d70 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/types.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/types.rs
@@ -1,9 +1,17 @@
-use ethers_core::{abi::ParamType, macros::ethers_core_crate};
+//! Types expansion
+
+use crate::{util, InternalStructs};
+use ethers_core::{
+    abi::{struct_def::StructFieldType, Event, EventParam, Param, ParamType},
+    macros::ethers_core_crate,
+};
 use eyre::{bail, Result};
+use inflector::Inflector;
 use proc_macro2::{Literal, TokenStream};
-use quote::quote;
+use quote::{quote, ToTokens};
 
-pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
+/// Expands a ParamType Solidity type to its Rust equivalent.
+pub fn expand(kind: &ParamType) -> Result<TokenStream> {
     let ethers_core = ethers_core_crate();
 
     match kind {
@@ -16,7 +24,7 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
             5..=8 => Ok(quote! { i64 }),
             9..=16 => Ok(quote! { i128 }),
             17..=32 => Ok(quote! { #ethers_core::types::I256 }),
-            _ => bail!("unsupported solidity type int{}", n),
+            _ => bail!("unsupported solidity type int{n}"),
         },
         ParamType::Uint(n) => match n / 8 {
             1 => Ok(quote! { u8 }),
@@ -25,41 +33,151 @@ pub(crate) fn expand(kind: &ParamType) -> Result<TokenStream> {
             5..=8 => Ok(quote! { u64 }),
             9..=16 => Ok(quote! { u128 }),
             17..=32 => Ok(quote! { #ethers_core::types::U256 }),
-            _ => bail!("unsupported solidity type uint{}", n),
+            _ => bail!("unsupported solidity type uint{n}"),
         },
-        ParamType::Bool => Ok(quote! { bool }),
-        ParamType::String => Ok(quote! { String }),
-        ParamType::Array(t) => {
-            let inner = expand(t)?;
-            Ok(quote! { Vec<#inner> })
-        }
-        ParamType::FixedBytes(n) => {
-            // TODO(nlordell): what is the performance impact of returning large
-            //   `FixedBytes` and `FixedArray`s with `web3`?
-            let size = Literal::usize_unsuffixed(*n);
-            Ok(quote! { [u8; #size] })
-        }
-        ParamType::FixedArray(t, n) => {
-            // TODO(nlordell): see above
-            let inner = match **t {
-                ParamType::Uint(size) => {
-                    if size / 8 == 1 {
-                        // this prevents type ambiguity with `FixedBytes`
-                        quote! { #ethers_core::types::Uint8}
-                    } else {
-                        expand(t)?
-                    }
-                }
-                _ => expand(t)?,
+        ParamType::Bool => Ok(quote!(bool)),
+        ParamType::String => Ok(quote!(::std::string::String)),
+        ParamType::Array(ty) => Ok(array(expand(ty)?, None)),
+        ParamType::FixedBytes(n) => Ok(array(quote!(u8), Some(*n))),
+        ParamType::FixedArray(ty, n) => {
+            let ty = match **ty {
+                // this prevents type ambiguity with `FixedBytes`
+                // see: https://github.com/gakonst/ethers-rs/issues/1636
+                ParamType::Uint(size) if size / 8 == 1 => quote!(#ethers_core::types::Uint8),
+                _ => expand(ty)?,
             };
-            let size = Literal::usize_unsuffixed(*n);
-            Ok(quote! { [#inner; #size] })
+            Ok(array(ty, Some(*n)))
         }
         ParamType::Tuple(members) => {
             eyre::ensure!(!members.is_empty(), "Tuple must have at least 1 member");
 
             let members = members.iter().map(expand).collect::<Result<Vec<_>, _>>()?;
-            Ok(quote! { (#(#members,)*) })
+            Ok(quote!(( #( #members ),* )))
+        }
+    }
+}
+
+/// Expands the event's inputs.
+pub fn expand_event_inputs(
+    event: &Event,
+    internal_structs: &InternalStructs,
+) -> Result<Vec<(TokenStream, TokenStream, bool)>> {
+    event
+        .inputs
+        .iter()
+        .enumerate()
+        .map(|(index, input)| {
+            // NOTE: Events can contain nameless values.
+            expand_event_input(input, &event.name, index, internal_structs)
+                .map(|ty| (util::expand_input_name(index, &input.name), ty, input.indexed))
+        })
+        .collect()
+}
+
+/// Expands an event property type.
+///
+/// Note that this is slightly different from expanding a Solidity type as complex types like arrays
+/// and strings get emitted as hashes when they are indexed.
+///
+/// If a complex types matches with a struct previously parsed by the internal structs, we can
+/// replace it.
+fn expand_event_input(
+    input: &EventParam,
+    name: &str,
+    index: usize,
+    internal_structs: &InternalStructs,
+) -> Result<TokenStream> {
+    let kind = &input.kind;
+    match (kind, input.indexed) {
+        (ParamType::Array(_), true) |
+        (ParamType::FixedArray(_, _), true) |
+        (ParamType::Tuple(_), true) |
+        (ParamType::Bytes, true) |
+        (ParamType::String, true) => {
+            let ethers_core = ethers_core_crate();
+            Ok(quote!(#ethers_core::types::H256))
+        }
+
+        (ParamType::Array(_), false) |
+        (ParamType::FixedArray(_, _), false) |
+        (ParamType::Tuple(_), false) => {
+            match internal_structs.get_event_input_struct_type(name, index) {
+                Some(ty) => {
+                    let ty = util::ident(ty);
+                    match kind {
+                        ParamType::Array(_) => Ok(array(ty, None)),
+                        ParamType::FixedArray(_, size) => Ok(array(ty, Some(*size))),
+                        ParamType::Tuple(_) => Ok(quote!(#ty)),
+                        _ => unreachable!(),
+                    }
+                }
+                None => expand(kind),
+            }
+        }
+
+        _ => expand(kind),
+    }
+}
+
+/// Expands `params` to `(name, type)` tokens pairs, while resolving tuples' types using the given
+/// function.
+pub fn expand_params<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>(
+    params: &'a [Param],
+    resolve_tuple: F,
+) -> Result<Vec<(TokenStream, TokenStream)>> {
+    params
+        .iter()
+        .enumerate()
+        .map(|(idx, param)| {
+            // NOTE: Params can be unnamed.
+            expand_resolved(&param.kind, param, &resolve_tuple)
+                .map(|ty| (util::expand_input_name(idx, &param.name), ty))
+        })
+        .collect()
+}
+
+/// Expands a ParamType Solidity type to its Rust equivalent, while resolving tuples' types using
+/// the given function.
+fn expand_resolved<'a, 'b, F: Fn(&'a Param) -> Option<&'b str>>(
+    kind: &'a ParamType,
+    param: &'a Param,
+    resolve_tuple: &F,
+) -> Result<TokenStream> {
+    match kind {
+        ParamType::Array(ty) => Ok(array(expand_resolved(ty, param, resolve_tuple)?, None)),
+        ParamType::FixedArray(ty, size) => {
+            Ok(array(expand_resolved(ty, param, resolve_tuple)?, Some(*size)))
+        }
+        ParamType::Tuple(_) => match resolve_tuple(param) {
+            Some(ty) => {
+                let ty = util::ident(ty);
+                Ok(quote!(#ty))
+            }
+            None => expand(kind),
+        },
+        _ => expand(kind),
+    }
+}
+
+/// Expands to the Rust struct type.
+pub fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream {
+    match struct_ty {
+        StructFieldType::Type(ty) => {
+            let ty = util::ident(&ty.name().to_pascal_case());
+            quote!(#ty)
+        }
+        StructFieldType::Array(ty) => array(expand_struct_type(ty), None),
+        StructFieldType::FixedArray(ty, size) => array(expand_struct_type(ty), Some(*size)),
+    }
+}
+
+/// Expands `ty` into a Rust array or vector.
+fn array<T: ToTokens>(ty: T, size: Option<usize>) -> TokenStream {
+    match size {
+        Some(size) => {
+            let size = Literal::usize_unsuffixed(size);
+            quote!([#ty; #size])
         }
+        None => quote!(::std::vec::Vec<#ty>),
     }
 }
diff --git a/ethers-contract/ethers-contract-derive/src/event.rs b/ethers-contract/ethers-contract-derive/src/event.rs
index e2a1b7ec..34c4b0a2 100644
--- a/ethers-contract/ethers-contract-derive/src/event.rs
+++ b/ethers-contract/ethers-contract-derive/src/event.rs
@@ -17,77 +17,74 @@ use hex::FromHex;
 use crate::{abi_ty, utils};
 
 /// Generates the `EthEvent` trait support
-pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> TokenStream {
-    // the ethers crates to use
-    let core_crate = ethers_core_crate();
-    let contract_crate = ethers_contract_crate();
-
+pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> Result<TokenStream, Error> {
     let name = &input.ident;
-    let attributes = match parse_event_attributes(&input) {
-        Ok(attributes) => attributes,
-        Err(errors) => return errors,
-    };
-
-    let event_name = attributes.name.map(|(s, _)| s).unwrap_or_else(|| input.ident.to_string());
+    let attributes = parse_event_attributes(&input)?;
 
     let mut event = if let Some((src, span)) = attributes.abi {
-        // try to parse as solidity event
-        if let Ok(event) = HumanReadableParser::parse_event(&src) {
-            event
-        } else {
-            match src.parse::<Source>().and_then(|s| s.get()) {
-                Ok(abi) => {
-                    // try to derive the signature from the abi from the parsed abi
-                    // TODO(mattsse): this will fail for events that contain other non
-                    // elementary types in their abi  because the parser
-                    // doesn't know how to substitute the types
-                    //  this could be mitigated by getting the ABI of each non elementary type
-                    // at runtime  and computing the the signature as
-                    // `static Lazy::...`
-                    match HumanReadableParser::parse_event(&abi) {
-                        Ok(event) => event,
-                        Err(err) => return Error::new(span, err).to_compile_error(),
+        // try to parse as a Solidity event
+        match HumanReadableParser::parse_event(&src) {
+            Ok(event) => Ok(event),
+            Err(parse_err) => {
+                match src.parse::<Source>().and_then(|s| s.get()) {
+                    Ok(abi) => {
+                        // try to derive the signature from the abi from the parsed abi
+                        // TODO(mattsse): this will fail for events that contain other non
+                        // elementary types in their abi because the parser
+                        // doesn't know how to substitute the types.
+                        // This could be mitigated by getting the ABI of each non elementary type
+                        // at runtime and computing the the signature as a Lazy static.
+                        match HumanReadableParser::parse_event(&abi) {
+                            Ok(event) => Ok(event),
+                            // Ignore parse_err since this is a valid [Source]
+                            Err(err) => Err(Error::new(span, err)),
+                        }
+                    }
+                    Err(source_err) => {
+                        // Return both error messages
+                        let message = format!("Failed parsing ABI: {parse_err} ({source_err})");
+                        Err(Error::new(span, message))
                     }
                 }
-                Err(err) => return Error::new(span, err).to_compile_error(),
             }
         }
     } else {
         // try to determine the abi from the fields
-        match derive_abi_event_from_fields(&input) {
-            Ok(event) => event,
-            Err(err) => return err.to_compile_error(),
-        }
-    };
+        derive_abi_event_from_fields(&input)
+    }?;
+
+    if let Some((attribute_name, _)) = attributes.name {
+        event.name = attribute_name;
+    }
 
-    event.name = event_name.clone();
     if let Some((anon, _)) = attributes.anonymous.as_ref() {
         event.anonymous = *anon;
     }
 
-    let decode_log_impl = match derive_decode_from_log_impl(&input, &event) {
-        Ok(log) => log,
-        Err(err) => return err.to_compile_error(),
-    };
+    let decode_log_impl = derive_decode_from_log_impl(&input, &event)?;
 
-    let (abi, hash) = (event.abi_signature(), event.signature());
+    let (abi, event_sig) = (event.abi_signature(), event.signature());
 
     let signature = if let Some((hash, _)) = attributes.signature_hash {
         utils::signature(&hash)
     } else {
-        utils::signature(hash.as_bytes())
+        utils::signature(event_sig.as_bytes())
     };
 
     let anon = attributes.anonymous.map(|(b, _)| b).unwrap_or_default();
+    let event_name = &event.name;
+
+    let ethers_core = ethers_core_crate();
+    let ethers_contract = ethers_contract_crate();
 
     let ethevent_impl = quote! {
-        impl #contract_crate::EthEvent for #name {
+        impl #ethers_contract::EthEvent for #name {
 
             fn name() -> ::std::borrow::Cow<'static, str> {
                 #event_name.into()
             }
 
-            fn signature() -> #core_crate::types::H256 {
+            fn signature() -> #ethers_core::types::H256 {
                 #signature
             }
 
@@ -95,7 +92,7 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> TokenStream {
                 #abi.into()
             }
 
-            fn decode_log(log: &#core_crate::abi::RawLog) -> ::std::result::Result<Self, #core_crate::abi::Error> where Self: Sized {
+            fn decode_log(log: &#ethers_core::abi::RawLog) -> ::std::result::Result<Self, #ethers_core::abi::Error> where Self: Sized {
                 #decode_log_impl
             }
 
@@ -107,10 +104,10 @@ pub(crate) fn derive_eth_event_impl(input: DeriveInput) -> TokenStream {
 
     let tokenize_impl = abi_ty::derive_tokenizeable_impl(&input);
 
-    quote! {
+    Ok(quote! {
         #tokenize_impl
         #ethevent_impl
-    }
+    })
 }
 
 /// Internal helper type for an event/log
@@ -130,7 +127,7 @@ fn derive_decode_from_log_impl(
     input: &DeriveInput,
     event: &Event,
 ) -> Result<proc_macro2::TokenStream, Error> {
-    let core_crate = ethers_core_crate();
+    let ethers_core = ethers_core_crate();
 
     let fields: Vec<_> = match input.data {
         Data::Struct(ref data) => match data.fields {
@@ -213,16 +210,16 @@ fn derive_decode_from_log_impl(
             },
             quote! {
                 if topic_tokens.len() != topics.len() {
-                    return Err(#core_crate::abi::Error::InvalidData);
+                    return Err(#ethers_core::abi::Error::InvalidData);
                 }
             },
         )
     } else {
         (
             quote! {
-                let event_signature = topics.get(0).ok_or(#core_crate::abi::Error::InvalidData)?;
+                let event_signature = topics.get(0).ok_or(#ethers_core::abi::Error::InvalidData)?;
                 if event_signature != &Self::signature() {
-                    return Err(#core_crate::abi::Error::InvalidData);
+                    return Err(#ethers_core::abi::Error::InvalidData);
                 }
             },
             quote! {
@@ -230,7 +227,7 @@ fn derive_decode_from_log_impl(
             },
             quote! {
                 if topic_tokens.len() != topics.len() - 1 {
-                    return Err(#core_crate::abi::Error::InvalidData);
+                    return Err(#ethers_core::abi::Error::InvalidData);
                 }
             },
         )
@@ -244,9 +241,9 @@ fn derive_decode_from_log_impl(
         .all(|(idx, f)| f.index == idx)
     {
         quote! {
-            let topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?;
+            let topic_tokens = #ethers_core::abi::decode(&topic_types, &flat_topics)?;
             #topic_tokens_len_check
-            let data_tokens = #core_crate::abi::decode(&data_types, data)?;
+            let data_tokens = #ethers_core::abi::decode(&data_types, data)?;
             let tokens:Vec<_> = topic_tokens.into_iter().chain(data_tokens.into_iter()).collect();
         }
     } else {
@@ -259,16 +256,16 @@ fn derive_decode_from_log_impl(
         });
 
         quote! {
-            let mut topic_tokens = #core_crate::abi::decode(&topic_types, &flat_topics)?;
+            let mut topic_tokens = #ethers_core::abi::decode(&topic_types, &flat_topics)?;
             #topic_tokens_len_check
-            let mut data_tokens = #core_crate::abi::decode(&data_types, &data)?;
+            let mut data_tokens = #ethers_core::abi::decode(&data_types, &data)?;
             let mut tokens = Vec::with_capacity(topics.len() + data_tokens.len());
             #( tokens.push(#swap_tokens); )*
         }
     };
     Ok(quote! {
 
-        let #core_crate::abi::RawLog {data, topics} = log;
+        let #ethers_core::abi::RawLog {data, topics} = log;
 
         #signature_check
 
@@ -279,14 +276,14 @@ fn derive_decode_from_log_impl(
 
         #tokens_init
 
-        #core_crate::abi::Tokenizable::from_token(#core_crate::abi::Token::Tuple(tokens)).map_err(|_|#core_crate::abi::Error::InvalidData)
+        #ethers_core::abi::Tokenizable::from_token(#ethers_core::abi::Token::Tuple(tokens)).map_err(|_|#ethers_core::abi::Error::InvalidData)
     })
 }
 
 /// Determine the event's ABI by parsing the AST
 fn derive_abi_event_from_fields(input: &DeriveInput) -> Result<Event, Error> {
     let event = Event {
-        name: "".to_string(),
+        name: input.ident.to_string(),
         inputs: utils::derive_abi_inputs_from_fields(input, "EthEvent")?
             .into_iter()
             .map(|(name, kind)| EventParam { name, kind, indexed: false })
@@ -355,9 +352,7 @@ struct EthEventAttributes {
 }
 
 /// extracts the attributes from the struct annotated with `EthEvent`
-fn parse_event_attributes(
-    input: &DeriveInput,
-) -> Result<EthEventAttributes, proc_macro2::TokenStream> {
+fn parse_event_attributes(input: &DeriveInput) -> Result<EthEventAttributes, Error> {
     let mut result = EthEventAttributes::default();
     for a in input.attrs.iter() {
         if let AttrStyle::Outer = a.style {
@@ -376,23 +371,20 @@ fn parse_event_attributes(
                                                 return Err(Error::new(
                                                     name.span(),
                                                     "anonymous already specified",
-                                                )
-                                                .to_compile_error())
+                                                ))
                                             }
                                         }
                                     }
                                     return Err(Error::new(
                                         path.span(),
                                         "unrecognized ethevent parameter",
-                                    )
-                                    .to_compile_error())
+                                    ))
                                 }
                                 Meta::List(meta) => {
                                     return Err(Error::new(
                                         meta.path.span(),
                                         "unrecognized ethevent parameter",
-                                    )
-                                    .to_compile_error())
+                                    ))
                                 }
                                 Meta::NameValue(meta) => {
                                     if meta.path.is_ident("anonymous") {
@@ -404,15 +396,13 @@ fn parse_event_attributes(
                                                 return Err(Error::new(
                                                     meta.span(),
                                                     "anonymous already specified",
-                                                )
-                                                .to_compile_error())
+                                                ))
                                             }
                                         } else {
                                             return Err(Error::new(
                                                 meta.span(),
                                                 "name must be a string",
-                                            )
-                                            .to_compile_error())
+                                            ))
                                         }
                                     } else if meta.path.is_ident("name") {
                                         if let Lit::Str(ref lit_str) = meta.lit {
@@ -423,15 +413,13 @@ fn parse_event_attributes(
                                                 return Err(Error::new(
                                                     meta.span(),
                                                     "name already specified",
-                                                )
-                                                .to_compile_error())
+                                                ))
                                             }
                                         } else {
                                             return Err(Error::new(
                                                 meta.span(),
                                                 "name must be a string",
-                                            )
-                                            .to_compile_error())
+                                            ))
                                         }
                                     } else if meta.path.is_ident("abi") {
                                         if let Lit::Str(ref lit_str) = meta.lit {
@@ -442,15 +430,13 @@ fn parse_event_attributes(
                                                 return Err(Error::new(
                                                     meta.span(),
                                                     "abi already specified",
-                                                )
-                                                .to_compile_error())
+                                                ))
                                             }
                                         } else {
                                             return Err(Error::new(
                                                 meta.span(),
                                                 "abi must be a string",
-                                            )
-                                            .to_compile_error())
+                                            ))
                                         }
                                     } else if meta.path.is_ident("signature") {
                                         if let Lit::Str(ref lit_str) = meta.lit {
@@ -466,30 +452,26 @@ fn parse_event_attributes(
                                                             format!(
                                                                 "Expected hex signature: {err:?}"
                                                             ),
-                                                        )
-                                                        .to_compile_error())
+                                                        ))
                                                     }
                                                 }
                                             } else {
                                                 return Err(Error::new(
                                                     meta.span(),
                                                     "signature already specified",
-                                                )
-                                                .to_compile_error())
+                                                ))
                                             }
                                         } else {
                                             return Err(Error::new(
                                                 meta.span(),
                                                 "signature must be a hex string",
-                                            )
-                                            .to_compile_error())
+                                            ))
                                         }
                                     } else {
                                         return Err(Error::new(
                                             meta.span(),
                                             "unrecognized ethevent parameter",
-                                        )
-                                        .to_compile_error())
+                                        ))
                                     }
                                 }
                             }
diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs
index 81f3c773..a5981094 100644
--- a/ethers-contract/ethers-contract-derive/src/lib.rs
+++ b/ethers-contract/ethers-contract-derive/src/lib.rs
@@ -41,6 +41,13 @@ pub(crate) mod utils;
 ///
 /// // npmjs
 /// abigen!(MyContract, "npm:@org/package@1.0.0/path/to/contract.json");
+///
+/// // Human readable ABI
+/// abigen!(MyContract, r"[
+///     function setValue(string)
+///     function getValue() external view returns (string)
+///     event ValueChanged(address indexed author, string oldValue, string newValue)
+/// ]");
 /// ```
 ///
 /// Note that Etherscan rate-limits requests to their API, to avoid this an
@@ -217,7 +224,10 @@ pub fn derive_eth_display(input: TokenStream) -> TokenStream {
 #[proc_macro_derive(EthEvent, attributes(ethevent))]
 pub fn derive_abi_event(input: TokenStream) -> TokenStream {
     let input = parse_macro_input!(input as DeriveInput);
-    TokenStream::from(event::derive_eth_event_impl(input))
+    match event::derive_eth_event_impl(input) {
+        Ok(tokens) => TokenStream::from(tokens),
+        Err(err) => err.to_compile_error().into(),
+    }
 }
 
 /// Derives the `EthCall` and `Tokenizeable` trait for the labeled type.
diff --git a/ethers-contract/src/contract.rs b/ethers-contract/src/contract.rs
index b842b120..2fa4f888 100644
--- a/ethers-contract/src/contract.rs
+++ b/ethers-contract/src/contract.rs
@@ -243,11 +243,12 @@ where
     /// Returns an [`Event`](crate::builders::Event) builder for the provided event.
     /// This function operates in a static context, then it does not require a `self`
     /// to reference to instantiate an [`Event`](crate::builders::Event) builder.
-    pub fn event_of_type<D: EthEvent>(client: &M) -> Event<M, D> {
+    pub fn event_of_type<D: EthEvent>(client: B) -> Event<B, M, D> {
         Event {
             provider: client,
             filter: Filter::new().event(&D::abi_signature()),
             datatype: PhantomData,
+            _m: PhantomData,
         }
     }
 }
@@ -262,27 +263,6 @@ where
         Self { base_contract: abi.into(), client, address: address.into(), _m: PhantomData }
     }
 
-    /// Returns an [`Event`](crate::builders::Event) builder for the provided event.
-    pub fn event<D: EthEvent>(&self) -> Event<M, D> {
-        self.event_with_filter(Filter::new().event(&D::abi_signature()))
-    }
-
-    /// Returns an [`Event`](crate::builders::Event) builder with the provided filter.
-    pub fn event_with_filter<D: EthLogDecode>(&self, filter: Filter) -> Event<M, D> {
-        Event {
-            provider: self.client.borrow(),
-            filter: filter.address(ValueOrArray::Value(self.address)),
-            datatype: PhantomData,
-        }
-    }
-
-    /// Returns an [`Event`](crate::builders::Event) builder with the provided name.
-    pub fn event_for_name<D: EthLogDecode>(&self, name: &str) -> Result<Event<M, D>, Error> {
-        // get the event's full name
-        let event = self.base_contract.abi.event(name)?;
-        Ok(self.event_with_filter(Filter::new().event(&event.abi_signature())))
-    }
-
     /// Returns a new contract instance using the provided client
     ///
     /// Clones `self` internally
@@ -321,6 +301,28 @@ where
     B: Clone + Borrow<M>,
     M: Middleware,
 {
+    /// Returns an [`Event`](crate::builders::Event) builder with the provided filter.
+    pub fn event_with_filter<D: EthLogDecode>(&self, filter: Filter) -> Event<B, M, D> {
+        Event {
+            provider: self.client.clone(),
+            filter: filter.address(ValueOrArray::Value(self.address)),
+            datatype: PhantomData,
+            _m: PhantomData,
+        }
+    }
+
+    /// Returns an [`Event`](crate::builders::Event) builder for the provided event.
+    pub fn event<D: EthEvent>(&self) -> Event<B, M, D> {
+        self.event_with_filter(Filter::new().event(&D::abi_signature()))
+    }
+
+    /// Returns an [`Event`](crate::builders::Event) builder with the provided name.
+    pub fn event_for_name<D: EthLogDecode>(&self, name: &str) -> Result<Event<B, M, D>, Error> {
+        // get the event's full name
+        let event = self.base_contract.abi.event(name)?;
+        Ok(self.event_with_filter(Filter::new().event(&event.abi_signature())))
+    }
+
     fn method_func<T: Tokenize, D: Detokenize>(
         &self,
         function: &Function,
diff --git a/ethers-contract/src/event.rs b/ethers-contract/src/event.rs
index c050749e..df529e6d 100644
--- a/ethers-contract/src/event.rs
+++ b/ethers-contract/src/event.rs
@@ -2,11 +2,22 @@
 
 use crate::{log::LogMeta, stream::EventStream, ContractError, EthLogDecode};
 use ethers_core::{
-    abi::{Address, Detokenize, RawLog},
+    abi::{Address, Detokenize, Error as AbiError, RawLog},
     types::{BlockNumber, Filter, Log, Topic, ValueOrArray, H256},
 };
 use ethers_providers::{FilterWatcher, Middleware, PubsubClient, SubscriptionStream};
-use std::{borrow::Cow, marker::PhantomData};
+use std::{
+    borrow::{Borrow, Cow},
+    marker::PhantomData,
+};
+
+/// Attempt to parse a log into a specific output type.
+pub fn parse_log<D>(log: Log) -> std::result::Result<D, AbiError>
+where
+    D: EthLogDecode,
+{
+    D::decode_log(&RawLog { topics: log.topics, data: log.data.to_vec() })
+}
 
 /// A trait for implementing event bindings
 pub trait EthEvent: Detokenize + Send + Sync {
@@ -31,12 +42,14 @@ pub trait EthEvent: Detokenize + Send + Sync {
     fn is_anonymous() -> bool;
 
     /// Returns an Event builder for the ethereum event represented by this types ABI signature.
-    fn new<M: Middleware>(filter: Filter, provider: &M) -> Event<M, Self>
+    fn new<B, M>(filter: Filter, provider: B) -> Event<B, M, Self>
     where
         Self: Sized,
+        B: Borrow<M>,
+        M: Middleware,
     {
         let filter = filter.event(&Self::abi_signature());
-        Event { filter, provider, datatype: PhantomData }
+        Event { filter, provider, datatype: PhantomData, _m: PhantomData }
     }
 }
 
@@ -53,16 +66,22 @@ impl<T: EthEvent> EthLogDecode for T {
 /// Helper for managing the event filter before querying or streaming its logs
 #[derive(Debug)]
 #[must_use = "event filters do nothing unless you `query` or `stream` them"]
-pub struct Event<'a, M, D> {
+pub struct Event<B, M, D> {
     /// The event filter's state
     pub filter: Filter,
-    pub(crate) provider: &'a M,
+    pub(crate) provider: B,
     /// Stores the event datatype
     pub(crate) datatype: PhantomData<D>,
+    pub(crate) _m: PhantomData<M>,
 }
 
 // TODO: Improve these functions
-impl<M, D: EthLogDecode> Event<'_, M, D> {
+impl<B, M, D> Event<B, M, D>
+where
+    B: Borrow<M>,
+    M: Middleware,
+    D: EthLogDecode,
+{
     /// Sets the filter's `from` block
     #[allow(clippy::wrong_self_convention)]
     pub fn from_block<T: Into<BlockNumber>>(mut self, block: T) -> Self {
@@ -116,8 +135,9 @@ impl<M, D: EthLogDecode> Event<'_, M, D> {
     }
 }
 
-impl<'a, M, D> Event<'a, M, D>
+impl<B, M, D> Event<B, M, D>
 where
+    B: Borrow<M>,
     M: Middleware,
     D: EthLogDecode,
 {
@@ -161,40 +181,49 @@ where
     /// # }
     /// ```
     pub async fn stream(
-        &'a self,
+        &self,
     ) -> Result<
         // Wraps the FilterWatcher with a mapping to the event
-        EventStream<'a, FilterWatcher<'a, M::Provider, Log>, D, ContractError<M>>,
+        EventStream<'_, FilterWatcher<'_, M::Provider, Log>, D, ContractError<M>>,
         ContractError<M>,
     > {
-        let filter =
-            self.provider.watch(&self.filter).await.map_err(ContractError::MiddlewareError)?;
-        Ok(EventStream::new(filter.id, filter, Box::new(move |log| self.parse_log(log))))
+        let filter = self
+            .provider
+            .borrow()
+            .watch(&self.filter)
+            .await
+            .map_err(ContractError::MiddlewareError)?;
+        Ok(EventStream::new(filter.id, filter, Box::new(move |log| Ok(parse_log(log)?))))
     }
 
     /// As [`Self::stream`], but does not discard [`Log`] metadata.
     pub async fn stream_with_meta(
-        &'a self,
+        &self,
     ) -> Result<
         // Wraps the FilterWatcher with a mapping to the event
-        EventStream<'a, FilterWatcher<'a, M::Provider, Log>, (D, LogMeta), ContractError<M>>,
+        EventStream<'_, FilterWatcher<'_, M::Provider, Log>, (D, LogMeta), ContractError<M>>,
         ContractError<M>,
     > {
-        let filter =
-            self.provider.watch(&self.filter).await.map_err(ContractError::MiddlewareError)?;
+        let filter = self
+            .provider
+            .borrow()
+            .watch(&self.filter)
+            .await
+            .map_err(ContractError::MiddlewareError)?;
         Ok(EventStream::new(
             filter.id,
             filter,
             Box::new(move |log| {
                 let meta = LogMeta::from(&log);
-                Ok((self.parse_log(log)?, meta))
+                Ok((parse_log(log)?, meta))
             }),
         ))
     }
 }
 
-impl<'a, M, D> Event<'a, M, D>
+impl<B, M, D> Event<B, M, D>
 where
+    B: Borrow<M>,
     M: Middleware,
     <M as Middleware>::Provider: PubsubClient,
     D: EthLogDecode,
@@ -203,29 +232,31 @@ where
     ///
     /// See also [Self::stream()].
     pub async fn subscribe(
-        &'a self,
+        &self,
     ) -> Result<
         // Wraps the SubscriptionStream with a mapping to the event
-        EventStream<'a, SubscriptionStream<'a, M::Provider, Log>, D, ContractError<M>>,
+        EventStream<'_, SubscriptionStream<'_, M::Provider, Log>, D, ContractError<M>>,
         ContractError<M>,
     > {
         let filter = self
             .provider
+            .borrow()
             .subscribe_logs(&self.filter)
             .await
             .map_err(ContractError::MiddlewareError)?;
-        Ok(EventStream::new(filter.id, filter, Box::new(move |log| self.parse_log(log))))
+        Ok(EventStream::new(filter.id, filter, Box::new(move |log| Ok(parse_log(log)?))))
     }
 
     pub async fn subscribe_with_meta(
-        &'a self,
+        &self,
     ) -> Result<
         // Wraps the SubscriptionStream with a mapping to the event
-        EventStream<'a, SubscriptionStream<'a, M::Provider, Log>, (D, LogMeta), ContractError<M>>,
+        EventStream<'_, SubscriptionStream<'_, M::Provider, Log>, (D, LogMeta), ContractError<M>>,
         ContractError<M>,
     > {
         let filter = self
             .provider
+            .borrow()
             .subscribe_logs(&self.filter)
             .await
             .map_err(ContractError::MiddlewareError)?;
@@ -234,25 +265,30 @@ where
             filter,
             Box::new(move |log| {
                 let meta = LogMeta::from(&log);
-                Ok((self.parse_log(log)?, meta))
+                Ok((parse_log(log)?, meta))
             }),
         ))
     }
 }
 
-impl<M, D> Event<'_, M, D>
+impl<B, M, D> Event<B, M, D>
 where
+    B: Borrow<M>,
     M: Middleware,
     D: EthLogDecode,
 {
     /// Queries the blockchain for the selected filter and returns a vector of matching
     /// event logs
     pub async fn query(&self) -> Result<Vec<D>, ContractError<M>> {
-        let logs =
-            self.provider.get_logs(&self.filter).await.map_err(ContractError::MiddlewareError)?;
+        let logs = self
+            .provider
+            .borrow()
+            .get_logs(&self.filter)
+            .await
+            .map_err(ContractError::MiddlewareError)?;
         let events = logs
             .into_iter()
-            .map(|log| self.parse_log(log))
+            .map(|log| Ok(parse_log(log)?))
             .collect::<Result<Vec<_>, ContractError<M>>>()?;
         Ok(events)
     }
@@ -260,20 +296,20 @@ where
     /// Queries the blockchain for the selected filter and returns a vector of logs
     /// along with their metadata
     pub async fn query_with_meta(&self) -> Result<Vec<(D, LogMeta)>, ContractError<M>> {
-        let logs =
-            self.provider.get_logs(&self.filter).await.map_err(ContractError::MiddlewareError)?;
+        let logs = self
+            .provider
+            .borrow()
+            .get_logs(&self.filter)
+            .await
+            .map_err(ContractError::MiddlewareError)?;
         let events = logs
             .into_iter()
             .map(|log| {
                 let meta = LogMeta::from(&log);
-                let event = self.parse_log(log)?;
+                let event = parse_log(log)?;
                 Ok((event, meta))
             })
             .collect::<Result<_, ContractError<M>>>()?;
         Ok(events)
     }
-
-    pub fn parse_log(&self, log: Log) -> Result<D, ContractError<M>> {
-        D::decode_log(&RawLog { topics: log.topics, data: log.data.to_vec() }).map_err(From::from)
-    }
 }
diff --git a/ethers-core/src/utils/genesis.rs b/ethers-core/src/utils/genesis.rs
index a19f8354..c6885631 100644
--- a/ethers-core/src/utils/genesis.rs
+++ b/ethers-core/src/utils/genesis.rs
@@ -2,7 +2,7 @@ use std::collections::HashMap;
 
 use crate::{
     types::{Address, Bytes, H256, U256, U64},
-    utils::{from_int_or_hex, from_int_or_hex_opt},
+    utils::{from_int_or_hex, from_int_or_hex_opt, from_u64_or_hex_opt},
 };
 use serde::{Deserialize, Serialize};
 
@@ -12,15 +12,19 @@ use serde::{Deserialize, Serialize};
 #[serde(rename_all = "camelCase")]
 pub struct Genesis {
     /// The fork configuration for this network.
+    #[serde(default)]
     pub config: ChainConfig,
 
     /// The genesis header nonce.
+    #[serde(default)]
     pub nonce: U64,
 
     /// The genesis header timestamp.
+    #[serde(default)]
     pub timestamp: U64,
 
     /// The genesis header extra data.
+    #[serde(default)]
     pub extra_data: Bytes,
 
     /// The genesis header gas limit.
@@ -31,13 +35,33 @@ pub struct Genesis {
     pub difficulty: U256,
 
     /// The genesis header mix hash.
+    #[serde(default)]
     pub mix_hash: H256,
 
     /// The genesis header coinbase address.
+    #[serde(default)]
     pub coinbase: Address,
 
     /// The initial state of the genesis block.
     pub alloc: HashMap<Address, GenesisAccount>,
+
+    // The following fields are only included for tests, and should not be used in real genesis
+    // blocks.
+    /// The block number
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub number: Option<U64>,
+
+    /// The block gas gasUsed
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub gas_used: Option<U64>,
+
+    /// The block parent hash
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub parent_hash: Option<H256>,
+
+    /// The base fee
+    #[serde(skip_serializing_if = "Option::is_none", default)]
+    pub base_fee_per_gas: Option<U256>,
 }
 
 impl Genesis {
@@ -100,12 +124,16 @@ impl Genesis {
 /// An account in the state of the genesis block.
 #[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
 pub struct GenesisAccount {
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(
+        skip_serializing_if = "Option::is_none",
+        deserialize_with = "from_u64_or_hex_opt",
+        default
+    )]
     pub nonce: Option<u64>,
     pub balance: U256,
-    #[serde(skip_serializing_if = "Option::is_none")]
+    #[serde(skip_serializing_if = "Option::is_none", default)]
     pub code: Option<Bytes>,
-    #[serde(flatten, skip_serializing_if = "Option::is_none")]
+    #[serde(flatten, skip_serializing_if = "Option::is_none", default)]
     pub storage: Option<HashMap<H256, H256>>,
 }
 
@@ -118,6 +146,7 @@ pub struct GenesisAccount {
 #[serde(default, rename_all = "camelCase")]
 pub struct ChainConfig {
     /// The network's chain ID.
+    #[serde(default = "one")]
     pub chain_id: u64,
 
     /// The homestead switch block (None = no fork, 0 = already homestead).
@@ -191,11 +220,9 @@ pub struct ChainConfig {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub shanghai_time: Option<u64>,
 
-    // TODO: change to cancunTime when <https://github.com/ethereum/go-ethereum/pull/26481> is
-    // merged in geth
-    /// Cancun hard fork block.
+    /// Cancun switch time.
     #[serde(skip_serializing_if = "Option::is_none")]
-    pub cancun_block: Option<u64>,
+    pub cancun_time: Option<u64>,
 
     /// Total difficulty reached that triggers the merge consensus upgrade.
     #[serde(skip_serializing_if = "Option::is_none", deserialize_with = "from_int_or_hex_opt")]
@@ -214,6 +241,12 @@ pub struct ChainConfig {
     pub clique: Option<CliqueConfig>,
 }
 
+// used only for serde
+#[inline]
+const fn one() -> u64 {
+    1
+}
+
 /// Empty consensus configuration for proof-of-work networks.
 #[derive(Clone, Debug, Deserialize, Serialize)]
 pub struct EthashConfig {}
@@ -227,3 +260,298 @@ pub struct CliqueConfig {
     /// Epoch length to reset votes and checkpoints.
     pub epoch: u64,
 }
+
+#[cfg(test)]
+mod tests {
+    use super::{Genesis, H256};
+
+    #[test]
+    fn parse_hive_genesis() {
+        let geth_genesis = r#"
+        {
+            "difficulty": "0x20000",
+            "gasLimit": "0x1",
+            "alloc": {},
+            "config": {
+              "ethash": {},
+              "chainId": 1
+            }
+        }
+        "#;
+
+        let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
+    }
+
+    #[test]
+    fn parse_hive_clique_smoke_genesis() {
+        let geth_genesis = r#"
+        {
+          "difficulty": "0x1",
+          "gasLimit": "0x400000",
+          "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+          "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+          "nonce": "0x0",
+          "timestamp": "0x5c51a607",
+          "alloc": {}
+        }
+        "#;
+
+        let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
+    }
+
+    #[test]
+    fn parse_hive_rpc_genesis() {
+        let geth_genesis = r#"
+        {
+          "config": {
+            "chainId": 7,
+            "homesteadBlock": 0,
+            "eip150Block": 0,
+            "eip150Hash": "0x5de1ee4135274003348e80b788e5afa4b18b18d320a5622218d5c493fedf5689",
+            "eip155Block": 0,
+            "eip158Block": 0
+          },
+          "coinbase": "0x0000000000000000000000000000000000000000",
+          "difficulty": "0x20000",
+          "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+          "gasLimit": "0x2fefd8",
+          "nonce": "0x0000000000000000",
+          "timestamp": "0x1234",
+          "alloc": {
+            "cf49fda3be353c69b41ed96333cd24302da4556f": {
+              "balance": "0x123450000000000000000"
+            },
+            "0161e041aad467a890839d5b08b138c1e6373072": {
+              "balance": "0x123450000000000000000"
+            },
+            "87da6a8c6e9eff15d703fc2773e32f6af8dbe301": {
+              "balance": "0x123450000000000000000"
+            },
+            "b97de4b8c857e4f6bc354f226dc3249aaee49209": {
+              "balance": "0x123450000000000000000"
+            },
+            "c5065c9eeebe6df2c2284d046bfc906501846c51": {
+              "balance": "0x123450000000000000000"
+            },
+            "0000000000000000000000000000000000000314": {
+              "balance": "0x0",
+              "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029",
+              "storage": {
+                "0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234",
+                "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01"
+              }
+            },
+            "0000000000000000000000000000000000000315": {
+              "balance": "0x9999999999999999999999999999999",
+              "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029"
+            }
+          }
+        }
+        "#;
+
+        let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
+    }
+
+    #[test]
+    fn parse_hive_graphql_genesis() {
+        let geth_genesis = r#"
+        {
+            "config"     : {},
+            "coinbase"   : "0x8888f1f195afa192cfee860698584c030f4c9db1",
+            "difficulty" : "0x020000",
+            "extraData"  : "0x42",
+            "gasLimit"   : "0x2fefd8",
+            "mixHash"    : "0x2c85bcbce56429100b2108254bb56906257582aeafcbd682bc9af67a9f5aee46",
+            "nonce"      : "0x78cc16f7b4f65485",
+            "parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
+            "timestamp"  : "0x54c98c81",
+            "alloc"      : {
+                "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": {
+                    "balance" : "0x09184e72a000"
+                }
+            }
+        }
+        "#;
+
+        let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
+    }
+
+    #[test]
+    fn parse_hive_engine_genesis() {
+        let geth_genesis = r#"
+        {
+          "config": {
+            "chainId": 7,
+            "homesteadBlock": 0,
+            "eip150Block": 0,
+            "eip150Hash": "0x5de1ee4135274003348e80b788e5afa4b18b18d320a5622218d5c493fedf5689",
+            "eip155Block": 0,
+            "eip158Block": 0,
+            "byzantiumBlock": 0,
+            "constantinopleBlock": 0,
+            "petersburgBlock": 0,
+            "istanbulBlock": 0,
+            "muirGlacierBlock": 0,
+            "berlinBlock": 0,
+            "yolov2Block": 0,
+            "yolov3Block": 0,
+            "londonBlock": 0
+          },
+          "coinbase": "0x0000000000000000000000000000000000000000",
+          "difficulty": "0x30000",
+          "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
+          "gasLimit": "0x2fefd8",
+          "nonce": "0x0000000000000000",
+          "timestamp": "0x1234",
+          "alloc": {
+            "cf49fda3be353c69b41ed96333cd24302da4556f": {
+              "balance": "0x123450000000000000000"
+            },
+            "0161e041aad467a890839d5b08b138c1e6373072": {
+              "balance": "0x123450000000000000000"
+            },
+            "87da6a8c6e9eff15d703fc2773e32f6af8dbe301": {
+              "balance": "0x123450000000000000000"
+            },
+            "b97de4b8c857e4f6bc354f226dc3249aaee49209": {
+              "balance": "0x123450000000000000000"
+            },
+            "c5065c9eeebe6df2c2284d046bfc906501846c51": {
+              "balance": "0x123450000000000000000"
+            },
+            "0000000000000000000000000000000000000314": {
+              "balance": "0x0",
+              "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029",
+              "storage": {
+                "0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234",
+                "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01"
+              }
+            },
+            "0000000000000000000000000000000000000315": {
+              "balance": "0x9999999999999999999999999999999",
+              "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029"
+            },
+            "0000000000000000000000000000000000000316": {
+              "balance": "0x0",
+              "code": "0x444355"
+            },
+            "0000000000000000000000000000000000000317": {
+              "balance": "0x0",
+              "code": "0x600160003555"
+            }
+          }
+        }
+        "#;
+
+        let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
+    }
+
+    #[test]
+    fn parse_hive_devp2p_genesis() {
+        let geth_genesis = r#"
+        {
+            "config": {
+                "chainId": 19763,
+                "homesteadBlock": 0,
+                "eip150Block": 0,
+                "eip155Block": 0,
+                "eip158Block": 0,
+                "byzantiumBlock": 0,
+                "ethash": {}
+            },
+            "nonce": "0xdeadbeefdeadbeef",
+            "timestamp": "0x0",
+            "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000",
+            "gasLimit": "0x80000000",
+            "difficulty": "0x20000",
+            "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+            "coinbase": "0x0000000000000000000000000000000000000000",
+            "alloc": {
+                "71562b71999873db5b286df957af199ec94617f7": {
+                    "balance": "0xffffffffffffffffffffffffff"
+                }
+            },
+            "number": "0x0",
+            "gasUsed": "0x0",
+            "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
+        }
+        "#;
+
+        let _genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
+    }
+
+    #[test]
+    fn parse_execution_apis_genesis() {
+        let geth_genesis = r#"
+        {
+          "config": {
+            "chainId": 1337,
+            "homesteadBlock": 0,
+            "eip150Block": 0,
+            "eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+            "eip155Block": 0,
+            "eip158Block": 0,
+            "byzantiumBlock": 0,
+            "constantinopleBlock": 0,
+            "petersburgBlock": 0,
+            "istanbulBlock": 0,
+            "muirGlacierBlock": 0,
+            "berlinBlock": 0,
+            "londonBlock": 0,
+            "arrowGlacierBlock": 0,
+            "grayGlacierBlock": 0,
+            "shanghaiTime": 0,
+            "terminalTotalDifficulty": 0,
+            "terminalTotalDifficultyPassed": true,
+            "ethash": {}
+          },
+          "nonce": "0x0",
+          "timestamp": "0x0",
+          "extraData": "0x",
+          "gasLimit": "0x4c4b40",
+          "difficulty": "0x1",
+          "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+          "coinbase": "0x0000000000000000000000000000000000000000",
+          "alloc": {
+            "658bdf435d810c91414ec09147daa6db62406379": {
+              "balance": "0x487a9a304539440000"
+            },
+            "aa00000000000000000000000000000000000000": {
+              "code": "0x6042",
+              "storage": {
+                "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
+                "0x0100000000000000000000000000000000000000000000000000000000000000": "0x0100000000000000000000000000000000000000000000000000000000000000",
+                "0x0200000000000000000000000000000000000000000000000000000000000000": "0x0200000000000000000000000000000000000000000000000000000000000000",
+                "0x0300000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000303"
+              },
+              "balance": "0x1",
+              "nonce": "0x1"
+            },
+            "bb00000000000000000000000000000000000000": {
+              "code": "0x600154600354",
+              "storage": {
+                "0x0000000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000000",
+                "0x0100000000000000000000000000000000000000000000000000000000000000": "0x0100000000000000000000000000000000000000000000000000000000000000",
+                "0x0200000000000000000000000000000000000000000000000000000000000000": "0x0200000000000000000000000000000000000000000000000000000000000000",
+                "0x0300000000000000000000000000000000000000000000000000000000000000": "0x0000000000000000000000000000000000000000000000000000000000000303"
+              },
+              "balance": "0x2",
+              "nonce": "0x1"
+            }
+          },
+          "number": "0x0",
+          "gasUsed": "0x0",
+          "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
+          "baseFeePerGas": "0x3b9aca00"
+        }
+        "#;
+
+        let genesis: Genesis = serde_json::from_str(geth_genesis).unwrap();
+
+        // ensure the test fields are parsed correctly
+        assert_eq!(genesis.base_fee_per_gas, Some(1000000000.into()));
+        assert_eq!(genesis.number, Some(0.into()));
+        assert_eq!(genesis.gas_used, Some(0.into()));
+        assert_eq!(genesis.parent_hash, Some(H256::zero()));
+    }
+}
diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs
index 9528fa01..6d29d9d0 100644
--- a/ethers-core/src/utils/mod.rs
+++ b/ethers-core/src/utils/mod.rs
@@ -36,7 +36,7 @@ pub use rlp;
 /// Re-export hex
 pub use hex;
 
-use crate::types::{Address, ParseI256Error, I256, U256};
+use crate::types::{Address, ParseI256Error, I256, U256, U64};
 use elliptic_curve::sec1::ToEncodedPoint;
 use ethabi::ethereum_types::FromDecStrErr;
 use k256::{ecdsa::SigningKey, PublicKey as K256PublicKey};
@@ -487,6 +487,25 @@ where
     }
 }
 
+/// Deserializes the input into a U64, accepting both 0x-prefixed hex and decimal strings with
+/// arbitrary precision, defined by serde_json's [`Number`](serde_json::Number).
+pub fn from_u64_or_hex<'de, D>(deserializer: D) -> Result<U64, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    #[derive(Deserialize)]
+    #[serde(untagged)]
+    enum IntOrHex {
+        Int(serde_json::Number),
+        Hex(String),
+    }
+
+    match IntOrHex::deserialize(deserializer)? {
+        IntOrHex::Hex(s) => U64::from_str(s.as_str()).map_err(serde::de::Error::custom),
+        IntOrHex::Int(n) => U64::from_dec_str(&n.to_string()).map_err(serde::de::Error::custom),
+    }
+}
+
 /// Deserializes the input into an `Option<U256>`, using [`from_int_or_hex`] to deserialize the
 /// inner value.
 pub fn from_int_or_hex_opt<'de, D>(deserializer: D) -> Result<Option<U256>, D::Error>
@@ -496,6 +515,15 @@ where
     Ok(Some(from_int_or_hex(deserializer)?))
 }
 
+/// Deserializes the input into an `Option<u64>`, using [`from_u64_or_hex`] to deserialize the
+/// inner value.
+pub fn from_u64_or_hex_opt<'de, D>(deserializer: D) -> Result<Option<u64>, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    Ok(Some(from_u64_or_hex(deserializer)?.as_u64()))
+}
+
 fn estimate_priority_fee(rewards: Vec<Vec<U256>>) -> U256 {
     let mut rewards: Vec<U256> =
         rewards.iter().map(|r| r[0]).filter(|r| *r > U256::zero()).collect();
diff --git a/ethers-middleware/src/transformer/ds_proxy/factory.rs b/ethers-middleware/src/transformer/ds_proxy/factory.rs
index 71d35043..665d5059 100644
--- a/ethers-middleware/src/transformer/ds_proxy/factory.rs
+++ b/ethers-middleware/src/transformer/ds_proxy/factory.rs
@@ -98,13 +98,13 @@ mod dsproxyfactory_mod {
                 .expect("method not found (this should never happen)")
         }
         ///Gets the contract's `Created` event
-        pub fn created_filter(&self) -> Event<M, CreatedFilter> {
+        pub fn created_filter(&self) -> Event<Arc<M>, M, CreatedFilter> {
             self.0.event()
         }
 
         /// Returns an [`Event`](ethers_contract::builders::Event) builder for all events of this
         /// contract
-        pub fn events(&self) -> Event<M, CreatedFilter> {
+        pub fn events(&self) -> Event<Arc<M>, M, CreatedFilter> {
             self.0.event_with_filter(Default::default())
         }
     }
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index 3530f53c..4a7cf974 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -18,7 +18,7 @@ keywords = ["ethereum", "web3", "solc", "solidity", "ethers"]
 [dependencies]
 ethers-core = { version = "^1.0.0", path = "../ethers-core", default-features = false }
 serde_json = "1.0.68"
-serde = { version = "1.0.130", features = ["derive"] }
+serde = { version = "1.0.130", features = ["derive", "rc"] }
 semver = { version = "1.0.16", features = ["serde"] }
 walkdir = "2.3.2"
 tokio = { version = "1.18", default-features = false, features = ["rt"] }
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index a90b0962..ddd1f47a 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -11,6 +11,7 @@ use std::{
     fmt, fs,
     path::{Path, PathBuf},
     str::FromStr,
+    sync::Arc,
 };
 use tracing::warn;
 use yansi::Paint;
@@ -105,10 +106,12 @@ impl CompilerInput {
     pub fn sanitized(mut self, version: &Version) -> Self {
         static PRE_V0_6_0: once_cell::sync::Lazy<VersionReq> =
             once_cell::sync::Lazy::new(|| VersionReq::parse("<0.6.0").unwrap());
-        static PRE_V0_8_10: once_cell::sync::Lazy<VersionReq> =
-            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.8.10").unwrap());
         static PRE_V0_7_5: once_cell::sync::Lazy<VersionReq> =
             once_cell::sync::Lazy::new(|| VersionReq::parse("<0.7.5").unwrap());
+        static PRE_V0_8_7: once_cell::sync::Lazy<VersionReq> =
+            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.8.7").unwrap());
+        static PRE_V0_8_10: once_cell::sync::Lazy<VersionReq> =
+            once_cell::sync::Lazy::new(|| VersionReq::parse("<0.8.10").unwrap());
         static PRE_V0_8_18: once_cell::sync::Lazy<VersionReq> =
             once_cell::sync::Lazy::new(|| VersionReq::parse("<0.8.18").unwrap());
 
@@ -122,6 +125,18 @@ impl CompilerInput {
             let _ = self.settings.debug.take();
         }
 
+        if PRE_V0_7_5.matches(version) {
+            // introduced in 0.7.5 <https://github.com/ethereum/solidity/releases/tag/v0.7.5>
+            self.settings.via_ir.take();
+        }
+
+        if PRE_V0_8_7.matches(version) {
+            // lower the disable version from 0.8.10 to 0.8.7, due to `divModNoSlacks`,
+            // `showUnproved` and `solvers` are implemented
+            // introduced in <https://github.com/ethereum/solidity/releases/tag/v0.8.7>
+            self.settings.model_checker = None;
+        }
+
         if PRE_V0_8_10.matches(version) {
             if let Some(ref mut debug) = self.settings.debug {
                 // introduced in <https://docs.soliditylang.org/en/v0.8.10/using-the-compiler.html#compiler-api>
@@ -129,13 +144,10 @@ impl CompilerInput {
                 debug.debug_info.clear();
             }
 
-            // 0.8.10 is the earliest version that has all model checker options.
-            self.settings.model_checker = None;
-        }
-
-        if PRE_V0_7_5.matches(version) {
-            // introduced in 0.7.5 <https://github.com/ethereum/solidity/releases/tag/v0.7.5>
-            self.settings.via_ir.take();
+            if let Some(ref mut model_checker) = self.settings.model_checker {
+                // introduced in <https://github.com/ethereum/solidity/releases/tag/v0.8.10>
+                model_checker.invariants = None;
+            }
         }
 
         if PRE_V0_8_18.matches(version) {
@@ -143,6 +155,13 @@ impl CompilerInput {
             if let Some(ref mut meta) = self.settings.metadata {
                 meta.cbor_metadata = None;
             }
+
+            if let Some(ref mut model_checker) = self.settings.model_checker {
+                if let Some(ref mut solvers) = model_checker.solvers {
+                    // elf solver introduced in 0.8.18 <https://github.com/ethereum/solidity/releases/tag/v0.8.18>
+                    solvers.retain(|solver| *solver != ModelCheckerSolver::Eld);
+                }
+            }
         }
 
         self
@@ -1020,6 +1039,12 @@ pub struct ModelCheckerSettings {
     pub targets: Option<Vec<ModelCheckerTarget>>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub invariants: Option<Vec<ModelCheckerInvariant>>,
+    #[serde(rename = "showUnproved", skip_serializing_if = "Option::is_none")]
+    pub show_unproved: Option<bool>,
+    #[serde(rename = "divModWithSlacks", skip_serializing_if = "Option::is_none")]
+    pub div_mod_with_slacks: Option<bool>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub solvers: Option<Vec<ModelCheckerSolver>>,
 }
 
 /// Which model checker engine to run.
@@ -1136,6 +1161,42 @@ impl FromStr for ModelCheckerInvariant {
     }
 }
 
+/// Which model checker solvers to check.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub enum ModelCheckerSolver {
+    Cvc4,
+    Eld,
+    Smtlib2,
+    Z3,
+}
+
+impl fmt::Display for ModelCheckerSolver {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let string = match self {
+            ModelCheckerSolver::Cvc4 => "cvc4",
+            ModelCheckerSolver::Eld => "eld",
+            ModelCheckerSolver::Smtlib2 => "smtlib2",
+            ModelCheckerSolver::Z3 => "z3",
+        };
+        write!(f, "{string}")
+    }
+}
+
+impl FromStr for ModelCheckerSolver {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "cvc4" => Ok(ModelCheckerSolver::Cvc4),
+            "eld" => Ok(ModelCheckerSolver::Cvc4),
+            "smtlib2" => Ok(ModelCheckerSolver::Smtlib2),
+            "z3" => Ok(ModelCheckerSolver::Z3),
+            s => Err(format!("Unknown model checker invariant: {s}")),
+        }
+    }
+}
+
 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
 pub struct Compiler {
     pub version: String,
@@ -1195,16 +1256,29 @@ pub struct DocLibraries {
     pub libs: BTreeMap<String, serde_json::Value>,
 }
 
+/// Content of a solidity file
+///
+/// This contains the actual source code of a file
 #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
 pub struct Source {
-    pub content: String,
+    /// Content of the file
+    ///
+    /// This is an `Arc` because it may be cloned. If the [Graph](crate::resolver::Graph) of the
+    /// project contains multiple conflicting versions then the same [Source] may be required by
+    /// conflicting versions and needs to be duplicated.
+    pub content: Arc<String>,
 }
 
 impl Source {
-    /// Reads the file content
+    /// Creates a new instance of [Source] with the given content.
+    pub fn new(content: impl Into<String>) -> Self {
+        Self { content: Arc::new(content.into()) }
+    }
+
+    /// Reads the file's content
     pub fn read(file: impl AsRef<Path>) -> Result<Self, SolcIoError> {
         let file = file.as_ref();
-        Ok(Self { content: fs::read_to_string(file).map_err(|err| SolcIoError::new(err, file))? })
+        Ok(Self::new(fs::read_to_string(file).map_err(|err| SolcIoError::new(err, file))?))
     }
 
     /// Recursively finds all source files under the given dir path and reads them all
@@ -1254,7 +1328,7 @@ impl Source {
     /// Generate a non-cryptographically secure checksum of the file's content
     pub fn content_hash(&self) -> String {
         let mut hasher = md5::Md5::new();
-        hasher.update(&self.content);
+        hasher.update(self);
         let result = hasher.finalize();
         hex::encode(result)
     }
@@ -1270,11 +1344,9 @@ impl Source {
     /// async version of `Self::read`
     pub async fn async_read(file: impl AsRef<Path>) -> Result<Self, SolcIoError> {
         let file = file.as_ref();
-        Ok(Self {
-            content: tokio::fs::read_to_string(file)
-                .await
-                .map_err(|err| SolcIoError::new(err, file))?,
-        })
+        Ok(Self::new(
+            tokio::fs::read_to_string(file).await.map_err(|err| SolcIoError::new(err, file))?,
+        ))
     }
 
     /// Finds all source files under the given dir path and reads them all
@@ -1306,6 +1378,12 @@ impl AsRef<str> for Source {
     }
 }
 
+impl AsRef<[u8]> for Source {
+    fn as_ref(&self) -> &[u8] {
+        self.content.as_bytes()
+    }
+}
+
 /// Output type `solc` produces
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
 pub struct CompilerOutput {
diff --git a/ethers-solc/src/buildinfo.rs b/ethers-solc/src/buildinfo.rs
index 97507872..27eaffa0 100644
--- a/ethers-solc/src/buildinfo.rs
+++ b/ethers-solc/src/buildinfo.rs
@@ -104,7 +104,7 @@ mod tests {
     fn build_info_serde() {
         let inputs = CompilerInput::with_sources(BTreeMap::from([(
             PathBuf::from("input.sol"),
-            Source { content: "".to_string() },
+            Source::new(""),
         )]));
         let output = CompilerOutput::default();
         let v: Version = "0.8.4+commit.c7e474f2".parse().unwrap();
diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs
index a00123c9..57b3310a 100644
--- a/ethers-solc/src/compile/mod.rs
+++ b/ethers-solc/src/compile/mod.rs
@@ -572,10 +572,7 @@ impl Solc {
 #[cfg(feature = "async")]
 impl Solc {
     /// Convenience function for compiling all sources under the given path
-    pub async fn async_compile_source<T: Serialize>(
-        &self,
-        path: impl AsRef<Path>,
-    ) -> Result<CompilerOutput> {
+    pub async fn async_compile_source(&self, path: impl AsRef<Path>) -> Result<CompilerOutput> {
         self.async_compile(&CompilerInput::with_sources(Source::async_read_all_from(path).await?))
             .await
     }
@@ -909,6 +906,6 @@ mod tests {
     ///// helpers
 
     fn source(version: &str) -> Source {
-        Source { content: format!("pragma solidity {version};\n") }
+        Source::new(format!("pragma solidity {version};\n"))
     }
 }
diff --git a/examples/README.md b/examples/README.md
index bb25a09d..c37836b0 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -22,7 +22,7 @@
     - [x] Events with meta
     - [ ] Methods
 - [ ] Events
-  - [ ] Logs and filtering
+  - [x] Logs and filtering
   - [ ] Solidity topics
 - [ ] Middleware
   - [x] Builder
diff --git a/examples/contracts/examples/abigen.rs b/examples/contracts/examples/abigen.rs
index b29fa2b2..f475dc17 100644
--- a/examples/contracts/examples/abigen.rs
+++ b/examples/contracts/examples/abigen.rs
@@ -32,10 +32,12 @@ async fn main() -> Result<()> {
 }
 
 fn rust_file_generation() -> Result<()> {
-    let base_dir = "./examples/contracts/examples/abi";
-    Abigen::new("IERC20", format!("{base_dir}/IERC20.json"))?
-        .generate()?
-        .write_to_file(format!("{base_dir}/ierc20.rs"))?;
+    let abi_source = "./examples/contracts/examples/abi/IERC20.json";
+    let out_file = std::env::temp_dir().join("ierc20.rs");
+    if out_file.exists() {
+        std::fs::remove_file(&out_file)?;
+    }
+    Abigen::new("IERC20", abi_source)?.generate()?.write_to_file(out_file)?;
     Ok(())
 }
 
diff --git a/examples/events/examples/filtering.rs b/examples/events/examples/filtering.rs
new file mode 100644
index 00000000..5791934e
--- /dev/null
+++ b/examples/events/examples/filtering.rs
@@ -0,0 +1,48 @@
+use ethers::{
+    core::types::{Address, Filter, H160, H256, U256},
+    providers::{Http, Middleware, Provider},
+};
+use eyre::Result;
+use std::sync::Arc;
+use tokio;
+
+const HTTP_URL: &str = "https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27";
+const V3FACTORY_ADDRESS: &str = "0x1F98431c8aD98523631AE4a59f267346ea31F984";
+const DAI_ADDRESS: &str = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
+const USDC_ADDRESS: &str = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48";
+const USDT_ADDRESS: &str = "0xdAC17F958D2ee523a2206206994597C13D831ec7";
+
+/// This example demonstrates filtering and parsing event logs by fetching all Uniswap V3 pools
+/// where both tokens are in the set [USDC, USDT, DAI].
+///
+/// V3 factory reference: https://github.com/Uniswap/v3-core/blob/main/contracts/interfaces/IUniswapV3Factory.sol
+#[tokio::main]
+async fn main() -> Result<()> {
+    let provider = Provider::<Http>::try_from(HTTP_URL)?;
+    let client = Arc::new(provider);
+    let token_topics = vec![
+        H256::from(USDC_ADDRESS.parse::<H160>()?),
+        H256::from(USDT_ADDRESS.parse::<H160>()?),
+        H256::from(DAI_ADDRESS.parse::<H160>()?),
+    ];
+    let filter = Filter::new()
+        .address(V3FACTORY_ADDRESS.parse::<Address>()?)
+        .event("PoolCreated(address,address,uint24,int24,address)")
+        .topic1(token_topics.to_vec())
+        .topic2(token_topics.to_vec())
+        .from_block(0);
+    let logs = client.get_logs(&filter).await?;
+    println!("{} pools found!", logs.iter().len());
+    for log in logs.iter() {
+        let token0 = Address::from(log.topics[1]);
+        let token1 = Address::from(log.topics[2]);
+        let fee_tier = U256::from_big_endian(&log.topics[3].as_bytes()[29..32]);
+        let tick_spacing = U256::from_big_endian(&log.data[29..32]);
+        let pool = Address::from(&log.data[44..64].try_into()?);
+        println!(
+            "pool = {}, token0 = {}, token1 = {}, fee = {}, spacing = {}",
+            pool, token0, token1, fee_tier, tick_spacing,
+        );
+    }
+    Ok(())
+}
diff --git a/examples/middleware/examples/gas_oracle.rs b/examples/middleware/examples/gas_oracle.rs
index 95105aeb..ea6795aa 100644
--- a/examples/middleware/examples/gas_oracle.rs
+++ b/examples/middleware/examples/gas_oracle.rs
@@ -39,13 +39,18 @@ async fn blocknative() {
 
 async fn etherscan() {
     let chain = Chain::Mainnet;
-    let api_key: String = std::env::var("ETHERSCAN_API_KEY_ETHEREUM").expect("Provide an API key");
-    if let Ok(client) = Client::new(chain, api_key) {
-        let oracle = Etherscan::new(client).category(GasCategory::Fast);
-        match oracle.fetch().await {
-            Ok(gas_price) => println!("[Etherscan]: Gas price is {gas_price:?}"),
-            Err(e) => panic!("[Etherscan]: Cannot estimate gas: {e:?}"),
-        }
+    let client = match Client::new_from_env(chain) {
+        Ok(client) => client,
+        Err(_) => Client::builder()
+            .chain(chain)
+            .expect("Mainnet is valid")
+            .build()
+            .expect("Mainnet is valid"),
+    };
+    let oracle = Etherscan::new(client).category(GasCategory::Fast);
+    match oracle.fetch().await {
+        Ok(gas_price) => println!("[Etherscan]: Gas price is {gas_price:?}"),
+        Err(e) => panic!("[Etherscan]: Cannot estimate gas: {e:?}"),
     }
 }
 
diff --git a/examples/subscriptions/examples/subscribe_events_by_type.rs b/examples/subscriptions/examples/subscribe_events_by_type.rs
index 355404a3..8f935c0b 100644
--- a/examples/subscriptions/examples/subscribe_events_by_type.rs
+++ b/examples/subscriptions/examples/subscribe_events_by_type.rs
@@ -26,7 +26,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
 
     // Build an Event by type. We are not tied to a contract instance. We use builder functions to
     // refine the event filter
-    let event = Contract::event_of_type::<AnswerUpdatedFilter>(&client)
+    let event = Contract::event_of_type::<AnswerUpdatedFilter>(client)
         .from_block(16022082)
         .address(ValueOrArray::Array(vec![
             PRICE_FEED_1.parse()?,
diff --git a/examples/transactions/examples/call_override.rs b/examples/transactions/examples/call_override.rs
index b80191e8..4a124fce 100644
--- a/examples/transactions/examples/call_override.rs
+++ b/examples/transactions/examples/call_override.rs
@@ -10,12 +10,27 @@ use ethers::{
     },
 };
 use eyre::Result;
-use std::sync::Arc;
+use std::{
+    process::{Command, Stdio},
+    sync::Arc,
+};
 
 abigen!(Greeter, "ethers-contract/tests/solidity-contracts/greeter.json",);
 
 #[tokio::main]
 async fn main() -> Result<()> {
+    match geth_version() {
+        e @ Ok(false) | e @ Err(_) => {
+            eprint!("Error spawning geth, skipping example");
+            if let Err(e) = e {
+                eprint!(": {e}");
+            }
+            eprintln!();
+            return Ok(())
+        }
+        Ok(true) => {}
+    }
+
     let geth = Geth::new().spawn();
     let provider = Provider::<Http>::try_from(geth.endpoint()).unwrap();
     let client = Arc::new(provider);
@@ -75,3 +90,13 @@ fn encode_string_for_storage(s: &str) -> H256 {
     bytes.push(len as u8 * 2);
     H256::from_slice(&bytes)
 }
+
+fn geth_version() -> Result<bool> {
+    Command::new("geth")
+        .arg("version")
+        .stdout(Stdio::null())
+        .stderr(Stdio::null())
+        .status()
+        .map(|s| s.success())
+        .map_err(Into::into)
+}


Test patch:
diff --git a/ethers-contract/tests/it/common/derive.rs b/ethers-contract/tests/it/common/derive.rs
index 477ee04f..50a73adf 100644
--- a/ethers-contract/tests/it/common/derive.rs
+++ b/ethers-contract/tests/it/common/derive.rs
@@ -647,3 +647,25 @@ fn can_use_human_readable_error() {
 
     assert_etherror::<MyError>();
 }
+
+// <https://github.com/gakonst/ethers-rs/issues/2142>
+#[test]
+fn derives_abi_name() {
+    #[derive(Debug, EthEvent)]
+    #[ethevent(abi = "Transfer(address,address,uint256)")]
+    struct Erc20TransferEvent {
+        #[ethevent(indexed, name = "_from")]
+        from: Address,
+        #[ethevent(indexed, name = "_to")]
+        to: Address,
+        #[ethevent(name = "_value")]
+        value: U256,
+    }
+
+    assert_eq!(Erc20TransferEvent::abi_signature(), "Transfer(address,address,uint256)");
+
+    assert_eq!(
+        Erc20TransferEvent::signature(),
+        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef".parse().unwrap()
+    );
+}
diff --git a/ethers-contract/tests/it/contract.rs b/ethers-contract/tests/it/contract.rs
index 0d3b7a64..b5e85b03 100644
--- a/ethers-contract/tests/it/contract.rs
+++ b/ethers-contract/tests/it/contract.rs
@@ -410,7 +410,7 @@ mod eth_tests {
 
         let anvil = Anvil::new().spawn();
         let client = connect(&anvil, 0);
-        let event = ethers_contract::Contract::event_of_type::<AnswerUpdatedFilter>(&client);
+        let event = ethers_contract::Contract::event_of_type::<AnswerUpdatedFilter>(client);
         assert_eq!(event.filter, Filter::new().event(&AnswerUpdatedFilter::abi_signature()));
     }
 
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index 3cc8c97e..41477fc0 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -1610,6 +1610,9 @@ fn can_compile_model_checker_sample() {
         engine: Some(CHC),
         targets: None,
         timeout: Some(10000),
+        show_unproved: None,
+        div_mod_with_slacks: None,
+        solvers: None,
         invariants: None,
     });
     let compiled = project.compile().unwrap();