Repository: gakonst/ethers-rs
Pull request #1436 (2022-06-30 13:35:34)

feat(solc): add remove_contract util function
<!--
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
add util function to remove a `Contract` via `ContractInfo`, used in forge's cli args that follow the `<path>:<contractname>` pattern
<!--
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

<!--
Summarize the solution and provide any necessary context needed to understand
the code change.
-->

## PR Checklist

- [ ] Added Tests
- [ ] Added Documentation
- [ ] Updated the changelog



Patch:
diff --git a/ethers-solc/src/artifact_output/configurable.rs b/ethers-solc/src/artifact_output/configurable.rs
index a964b20e..7958a211 100644
--- a/ethers-solc/src/artifact_output/configurable.rs
+++ b/ethers-solc/src/artifact_output/configurable.rs
@@ -1,4 +1,12 @@
 //! A configurable artifacts handler implementation
+//!
+//! Configuring artifacts requires two pieces: the `ConfigurableArtifacts` handler, which contains
+//! the configuration of how to construct the `ConfigurableArtifact` type based on a `Contract`. The
+//! `ConfigurableArtifacts` populates a single `Artifact`, the `ConfigurableArtifact`, by default
+//! with essential entries only, such as `abi`, `bytecode`,..., but may include additional values
+//! based on its `ExtraOutputValues` that maps to various objects in the solc contract output, see
+//! also: [`OutputSelection`](crate::artifacts::OutputSelection). In addition to that some output
+//! values can also be emitted as standalone files.
 
 use crate::{
     artifacts::{
@@ -34,6 +42,8 @@ pub struct ConfigurableContractArtifact {
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub assembly: Option<String>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub opcodes: Option<String>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
     pub method_identifiers: Option<BTreeMap<String, String>>,
     #[serde(default, skip_serializing_if = "Vec::is_empty")]
     pub generated_sources: Vec<GeneratedSource>,
@@ -248,6 +258,7 @@ impl ArtifactOutput for ConfigurableArtifacts {
         let mut artifact_assembly = None;
         let mut artifact_storage_layout = None;
         let mut generated_sources = None;
+        let mut opcodes = None;
 
         let Contract {
             abi,
@@ -298,13 +309,17 @@ impl ArtifactOutput for ConfigurableArtifacts {
 
             if self.additional_values.function_debug_data {
                 artifact_function_debug_data =
-                    bytecode.as_ref().map(|b| b.function_debug_data.clone());
+                    bytecode.as_mut().map(|code| std::mem::take(&mut code.function_debug_data));
             }
             if self.additional_values.generated_sources {
                 generated_sources =
                     bytecode.as_mut().map(|code| std::mem::take(&mut code.generated_sources));
             }
 
+            if self.additional_values.opcodes {
+                opcodes = bytecode.as_mut().and_then(|code| code.opcodes.take())
+            }
+
             artifact_bytecode = bytecode.map(Into::into);
             artifact_deployed_bytecode = deployed_bytecode.map(Into::into);
             artifact_method_identifiers = Some(method_identifiers);
@@ -322,6 +337,7 @@ impl ArtifactOutput for ConfigurableArtifacts {
             bytecode: artifact_bytecode,
             deployed_bytecode: artifact_deployed_bytecode,
             assembly: artifact_assembly,
+            opcodes,
             function_debug_data: artifact_function_debug_data,
             method_identifiers: artifact_method_identifiers,
             gas_estimates: artifact_gas_estimates,
@@ -373,6 +389,7 @@ pub struct ExtraOutputValues {
     pub function_debug_data: bool,
     pub generated_sources: bool,
     pub source_map: bool,
+    pub opcodes: bool,
 
     /// PRIVATE: This structure may grow, As such, constructing this structure should
     /// _always_ be done using a public constructor or update syntax:
@@ -408,6 +425,7 @@ impl ExtraOutputValues {
             function_debug_data: true,
             generated_sources: true,
             source_map: true,
+            opcodes: true,
             __non_exhaustive: (),
         }
     }
@@ -444,6 +462,7 @@ impl ExtraOutputValues {
                         config.method_identifiers = true;
                         config.generated_sources = true;
                         config.source_map = true;
+                        config.opcodes = true;
                     }
                     EvmOutputSelection::Assembly => {
                         config.assembly = true;
@@ -457,6 +476,9 @@ impl ExtraOutputValues {
                     EvmOutputSelection::ByteCode(BytecodeOutputSelection::FunctionDebugData) => {
                         config.function_debug_data = true;
                     }
+                    EvmOutputSelection::ByteCode(BytecodeOutputSelection::Opcodes) => {
+                        config.opcodes = true;
+                    }
                     EvmOutputSelection::ByteCode(BytecodeOutputSelection::GeneratedSources) => {
                         config.generated_sources = true;
                     }
diff --git a/ethers-solc/src/artifact_output/mod.rs b/ethers-solc/src/artifact_output/mod.rs
index db5be8f9..616e6cdb 100644
--- a/ethers-solc/src/artifact_output/mod.rs
+++ b/ethers-solc/src/artifact_output/mod.rs
@@ -1,8 +1,16 @@
 //! Output artifact handling
 
 use crate::{
-    artifacts::FileToContractsMap, error::Result, utils, HardhatArtifact, ProjectPathsConfig,
-    SolcError,
+    artifacts::{
+        contract::{CompactContract, CompactContractBytecode, Contract},
+        BytecodeObject, CompactBytecode, CompactContractBytecodeCow, CompactDeployedBytecode,
+        FileToContractsMap, SourceFile,
+    },
+    compile::output::{contracts::VersionedContracts, sources::VersionedSourceFiles},
+    error::Result,
+    sourcemap::{SourceMap, SyntaxError},
+    sources::VersionedSourceFile,
+    utils, HardhatArtifact, ProjectPathsConfig, SolcError,
 };
 use ethers_core::{abi::Abi, types::Bytes};
 use semver::Version;
@@ -13,18 +21,7 @@ use std::{
     fmt, fs, io,
     path::{Path, PathBuf},
 };
-
 mod configurable;
-use crate::{
-    artifacts::{
-        contract::{CompactContract, CompactContractBytecode, Contract},
-        BytecodeObject, CompactBytecode, CompactContractBytecodeCow, CompactDeployedBytecode,
-        SourceFile,
-    },
-    compile::output::{contracts::VersionedContracts, sources::VersionedSourceFiles},
-    sourcemap::{SourceMap, SyntaxError},
-    sources::VersionedSourceFile,
-};
 pub use configurable::*;
 
 /// Represents unique artifact metadata for identifying artifacts on output
diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs
index eed1593f..c08b64b2 100644
--- a/ethers-solc/src/artifacts/mod.rs
+++ b/ethers-solc/src/artifacts/mod.rs
@@ -422,6 +422,24 @@ impl Settings {
                 })
                 .collect(),
         );
+
+        if let Some(mut model_checker) = self.model_checker.take() {
+            model_checker.contracts = model_checker
+                .contracts
+                .into_iter()
+                .map(|(path, contracts)| {
+                    (
+                        Path::new(&path)
+                            .strip_prefix(base)
+                            .map(|p| format!("{}", p.display()))
+                            .unwrap_or(path),
+                        contracts,
+                    )
+                })
+                .collect();
+            self.model_checker = Some(model_checker);
+        }
+
         self
     }
 }
diff --git a/ethers-solc/src/compile/output/contracts.rs b/ethers-solc/src/compile/output/contracts.rs
index 6a2f0873..f8de71b2 100644
--- a/ethers-solc/src/compile/output/contracts.rs
+++ b/ethers-solc/src/compile/output/contracts.rs
@@ -166,6 +166,28 @@ impl VersionedContracts {
         })
     }
 
+    /// Returns an iterator over (`file`, `name`, `Contract`)
+    pub fn into_contracts_with_files(self) -> impl Iterator<Item = (String, String, Contract)> {
+        self.0.into_iter().flat_map(|(file, contracts)| {
+            contracts.into_iter().flat_map(move |(name, c)| {
+                let file = file.clone();
+                c.into_iter().map(move |c| (file.clone(), name.clone(), c.contract))
+            })
+        })
+    }
+
+    /// Returns an iterator over (`file`, `name`, `Contract`, `Version`)
+    pub fn into_contracts_with_files_and_version(
+        self,
+    ) -> impl Iterator<Item = (String, String, Contract, Version)> {
+        self.0.into_iter().flat_map(|(file, contracts)| {
+            contracts.into_iter().flat_map(move |(name, c)| {
+                let file = file.clone();
+                c.into_iter().map(move |c| (file.clone(), name.clone(), c.contract, c.version))
+            })
+        })
+    }
+
     /// Sets the contract's file paths to `root` adjoined to `self.file`.
     pub fn join_all(&mut self, root: impl AsRef<Path>) -> &mut Self {
         let root = root.as_ref();
diff --git a/ethers-solc/src/compile/output/info.rs b/ethers-solc/src/compile/output/info.rs
index 8e118cea..3094bd9a 100644
--- a/ethers-solc/src/compile/output/info.rs
+++ b/ethers-solc/src/compile/output/info.rs
@@ -1,5 +1,5 @@
 //! Commonly used identifiers for contracts in the compiled output
-use std::{convert::TryFrom, fmt, str::FromStr};
+use std::{borrow::Cow, convert::TryFrom, fmt, str::FromStr};
 
 #[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
 #[error("{0}")]
@@ -77,6 +77,39 @@ impl From<FullContractInfo> for ContractInfo {
     }
 }
 
+/// The reference type for `ContractInfo`
+#[derive(Clone, Debug, Eq, PartialEq, Hash)]
+pub struct ContractInfoRef<'a> {
+    pub path: Option<Cow<'a, str>>,
+    pub name: Cow<'a, str>,
+}
+
+impl<'a> From<ContractInfo> for ContractInfoRef<'a> {
+    fn from(info: ContractInfo) -> Self {
+        ContractInfoRef { path: info.path.map(Into::into), name: info.name.into() }
+    }
+}
+
+impl<'a> From<&'a ContractInfo> for ContractInfoRef<'a> {
+    fn from(info: &'a ContractInfo) -> Self {
+        ContractInfoRef {
+            path: info.path.as_deref().map(Into::into),
+            name: info.name.as_str().into(),
+        }
+    }
+}
+impl<'a> From<FullContractInfo> for ContractInfoRef<'a> {
+    fn from(info: FullContractInfo) -> Self {
+        ContractInfoRef { path: Some(info.path.into()), name: info.name.into() }
+    }
+}
+
+impl<'a> From<&'a FullContractInfo> for ContractInfoRef<'a> {
+    fn from(info: &'a FullContractInfo) -> Self {
+        ContractInfoRef { path: Some(info.path.as_str().into()), name: info.name.as_str().into() }
+    }
+}
+
 /// Represents the common contract argument pattern `<path>:<contractname>`
 #[derive(Clone, Debug, Eq, PartialEq, Hash)]
 pub struct FullContractInfo {
diff --git a/ethers-solc/src/compile/output/mod.rs b/ethers-solc/src/compile/output/mod.rs
index c7e10779..bcb5e1e8 100644
--- a/ethers-solc/src/compile/output/mod.rs
+++ b/ethers-solc/src/compile/output/mod.rs
@@ -6,6 +6,7 @@ use crate::{
         Error,
     },
     buildinfo::RawBuildInfo,
+    info::ContractInfoRef,
     sources::{VersionedSourceFile, VersionedSourceFiles},
     ArtifactId, ArtifactOutput, Artifacts, CompilerOutput, ConfigurableArtifacts, SolcIoError,
 };
@@ -378,6 +379,38 @@ impl AggregatedCompilerOutput {
         self.contracts.remove(path, contract)
     }
 
+    /// Removes the contract with matching path and name using the `<path>:<contractname>` pattern
+    /// where `path` is optional.
+    ///
+    /// If the `path` segment is `None`, then the first matching `Contract` is returned, see
+    /// [Self::remove_first]
+    ///
+    ///
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use ethers_solc::Project;
+    /// use ethers_solc::artifacts::*;
+    /// use ethers_solc::info::ContractInfo;
+    /// # fn demo(project: Project) {
+    /// let mut output = project.compile().unwrap().output();
+    /// let info = ContractInfo::new("src/Greeter.sol:Greeter");
+    /// let contract = output.remove_contract(&info).unwrap();
+    /// # }
+    /// ```
+    pub fn remove_contract<'a>(
+        &mut self,
+        info: impl Into<ContractInfoRef<'a>>,
+    ) -> Option<Contract> {
+        let ContractInfoRef { path, name } = info.into();
+        if let Some(path) = path {
+            self.remove(path, name)
+        } else {
+            self.remove_first(name)
+        }
+    }
+
     /// Iterate over all contracts and their names
     pub fn contracts_iter(&self) -> impl Iterator<Item = (&String, &Contract)> {
         self.contracts.contracts()
@@ -388,6 +421,32 @@ impl AggregatedCompilerOutput {
         self.contracts.into_contracts()
     }
 
+    /// Returns an iterator over (`file`, `name`, `Contract`)
+    pub fn contracts_with_files_iter(&self) -> impl Iterator<Item = (&String, &String, &Contract)> {
+        self.contracts.contracts_with_files()
+    }
+
+    /// Returns an iterator over (`file`, `name`, `Contract`)
+    pub fn contracts_with_files_into_iter(
+        self,
+    ) -> impl Iterator<Item = (String, String, Contract)> {
+        self.contracts.into_contracts_with_files()
+    }
+
+    /// Returns an iterator over (`file`, `name`, `Contract`, `Version`)
+    pub fn contracts_with_files_and_version_iter(
+        &self,
+    ) -> impl Iterator<Item = (&String, &String, &Contract, &Version)> {
+        self.contracts.contracts_with_files_and_version()
+    }
+
+    /// Returns an iterator over (`file`, `name`, `Contract`, `Version`)
+    pub fn contracts_with_files_and_version_into_iter(
+        self,
+    ) -> impl Iterator<Item = (String, String, Contract, Version)> {
+        self.contracts.into_contracts_with_files_and_version()
+    }
+
     /// Given the contract file's path and the contract's name, tries to return the contract's
     /// bytecode, runtime bytecode, and abi
     /// # Example


Test patch:
diff --git a/ethers-solc/tests/project.rs b/ethers-solc/tests/project.rs
index f02ef995..f126f5c3 100644
--- a/ethers-solc/tests/project.rs
+++ b/ethers-solc/tests/project.rs
@@ -15,6 +15,7 @@ use ethers_solc::{
     },
     buildinfo::BuildInfo,
     cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
+    info::ContractInfo,
     project_util::*,
     remappings::Remapping,
     CompilerInput, ConfigurableArtifacts, ExtraOutputValues, Graph, Project, ProjectCompileOutput,
@@ -139,6 +140,7 @@ fn can_compile_configured() {
             metadata: true,
             ir: true,
             ir_optimized: true,
+            opcodes: true,
             ..Default::default()
         },
         ..Default::default()
@@ -152,6 +154,7 @@ fn can_compile_configured() {
     assert!(artifact.raw_metadata.is_some());
     assert!(artifact.ir.is_some());
     assert!(artifact.ir_optimized.is_some());
+    assert!(artifact.opcodes.is_some());
 }
 
 #[test]
@@ -1546,9 +1549,16 @@ fn can_compile_sparse_with_link_references() {
     let lib = dup.remove_first("MyLib");
     assert!(lib.is_none());
 
-    let lib = output.remove(my_lib_path.to_string_lossy(), "MyLib");
+    dup = output.clone();
+    let lib = dup.remove(my_lib_path.to_string_lossy(), "MyLib");
     assert!(lib.is_some());
-    let lib = output.remove(my_lib_path.to_string_lossy(), "MyLib");
+    let lib = dup.remove(my_lib_path.to_string_lossy(), "MyLib");
+    assert!(lib.is_none());
+
+    let info = ContractInfo::new(format!("{}:{}", my_lib_path.to_string_lossy(), "MyLib"));
+    let lib = output.remove_contract(&info);
+    assert!(lib.is_some());
+    let lib = output.remove_contract(&info);
     assert!(lib.is_none());
 }