Repository: gakonst/ethers-rs
Pull request #2128 (2023-02-08 11:22:00)

contracts: allow upper case acronyms lint
<!--
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

I have a contract named `DKG` for which the auto-generated code created the following clippy warning:
```
warning: name `DKG` contains a capitalized acronym
  --> crates/dkg-cli/src/dkg_contract.rs:25:16
   |
25 |     pub struct DKG<M>(ethers::contract::Contract<M>);
   |                ^^^ help: consider making the acronym lowercase, except the initial letter: `Dkg`
   |
   = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#upper_case_acronyms
   = note: `#[warn(clippy::upper_case_acronyms)]` on by default
```

According to the solidity style guide the name is fine: https://docs.soliditylang.org/en/v0.5.3/style-guide.html#naming-styles

## Solution

Allow that clippy lint by default

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

-   [ ] Added Tests
-   [ ] Added Documentation
-   [ ] Updated the changelog
-   [ ] Breaking changes



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 2fee682e..e1ffa773 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4027,9 +4027,9 @@ dependencies = [
 
 [[package]]
 name = "solang-parser"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e9653f278d9531b60f042c29732bb835d519943f4167b1e5684c7e820dd9fec"
+checksum = "58feb14d4002859db0b744b0915840bae403b8a5675efded3e8adddb6beb85b2"
 dependencies = [
  "itertools",
  "lalrpop",
diff --git a/ethers-contract/ethers-contract-abigen/src/contract.rs b/ethers-contract/ethers-contract-abigen/src/contract.rs
index 95492294..99e83c67 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract.rs
@@ -56,7 +56,11 @@ impl ExpandedContract {
            // export all the created data types
             pub use #module::*;
 
-            #[allow(clippy::too_many_arguments, non_camel_case_types)]
+            #[allow(
+                clippy::too_many_arguments,
+                non_camel_case_types,
+                clippy::upper_case_acronyms
+            )]
             pub mod #module {
                 #imports
                 #contract
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/common.rs b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
index c48e306d..b53b08d6 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/common.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/common.rs
@@ -62,7 +62,7 @@ where
 }
 
 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");
+    let doc_str = format!("{name} was auto-generated with ethers-rs Abigen. More information at: <https://github.com/gakonst/ethers-rs>");
 
     let ethers_core = ethers_core_crate();
     let ethers_providers = ethers_providers_crate();
diff --git a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
index 4002e7b6..b2b3dbeb 100644
--- a/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
+++ b/ethers-contract/ethers-contract-abigen/src/contract/structs.rs
@@ -596,7 +596,7 @@ fn struct_type_projections(name: &str) -> Vec<String> {
 fn expand_struct_type(struct_ty: &StructFieldType) -> TokenStream {
     match struct_ty {
         StructFieldType::Type(ty) => {
-            let ty = util::ident(ty.name());
+            let ty = util::ident(&ty.name().to_pascal_case());
             quote! {#ty}
         }
         StructFieldType::Array(ty) => {
diff --git a/ethers-core/src/types/chain.rs b/ethers-core/src/types/chain.rs
index e423b1a6..1d713548 100644
--- a/ethers-core/src/types/chain.rs
+++ b/ethers-core/src/types/chain.rs
@@ -280,10 +280,7 @@ impl Chain {
             ArbitrumTestnet => {
                 ("https://api-testnet.arbiscan.io/api", "https://testnet.arbiscan.io")
             }
-            ArbitrumGoerli => (
-                "https://goerli-rollup-explorer.arbitrum.io/api",
-                "https://goerli-rollup-explorer.arbitrum.io",
-            ),
+            ArbitrumGoerli => ("https://api-goerli.arbiscan.io/api", "https://goerli.arbiscan.io"),
             ArbitrumNova => ("https://api-nova.arbiscan.io/api", "https://nova.arbiscan.io/"),
             Cronos => ("https://api.cronoscan.com/api", "https://cronoscan.com"),
             CronosTestnet => {
diff --git a/ethers-etherscan/src/errors.rs b/ethers-etherscan/src/errors.rs
index f10920ba..f6cc587d 100644
--- a/ethers-etherscan/src/errors.rs
+++ b/ethers-etherscan/src/errors.rs
@@ -41,6 +41,8 @@ pub enum EtherscanError {
     BlockedByCloudflare,
     #[error("The Requested prompted a cloudflare captcha security challenge to review the security of your connection before proceeding.")]
     CloudFlareSecurityChallenge,
+    #[error("Received `Page not found` response. API server is likely down")]
+    PageNotFound,
 }
 
 /// etherscan/polyscan is protected by cloudflare, which can lead to html responses like `Sorry, you have been blocked` See also <https://community.cloudflare.com/t/sorry-you-have-been-blocked/110790>
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index b3fa1c6e..ee8437b6 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -207,7 +207,9 @@ impl Client {
         let res = res.as_ref();
         let res: ResponseData<T> = serde_json::from_str(res).map_err(|err| {
             error!(target: "etherscan", ?res, "Failed to deserialize response: {}", err);
-            if is_blocked_by_cloudflare_response(res) {
+            if res == "Page not found" {
+                EtherscanError::PageNotFound
+            } else if is_blocked_by_cloudflare_response(res) {
                 EtherscanError::BlockedByCloudflare
             } else if is_cloudflare_security_challenge(res) {
                 EtherscanError::CloudFlareSecurityChallenge
diff --git a/ethers-middleware/src/transformer/ds_proxy/factory.rs b/ethers-middleware/src/transformer/ds_proxy/factory.rs
index 122c7bf1..71d35043 100644
--- a/ethers-middleware/src/transformer/ds_proxy/factory.rs
+++ b/ethers-middleware/src/transformer/ds_proxy/factory.rs
@@ -42,7 +42,7 @@ mod dsproxyfactory_mod {
         types::*,
     };
     use ethers_providers::Middleware;
-    #[doc = "DsProxyFactory was auto-generated with ethers-rs Abigen. More information at: https://github.com/gakonst/ethers-rs"]
+    #[doc = "DsProxyFactory was auto-generated with ethers-rs Abigen. More information at: <https://github.com/gakonst/ethers-rs>"]
     use std::sync::Arc;
 
     pub static DSPROXYFACTORY_ABI: Lazy<Abi> = Lazy::new(|| {
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index 7b432680..3530f53c 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -37,7 +37,7 @@ tempfile = { version = "3.3.0", optional = true }
 fs_extra = { version = "1.3.0", optional = true }
 sha2 = { version = "0.10.6", default-features = false, optional = true }
 dunce = "1.0.3"
-solang-parser = { default-features = false, version = "=0.2.1" }
+solang-parser = { default-features = false, version = "=0.2.2" }
 rayon = "1.6.0"
 rand = { version = "0.8.5", optional = true }
 path-slash = "0.2.1"
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index 94806f9c..a90b0962 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -1018,6 +1018,8 @@ pub struct ModelCheckerSettings {
     pub timeout: Option<u32>,
     #[serde(skip_serializing_if = "Option::is_none")]
     pub targets: Option<Vec<ModelCheckerTarget>>,
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub invariants: Option<Vec<ModelCheckerInvariant>>,
 }
 
 /// Which model checker engine to run.
@@ -1104,6 +1106,36 @@ impl FromStr for ModelCheckerTarget {
     }
 }
 
+/// Which model checker invariants to check.
+#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub enum ModelCheckerInvariant {
+    Contract,
+    Reentrancy,
+}
+
+impl fmt::Display for ModelCheckerInvariant {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let string = match self {
+            ModelCheckerInvariant::Contract => "contract",
+            ModelCheckerInvariant::Reentrancy => "reentrancy",
+        };
+        write!(f, "{string}")
+    }
+}
+
+impl FromStr for ModelCheckerInvariant {
+    type Err = String;
+
+    fn from_str(s: &str) -> Result<Self, Self::Err> {
+        match s {
+            "contract" => Ok(ModelCheckerInvariant::Contract),
+            "reentrancy" => Ok(ModelCheckerInvariant::Reentrancy),
+            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/resolver/mod.rs b/ethers-solc/src/resolver/mod.rs
index 732b5d1a..08b1b0f1 100644
--- a/ethers-solc/src/resolver/mod.rs
+++ b/ethers-solc/src/resolver/mod.rs
@@ -455,17 +455,20 @@ impl Graph {
             all_nodes: &mut HashMap<usize, (PathBuf, Source)>,
             sources: &mut Sources,
             edges: &[Vec<usize>],
-            num_input_files: usize,
+            processed_sources: &mut HashSet<usize>,
         ) {
+            // iterate over all dependencies not processed yet
             for dep in edges[idx].iter().copied() {
-                // we only process nodes that were added as part of the resolve step because input
-                // nodes are handled separately
-                if dep >= num_input_files {
-                    // library import
-                    if let Some((path, source)) = all_nodes.remove(&dep) {
-                        sources.insert(path, source);
-                        insert_imports(dep, all_nodes, sources, edges, num_input_files);
-                    }
+                // keep track of processed dependencies, if the dep was already in the set we have
+                // processed it already
+                if !processed_sources.insert(dep) {
+                    continue
+                }
+
+                // library import
+                if let Some((path, source)) = all_nodes.get(&dep).cloned() {
+                    sources.insert(path, source);
+                    insert_imports(dep, all_nodes, sources, edges, processed_sources);
                 }
             }
         }
@@ -480,17 +483,21 @@ impl Graph {
         // determine the `Sources` set for each solc version
         for (version, input_node_indices) in versioned_nodes {
             let mut sources = Sources::new();
+
+            // all input nodes will be processed
+            let mut processed_sources = input_node_indices.iter().copied().collect();
+
             // we only process input nodes (from sources, tests for example)
             for idx in input_node_indices {
                 // insert the input node in the sources set and remove it from the available set
-                let (path, source) = all_nodes.remove(&idx).expect("node is preset. qed");
+                let (path, source) = all_nodes.get(&idx).cloned().expect("node is preset. qed");
                 sources.insert(path, source);
                 insert_imports(
                     idx,
                     &mut all_nodes,
                     &mut sources,
                     &edges.edges,
-                    edges.num_input_files,
+                    &mut processed_sources,
                 );
             }
             versioned_sources.insert(version, sources);


Test patch:
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index 45aa9079..3cc8c97e 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -1610,6 +1610,7 @@ fn can_compile_model_checker_sample() {
         engine: Some(CHC),
         targets: None,
         timeout: Some(10000),
+        invariants: None,
     });
     let compiled = project.compile().unwrap();
 
@@ -1860,7 +1861,7 @@ fn can_parse_doc() {
 
     let contract = r#"
 // SPDX-License-Identifier: GPL-3.0-only
-pragma solidity ^0.8.0;
+pragma solidity 0.8.17;
 
 /// @title Not an ERC20.
 /// @author Notadev