Repository: gakonst/ethers-rs
Pull request #1351 (2022-06-06 19:20:08)

chore(deps): bump crossbeam-utils from 0.8.6 to 0.8.8
Bumps [crossbeam-utils](https://github.com/crossbeam-rs/crossbeam) from 0.8.6 to 0.8.8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a href="https://github.com/crossbeam-rs/crossbeam/releases">crossbeam-utils's releases</a>.</em></p>
<blockquote>
<h2>crossbeam-utils 0.8.8</h2>
<ul>
<li>Fix a bug when unstable <code>loom</code> support is enabled. (<a href="https://github-redirect.dependabot.com/crossbeam-rs/crossbeam/issues/787">#787</a>)</li>
</ul>
<h2>crossbeam-utils 0.8.7</h2>
<ul>
<li>Add <code>AtomicCell&lt;{i*,u*}&gt;::{fetch_max,fetch_min}</code>. (<a href="https://github-redirect.dependabot.com/crossbeam-rs/crossbeam/issues/785">#785</a>)</li>
<li>Add <code>AtomicCell&lt;{i*,u*,bool}&gt;::fetch_nand</code>. (<a href="https://github-redirect.dependabot.com/crossbeam-rs/crossbeam/issues/785">#785</a>)</li>
<li>Fix unsoundness of <code>AtomicCell&lt;{i,u}64&gt;</code> arithmetics on 32-bit targets that support <code>Atomic{I,U}64</code> (<a href="https://github-redirect.dependabot.com/crossbeam-rs/crossbeam/issues/781">#781</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="https://github.com/crossbeam-rs/crossbeam/commit/5b4d80835a68c47ab4b8842b3e46618c9ed7c3cb"><code>5b4d808</code></a> Merge <a href="https://github-redirect.dependabot.com/crossbeam-rs/crossbeam/issues/800">#800</a></li>
<li><a href="https://github.com/crossbeam-rs/crossbeam/commit/6fa72466ce222ed6b4c47c1d7ce4d9cf51cdf5e1"><code>6fa7246</code></a> Prepare for the next release</li>
<li><a href="https://github.com/crossbeam-rs/crossbeam/commit/8efcd0261c326c42a06579fa9be53d3ea32185d4"><code>8efcd02</code></a> Merge <a href="https://github-redirect.dependabot.com/crossbeam-rs/crossbeam/issues/798">#798</a></li>
<li><a href="https://github.com/crossbeam-rs/crossbeam/commit/baedeb003eae35c5cfa9b702948b5ab29fe5b33a"><code>baedeb0</code></a> channel: Do not panic on very large timeout</li>
<li><a href="https://github.com/crossbeam-rs/crossbeam/commit/113daf45a4a5586af87aa5db0962edda51905f9e"><code>113daf4</code></a> Merge <a href="https://github-redirect.dependabot.com/crossbeam-rs/crossbeam/issues/799">#799</a></li>
<li><a href="https://github.com/crossbeam-rs/crossbeam/commit/5cb7cac9f230ca2449c9f6553d43af72b3cf87ec"><code>5cb7cac</code></a> Update no_atomic.rs</li>
<li><a href="https://github.com/crossbeam-rs/crossbeam/commit/0081fcc510e602948faffbf5efa67925836f8b52"><code>0081fcc</code></a> Merge <a href="https://github-redirect.dependabot.com/crossbeam-rs/crossbeam/issues/789">#789</a> <a href="https://github-redirect.dependabot.com/crossbeam-rs/crossbeam/issues/797">#797</a></li>
<li><a href="https://github.com/crossbeam-rs/crossbeam/commit/7b2d65fa21c9742c83f4a7e606418872405ad7d4"><code>7b2d65f</code></a> Update to stabilized const_fn_trait_bound</li>
<li><a href="https://github.com/crossbeam-rs/crossbeam/commit/bd75c3c45edb78a731956c01458b75e5b69a8146"><code>bd75c3c</code></a> Add force_push to ArrayQueue</li>
<li><a href="https://github.com/crossbeam-rs/crossbeam/commit/b11f1a83e6362589979a4c58c79895cc936b09fb"><code>b11f1a8</code></a> Merge <a href="https://github-redirect.dependabot.com/crossbeam-rs/crossbeam/issues/793">#793</a></li>
<li>Additional commits viewable in <a href="https://github.com/crossbeam-rs/crossbeam/compare/crossbeam-utils-0.8.6...crossbeam-utils-0.8.8">compare view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crossbeam-utils&package-manager=cargo&previous-version=0.8.6&new-version=0.8.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/gakonst/ethers-rs/network/alerts).

</details>


Patch:
diff --git a/Cargo.lock b/Cargo.lock
index b4f964d3..d3ecebb9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -774,9 +774,9 @@ dependencies = [
 
 [[package]]
 name = "crossbeam-utils"
-version = "0.8.6"
+version = "0.8.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cfcae03edb34f947e64acdb1c33ec169824e20657e9ecb61cef6c8c74dcb8120"
+checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
 dependencies = [
  "cfg-if 1.0.0",
  "lazy_static",
diff --git a/ethers-core/src/types/chain.rs b/ethers-core/src/types/chain.rs
index acb8720e..41a1b777 100644
--- a/ethers-core/src/types/chain.rs
+++ b/ethers-core/src/types/chain.rs
@@ -1,11 +1,12 @@
-use serde::Deserialize;
-use thiserror::Error;
-
-use core::convert::TryFrom;
-use std::{convert::TryInto, default, fmt, str::FromStr};
-
 use crate::types::U256;
+use serde::Deserialize;
+use std::{
+    convert::{TryFrom, TryInto},
+    fmt,
+    str::FromStr,
+};
 use strum::EnumVariantNames;
+use thiserror::Error;
 
 #[derive(Debug, Clone, Error)]
 #[error("Failed to parse chain: {0}")]
@@ -26,6 +27,7 @@ pub enum Chain {
     Polygon = 137,
     Fantom = 250,
     Dev = 1337,
+    AnvilHardhat = 31337,
     FantomTestnet = 4002,
     PolygonMumbai = 80001,
     Avalanche = 43114,
@@ -86,6 +88,7 @@ impl fmt::Display for Chain {
             Chain::Oasis => "oasis",
             Chain::Emerald => "emerald",
             Chain::EmeraldTestnet => "emerald-testnet",
+            Chain::AnvilHardhat => "anvil-hardhat",
         };
 
         write!(formatter, "{}", chain)
@@ -123,6 +126,7 @@ impl TryFrom<u64> for Chain {
             100 => Chain::XDai,
             137 => Chain::Polygon,
             1337 => Chain::Dev,
+            31337 => Chain::Dev,
             250 => Chain::Fantom,
             4002 => Chain::FantomTestnet,
             80001 => Chain::PolygonMumbai,
@@ -185,6 +189,7 @@ impl FromStr for Chain {
             "fantom" => Chain::Fantom,
             "fantom-testnet" => Chain::FantomTestnet,
             "dev" => Chain::Dev,
+            "anvil" | "hardhat" | "anvil-hardhat" => Chain::AnvilHardhat,
             "bsc" => Chain::BinanceSmartChain,
             "bsc-testnet" => Chain::BinanceSmartChainTestnet,
             "arbitrum" => Chain::Arbitrum,
@@ -225,7 +230,7 @@ impl Chain {
     }
 }
 
-impl default::Default for Chain {
+impl Default for Chain {
     fn default() -> Self {
         Chain::Mainnet
     }
diff --git a/ethers-core/src/types/trace/filter.rs b/ethers-core/src/types/trace/filter.rs
index f1a2d22c..9d0a6aaa 100644
--- a/ethers-core/src/types/trace/filter.rs
+++ b/ethers-core/src/types/trace/filter.rs
@@ -181,7 +181,7 @@ pub struct Call {
     pub from: Address,
     /// Recipient
     pub to: Address,
-    /// Transfered Value
+    /// Transferred Value
     pub value: U256,
     /// Gas
     pub gas: U256,
diff --git a/ethers-core/src/types/transaction/eip1559.rs b/ethers-core/src/types/transaction/eip1559.rs
index 396e88da..00fcabf3 100644
--- a/ethers-core/src/types/transaction/eip1559.rs
+++ b/ethers-core/src/types/transaction/eip1559.rs
@@ -36,7 +36,7 @@ pub struct Eip1559TransactionRequest {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub gas: Option<U256>,
 
-    /// Transfered value (None for no transfer)
+    /// Transferred value (None for no transfer)
     #[serde(skip_serializing_if = "Option::is_none")]
     pub value: Option<U256>,
 
diff --git a/ethers-core/src/types/transaction/request.rs b/ethers-core/src/types/transaction/request.rs
index 03712b84..25270d92 100644
--- a/ethers-core/src/types/transaction/request.rs
+++ b/ethers-core/src/types/transaction/request.rs
@@ -42,7 +42,7 @@ pub struct TransactionRequest {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub gas_price: Option<U256>,
 
-    /// Transfered value (None for no transfer)
+    /// Transferred value (None for no transfer)
     #[serde(skip_serializing_if = "Option::is_none")]
     pub value: Option<U256>,
 
diff --git a/ethers-core/src/types/transaction/response.rs b/ethers-core/src/types/transaction/response.rs
index 056dbdfd..16bd1502 100644
--- a/ethers-core/src/types/transaction/response.rs
+++ b/ethers-core/src/types/transaction/response.rs
@@ -42,7 +42,7 @@ pub struct Transaction {
     #[serde(skip_serializing_if = "Option::is_none")]
     pub to: Option<Address>,
 
-    /// Transfered value
+    /// Transferred value
     pub value: U256,
 
     /// Gas Price, null for Type 2 transactions
diff --git a/ethers-core/src/types/txpool.rs b/ethers-core/src/types/txpool.rs
index 93d7b75e..0b023cf4 100644
--- a/ethers-core/src/types/txpool.rs
+++ b/ethers-core/src/types/txpool.rs
@@ -11,7 +11,7 @@ use std::{collections::BTreeMap, fmt, str::FromStr};
 pub struct TxpoolInspectSummary {
     /// Recipient (None when contract creation)
     pub to: Option<Address>,
-    /// Transfered value
+    /// Transferred value
     pub value: U256,
     /// Gas amount
     pub gas: U256,
diff --git a/ethers-etherscan/Cargo.toml b/ethers-etherscan/Cargo.toml
index 23c40687..8609712b 100644
--- a/ethers-etherscan/Cargo.toml
+++ b/ethers-etherscan/Cargo.toml
@@ -15,7 +15,6 @@ keywords = ["ethereum", "web3", "etherscan", "ethers"]
 
 [dependencies]
 ethers-core = { version = "^0.6.0", path = "../ethers-core", default-features = false }
-ethers-solc = { version = "^0.3.0", path = "../ethers-solc", default-features = false }
 reqwest = { version = "0.11.10", default-features = false, features = ["json"] }
 serde = { version = "1.0.124", default-features = false, features = ["derive"] }
 serde_json = { version = "1.0.64", default-features = false }
@@ -29,6 +28,7 @@ tempfile = "3.3.0"
 tokio = { version = "1.18", features = ["macros", "rt-multi-thread", "time"] }
 serial_test = "0.6.0"
 tracing-subscriber = { version = "0.3", default-features = false, features = ["env-filter", "fmt"] }
+ethers-solc = { version = "^0.3.0", path = "../ethers-solc", default-features = false }
 
 [package.metadata.docs.rs]
 all-features = true
diff --git a/ethers-etherscan/src/lib.rs b/ethers-etherscan/src/lib.rs
index 4f1af333..cc800719 100644
--- a/ethers-etherscan/src/lib.rs
+++ b/ethers-etherscan/src/lib.rs
@@ -1,23 +1,20 @@
 //! Bindings for [etherscan.io web api](https://docs.etherscan.io/)
 
+use contract::ContractMetadata;
+use errors::EtherscanError;
+use ethers_core::{
+    abi::{Abi, Address},
+    types::{Chain, H256},
+};
+use reqwest::{header, IntoUrl, Url};
+use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use std::{
     borrow::Cow,
     io::Write,
     path::PathBuf,
     time::{Duration, SystemTime, UNIX_EPOCH},
 };
-
-use contract::ContractMetadata;
-use reqwest::{header, IntoUrl, Url};
-use serde::{de::DeserializeOwned, Deserialize, Serialize};
 use tracing::trace;
-
-use errors::EtherscanError;
-use ethers_core::{
-    abi::{Abi, Address},
-    types::{Chain, H256},
-};
-
 pub mod account;
 pub mod contract;
 pub mod errors;
@@ -108,7 +105,9 @@ impl Client {
             Chain::Moonbeam | Chain::MoonbeamDev | Chain::Moonriver => {
                 std::env::var("MOONSCAN_API_KEY")?
             }
-            Chain::Dev => return Err(EtherscanError::LocalNetworksNotSupported),
+            Chain::AnvilHardhat | Chain::Dev => {
+                return Err(EtherscanError::LocalNetworksNotSupported)
+            }
         };
         Self::new(chain, api_key)
     }
@@ -297,7 +296,9 @@ impl ClientBuilder {
                 "https://testnet.explorer.emerald.oasis.dev/api",
                 "https://testnet.explorer.emerald.oasis.dev/",
             ),
-            Chain::Dev => return Err(EtherscanError::LocalNetworksNotSupported),
+            Chain::AnvilHardhat | Chain::Dev => {
+                return Err(EtherscanError::LocalNetworksNotSupported)
+            }
             chain => return Err(EtherscanError::ChainNotSupported(chain)),
         };
         self.with_api_url(etherscan_api_url?)?.with_url(etherscan_url?)
diff --git a/ethers-providers/src/call_raw.rs b/ethers-providers/src/call_raw.rs
index e0b95bad..24e19b76 100644
--- a/ethers-providers/src/call_raw.rs
+++ b/ethers-providers/src/call_raw.rs
@@ -495,10 +495,10 @@ pub mod spoof {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{Http, Middleware, Provider};
+    use crate::{Http, Provider};
     use ethers_core::{
         types::TransactionRequest,
-        utils::{get_contract_address, keccak256, parse_ether, Anvil, Geth},
+        utils::{get_contract_address, keccak256, parse_ether, Geth},
     };
     use std::convert::TryFrom;
 
@@ -516,7 +516,7 @@ mod tests {
     }
 
     // Tests "roundtrip" serialization of calls: deserialize(serialize(call)) == call
-    fn test_encode<'a, P>(call: CallBuilder<'a, P>) {
+    fn test_encode<P>(call: CallBuilder<P>) {
         let input = call.unwrap().input;
         let ser = utils::serialize(&input).to_string();
         let de: CallInputOwned = serde_json::from_str(&ser).unwrap();
diff --git a/ethers-providers/src/pending_escalator.rs b/ethers-providers/src/pending_escalator.rs
index b813f986..3dca65fb 100644
--- a/ethers-providers/src/pending_escalator.rs
+++ b/ethers-providers/src/pending_escalator.rs
@@ -190,7 +190,7 @@ where
                 poll_broadcast_fut!(cx, this, fut);
             }
             Sleeping(delay) => {
-                let _ready = futures_util::ready!(delay.as_mut().poll(cx));
+                futures_util::ready!(delay.as_mut().poll(cx));
                 // if broadcast timer has elapsed and if we have a TX to
                 // broadcast, broadcast it
                 if this.last.elapsed() > *this.broadcast_interval {
diff --git a/ethers-providers/src/pending_transaction.rs b/ethers-providers/src/pending_transaction.rs
index 65549f20..40fbdc0e 100644
--- a/ethers-providers/src/pending_transaction.rs
+++ b/ethers-providers/src/pending_transaction.rs
@@ -172,7 +172,7 @@ impl<'a, P: JsonRpcClient> Future for PendingTransaction<'a, P> {
 
         match this.state {
             PendingTxState::InitialDelay(fut) => {
-                let _ready = futures_util::ready!(fut.as_mut().poll(ctx));
+                futures_util::ready!(fut.as_mut().poll(ctx));
                 tracing::debug!("Starting to poll pending tx {:?}", *this.tx_hash);
                 let fut = Box::pin(this.provider.get_transaction(*this.tx_hash));
                 rewake_with_new_state!(ctx, this, PendingTxState::GettingTx(fut));
diff --git a/ethers-providers/src/provider.rs b/ethers-providers/src/provider.rs
index 08ed88cb..62c9d7c9 100644
--- a/ethers-providers/src/provider.rs
+++ b/ethers-providers/src/provider.rs
@@ -1645,7 +1645,7 @@ mod tests {
             // HTTPS
             ("alisha.eth", "https://ipfs.io/ipfs/QmeQm91kAdPGnUKsE74WvkqYKUeHvc2oHd2FW11V3TrqkQ"),
             // ERC-1155
-            ("nick.eth", "https://lh3.googleusercontent.com/hKHZTZSTmcznonu8I6xcVZio1IF76fq0XmcxnvUykC-FGuVJ75UPdLDlKJsfgVXH9wOSmkyHw0C39VAYtsGyxT7WNybjQ6s3fM3macE"),
+            ("nick.eth", "https://img.seadn.io/files/3ae7be6c41ad4767bf3ecbc0493b4bfb.png"),
             // HTTPS
             ("parishilton.eth", "https://i.imgur.com/YW3Hzph.jpg"),
             // ERC-721 with IPFS link
diff --git a/ethers-providers/src/stream.rs b/ethers-providers/src/stream.rs
index 44bb39bf..0a0eb812 100644
--- a/ethers-providers/src/stream.rs
+++ b/ethers-providers/src/stream.rs
@@ -284,9 +284,13 @@ mod tests {
         let tx = TransactionRequest::new().from(accounts[0]).to(accounts[0]).value(1e18 as u64);
 
         let mut sending = futures_util::future::join_all(
-            std::iter::repeat(tx.clone()).take(num_txs).map(|tx| async {
-                provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap()
-            }),
+            std::iter::repeat(tx.clone())
+                .take(num_txs)
+                .enumerate()
+                .map(|(nonce, tx)| tx.nonce(nonce))
+                .map(|tx| async {
+                    provider.send_transaction(tx, None).await.unwrap().await.unwrap().unwrap()
+                }),
         )
         .fuse();
 
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index 24e0247e..acc9229e 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -1366,6 +1366,10 @@ pub struct UserDoc {
     pub kind: Option<String>,
     #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
     pub methods: BTreeMap<String, UserDocNotice>,
+    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
+    pub events: BTreeMap<String, UserDocNotice>,
+    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
+    pub errors: BTreeMap<String, Vec<UserDocNotice>>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub notice: Option<String>,
 }
@@ -1375,7 +1379,7 @@ pub struct UserDoc {
 pub enum UserDocNotice {
     // NOTE: this a variant used for constructors on older solc versions
     Constructor(String),
-    Method { notice: String },
+    Notice { notice: String },
 }
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
@@ -1392,6 +1396,10 @@ pub struct DevDoc {
     pub custom_experimental: Option<String>,
     #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
     pub methods: BTreeMap<String, MethodDoc>,
+    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
+    pub events: BTreeMap<String, EventDoc>,
+    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
+    pub errors: BTreeMap<String, Vec<ErrorDoc>>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub title: Option<String>,
 }
@@ -1402,8 +1410,24 @@ pub struct MethodDoc {
     pub details: Option<String>,
     #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
     pub params: BTreeMap<String, String>,
+    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
+    pub returns: BTreeMap<String, String>,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
+pub struct EventDoc {
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub details: Option<String>,
+    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
+    pub params: BTreeMap<String, String>,
+}
+
+#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
+pub struct ErrorDoc {
     #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub r#return: Option<String>,
+    pub details: Option<String>,
+    #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
+    pub params: BTreeMap<String, String>,
 }
 
 #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]


Test patch:
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index 8d4b1b30..4600d09a 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -10,8 +10,8 @@ use std::{
 use ethers_core::types::Address;
 use ethers_solc::{
     artifacts::{
-        BytecodeHash, Libraries, ModelCheckerEngine::CHC, ModelCheckerSettings, UserDoc,
-        UserDocNotice,
+        BytecodeHash, DevDoc, ErrorDoc, EventDoc, Libraries, MethodDoc, ModelCheckerEngine::CHC,
+        ModelCheckerSettings, UserDoc, UserDocNotice,
     },
     cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
     project_util::*,
@@ -1677,10 +1677,12 @@ fn can_parse_notice() {
             version: None,
             kind: None,
             methods: BTreeMap::from([
-                ("abc()".to_string(), UserDocNotice::Method { notice: "hello".to_string() }),
-                ("xyz()".to_string(), UserDocNotice::Method { notice: "hello".to_string() }),
+                ("abc()".to_string(), UserDocNotice::Notice { notice: "hello".to_string() }),
+                ("xyz()".to_string(), UserDocNotice::Notice { notice: "hello".to_string() }),
                 ("constructor".to_string(), UserDocNotice::Constructor("hello".to_string())),
             ]),
+            events: BTreeMap::new(),
+            errors: BTreeMap::new(),
             notice: None
         })
     );
@@ -1699,15 +1701,201 @@ fn can_parse_notice() {
             version: Some(1),
             kind: Some("user".to_string()),
             methods: BTreeMap::from([
-                ("abc()".to_string(), UserDocNotice::Method { notice: "hello".to_string() }),
-                ("xyz()".to_string(), UserDocNotice::Method { notice: "hello".to_string() }),
-                ("constructor".to_string(), UserDocNotice::Method { notice: "hello".to_string() }),
+                ("abc()".to_string(), UserDocNotice::Notice { notice: "hello".to_string() }),
+                ("xyz()".to_string(), UserDocNotice::Notice { notice: "hello".to_string() }),
+                ("constructor".to_string(), UserDocNotice::Notice { notice: "hello".to_string() }),
             ]),
+            events: BTreeMap::new(),
+            errors: BTreeMap::new(),
             notice: None
         })
     );
 }
 
+#[test]
+fn can_parse_doc() {
+    let mut project = TempProject::<ConfigurableArtifacts>::dapptools().unwrap();
+    project.project_mut().artifacts.additional_values.userdoc = true;
+    project.project_mut().artifacts.additional_values.devdoc = true;
+    project.project_mut().solc_config.settings = project.project_mut().artifacts.settings();
+
+    let contract = r#"
+// SPDX-License-Identifier: GPL-3.0-only
+pragma solidity ^0.8.0;
+
+/// @title Not an ERC20.
+/// @author Notadev
+/// @notice Do not use this.
+/// @dev This is not an ERC20 implementation.
+/// @custom:experimental This is an experimental contract.
+interface INotERC20 {
+    /// @notice Transfer tokens.
+    /// @dev Transfer `amount` tokens to account `to`.
+    /// @param to Target account.
+    /// @param amount Transfer amount.
+    /// @return A boolean value indicating whether the operation succeeded.
+    function transfer(address to, uint256 amount) external returns (bool);
+
+    /// @notice Transfer some tokens.
+    /// @dev Emitted when transfer.
+    /// @param from Source account.
+    /// @param to Target account.
+    /// @param value Transfer amount.
+    event Transfer(address indexed from, address indexed to, uint256 value);
+
+    /// @notice Insufficient balance for transfer.
+    /// @dev Needed `required` but only `available` available.
+    /// @param available Balance available.
+    /// @param required Requested amount to transfer.
+    error InsufficientBalance(uint256 available, uint256 required);
+}
+
+contract NotERC20 is INotERC20 {
+    /// @inheritdoc INotERC20
+    function transfer(address to, uint256 amount) external returns (bool) {
+        return false;
+    }
+}
+    "#;
+    project.add_source("Contract", contract).unwrap();
+
+    let mut compiled = project.compile().unwrap();
+    assert!(!compiled.has_compiler_errors());
+    assert!(!compiled.is_unchanged());
+
+    assert!(compiled.find("INotERC20").is_some());
+    let contract = compiled.remove("INotERC20").unwrap();
+    assert_eq!(
+        contract.userdoc,
+        Some(UserDoc {
+            version: Some(1),
+            kind: Some("user".to_string()),
+            notice: Some("Do not use this.".to_string()),
+            methods: BTreeMap::from([(
+                "transfer(address,uint256)".to_string(),
+                UserDocNotice::Notice { notice: "Transfer tokens.".to_string() }
+            ),]),
+            events: BTreeMap::from([(
+                "Transfer(address,address,uint256)".to_string(),
+                UserDocNotice::Notice { notice: "Transfer some tokens.".to_string() }
+            ),]),
+            errors: BTreeMap::from([(
+                "InsufficientBalance(uint256,uint256)".to_string(),
+                vec![UserDocNotice::Notice {
+                    notice: "Insufficient balance for transfer.".to_string()
+                }]
+            ),]),
+        })
+    );
+    assert_eq!(
+        contract.devdoc,
+        Some(DevDoc {
+            version: Some(1),
+            kind: Some("dev".to_string()),
+            author: Some("Notadev".to_string()),
+            details: Some("This is not an ERC20 implementation.".to_string()),
+            custom_experimental: Some("This is an experimental contract.".to_string()),
+            methods: BTreeMap::from([(
+                "transfer(address,uint256)".to_string(),
+                MethodDoc {
+                    details: Some("Transfer `amount` tokens to account `to`.".to_string()),
+                    params: BTreeMap::from([
+                        ("to".to_string(), "Target account.".to_string()),
+                        ("amount".to_string(), "Transfer amount.".to_string())
+                    ]),
+                    returns: BTreeMap::from([(
+                        "_0".to_string(),
+                        "A boolean value indicating whether the operation succeeded.".to_string()
+                    ),])
+                }
+            ),]),
+            events: BTreeMap::from([(
+                "Transfer(address,address,uint256)".to_string(),
+                EventDoc {
+                    details: Some("Emitted when transfer.".to_string()),
+                    params: BTreeMap::from([
+                        ("from".to_string(), "Source account.".to_string()),
+                        ("to".to_string(), "Target account.".to_string()),
+                        ("value".to_string(), "Transfer amount.".to_string()),
+                    ]),
+                }
+            ),]),
+            errors: BTreeMap::from([(
+                "InsufficientBalance(uint256,uint256)".to_string(),
+                vec![ErrorDoc {
+                    details: Some("Needed `required` but only `available` available.".to_string()),
+                    params: BTreeMap::from([
+                        ("available".to_string(), "Balance available.".to_string()),
+                        ("required".to_string(), "Requested amount to transfer.".to_string())
+                    ]),
+                }]
+            ),]),
+            title: Some("Not an ERC20.".to_string())
+        })
+    );
+
+    assert!(compiled.find("NotERC20").is_some());
+    let contract = compiled.remove("NotERC20").unwrap();
+    assert_eq!(
+        contract.userdoc,
+        Some(UserDoc {
+            version: Some(1),
+            kind: Some("user".to_string()),
+            notice: None,
+            methods: BTreeMap::from([(
+                "transfer(address,uint256)".to_string(),
+                UserDocNotice::Notice { notice: "Transfer tokens.".to_string() }
+            ),]),
+            events: BTreeMap::from([(
+                "Transfer(address,address,uint256)".to_string(),
+                UserDocNotice::Notice { notice: "Transfer some tokens.".to_string() }
+            ),]),
+            errors: BTreeMap::from([(
+                "InsufficientBalance(uint256,uint256)".to_string(),
+                vec![UserDocNotice::Notice {
+                    notice: "Insufficient balance for transfer.".to_string()
+                }]
+            ),]),
+        })
+    );
+    assert_eq!(
+        contract.devdoc,
+        Some(DevDoc {
+            version: Some(1),
+            kind: Some("dev".to_string()),
+            author: None,
+            details: None,
+            custom_experimental: None,
+            methods: BTreeMap::from([(
+                "transfer(address,uint256)".to_string(),
+                MethodDoc {
+                    details: Some("Transfer `amount` tokens to account `to`.".to_string()),
+                    params: BTreeMap::from([
+                        ("to".to_string(), "Target account.".to_string()),
+                        ("amount".to_string(), "Transfer amount.".to_string())
+                    ]),
+                    returns: BTreeMap::from([(
+                        "_0".to_string(),
+                        "A boolean value indicating whether the operation succeeded.".to_string()
+                    ),])
+                }
+            ),]),
+            events: BTreeMap::new(),
+            errors: BTreeMap::from([(
+                "InsufficientBalance(uint256,uint256)".to_string(),
+                vec![ErrorDoc {
+                    details: Some("Needed `required` but only `available` available.".to_string()),
+                    params: BTreeMap::from([
+                        ("available".to_string(), "Balance available.".to_string()),
+                        ("required".to_string(), "Requested amount to transfer.".to_string())
+                    ]),
+                }]
+            ),]),
+            title: None
+        })
+    );
+}
+
 #[test]
 fn test_relative_cache_entries() {
     let project = TempProject::dapptools().unwrap();