Repository: gakonst/ethers-rs
Pull request #1283 (2022-05-19 15:08:55)

fix(solc): support constructor user docs
<!--
Thank you for your Pull Request. Please provide a description above and review
the requirements below.

Bug fixes and new features should include tests.

Contributors guide: https://github.com/gakonst/ethers-rs/blob/master/CONTRIBUTING.md

The contributors guide includes instructions for running rustfmt and building the
documentation.
-->

## Motivation
Closes https://github.com/foundry-rs/foundry/issues/1662

turns out tags for constructor are serialized differently on older versions
<!--
Explain the context and why you're making that change. What is the problem
you're trying to solve? In some cases there is not a problem and this can be
thought of as being the motivation for your change.
-->

## Solution
support `constructor: value` variant
<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [ ] Added Documentation
- [ ] Updated the changelog



Patch:
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index 497e008a..1547313f 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -1299,15 +1299,17 @@ pub struct UserDoc {
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub kind: Option<String>,
     #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
-    pub methods: BTreeMap<String, MethodNotice>,
+    pub methods: BTreeMap<String, UserDocNotice>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub notice: Option<String>,
 }
 
-#[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]
-pub struct MethodNotice {
-    #[serde(default, skip_serializing_if = "Option::is_none")]
-    pub notice: Option<String>,
+#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
+#[serde(untagged)]
+pub enum UserDocNotice {
+    // NOTE: this a variant used for constructors on older solc versions
+    Constructor(String),
+    Method { notice: String },
 }
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)]


Test patch:
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index f2cee711..55529d51 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -9,7 +9,10 @@ use std::{
 
 use ethers_core::types::Address;
 use ethers_solc::{
-    artifacts::{BytecodeHash, Libraries, ModelCheckerEngine::CHC, ModelCheckerSettings},
+    artifacts::{
+        BytecodeHash, Libraries, ModelCheckerEngine::CHC, ModelCheckerSettings, UserDoc,
+        UserDocNotice,
+    },
     cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
     project_util::*,
     remappings::Remapping,
@@ -1411,3 +1414,78 @@ fn can_purge_obsolete_artifacts() {
     assert!(!compiled.is_unchanged());
     assert_eq!(compiled.into_artifacts().count(), 1);
 }
+
+#[test]
+fn can_parse_notice() {
+    let mut project = TempProject::<ConfigurableArtifacts>::dapptools().unwrap();
+    project.project_mut().artifacts.additional_values.userdoc = true;
+    project.project_mut().solc_config.settings = project.project_mut().artifacts.settings();
+
+    let contract = r#"
+    pragma solidity $VERSION;
+
+   contract Contract {
+      string greeting;
+
+        /**
+         * @notice hello
+         */    
+         constructor(string memory _greeting) public {
+            greeting = _greeting;
+        }
+        
+        /**
+         * @notice hello
+         */
+        function xyz() public {
+        }
+        
+        /// @notice hello
+        function abc() public {
+        }
+   }
+   "#;
+    project.add_source("Contract", contract.replace("$VERSION", "=0.5.17")).unwrap();
+
+    let mut compiled = project.compile().unwrap();
+    assert!(!compiled.has_compiler_errors());
+    assert!(!compiled.is_unchanged());
+    assert!(compiled.find("Contract").is_some());
+    let userdoc = compiled.remove("Contract").unwrap().userdoc;
+
+    assert_eq!(
+        userdoc,
+        Some(UserDoc {
+            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() }),
+                ("constructor".to_string(), UserDocNotice::Constructor("hello".to_string())),
+            ]),
+            notice: None
+        })
+    );
+
+    project.add_source("Contract", contract.replace("$VERSION", "^0.8.10")).unwrap();
+
+    let mut compiled = project.compile().unwrap();
+    assert!(!compiled.has_compiler_errors());
+    assert!(!compiled.is_unchanged());
+    assert!(compiled.find("Contract").is_some());
+    let userdoc = compiled.remove("Contract").unwrap().userdoc;
+
+    assert_eq!(
+        userdoc,
+        Some(UserDoc {
+            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() }),
+            ]),
+            notice: None
+        })
+    );
+}