Repository: gakonst/ethers-rs
Pull request #1018 (2022-03-13 22:07:40)

fix(solc): skip artifacts check for files without artifacts
<!--
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
Fix false positive dirty detection for files that don't emit artifacts
<!--
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
Only check for artifacts if the CacheEntry links to artifacts, so that files like:
 
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/interfaces/IERC165.sol

are skipped when determining the change status of a file 

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [x] Added Tests
- [x] Added Documentation
- [ ] Updated the changelog



Patch:
diff --git a/ethers-solc/src/cache.rs b/ethers-solc/src/cache.rs
index 9c0edb8e..3a365e2f 100644
--- a/ethers-solc/src/cache.rs
+++ b/ethers-solc/src/cache.rs
@@ -638,23 +638,28 @@ impl<'a, T: ArtifactOutput> ArtifactsCacheInner<'a, T> {
                     return true
                 }
 
-                if !entry.contains_version(version) {
-                    tracing::trace!(
-                        "missing linked artifacts for source file `{}` for version \"{}\"",
-                        file.display(),
-                        version
-                    );
-                    return true
-                }
+                // only check artifact's existence if the file generated artifacts.
+                // e.g. a solidity file consisting only of import statements (like interfaces that
+                // re-export) do not create artifacts
+                if !entry.artifacts.is_empty() {
+                    if !entry.contains_version(version) {
+                        tracing::trace!(
+                            "missing linked artifacts for source file `{}` for version \"{}\"",
+                            file.display(),
+                            version
+                        );
+                        return true
+                    }
 
-                if entry.artifacts_for_version(version).any(|artifact_path| {
-                    let missing_artifact = !self.cached_artifacts.has_artifact(artifact_path);
-                    if missing_artifact {
-                        tracing::trace!("missing artifact \"{}\"", artifact_path.display());
+                    if entry.artifacts_for_version(version).any(|artifact_path| {
+                        let missing_artifact = !self.cached_artifacts.has_artifact(artifact_path);
+                        if missing_artifact {
+                            tracing::trace!("missing artifact \"{}\"", artifact_path.display());
+                        }
+                        missing_artifact
+                    }) {
+                        return true
                     }
-                    missing_artifact
-                }) {
-                    return true
                 }
                 // all things match, can be reused
                 return false


Test patch:
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index c1e7ee28..e2fd432f 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -683,3 +683,51 @@ fn can_recompile_with_changes() {
     assert!(compiled.find("A").is_some());
     assert!(compiled.find("B").is_some());
 }
+
+#[test]
+fn can_recompile_unchanged_with_empty_files() {
+    let tmp = TempProject::dapptools().unwrap();
+
+    tmp.add_source(
+        "A",
+        r#"
+    pragma solidity ^0.8.10;
+    import "./B.sol";
+    contract A {}
+   "#,
+    )
+    .unwrap();
+
+    tmp.add_source(
+        "B",
+        r#"
+    pragma solidity ^0.8.10;
+    import "./C.sol";
+   "#,
+    )
+    .unwrap();
+
+    let c = r#"
+    pragma solidity ^0.8.10;
+    contract C {}
+   "#;
+    tmp.add_source("C", c).unwrap();
+
+    let compiled = tmp.compile().unwrap();
+    assert!(!compiled.has_compiler_errors());
+    assert!(compiled.find("A").is_some());
+    assert!(compiled.find("C").is_some());
+
+    let compiled = tmp.compile().unwrap();
+    assert!(compiled.find("A").is_some());
+    assert!(compiled.find("C").is_some());
+    assert!(compiled.is_unchanged());
+
+    // modify C.sol
+    tmp.add_source("C", format!("{}\n", c)).unwrap();
+    let compiled = tmp.compile().unwrap();
+    assert!(!compiled.has_compiler_errors());
+    assert!(!compiled.is_unchanged());
+    assert!(compiled.find("A").is_some());
+    assert!(compiled.find("C").is_some());
+}