Repository: gakonst/ethers-rs
Pull request #1065 (2022-03-19 16:31:36)

chore(etherscan): Return error for unverified contracts
Given contract source code is not useful in the case of unverified contracts
have the contract_source_code method return an error in the case if the item.abi
has the "Contract source code not verified" string.

Uncomment unit test ignore and run test:
`cargo test --lib ethers-etherscan -- get_error`



Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 5173bfd1..374b7573 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1198,7 +1198,6 @@ dependencies = [
  "eyre",
  "getrandom 0.2.5",
  "hex",
- "once_cell",
  "proc-macro2",
  "quote",
  "reqwest",
@@ -1232,17 +1231,14 @@ dependencies = [
  "bytes",
  "cargo_metadata",
  "convert_case",
- "ecdsa",
  "elliptic-curve",
  "ethabi",
- "futures-util",
  "generic-array 0.14.5",
  "hex",
  "hex-literal",
  "k256",
  "once_cell",
  "proc-macro2",
- "quote",
  "rand 0.8.5",
  "rlp",
  "rlp-derive",
@@ -1251,7 +1247,6 @@ dependencies = [
  "syn",
  "thiserror",
  "tiny-keccak",
- "tokio",
 ]
 
 [[package]]
@@ -1263,9 +1258,7 @@ dependencies = [
  "ethers-core",
  "ethers-signers",
  "hex",
- "proc-macro2",
  "quote",
- "serde",
  "serde_json",
  "syn",
 ]
@@ -1377,7 +1370,6 @@ dependencies = [
  "thiserror",
  "tokio",
  "tracing",
- "tracing-futures",
  "tracing-subscriber",
  "trezor-client",
  "yubihsm",
diff --git a/ethers-contract/Cargo.toml b/ethers-contract/Cargo.toml
index 4bac27aa..f66bd160 100644
--- a/ethers-contract/Cargo.toml
+++ b/ethers-contract/Cargo.toml
@@ -38,7 +38,6 @@ ethers-solc = { version = "^0.3.0", path = "../ethers-solc", default-features =
 tokio = { version = "1.5", default-features = false, features = ["macros"] }
 
 [features]
-default = ["rustls"]
 eip712 = ["ethers-derive-eip712", "ethers-core/eip712"]
 abigen = ["ethers-contract-abigen/reqwest", "ethers-contract-derive"]
 abigen-offline = ["ethers-contract-abigen", "ethers-contract-derive"]
diff --git a/ethers-contract/ethers-contract-abigen/Cargo.toml b/ethers-contract/ethers-contract-abigen/Cargo.toml
index 77743140..dbe63ad5 100644
--- a/ethers-contract/ethers-contract-abigen/Cargo.toml
+++ b/ethers-contract/ethers-contract-abigen/Cargo.toml
@@ -21,7 +21,6 @@ serde_json = "1.0.61"
 serde = { version = "1.0.124", features = ["derive"] }
 hex = { version = "0.4.2", default-features = false, features = ["std"] }
 reqwest = { version = "0.11.3", default-features = false, features = ["blocking"] , optional = true }
-once_cell = "1.8.0"
 cfg-if = "1.0.0"
 dunce = "1.0.2"
 walkdir = "2.3.2"
diff --git a/ethers-contract/ethers-contract-abigen/src/lib.rs b/ethers-contract/ethers-contract-abigen/src/lib.rs
index ac83f68d..a9b7e76e 100644
--- a/ethers-contract/ethers-contract-abigen/src/lib.rs
+++ b/ethers-contract/ethers-contract-abigen/src/lib.rs
@@ -1,4 +1,5 @@
 #![deny(missing_docs, unsafe_code)]
+#![deny(rustdoc::broken_intra_doc_links)]
 
 //! Module for generating type-safe bindings to Ethereum smart contracts. This
 //! module is intended to be used either indirectly with the `abigen` procedural
diff --git a/ethers-contract/ethers-contract-derive/src/lib.rs b/ethers-contract/ethers-contract-derive/src/lib.rs
index 5bb89d56..b505d19e 100644
--- a/ethers-contract/ethers-contract-derive/src/lib.rs
+++ b/ethers-contract/ethers-contract-derive/src/lib.rs
@@ -1,6 +1,7 @@
 //! Implementation of procedural macro for generating type-safe bindings to an
 //! ethereum smart contract.
-#![deny(missing_docs, unsafe_code)]
+#![deny(missing_docs, unsafe_code, unused_crate_dependencies)]
+#![deny(rustdoc::broken_intra_doc_links)]
 
 use proc_macro::TokenStream;
 use syn::{parse_macro_input, DeriveInput};
diff --git a/ethers-contract/ethers-contract-derive/src/utils.rs b/ethers-contract/ethers-contract-derive/src/utils.rs
index cf23bf80..066cbbfa 100644
--- a/ethers-contract/ethers-contract-derive/src/utils.rs
+++ b/ethers-contract/ethers-contract-derive/src/utils.rs
@@ -32,7 +32,7 @@ pub fn parse_int_param_type(s: &str) -> Option<ParamType> {
 // Converts param types for indexed parameters to bytes32 where appropriate
 // This applies to strings, arrays, structs and bytes to follow the encoding of
 // these indexed param types according to
-// https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters
+// <https://solidity.readthedocs.io/en/develop/abi-spec.html#encoding-of-indexed-event-parameters>
 pub fn topic_param_type_quote(kind: &ParamType) -> proc_macro2::TokenStream {
     let core_crate = ethers_core_crate();
     match kind {
diff --git a/ethers-contract/src/base.rs b/ethers-contract/src/base.rs
index 32db0c47..56792137 100644
--- a/ethers-contract/src/base.rs
+++ b/ethers-contract/src/base.rs
@@ -23,7 +23,7 @@ pub struct BaseContract {
     /// A mapping from method signature to a name-index pair for accessing
     /// functions in the contract ABI. This is used to avoid allocation when
     /// searching for matching functions by signature.
-    // Adapted from: https://github.com/gnosis/ethcontract-rs/blob/master/src/contract.rs
+    // Adapted from: <https://github.com/gnosis/ethcontract-rs/blob/master/src/contract.rs>
     pub methods: HashMap<Selector, (String, usize)>,
 }
 
diff --git a/ethers-contract/src/lib.rs b/ethers-contract/src/lib.rs
index 1d1532ac..21b14b5c 100644
--- a/ethers-contract/src/lib.rs
+++ b/ethers-contract/src/lib.rs
@@ -1,5 +1,7 @@
 #![cfg_attr(docsrs, feature(doc_cfg))]
 #![doc = include_str!("../README.md")]
+#![deny(unsafe_code)]
+
 mod contract;
 pub use contract::Contract;
 
diff --git a/ethers-contract/src/multicall/mod.rs b/ethers-contract/src/multicall/mod.rs
index 00138cc3..2055b133 100644
--- a/ethers-contract/src/multicall/mod.rs
+++ b/ethers-contract/src/multicall/mod.rs
@@ -119,7 +119,7 @@ pub static ADDRESS_BOOK: Lazy<HashMap<U256, Address>> = Lazy::new(|| {
 ///
 /// [`new`]: method@crate::Multicall::new
 /// [`block`]: method@crate::Multicall::block
-/// [`add_call`]: methond@crate::Multicall::add_call
+/// [`add_call`]: method@crate::Multicall::add_call
 #[derive(Clone)]
 pub struct Multicall<M> {
     calls: Vec<Call>,
diff --git a/ethers-core/Cargo.toml b/ethers-core/Cargo.toml
index fa012284..17ac220a 100644
--- a/ethers-core/Cargo.toml
+++ b/ethers-core/Cargo.toml
@@ -16,10 +16,9 @@ arrayvec = { version = "0.7.2", default-features = false }
 rlp-derive = { version = "0.1.0", default-features = false }
 
 # crypto
-ecdsa = { version = "0.13.4", default-features = false, features = ["std"] }
 elliptic-curve = { version = "0.11.12", default-features = false }
 generic-array = { version = "0.14.5", default-features = false }
-k256 = { version = "0.10.4", default-features = false, features = ["keccak256", "ecdsa"] }
+k256 = { version = "0.10.4", default-features = false, features = ["keccak256", "ecdsa", "std"] }
 rand = { version = "0.8.5", default-features = false }
 tiny-keccak = { version = "2.0.2", default-features = false }
 
@@ -29,7 +28,7 @@ serde_json = { version = "1.0.64", default-features = false }
 thiserror = { version = "1.0.30", default-features = false }
 bytes = { version = "1.1.0", features = ["serde"] }
 hex = { version = "0.4.3", default-features = false, features = ["std"] }
-once_cell = "1.10.0"
+once_cell = { version = "1.10.0", optional = true }
 
 # macros feature enabled dependencies
 cargo_metadata = { version = "0.14.2", optional = true }
@@ -37,28 +36,20 @@ cargo_metadata = { version = "0.14.2", optional = true }
 # eip712 feature enabled dependencies
 convert_case = { version = "0.5.0", optional = true }
 syn = { version = "1.0.89", optional = true }
-quote = { version = "1.0.16", optional = true }
 proc-macro2 = { version = "1.0.36", optional = true }
 
-[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
-# async
-tokio = { version = "1.5", default-features = false, optional = true}
-futures-util = { version = "^0.3", optional = true }
-
 [dev-dependencies]
 serde_json = { version = "1.0.64", default-features = false }
 bincode = { version = "1.3.3", default-features = false }
 once_cell = { version = "1.10.0" }
 hex-literal = "0.3.4"
-futures-util = { version = "^0.3" }
 rand = "0.8.5"
 
 [features]
 celo = ["legacy"] # celo support extends the transaction format with extra fields
-setup = ["tokio", "futures-util"] # async support for concurrent setup
 legacy = []
-eip712 = ["convert_case", "syn", "quote", "proc-macro2"]
-macros = ["syn", "cargo_metadata"]
+eip712 = ["convert_case", "syn", "proc-macro2"]
+macros = ["syn", "cargo_metadata", "once_cell"]
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/ethers-core/ethers-derive-eip712/Cargo.toml b/ethers-core/ethers-derive-eip712/Cargo.toml
index ee6a1209..82155397 100644
--- a/ethers-core/ethers-derive-eip712/Cargo.toml
+++ b/ethers-core/ethers-derive-eip712/Cargo.toml
@@ -13,9 +13,7 @@ quote = "1.0.9"
 syn = "1.0.77"
 ethers-core = { version = "^0.6.0", path = "../", default-features = false, features = ["eip712", "macros"] }
 hex = "0.4.3"
-serde = "1.0.130"
 serde_json = "1.0.68"
-proc-macro2 = "1.0.29"
 
 [dev-dependencies]
 ethers-contract = { version = "^0.6.0", path = "../../ethers-contract", features = ["abigen"]}
diff --git a/ethers-core/ethers-derive-eip712/src/lib.rs b/ethers-core/ethers-derive-eip712/src/lib.rs
index f42a6cbe..3b3c8fd7 100644
--- a/ethers-core/ethers-derive-eip712/src/lib.rs
+++ b/ethers-core/ethers-derive-eip712/src/lib.rs
@@ -59,6 +59,10 @@
 //!
 //! There is an Inner helper attribute `#[eip712]` for fields that will eventually be used to
 //! determine if there is a nested eip712 struct. However, this work is not yet complete.
+
+#![deny(rustdoc::broken_intra_doc_links)]
+#![deny(unused_crate_dependencies)]
+
 use std::convert::TryFrom;
 
 use ethers_core::{macros::ethers_core_crate, types::transaction::eip712};
diff --git a/ethers-core/src/abi/human_readable.rs b/ethers-core/src/abi/human_readable.rs
index b67f85f8..349b520b 100644
--- a/ethers-core/src/abi/human_readable.rs
+++ b/ethers-core/src/abi/human_readable.rs
@@ -362,14 +362,14 @@ impl AbiParser {
     /// }
     /// ```
     ///
-    /// See https://github.com/rust-ethereum/ethabi/issues/254
+    /// See <https://github.com/rust-ethereum/ethabi/issues/254>
     ///
     /// Therefore, we need to double-check if the `ethabi::Reader` parsed an `uint8`, and ignore the
     /// type if `type_str` is not uint8. However can lead to some problems if a function param is
     /// array of custom types for example, like `Foo[]`, which the `Reader` would identify as
     /// `uint8[]`. Therefor if the `Reader` returns an `uint8` we also check that the input string
     /// contains a `uint8`. This however can still lead to false detection of `uint8` and is only
-    /// solvable with a more sophisticated parser: https://github.com/gakonst/ethers-rs/issues/474
+    /// solvable with a more sophisticated parser: <https://github.com/gakonst/ethers-rs/issues/474>
     fn parse_type(&self, type_str: &str) -> Result<(ParamType, Option<String>)> {
         if let Ok(kind) = Reader::read(type_str) {
             if is_likely_tuple_not_uint8(&kind, type_str) {
diff --git a/ethers-core/src/lib.rs b/ethers-core/src/lib.rs
index c381d19f..f9b4d2ac 100644
--- a/ethers-core/src/lib.rs
+++ b/ethers-core/src/lib.rs
@@ -1,5 +1,8 @@
 #![cfg_attr(docsrs, feature(doc_cfg))]
 #![doc = include_str!("../README.md")]
+#![deny(rustdoc::broken_intra_doc_links)]
+#![deny(unused_crate_dependencies)]
+
 pub mod types;
 
 pub mod abi;
diff --git a/ethers-core/src/types/block.rs b/ethers-core/src/types/block.rs
index a0ef2b5a..72577900 100644
--- a/ethers-core/src/types/block.rs
+++ b/ethers-core/src/types/block.rs
@@ -1,4 +1,4 @@
-// Taken from https://github.com/tomusdrw/rust-web3/blob/master/src/types/block.rs
+// Taken from <https://github.com/tomusdrw/rust-web3/blob/master/src/types/block.rs>
 use crate::types::{Address, Bloom, Bytes, H256, U256, U64};
 #[cfg(not(feature = "celo"))]
 use core::cmp::Ordering;
@@ -91,7 +91,7 @@ pub struct Block<TX> {
     pub epoch_snark_data: Option<EpochSnarkData>,
 }
 
-// ref https://eips.ethereum.org/EIPS/eip-1559
+// ref <https://eips.ethereum.org/EIPS/eip-1559>
 #[cfg(not(feature = "celo"))]
 pub const ELASTICITY_MULTIPLIER: U256 = U256([2u64, 0, 0, 0]);
 // max base fee delta is 12.5%
@@ -106,7 +106,7 @@ impl<TX> Block<TX> {
     }
 
     /// The next block's base fee, it is a function of parent block's base fee and gas usage.
-    /// Reference: https://eips.ethereum.org/EIPS/eip-1559
+    /// Reference: <https://eips.ethereum.org/EIPS/eip-1559>
     #[cfg(not(feature = "celo"))]
     pub fn next_block_base_fee(&self) -> Option<U256> {
         let target_usage = self.gas_target();
@@ -162,7 +162,7 @@ pub struct EpochSnarkData {
 /// A Block Hash or Block Number
 pub enum BlockId {
     // TODO: May want to expand this to include the requireCanonical field
-    // https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md
+    // <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1898.md>
     /// A block hash
     Hash(H256),
     /// A block number
@@ -291,7 +291,7 @@ mod tests {
     }
 
     #[test]
-    // https://github.com/tomusdrw/rust-web3/commit/3a32ee962c0f2f8d50a5e25be9f2dfec7ae0750d
+    // <https://github.com/tomusdrw/rust-web3/commit/3a32ee962c0f2f8d50a5e25be9f2dfec7ae0750d>
     fn post_london_block() {
         let json = serde_json::json!(
         {
@@ -330,7 +330,7 @@ mod tests {
 
     #[test]
     fn test_next_block_base_fee() {
-        // https://etherscan.io/block/14402566
+        // <https://etherscan.io/block/14402566>
         let mut block_14402566 = Block::<TxHash>::default();
         block_14402566.number = Some(U64::from(14402566u64));
         block_14402566.base_fee_per_gas = Some(U256::from(36_803_013_756u128));
diff --git a/ethers-core/src/types/i256.rs b/ethers-core/src/types/i256.rs
index 9a115545..e568c8b2 100644
--- a/ethers-core/src/types/i256.rs
+++ b/ethers-core/src/types/i256.rs
@@ -1,5 +1,5 @@
 //! This module contains an 256-bit signed integer implementation.
-//! This module was derived for ethers-core via https://github.com/gnosis/ethcontract-rs/
+//! This module was derived for ethers-core via <https://github.com/gnosis/ethcontract-rs/>
 #![allow(clippy::wrong_self_convention)]
 use crate::{
     abi::{InvalidOutputType, Token, Tokenizable},
diff --git a/ethers-core/src/types/trace/mod.rs b/ethers-core/src/types/trace/mod.rs
index 2c06fbf1..3eceb6a7 100644
--- a/ethers-core/src/types/trace/mod.rs
+++ b/ethers-core/src/types/trace/mod.rs
@@ -1,6 +1,6 @@
 //! Types for the Parity Ad-Hoc Trace API
 //!
-//! https://openethereum.github.io/wiki/JSONRPC-trace-module
+//! <https://openethereum.github.io/wiki/JSONRPC-trace-module>
 use crate::types::{Bytes, H160, H256, U256};
 use serde::{Deserialize, Serialize};
 use std::collections::BTreeMap;
diff --git a/ethers-core/src/types/transaction/eip2718.rs b/ethers-core/src/types/transaction/eip2718.rs
index fe211df7..8fffc6ba 100644
--- a/ethers-core/src/types/transaction/eip2718.rs
+++ b/ethers-core/src/types/transaction/eip2718.rs
@@ -141,10 +141,10 @@ impl TypedTransaction {
             Eip2930(inner) => inner.tx.gas_price,
             Eip1559(inner) => {
                 match (inner.max_fee_per_gas, inner.max_priority_fee_per_gas) {
-                    (Some(basefee), Some(prio_fee)) => Some(basefee + prio_fee),
+                    (Some(max_fee), Some(_)) => Some(max_fee),
                     // this also covers the None, None case
                     (None, prio_fee) => prio_fee,
-                    (basefee, None) => basefee,
+                    (max_fee, None) => max_fee,
                 }
             }
         }
diff --git a/ethers-core/src/types/transaction/eip712.rs b/ethers-core/src/types/transaction/eip712.rs
index c1875707..61d7edcf 100644
--- a/ethers-core/src/types/transaction/eip712.rs
+++ b/ethers-core/src/types/transaction/eip712.rs
@@ -56,7 +56,7 @@ pub enum Eip712Error {
 /// for `derive_eip712` for more information and example usage.
 ///
 /// For those who wish to manually implement this trait, see:
-/// https://eips.ethereum.org/EIPS/eip-712
+/// <https://eips.ethereum.org/EIPS/eip-712>
 ///
 /// Any rust struct implementing Eip712 must also have a corresponding
 /// struct in the verifying ethereum contract that matches its signature.
@@ -412,7 +412,7 @@ impl TryFrom<&syn::DeriveInput> for EIP712Domain {
 }
 
 /// Parse the eth abi parameter type based on the syntax type;
-/// this method is copied from https://github.com/gakonst/ethers-rs/blob/master/ethers-contract/ethers-contract-derive/src/lib.rs#L600
+/// this method is copied from <https://github.com/gakonst/ethers-rs/blob/master/ethers-contract/ethers-contract-derive/src/lib.rs#L600>
 /// with additional modifications for finding byte arrays
 pub fn find_parameter_type(ty: &Type) -> Result<ParamType, TokenStream> {
     match ty {
diff --git a/ethers-core/src/utils/mod.rs b/ethers-core/src/utils/mod.rs
index cbbc8f9b..6dce364b 100644
--- a/ethers-core/src/utils/mod.rs
+++ b/ethers-core/src/utils/mod.rs
@@ -252,7 +252,7 @@ pub fn secret_key_to_address(secret_key: &SigningKey) -> Address {
 }
 
 /// Converts an Ethereum address to the checksum encoding
-/// Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
+/// Ref: <https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md>
 pub fn to_checksum(addr: &Address, chain_id: Option<u8>) -> String {
     let prefixed_addr = match chain_id {
         Some(chain_id) => format!("{}0x{:x}", chain_id, addr),
diff --git a/ethers-core/src/utils/moonbeam.rs b/ethers-core/src/utils/moonbeam.rs
index 33136305..67af8be7 100644
--- a/ethers-core/src/utils/moonbeam.rs
+++ b/ethers-core/src/utils/moonbeam.rs
@@ -4,7 +4,7 @@ use std::collections::BTreeMap;
 
 use k256::SecretKey;
 
-/// Returns the private developer keys https://docs.moonbeam.network/snippets/code/setting-up-node/dev-accounts/
+/// Returns the private developer keys <https://docs.moonbeam.network/snippets/code/setting-up-node/dev-accounts/>
 pub fn dev_keys() -> Vec<SecretKey> {
     MoonbeamDev::default().into_keys().collect()
 }
diff --git a/ethers-etherscan/src/contract.rs b/ethers-etherscan/src/contract.rs
index a3d11146..29a63ea0 100644
--- a/ethers-etherscan/src/contract.rs
+++ b/ethers-etherscan/src/contract.rs
@@ -310,6 +310,9 @@ impl Client {
         let query =
             self.create_query("contract", "getsourcecode", HashMap::from([("address", address)]));
         let response: Response<Vec<Metadata>> = self.get_json(&query).await?;
+        if response.result.iter().any(|item| item.abi == "Contract source code not verified") {
+            return Err(EtherscanError::ContractCodeNotVerified(address))
+        }
         Ok(ContractMetadata { items: response.result })
     }
 }
@@ -323,7 +326,7 @@ mod tests {
     use ethers_core::types::Chain;
     use ethers_solc::{Project, ProjectPathsConfig};
 
-    use crate::{contract::VerifyContract, tests::run_at_least_duration, Client};
+    use crate::{contract::VerifyContract, tests::run_at_least_duration, Client, EtherscanError};
 
     #[tokio::test]
     #[serial]
@@ -355,6 +358,27 @@ mod tests {
         .await
     }
 
+    #[tokio::test]
+    #[serial]
+    #[ignore]
+    async fn can_get_error_on_unverified_contract() {
+        run_at_least_duration(Duration::from_millis(250), async {
+            let client = Client::new_from_env(Chain::Mainnet).unwrap();
+            let unverified_addr = "0xb5c31a0e22cae98ac08233e512bd627885aa24e5".parse().unwrap();
+            let result = client.contract_source_code(unverified_addr).await;
+            match result.err() {
+                Some(error) => match error {
+                    EtherscanError::ContractCodeNotVerified(addr) => {
+                        assert_eq!(addr, unverified_addr);
+                    }
+                    _ => panic!("Invalid EtherscanError type"),
+                },
+                None => panic!("Result should contain ContractCodeNotVerified error"),
+            }
+        })
+        .await
+    }
+
     /// Query a contract that has a single string source entry instead of underlying JSON metadata.
     #[tokio::test]
     #[serial]
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index b3e52e1c..4bf20b35 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -24,9 +24,9 @@ pub struct Client {
     client: reqwest::Client,
     /// Etherscan API key
     api_key: String,
-    /// Etherscan API endpoint like https://api(-chain).etherscan.io/api
+    /// Etherscan API endpoint like <https://api(-chain).etherscan.io/api>
     etherscan_api_url: Url,
-    /// Etherscan base endpoint like https://etherscan.io
+    /// Etherscan base endpoint like <https://etherscan.io>
     etherscan_url: Url,
 }
 
diff --git a/ethers-middleware/src/gas_escalator/geometric.rs b/ethers-middleware/src/gas_escalator/geometric.rs
index d45f64b3..52f9cb03 100644
--- a/ethers-middleware/src/gas_escalator/geometric.rs
+++ b/ethers-middleware/src/gas_escalator/geometric.rs
@@ -7,7 +7,7 @@ use ethers_core::types::U256;
 /// Coefficient defaults to 1.125 (12.5%), the minimum increase for Parity to replace a transaction.
 /// Coefficient can be adjusted, and there is an optional upper limit.
 ///
-/// https://github.com/makerdao/pymaker/blob/master/pymaker/gas.py#L168
+/// <https://github.com/makerdao/pymaker/blob/master/pymaker/gas.py#L168>
 #[derive(Clone, Debug)]
 pub struct GeometricGasPrice {
     every_secs: u64,
diff --git a/ethers-middleware/src/gas_escalator/linear.rs b/ethers-middleware/src/gas_escalator/linear.rs
index 3e267c1e..308b8ba9 100644
--- a/ethers-middleware/src/gas_escalator/linear.rs
+++ b/ethers-middleware/src/gas_escalator/linear.rs
@@ -7,7 +7,7 @@ use ethers_core::types::U256;
 /// Start with `initial_price`, then increase it by fixed amount `increase_by` every `every_secs`
 /// seconds until the transaction gets confirmed. There is an optional upper limit.
 ///
-/// https://github.com/makerdao/pymaker/blob/master/pymaker/gas.py#L129
+/// <https://github.com/makerdao/pymaker/blob/master/pymaker/gas.py#L129>
 #[derive(Clone, Debug)]
 pub struct LinearGasPrice {
     every_secs: u64,
diff --git a/ethers-middleware/src/lib.rs b/ethers-middleware/src/lib.rs
index 357447ab..33e90ef2 100644
--- a/ethers-middleware/src/lib.rs
+++ b/ethers-middleware/src/lib.rs
@@ -1,4 +1,6 @@
 #![doc = include_str!("../README.md")]
+#![deny(unsafe_code)]
+#![deny(rustdoc::broken_intra_doc_links)]
 
 /// The [Gas Escalator middleware](crate::gas_escalator::GasEscalatorMiddleware)
 /// is used to re-broadcast transactions with an increasing gas price to guarantee
@@ -15,8 +17,9 @@ pub mod gas_oracle;
 pub mod nonce_manager;
 pub use nonce_manager::NonceManagerMiddleware;
 
-/// The [Transformer](crate::TransformerMiddleware) is used to intercept transactions and transform
-/// them to be sent via various supported transformers, e.g., [DSProxy](crate::transformer::DsProxy)
+/// The [Transformer](crate::transformer::TransformerMiddleware) is used to intercept transactions
+/// and transform them to be sent via various supported transformers, e.g.,
+/// [DSProxy](crate::transformer::DsProxy)
 pub mod transformer;
 
 /// The [Signer](crate::SignerMiddleware) is used to locally sign transactions and messages
diff --git a/ethers-middleware/src/transformer/mod.rs b/ethers-middleware/src/transformer/mod.rs
index ab31b5ee..4bd1542e 100644
--- a/ethers-middleware/src/transformer/mod.rs
+++ b/ethers-middleware/src/transformer/mod.rs
@@ -21,11 +21,9 @@ pub enum TransformerError {
     AbiError(#[from] AbiError),
 }
 
-/// `Transformer` is a trait to be implemented by a proxy wallet, eg. [`DSProxy`], that intends to
+/// `Transformer` is a trait to be implemented by a proxy wallet, eg. [`DsProxy`], that intends to
 /// intercept a transaction request and transform it into one that is instead sent via the proxy
 /// contract.
-///
-/// [`DSProxy`]: struct@crate::ds_proxy::DsProxy
 pub trait Transformer: Send + Sync + std::fmt::Debug {
     /// Transforms a [`transaction request`] into one that can be broadcasted and execute via the
     /// proxy contract.
diff --git a/ethers-providers/Cargo.toml b/ethers-providers/Cargo.toml
index 6fa86962..f0b5bfe6 100644
--- a/ethers-providers/Cargo.toml
+++ b/ethers-providers/Cargo.toml
@@ -31,7 +31,7 @@ base64 = "0.13"
 futures-core = { version = "0.3.16", default-features = false }
 futures-util = { version = "^0.3" }
 futures-timer = { version = "3.0.2", default-features = false }
-futures-channel = { version = "0.3.16", default-features = false }
+futures-channel = { version = "0.3.16", default-features = false, optional = true }
 pin-project = { version = "1.0.7", default-features = false }
 
 # tracing
@@ -63,8 +63,8 @@ tempfile = "3.3.0"
 [features]
 default = ["ws", "rustls"]
 celo = ["ethers-core/celo"]
-ws = ["tokio", "tokio-tungstenite"]
-ipc = ["tokio", "tokio/io-util", "tokio-util", "bytes"]
+ws = ["tokio", "tokio-tungstenite", "futures-channel"]
+ipc = ["tokio", "tokio/io-util", "tokio-util", "bytes", "futures-channel"]
 
 openssl = ["tokio-tungstenite/native-tls", "reqwest/native-tls"]
 # we use the webpki roots so we can build static binaries w/o any root cert dependencies
diff --git a/ethers-providers/src/ens.rs b/ethers-providers/src/ens.rs
index 4db47d53..8e349ba1 100644
--- a/ethers-providers/src/ens.rs
+++ b/ethers-providers/src/ens.rs
@@ -1,5 +1,5 @@
 //! [Ethereum Name Service](https://docs.ens.domains/) support
-//! Adapted from https://github.com/hhatto/rust-ens/blob/master/src/lib.rs
+//! Adapted from <https://github.com/hhatto/rust-ens/blob/master/src/lib.rs>
 use ethers_core::{
     types::{Address, NameOrAddress, Selector, TransactionRequest, H160, H256},
     utils::keccak256,
diff --git a/ethers-providers/src/lib.rs b/ethers-providers/src/lib.rs
index e373e085..010eaab9 100644
--- a/ethers-providers/src/lib.rs
+++ b/ethers-providers/src/lib.rs
@@ -1,5 +1,6 @@
 #![cfg_attr(docsrs, feature(doc_cfg))]
-#![deny(broken_intra_doc_links)]
+#![deny(unsafe_code)]
+#![deny(rustdoc::broken_intra_doc_links)]
 #![allow(clippy::type_complexity)]
 #![doc = include_str!("../README.md")]
 mod transports;
diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs
index 6f6c7ea8..a97cc296 100644
--- a/ethers-providers/src/provider.rs
+++ b/ethers-providers/src/provider.rs
@@ -754,7 +754,7 @@ impl<P: JsonRpcClient> Middleware for Provider<P> {
     }
 
     /// Returns the EIP-1186 proof response
-    /// https://github.com/ethereum/EIPs/issues/1186
+    /// <https://github.com/ethereum/EIPs/issues/1186>
     async fn get_proof<T: Into<NameOrAddress> + Send + Sync>(
         &self,
         from: T,
@@ -1718,7 +1718,7 @@ mod tests {
         provider.from = Some("0x6fC21092DA55B392b045eD78F4732bff3C580e2c".parse().unwrap());
 
         let gas = U256::from(21000_usize);
-        let basefee = U256::from(25_usize);
+        let max_fee = U256::from(25_usize);
         let prio_fee = U256::from(25_usize);
         let access_list: AccessList = vec![Default::default()].into();
 
@@ -1729,7 +1729,7 @@ mod tests {
             .from(from)
             .to(to)
             .gas(gas)
-            .max_fee_per_gas(basefee)
+            .max_fee_per_gas(max_fee)
             .max_priority_fee_per_gas(prio_fee)
             .access_list(access_list.clone())
             .into();
@@ -1738,7 +1738,7 @@ mod tests {
         assert_eq!(tx.from(), Some(&from));
         assert_eq!(tx.to(), Some(&to.into()));
         assert_eq!(tx.gas(), Some(&gas));
-        assert_eq!(tx.gas_price(), Some(basefee + prio_fee));
+        assert_eq!(tx.gas_price(), Some(max_fee));
         assert_eq!(tx.access_list(), Some(&access_list));
 
         // --- fills a 1559 transaction, leaving the existing gas limit unchanged, but including
@@ -1746,7 +1746,7 @@ mod tests {
         let gas_with_al = gas - 1;
         let mut tx = Eip1559TransactionRequest::new()
             .gas(gas)
-            .max_fee_per_gas(basefee)
+            .max_fee_per_gas(max_fee)
             .max_priority_fee_per_gas(prio_fee)
             .into();
 
@@ -1766,7 +1766,7 @@ mod tests {
         // --- fills a 1559 transaction, ignoring access list if more expensive
         let gas_with_al = gas + 1;
         let mut tx = Eip1559TransactionRequest::new()
-            .max_fee_per_gas(basefee)
+            .max_fee_per_gas(max_fee)
             .max_priority_fee_per_gas(prio_fee)
             .into();
 
@@ -1786,7 +1786,7 @@ mod tests {
 
         // --- fills a 1559 transaction, using estimated gas if create_access_list() errors
         let mut tx = Eip1559TransactionRequest::new()
-            .max_fee_per_gas(basefee)
+            .max_fee_per_gas(max_fee)
             .max_priority_fee_per_gas(prio_fee)
             .into();
 
@@ -1803,7 +1803,7 @@ mod tests {
 
         // --- propogates estimate_gas() error
         let mut tx = Eip1559TransactionRequest::new()
-            .max_fee_per_gas(basefee)
+            .max_fee_per_gas(max_fee)
             .max_priority_fee_per_gas(prio_fee)
             .into();
 
diff --git a/ethers-providers/src/transports/ipc.rs b/ethers-providers/src/transports/ipc.rs
index 079f0ad9..baf5f155 100644
--- a/ethers-providers/src/transports/ipc.rs
+++ b/ethers-providers/src/transports/ipc.rs
@@ -314,8 +314,7 @@ impl From<IpcError> for ProviderError {
         ProviderError::JsonRpcClientError(Box::new(src))
     }
 }
-
-#[cfg(all(test, unix))]
+#[cfg(all(test, target_family = "unix"))]
 #[cfg(not(feature = "celo"))]
 mod test {
     use super::*;
diff --git a/ethers-signers/Cargo.toml b/ethers-signers/Cargo.toml
index ec782c9d..a43fd4f8 100644
--- a/ethers-signers/Cargo.toml
+++ b/ethers-signers/Cargo.toml
@@ -25,21 +25,20 @@ elliptic-curve = { version = "0.11.12", default-features = false }
 sha2 = { version = "0.9.8", default-features = false }
 rand = { version = "0.8.5", default-features = false }
 yubihsm = { version = "0.40.0", features = ["secp256k1", "http", "usb"], optional = true }
-futures-util = "^0.3"
-futures-executor = "^0.3"
-semver = "1.0.6"
+futures-util = { version = "^0.3", optional = true }
+futures-executor = { version = "^0.3", optional = true }
+semver = { version =  "1.0.6", optional = true }
 trezor-client = { version = "0.0.5", optional = true, default-features = false, features = ["f_ethereum"] }
 
 # aws
 rusoto_core = { version = "0.47.0", optional = true }
 rusoto_kms = { version = "0.47.0", optional = true }
 tracing = { version = "0.1.32", optional = true }
-tracing-futures = { version = "0.2.5", optional = true }
 spki = { version = "0.5.4", optional = true }
 
 [target.'cfg(not(target_arch = "wasm32"))'.dependencies]
 eth-keystore = { version = "0.4.1" }
-home = "0.5.3"
+home = { version = "0.5.3", optional = true }
 
 [dev-dependencies]
 ethers-contract = { version = "^0.6.0", path = "../ethers-contract", features = ["eip712", "abigen"]}
@@ -54,8 +53,9 @@ tokio = { version = "1.5", default-features = false, features = ["macros", "rt"]
 tempfile = "3.3.0"
 
 [features]
+futures = ["futures-util", "futures-executor"]
 celo = ["ethers-core/celo"]
-ledger = ["coins-ledger"]
+ledger = ["coins-ledger", "futures", "semver"]
 yubi = ["yubihsm"]
-aws = ["rusoto_core", "rusoto_kms", "tracing", "tracing-futures", "spki"]
-trezor = ["trezor-client"]
+aws = ["rusoto_core", "rusoto_kms", "tracing", "spki"]
+trezor = ["trezor-client", "futures", "semver", "home"]
\ No newline at end of file
diff --git a/ethers-signers/src/lib.rs b/ethers-signers/src/lib.rs
index 86888911..5287999f 100644
--- a/ethers-signers/src/lib.rs
+++ b/ethers-signers/src/lib.rs
@@ -1,4 +1,7 @@
 //! Provides a unified interface for locally signing transactions.
+#![deny(unsafe_code)]
+#![deny(rustdoc::broken_intra_doc_links)]
+
 mod wallet;
 pub use wallet::{MnemonicBuilder, Wallet, WalletError};
 
diff --git a/ethers-solc/Cargo.toml b/ethers-solc/Cargo.toml
index c902b24f..9ea020ed 100644
--- a/ethers-solc/Cargo.toml
+++ b/ethers-solc/Cargo.toml
@@ -33,7 +33,7 @@ num_cpus = "1.13.1"
 tiny-keccak = { version = "2.0.2", default-features = false }
 tempfile = { version = "3.3.0", optional = true }
 fs_extra = { version = "1.2.0", optional = true }
-sha2 = { version = "0.9.8", default-features = false }
+sha2 = { version = "0.9.8", default-features = false, optional = true }
 dunce = "1.0.2"
 solang-parser = { default-features = false, version = "0.1.10" }
 rayon = "1.5.1"
diff --git a/ethers-solc/src/artifact_output/configurable.rs b/ethers-solc/src/artifact_output/configurable.rs
index 74bee85a..47c4742c 100644
--- a/ethers-solc/src/artifact_output/configurable.rs
+++ b/ethers-solc/src/artifact_output/configurable.rs
@@ -20,7 +20,7 @@ use std::{borrow::Cow, collections::BTreeMap, fs, path::Path};
 #[serde(rename_all = "camelCase")]
 pub struct ConfigurableContractArtifact {
     /// The Ethereum Contract ABI. If empty, it is represented as an empty
-    /// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
+    /// array. See <https://docs.soliditylang.org/en/develop/abi-spec.html>
     pub abi: Option<LosslessAbi>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub bytecode: Option<CompactBytecode>,
diff --git a/ethers-solc/src/artifact_output/mod.rs b/ethers-solc/src/artifact_output/mod.rs
index 4d6f4623..5f4bcfd0 100644
--- a/ethers-solc/src/artifact_output/mod.rs
+++ b/ethers-solc/src/artifact_output/mod.rs
@@ -421,7 +421,7 @@ where
 /// this includes artifact file location and naming.
 ///
 /// Depending on the [`crate::Project`] contracts and their compatible versions,
-/// [`crate::ProjectCompiler::compile()`] may invoke different `solc` executables on the same
+/// The project compiler may invoke different `solc` executables on the same
 /// solidity file leading to multiple [`crate::CompilerOutput`]s for the same `.sol` file.
 /// In addition to the `solidity file` to `contract` relationship (1-N*)
 /// [`crate::VersionedContracts`] also tracks the `contract` to (`artifact` + `solc version`)
@@ -456,11 +456,11 @@ pub trait ArtifactOutput {
     /// Writes additional files for the contracts if the included in the `Contract`, such as `ir`,
     /// `ewasm`, `iropt`.
     ///
-    /// By default, these fields are _not_ enabled in the [`crate::Settings`], see
-    /// [`crate::Settings::default_output_selection()`], and the respective fields of the
-    /// [`Contract`] will `None`. If they'll be manually added to the `output_selection`, then
-    /// we're also creating individual files for this output, such as `Greeter.iropt`,
-    /// `Gretter.ewasm`
+    /// By default, these fields are _not_ enabled in the [`crate::artifacts::Settings`], see
+    /// [`crate::artifacts::output_selection::OutputSelection::default_output_selection()`], and the
+    /// respective fields of the [`Contract`] will `None`. If they'll be manually added to the
+    /// `output_selection`, then we're also creating individual files for this output, such as
+    /// `Greeter.iropt`, `Gretter.ewasm`
     fn write_extras(
         &self,
         contracts: &VersionedContracts,
diff --git a/ethers-solc/src/artifacts/bytecode.rs b/ethers-solc/src/artifacts/bytecode.rs
index b8814177..6025669c 100644
--- a/ethers-solc/src/artifacts/bytecode.rs
+++ b/ethers-solc/src/artifacts/bytecode.rs
@@ -117,7 +117,7 @@ impl From<BytecodeObject> for Bytecode {
 impl Bytecode {
     /// Returns the parsed source map
     ///
-    /// See also https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html
+    /// See also <https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html>
     pub fn source_map(&self) -> Option<Result<SourceMap, SyntaxError>> {
         self.source_map.as_ref().map(|map| sourcemap::parse(map))
     }
@@ -279,7 +279,7 @@ impl BytecodeObject {
     ///
     /// This will replace all occurrences of the library placeholder with the given address.
     ///
-    /// See also: https://docs.soliditylang.org/en/develop/using-the-compiler.html#library-linking
+    /// See also: <https://docs.soliditylang.org/en/develop/using-the-compiler.html#library-linking>
     pub fn link_fully_qualified(&mut self, name: impl AsRef<str>, addr: Address) -> &mut Self {
         if let BytecodeObject::Unlinked(ref mut unlinked) = self {
             let name = name.as_ref();
diff --git a/ethers-solc/src/artifacts/contract.rs b/ethers-solc/src/artifacts/contract.rs
index 12a4717a..2779e63e 100644
--- a/ethers-solc/src/artifacts/contract.rs
+++ b/ethers-solc/src/artifacts/contract.rs
@@ -15,7 +15,7 @@ use std::{borrow::Cow, collections::BTreeMap, convert::TryFrom};
 #[serde(rename_all = "camelCase")]
 pub struct Contract {
     /// The Ethereum Contract Metadata.
-    /// See https://docs.soliditylang.org/en/develop/metadata.html
+    /// See <https://docs.soliditylang.org/en/develop/metadata.html>
     pub abi: Option<LosslessAbi>,
     #[serde(
         default,
@@ -66,7 +66,7 @@ impl<'a> From<&'a Contract> for CompactContractBytecodeCow<'a> {
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
 pub struct ContractBytecode {
     /// The Ethereum Contract ABI. If empty, it is represented as an empty
-    /// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
+    /// array. See <https://docs.soliditylang.org/en/develop/abi-spec.html>
     pub abi: Option<Abi>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub bytecode: Option<Bytecode>,
@@ -136,7 +136,7 @@ impl From<Contract> for ContractBytecode {
 #[serde(rename_all = "camelCase")]
 pub struct CompactContractBytecode {
     /// The Ethereum Contract ABI. If empty, it is represented as an empty
-    /// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
+    /// array. See <https://docs.soliditylang.org/en/develop/abi-spec.html>
     pub abi: Option<Abi>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub bytecode: Option<CompactBytecode>,
@@ -245,7 +245,7 @@ impl TryFrom<ContractBytecode> for ContractBytecodeSome {
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
 pub struct CompactContractSome {
     /// The Ethereum Contract ABI. If empty, it is represented as an empty
-    /// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
+    /// array. See <https://docs.soliditylang.org/en/develop/abi-spec.html>
     pub abi: Abi,
     pub bin: BytecodeObject,
     #[serde(rename = "bin-runtime")]
@@ -269,7 +269,7 @@ impl TryFrom<CompactContract> for CompactContractSome {
 #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)]
 pub struct CompactContract {
     /// The Ethereum Contract ABI. If empty, it is represented as an empty
-    /// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
+    /// array. See <https://docs.soliditylang.org/en/develop/abi-spec.html>
     pub abi: Option<Abi>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub bin: Option<BytecodeObject>,
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index a263b2aa..5182f524 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -60,7 +60,7 @@ impl CompilerInput {
         Source::read_all_from(path.as_ref()).map(Self::with_sources)
     }
 
-    /// Creates a new [CompilerInput](s) with default settings and the given sources
+    /// Creates a new [CompilerInput]s with default settings and the given sources
     ///
     /// A [CompilerInput] expects a language setting, supported by solc are solidity or yul.
     /// In case the `sources` is a mix of solidity and yul files, 2 CompilerInputs are returned
@@ -806,14 +806,14 @@ impl OutputContracts {
     }
 }
 
-/// A helper type that ensures lossless (de)serialisation unlike [`ethabi::Contract`] which omits
-/// some information of (nested) components in a serde roundtrip. This is a problem for
-/// abienconderv2 structs because `ethabi::Contract`'s representation of those are [`ethabi::Param`]
-/// and the `kind` field of type [`ethabi::ParamType`] does not support deeply nested components as
-/// it's the case for structs. This is not easily fixable in ethabi as it would require a redesign
-/// of the overall `Param` and `ParamType` types. Instead, this type keeps a copy of the
-/// [`serde_json::Value`] when deserialized from the `solc` json compiler output and uses it to
-/// serialize the `abi` without loss.
+/// A helper type that ensures lossless (de)serialisation unlike [`ethers_core::abi::Abi`] which
+/// omits some information of (nested) components in a serde roundtrip. This is a problem for
+/// abienconderv2 structs because [`ethers_core::abi::Contract`]'s representation of those are
+/// [`ethers_core::abi::Param`] and the `kind` field of type [`ethers_core::abi::ParamType`] does
+/// not support deeply nested components as it's the case for structs. This is not easily fixable in
+/// ethabi as it would require a redesign of the overall `Param` and `ParamType` types. Instead,
+/// this type keeps a copy of the [`serde_json::Value`] when deserialized from the `solc` json
+/// compiler output and uses it to serialize the `abi` without loss.
 #[derive(Clone, Debug, PartialEq, Default)]
 pub struct LosslessAbi {
     /// The complete abi as json value
@@ -1243,7 +1243,7 @@ mod tests {
 
     #[test]
     fn can_link_bytecode() {
-        // test cases taken from https://github.com/ethereum/solc-js/blob/master/test/linker.js
+        // test cases taken from <https://github.com/ethereum/solc-js/blob/master/test/linker.js>
 
         #[derive(Serialize, Deserialize)]
         struct Mockject {
diff --git a/ethers-solc/src/cache.rs b/ethers-solc/src/cache.rs
index 40faf46f..a7048329 100644
--- a/ethers-solc/src/cache.rs
+++ b/ethers-solc/src/cache.rs
@@ -364,7 +364,7 @@ pub struct CacheEntry {
     pub last_modification_date: u64,
     /// hash to identify whether the content of the file changed
     pub content_hash: String,
-    /// identifier name see [`crate::util::source_name()`]
+    /// identifier name see [`crate::utils::source_name()`]
     pub source_name: PathBuf,
     /// what config was set when compiling this file
     pub solc_config: SolcConfig,
diff --git a/ethers-solc/src/compile/mod.rs b/ethers-solc/src/compile/mod.rs
index 46a36131..dcdb4a7a 100644
--- a/ethers-solc/src/compile/mod.rs
+++ b/ethers-solc/src/compile/mod.rs
@@ -23,23 +23,23 @@ pub mod project;
 pub const SOLC: &str = "solc";
 
 /// Support for configuring the EVM version
-/// https://blog.soliditylang.org/2018/03/08/solidity-0.4.21-release-announcement/
+/// <https://blog.soliditylang.org/2018/03/08/solidity-0.4.21-release-announcement/>
 pub const CONSTANTINOPLE_SOLC: Version = Version::new(0, 4, 21);
 
 /// Petersburg support
-/// https://blog.soliditylang.org/2019/03/05/solidity-0.5.5-release-announcement/
+/// <https://blog.soliditylang.org/2019/03/05/solidity-0.5.5-release-announcement/>
 pub const PETERSBURG_SOLC: Version = Version::new(0, 5, 5);
 
 /// Istanbul support
-/// https://blog.soliditylang.org/2019/12/09/solidity-0.5.14-release-announcement/
+/// <https://blog.soliditylang.org/2019/12/09/solidity-0.5.14-release-announcement/>
 pub const ISTANBUL_SOLC: Version = Version::new(0, 5, 14);
 
 /// Berlin support
-/// https://blog.soliditylang.org/2021/06/10/solidity-0.8.5-release-announcement/
+/// <https://blog.soliditylang.org/2021/06/10/solidity-0.8.5-release-announcement/>
 pub const BERLIN_SOLC: Version = Version::new(0, 8, 5);
 
 /// London support
-/// https://blog.soliditylang.org/2021/08/11/solidity-0.8.7-release-announcement/
+/// <https://blog.soliditylang.org/2021/08/11/solidity-0.8.7-release-announcement/>
 pub const LONDON_SOLC: Version = Version::new(0, 8, 7);
 
 #[cfg(any(test, feature = "tests"))]
diff --git a/ethers-solc/src/compile/output.rs b/ethers-solc/src/compile/output.rs
index a45e36b3..a05b16f2 100644
--- a/ethers-solc/src/compile/output.rs
+++ b/ethers-solc/src/compile/output.rs
@@ -16,8 +16,6 @@ use std::{collections::BTreeMap, fmt, path::Path};
 #[derive(Debug, Clone, PartialEq, Default)]
 pub struct ProjectCompileOutput<T: ArtifactOutput = ConfigurableArtifacts> {
     /// contains the aggregated `CompilerOutput`
-    ///
-    /// See [`CompilerSources::compile`]
     pub(crate) compiler_output: AggregatedCompilerOutput,
     /// all artifact files from `output` that were freshly compiled and written
     pub(crate) compiled_artifacts: Artifacts<T::Artifact>,
@@ -134,18 +132,21 @@ impl<T: ArtifactOutput> ProjectCompileOutput<T> {
         self.cached_artifacts.remove(contract_name)
     }
 
-    /// Returns the set of `Artifacts` that were cached and got reused during [`Project::compile()`]
+    /// Returns the set of `Artifacts` that were cached and got reused during
+    /// [`crate::Project::compile()`]
     pub fn cached_artifacts(&self) -> &Artifacts<T::Artifact> {
         &self.cached_artifacts
     }
 
-    /// Returns the set of `Artifacts` that were compiled with `solc` in [`Project::compile()`]
+    /// Returns the set of `Artifacts` that were compiled with `solc` in
+    /// [`crate::Project::compile()`]
     pub fn compiled_artifacts(&self) -> &Artifacts<T::Artifact> {
         &self.compiled_artifacts
     }
 
-    /// Returns a `BTreeMap` that maps the compiler version used during [`Project::compile()`]
-    /// to a Vector of tuples containing the contract name and the `Contract`
+    /// Returns a `BTreeMap` that maps the compiler version used during
+    /// [`crate::Project::compile()`] to a Vector of tuples containing the contract name and the
+    /// `Contract`
     pub fn compiled_contracts_by_compiler_version(
         &self,
     ) -> BTreeMap<Version, Vec<(String, Contract)>> {
@@ -177,7 +178,7 @@ where
 
 impl ProjectCompileOutput<ConfigurableArtifacts> {
     /// A helper functions that extracts the underlying [`CompactContractBytecode`] from the
-    /// [`ConfigurableContractArtifact`]
+    /// [`crate::ConfigurableContractArtifact`]
     ///
     /// # Example
     ///
diff --git a/ethers-solc/src/compile/project.rs b/ethers-solc/src/compile/project.rs
index e3df3a3d..388466b4 100644
--- a/ethers-solc/src/compile/project.rs
+++ b/ethers-solc/src/compile/project.rs
@@ -5,13 +5,13 @@
 //! First the project's dependency graph [`crate::Graph`] is constructed and all imported
 //! dependencies are resolved. The graph holds all the relationships between the files and their
 //! versions. From there the appropriate version set is derived
-//! [`crate::Graph::into_sources_by_version()`] which need to be compiled with different
+//! [`crate::Graph`] which need to be compiled with different
 //! [`crate::Solc`] versions.
 //!
 //! At this point we check if we need to compile a source file or whether we can reuse an _existing_
 //! `Artifact`. We don't to compile if:
 //!     - caching is enabled
-//!     - the file is **not** dirty [`Cache::is_dirty()`]
+//!     - the file is **not** dirty
 //!     - the artifact for that file exists
 //!
 //! This concludes the preprocessing, and we now have either
@@ -58,7 +58,7 @@
 //! import "/project/lib/util.sol";         // source unit name: /project/lib/util.sol
 //! import "lib/util.sol";                  // source unit name: lib/util.sol
 //! import "@openzeppelin/address.sol";     // source unit name: @openzeppelin/address.sol
-//! import "https://example.com/token.sol"; // source unit name: https://example.com/token.sol
+//! import "https://example.com/token.sol"; // source unit name: <https://example.com/token.sol>
 //! ```
 //!
 //! After applying any import remappings the import path simply becomes the source unit name.
@@ -78,11 +78,11 @@
 //! ### Caching and Change detection
 //!
 //! If caching is enabled in the [Project](crate::Project) a cache file will be created upon a
-//! successful solc build. The [cache file](crate::SolFilesCache) stores metadata for all the files
-//! that were provided to solc.
+//! successful solc build. The [cache file](crate::cache::SolFilesCache) stores metadata for all the
+//! files that were provided to solc.
 //! For every file the cache file contains a dedicated [cache
-//! entry](crate::CacheEntry), which represents the state of the file. A solidity file can contain
-//! several contracts, for every contract a separate [artifact](crate::Artifact) is emitted.
+//! entry](crate::cache::CacheEntry), which represents the state of the file. A solidity file can
+//! contain several contracts, for every contract a separate [artifact](crate::Artifact) is emitted.
 //! Therefor the entry also tracks all artifacts emitted by a file. A solidity file can also be
 //! compiled with several solc versions.
 //!
@@ -124,7 +124,7 @@ pub struct ProjectCompiler<'a, T: ArtifactOutput> {
     project: &'a Project<T>,
     /// how to compile all the sources
     sources: CompilerSources,
-    /// How to select solc [CompilerOutput] for files
+    /// How to select solc [`crate::artifacts::CompilerOutput`] for files
     sparse_output: SparseOutputFileFilter,
 }
 
@@ -182,7 +182,7 @@ impl<'a, T: ArtifactOutput> ProjectCompiler<'a, T> {
         Ok(Self { edges, project, sources, sparse_output: Default::default() })
     }
 
-    /// Applies the specified [SparseOutputFileFilter] to be applied when selecting solc output for
+    /// Applies the specified filter to be applied when selecting solc output for
     /// specific files to be compiled
     pub fn with_sparse_output(mut self, sparse_output: impl Into<SparseOutputFileFilter>) -> Self {
         self.sparse_output = sparse_output.into();
diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs
index a2689d0e..c7865cd8 100644
--- a/ethers-solc/src/config.rs
+++ b/ethers-solc/src/config.rs
@@ -167,12 +167,13 @@ impl ProjectPathsConfig {
     ///
     /// `import "@openzeppelin/token/ERC20/IERC20.sol";`
     ///
-    /// There is no strict rule behind this, but because [`Remappings::find_many`] returns
-    /// `'@openzeppelin/=node_modules/@openzeppelin/contracts/'` we should handle the case if the
-    /// remapping path ends with `contracts` and the import path starts with `<remapping
-    /// name>/contracts`. Otherwise we can end up with a resolved path that has a duplicate
-    /// `contracts` segment: `@openzeppelin/contracts/contracts/token/ERC20/IERC20.sol` we check
-    /// for this edge case here so that both styles work out of the box.
+    /// There is no strict rule behind this, but because [`crate::remappings::Remapping::find_many`]
+    /// returns `'@openzeppelin/=node_modules/@openzeppelin/contracts/'` we should handle the
+    /// case if the remapping path ends with `contracts` and the import path starts with
+    /// `<remapping name>/contracts`. Otherwise we can end up with a resolved path that has a
+    /// duplicate `contracts` segment:
+    /// `@openzeppelin/contracts/contracts/token/ERC20/IERC20.sol` we check for this edge case
+    /// here so that both styles work out of the box.
     pub fn resolve_library_import(&self, import: &Path) -> Option<PathBuf> {
         // if the import path starts with the name of the remapping then we get the resolved path by
         // removing the name and adding the remainder to the path of the remapping
diff --git a/ethers-solc/src/filter.rs b/ethers-solc/src/filter.rs
index 3e68d038..ad6ee238 100644
--- a/ethers-solc/src/filter.rs
+++ b/ethers-solc/src/filter.rs
@@ -26,7 +26,7 @@ where
     }
 }
 
-/// An [InputFileFilter] that matches all solidity files that end with `.t.sol`
+/// An [FileFilter] that matches all solidity files that end with `.t.sol`
 #[derive(Default)]
 pub struct TestFileFilter {
     _priv: (),
diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs
index d4da28e5..53e30771 100644
--- a/ethers-solc/src/lib.rs
+++ b/ethers-solc/src/lib.rs
@@ -1,3 +1,4 @@
+#![deny(rustdoc::broken_intra_doc_links)]
 pub mod artifacts;
 pub mod sourcemap;
 
@@ -192,7 +193,7 @@ impl<T: ArtifactOutput> Project<T> {
     /// `CompilerOutput::has_error` instead.
     ///
     /// NB: If the `svm` feature is enabled, this function will automatically detect
-    /// solc versions across files, see [`Self::svm_compile()`]
+    /// solc versions across files.
     ///
     /// # Example
     ///
diff --git a/ethers-solc/src/remappings.rs b/ethers-solc/src/remappings.rs
index 9ff4c73e..e82006ae 100644
--- a/ethers-solc/src/remappings.rs
+++ b/ethers-solc/src/remappings.rs
@@ -679,7 +679,7 @@ mod tests {
         assert_eq!(err, RemappingError::NoTarget);
     }
 
-    // https://doc.rust-lang.org/rust-by-example/std_misc/fs.html
+    // <https://doc.rust-lang.org/rust-by-example/std_misc/fs.html>
     fn touch(path: &std::path::Path) -> std::io::Result<()> {
         match std::fs::OpenOptions::new().create(true).write(true).open(path) {
             Ok(_) => Ok(()),
diff --git a/ethers-solc/src/report/compiler.rs b/ethers-solc/src/report/compiler.rs
index 739c98cf..b77f1b65 100644
--- a/ethers-solc/src/report/compiler.rs
+++ b/ethers-solc/src/report/compiler.rs
@@ -8,8 +8,8 @@
 use crate::{CompilerInput, CompilerOutput};
 use std::{env, path::PathBuf, str::FromStr};
 
-/// Debug Helper type that can be used to write the [Solc] [CompilerInput] and [CompilerOutput] to
-/// disk if configured.
+/// Debug Helper type that can be used to write the [crate::Solc] [CompilerInput] and
+/// [CompilerOutput] to disk if configured.
 ///
 /// # Example
 ///
diff --git a/ethers-solc/src/report/mod.rs b/ethers-solc/src/report/mod.rs
index 518ce6b6..d4c224a2 100644
--- a/ethers-solc/src/report/mod.rs
+++ b/ethers-solc/src/report/mod.rs
@@ -1,18 +1,18 @@
 //! Subscribe to events in the compiler pipeline
 //!
-//! The _reporter_ is the component of the [`Project::compile()`] pipeline which is responsible
-//! for reporting on specific steps in the process.
+//! The _reporter_ is the component of the [`crate::Project::compile()`] pipeline which is
+//! responsible for reporting on specific steps in the process.
 //!
 //! By default, the current reporter is a noop that does
 //! nothing.
 //!
 //! To use another report implementation, it must be set as the current reporter.
 //! There are two methods for doing so: [`with_scoped`] and
-//! [`set_global`]. `with_scoped` sets the reporter for the
+//! [`try_init`]. `with_scoped` sets the reporter for the
 //! duration of a scope, while `set_global` sets a global default report
 //! for the entire process.
 
-// https://github.com/tokio-rs/tracing/blob/master/tracing-core/src/dispatch.rs
+// <https://github.com/tokio-rs/tracing/blob/master/tracing-core/src/dispatch.rs>
 
 use crate::{remappings::Remapping, CompilerInput, CompilerOutput, Solc};
 use semver::Version;
@@ -103,8 +103,8 @@ pub trait Reporter: 'static {
     /// contains the files that absolutely must be recompiled, while the [CompilerInput] contains
     /// all files, the dirty files and all their dependencies.
     ///
-    /// If this is a fresh compile then the [Sources] set of the [CompilerInput] matches the dirty
-    /// files set.
+    /// If this is a fresh compile then the [crate::artifacts::Sources] set of the [CompilerInput]
+    /// matches the dirty files set.
     fn on_solc_spawn(
         &self,
         _solc: &Solc,
@@ -114,7 +114,7 @@ pub trait Reporter: 'static {
     ) {
     }
 
-    /// Invoked with the `CompilerOutput` if [`Solc::compiled()`] was successful
+    /// Invoked with the `CompilerOutput` if [`Solc::compile()`] was successful
     fn on_solc_success(&self, _solc: &Solc, _version: &Version, _output: &CompilerOutput) {}
 
     /// Invoked before a new [`Solc`] bin is installed
@@ -133,17 +133,17 @@ pub trait Reporter: 'static {
     /// [`NonNull`] pointer to that type. Otherwise, returns `None`.
     ///
     /// If you wish to downcast a `Reporter`, it is strongly advised to use
-    /// the safe API provided by [`downcast_ref`] instead.
+    /// the safe API provided by downcast_ref instead.
     ///
     /// This API is required for `downcast_raw` to be a trait method; a method
-    /// signature like [`downcast_ref`] (with a generic type parameter) is not
+    /// signature like downcast_ref (with a generic type parameter) is not
     /// object-safe, and thus cannot be a trait method for `Reporter`. This
-    /// means that if we only exposed `downcast_ref`, `Reporter`
+    /// means that if we only exposed downcast_ref, `Reporter`
     /// implementations could not override the downcasting behavior
     ///
     /// # Safety
     ///
-    /// The [`downcast_ref`] method expects that the pointer returned by
+    /// The downcast_ref method expects that the pointer returned by
     /// `downcast_raw` points to a valid instance of the type
     /// with the provided `TypeId`. Failure to ensure this will result in
     /// undefined behaviour, so implementing `downcast_raw` is unsafe.
@@ -217,7 +217,7 @@ fn get_global() -> Option<&'static Report> {
     }
 }
 
-/// Executes a closure with a reference to this thread's current [reporter].
+/// Executes a closure with a reference to this thread's current reporter.
 #[inline(always)]
 pub fn get_default<T, F>(mut f: F) -> T
 where
diff --git a/ethers-solc/src/resolver/mod.rs b/ethers-solc/src/resolver/mod.rs
index 43dc9e75..cceb013b 100644
--- a/ethers-solc/src/resolver/mod.rs
+++ b/ethers-solc/src/resolver/mod.rs
@@ -156,7 +156,7 @@ impl GraphEdges {
 
 /// Represents a fully-resolved solidity dependency graph. Each node in the graph
 /// is a file and edges represent dependencies between them.
-/// See also https://docs.soliditylang.org/en/latest/layout-of-source-files.html?highlight=import#importing-other-source-files
+/// See also <https://docs.soliditylang.org/en/latest/layout-of-source-files.html?highlight=import#importing-other-source-files>
 #[derive(Debug)]
 pub struct Graph {
     nodes: Vec<Node>,
diff --git a/ethers-solc/src/sourcemap.rs b/ethers-solc/src/sourcemap.rs
index 24c34c0e..6b47c65c 100644
--- a/ethers-solc/src/sourcemap.rs
+++ b/ethers-solc/src/sourcemap.rs
@@ -142,7 +142,7 @@ impl<'a> fmt::Display for Jump {
 
 /// Represents a whole source map as list of `SourceElement`s
 ///
-/// See also https://docs.soliditylang.org/en/latest/internals/source_mappings.html#source-mappings
+/// See also <https://docs.soliditylang.org/en/latest/internals/source_mappings.html#source-mappings>
 pub type SourceMap = Vec<SourceElement>;
 
 /// Represents a single element in the source map
diff --git a/ethers-solc/src/utils.rs b/ethers-solc/src/utils.rs
index b269d52f..cbc42d51 100644
--- a/ethers-solc/src/utils.rs
+++ b/ethers-solc/src/utils.rs
@@ -15,7 +15,7 @@ use walkdir::WalkDir;
 
 /// A regex that matches the import path and identifier of a solidity import
 /// statement with the named groups "path", "id".
-// Adapted from https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L100
+// Adapted from <https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L100>
 pub static RE_SOL_IMPORT: Lazy<Regex> = Lazy::new(|| {
     Regex::new(r#"import\s+(?:(?:"(?P<p1>[^;]*)"|'(?P<p2>[^;]*)')(?:;|\s+as\s+(?P<id>[^;]*);)|.+from\s+(?:"(?P<p3>.*)"|'(?P<p4>.*)');)"#).unwrap()
 });
@@ -23,7 +23,7 @@ pub static RE_SOL_IMPORT: Lazy<Regex> = Lazy::new(|| {
 /// A regex that matches the version part of a solidity pragma
 /// as follows: `pragma solidity ^0.5.2;` => `^0.5.2`
 /// statement with the named group "version".
-// Adapted from https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L119
+// Adapted from <https://github.com/nomiclabs/hardhat/blob/cced766c65b25d3d0beb39ef847246ac9618bdd9/packages/hardhat-core/src/internal/solidity/parse.ts#L119>
 pub static RE_SOL_PRAGMA_VERSION: Lazy<Regex> =
     Lazy::new(|| Regex::new(r"pragma\s+solidity\s+(?P<version>.+?);").unwrap());
 
@@ -35,7 +35,7 @@ pub static RE_SOL_SDPX_LICENSE_IDENTIFIER: Lazy<Regex> =
 /// Returns all path parts from any solidity import statement in a string,
 /// `import "./contracts/Contract.sol";` -> `"./contracts/Contract.sol"`.
 ///
-/// See also https://docs.soliditylang.org/en/v0.8.9/grammar.html
+/// See also <https://docs.soliditylang.org/en/v0.8.9/grammar.html>
 pub fn find_import_paths(contract: &str) -> impl Iterator<Item = Match> {
     RE_SOL_IMPORT.captures_iter(contract).filter_map(|cap| {
         cap.name("p1")
@@ -216,7 +216,7 @@ pub fn library_hash_placeholder(name: impl AsRef<[u8]>) -> String {
 /// The placeholder is a 34 character prefix of the hex encoding of the keccak256 hash of the fully
 /// qualified library name.
 ///
-/// See also https://docs.soliditylang.org/en/develop/using-the-compiler.html#library-linking
+/// See also <https://docs.soliditylang.org/en/develop/using-the-compiler.html#library-linking>
 pub fn library_hash(name: impl AsRef<[u8]>) -> [u8; 17] {
     let mut output = [0u8; 17];
     let mut hasher = Keccak::v256();
diff --git a/examples/permit_hash.rs b/examples/permit_hash.rs
index 3c0e346a..b313b3a5 100644
--- a/examples/permit_hash.rs
+++ b/examples/permit_hash.rs
@@ -5,8 +5,8 @@ use ethers::{
 };
 
 // Generate the EIP712 permit hash to sign for a Uniswap V2 pair.
-// https://eips.ethereum.org/EIPS/eip-712
-// https://eips.ethereum.org/EIPS/eip-2612
+// <https://eips.ethereum.org/EIPS/eip-712>
+// <https://eips.ethereum.org/EIPS/eip-2612>
 #[derive(Eip712, EthAbiType, Clone)]
 #[eip712(
     name = "Uniswap V2",


Test patch:
diff --git a/ethers-middleware/tests/nonce_manager.rs b/ethers-middleware/tests/nonce_manager.rs
index 63331037..31e36909 100644
--- a/ethers-middleware/tests/nonce_manager.rs
+++ b/ethers-middleware/tests/nonce_manager.rs
@@ -30,7 +30,7 @@ async fn nonce_manager() {
         .unwrap()
         .as_u64();
 
-    let num_tx = 5;
+    let num_tx = 3;
     let mut tx_hashes = Vec::with_capacity(num_tx);
     for _ in 0..num_tx {
         let tx = provider
@@ -44,7 +44,7 @@ async fn nonce_manager() {
     }
 
     // sleep a bit to ensure there's no flakiness in the test
-    std::thread::sleep(std::time::Duration::new(3, 0));
+    std::thread::sleep(std::time::Duration::new(5, 0));
 
     let mut nonces = Vec::with_capacity(num_tx);
     for tx_hash in tx_hashes {