Repository: gakonst/ethers-rs
Pull request #2144 (2023-02-13 17:51:48)

fix(abigen): use event name from abi attribute
<!--
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
Closes https://github.com/gakonst/ethers-rs/issues/2142

use the parsed event name from the `abi` attribute instead of the ident by default.
<!--
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

-   [x] Added Tests
-   [ ] Added Documentation
-   [ ] Updated the changelog
-   [ ] Breaking changes



Patch:
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-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-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index 35bedb92..ddd1f47a 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -106,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());
 
@@ -123,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>
@@ -130,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) {
@@ -144,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
@@ -1021,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.
@@ -1137,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,
diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs
index 25d5307f..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
     }
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/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/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-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();