feat(solc): store source files with their solc version (#1231)

* feat(solc): add versioned sources

* feat(solc): support versioned sources
This commit is contained in:
Matthias Seitz 2022-05-06 20:42:01 +02:00 committed by GitHub
parent c7765e1721
commit 44cbbc769a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 298 additions and 30 deletions

View File

@ -1,8 +1,8 @@
//! Output artifact handling //! Output artifact handling
use crate::{ use crate::{
artifacts::FileToContractsMap, contracts::VersionedContracts, error::Result, utils, artifacts::FileToContractsMap, error::Result, utils, HardhatArtifact, ProjectPathsConfig,
HardhatArtifact, ProjectPathsConfig, SolcError, SolcError,
}; };
use ethers_core::{abi::Abi, types::Bytes}; use ethers_core::{abi::Abi, types::Bytes};
use semver::Version; use semver::Version;
@ -15,10 +15,13 @@ use std::{
}; };
mod configurable; mod configurable;
use crate::artifacts::{ use crate::{
contract::{CompactContract, CompactContractBytecode, Contract}, artifacts::{
BytecodeObject, CompactBytecode, CompactContractBytecodeCow, CompactDeployedBytecode, contract::{CompactContract, CompactContractBytecode, Contract},
SourceFile, BytecodeObject, CompactBytecode, CompactContractBytecodeCow, CompactDeployedBytecode,
SourceFile,
},
compile::output::{contracts::VersionedContracts, sources::VersionedSourceFiles},
}; };
pub use configurable::*; pub use configurable::*;
@ -447,7 +450,7 @@ pub trait ArtifactOutput {
fn on_output( fn on_output(
&self, &self,
contracts: &VersionedContracts, contracts: &VersionedContracts,
sources: &BTreeMap<String, SourceFile>, sources: &VersionedSourceFiles,
layout: &ProjectPathsConfig, layout: &ProjectPathsConfig,
) -> Result<Artifacts<Self::Artifact>> { ) -> Result<Artifacts<Self::Artifact>> {
let mut artifacts = self.output_to_artifacts(contracts, sources); let mut artifacts = self.output_to_artifacts(contracts, sources);
@ -609,16 +612,17 @@ pub trait ArtifactOutput {
fn output_to_artifacts( fn output_to_artifacts(
&self, &self,
contracts: &VersionedContracts, contracts: &VersionedContracts,
sources: &BTreeMap<String, SourceFile>, sources: &VersionedSourceFiles,
) -> Artifacts<Self::Artifact> { ) -> Artifacts<Self::Artifact> {
let mut artifacts = ArtifactsMap::new(); let mut artifacts = ArtifactsMap::new();
for (file, contracts) in contracts.as_ref().iter() { for (file, contracts) in contracts.as_ref().iter() {
let source_file = sources.get(file);
let mut entries = BTreeMap::new(); let mut entries = BTreeMap::new();
for (name, versioned_contracts) in contracts { for (name, versioned_contracts) in contracts {
let mut contracts = Vec::with_capacity(versioned_contracts.len()); let mut contracts = Vec::with_capacity(versioned_contracts.len());
// check if the same contract compiled with multiple solc versions // check if the same contract compiled with multiple solc versions
for contract in versioned_contracts { for contract in versioned_contracts {
let source_file = sources.find_file_and_version(file, &contract.version);
let artifact_path = if versioned_contracts.len() > 1 { let artifact_path = if versioned_contracts.len() > 1 {
Self::output_file_versioned(file, name, &contract.version) Self::output_file_versioned(file, name, &contract.version)
} else { } else {
@ -689,7 +693,7 @@ impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback {
fn on_output( fn on_output(
&self, &self,
output: &VersionedContracts, output: &VersionedContracts,
sources: &BTreeMap<String, SourceFile>, sources: &VersionedSourceFiles,
layout: &ProjectPathsConfig, layout: &ProjectPathsConfig,
) -> Result<Artifacts<Self::Artifact>> { ) -> Result<Artifacts<Self::Artifact>> {
MinimalCombinedArtifacts::default().on_output(output, sources, layout) MinimalCombinedArtifacts::default().on_output(output, sources, layout)

View File

@ -46,10 +46,10 @@ pub type Contracts = FileToContractsMap<Contract>;
pub type Sources = BTreeMap<PathBuf, Source>; pub type Sources = BTreeMap<PathBuf, Source>;
/// A set of different Solc installations with their version and the sources to be compiled /// A set of different Solc installations with their version and the sources to be compiled
pub type VersionedSources = BTreeMap<Solc, (Version, Sources)>; pub(crate) type VersionedSources = BTreeMap<Solc, (Version, Sources)>;
/// A set of different Solc installations with their version and the sources to be compiled /// A set of different Solc installations with their version and the sources to be compiled
pub type VersionedFilteredSources = BTreeMap<Solc, (Version, FilteredSources)>; pub(crate) type VersionedFilteredSources = BTreeMap<Solc, (Version, FilteredSources)>;
const SOLIDITY: &str = "Solidity"; const SOLIDITY: &str = "Solidity";

View File

@ -5,7 +5,6 @@ use crate::{
}; };
use semver::{Version, VersionReq}; use semver::{Version, VersionReq};
use serde::{de::DeserializeOwned, Deserialize, Serialize}; use serde::{de::DeserializeOwned, Deserialize, Serialize};
use std::{ use std::{
fmt, fmt,
io::BufRead, io::BufRead,
@ -13,10 +12,9 @@ use std::{
process::{Command, Output, Stdio}, process::{Command, Output, Stdio},
str::FromStr, str::FromStr,
}; };
pub mod contracts;
pub mod many; pub mod many;
pub mod output; pub mod output;
pub use output::{contracts, sources};
pub mod project; pub mod project;
/// The name of the `solc` binary on the system /// The name of the `solc` binary on the system

View File

@ -3,14 +3,18 @@
use crate::{ use crate::{
artifacts::{ artifacts::{
contract::{CompactContractBytecode, CompactContractRef, Contract}, contract::{CompactContractBytecode, CompactContractRef, Contract},
Error, SourceFile, SourceFiles, Error,
}, },
contracts::{VersionedContract, VersionedContracts}, sources::{VersionedSourceFile, VersionedSourceFiles},
ArtifactId, ArtifactOutput, Artifacts, CompilerOutput, ConfigurableArtifacts, ArtifactId, ArtifactOutput, Artifacts, CompilerOutput, ConfigurableArtifacts,
}; };
use contracts::{VersionedContract, VersionedContracts};
use semver::Version; use semver::Version;
use std::{collections::BTreeMap, fmt, path::Path}; use std::{collections::BTreeMap, fmt, path::Path};
pub mod contracts;
pub mod sources;
/// Contains a mixture of already compiled/cached artifacts and the input set of sources that still /// Contains a mixture of already compiled/cached artifacts and the input set of sources that still
/// need to be compiled. /// need to be compiled.
#[derive(Debug, Clone, PartialEq, Default)] #[derive(Debug, Clone, PartialEq, Default)]
@ -69,7 +73,9 @@ impl<T: ArtifactOutput> ProjectCompileOutput<T> {
} }
/// All artifacts together with their ID and the sources of the project. /// All artifacts together with their ID and the sources of the project.
pub fn into_artifacts_with_sources(self) -> (BTreeMap<ArtifactId, T::Artifact>, SourceFiles) { pub fn into_artifacts_with_sources(
self,
) -> (BTreeMap<ArtifactId, T::Artifact>, VersionedSourceFiles) {
let Self { cached_artifacts, compiled_artifacts, compiler_output, .. } = self; let Self { cached_artifacts, compiled_artifacts, compiler_output, .. } = self;
( (
@ -77,7 +83,7 @@ impl<T: ArtifactOutput> ProjectCompileOutput<T> {
.into_artifacts::<T>() .into_artifacts::<T>()
.chain(compiled_artifacts.into_artifacts::<T>()) .chain(compiled_artifacts.into_artifacts::<T>())
.collect(), .collect(),
SourceFiles(compiler_output.sources), compiler_output.sources,
) )
} }
@ -228,8 +234,8 @@ impl<T: ArtifactOutput> fmt::Display for ProjectCompileOutput<T> {
pub struct AggregatedCompilerOutput { pub struct AggregatedCompilerOutput {
/// all errors from all `CompilerOutput` /// all errors from all `CompilerOutput`
pub errors: Vec<Error>, pub errors: Vec<Error>,
/// All source files /// All source files combined with the solc version used to compile them
pub sources: BTreeMap<String, SourceFile>, pub sources: VersionedSourceFiles,
/// All compiled contracts combined with the solc version used to compile them /// All compiled contracts combined with the solc version used to compile them
pub contracts: VersionedContracts, pub contracts: VersionedContracts,
} }
@ -274,10 +280,15 @@ impl AggregatedCompilerOutput {
/// adds a new `CompilerOutput` to the aggregated output /// adds a new `CompilerOutput` to the aggregated output
pub fn extend(&mut self, version: Version, output: CompilerOutput) { pub fn extend(&mut self, version: Version, output: CompilerOutput) {
self.errors.extend(output.errors); let CompilerOutput { errors, sources, contracts } = output;
self.sources.extend(output.sources); self.errors.extend(errors);
for (file_name, new_contracts) in output.contracts { for (path, source_file) in sources {
let sources = self.sources.as_mut().entry(path).or_default();
sources.push(VersionedSourceFile { source_file, version: version.clone() });
}
for (file_name, new_contracts) in contracts {
let contracts = self.contracts.as_mut().entry(file_name).or_default(); let contracts = self.contracts.as_mut().entry(file_name).or_default();
for (contract_name, contract) in new_contracts { for (contract_name, contract) in new_contracts {
let versioned = contracts.entry(contract_name).or_default(); let versioned = contracts.entry(contract_name).or_default();
@ -346,8 +357,8 @@ impl AggregatedCompilerOutput {
/// let (sources, contracts) = output.split(); /// let (sources, contracts) = output.split();
/// # } /// # }
/// ``` /// ```
pub fn split(self) -> (SourceFiles, VersionedContracts) { pub fn split(self) -> (VersionedSourceFiles, VersionedContracts) {
(SourceFiles(self.sources), self.contracts) (self.sources, self.contracts)
} }
} }

View File

@ -0,0 +1,254 @@
use crate::SourceFile;
use semver::Version;
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
/// (source_file path -> `SourceFile` + solc version)
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
#[serde(transparent)]
pub struct VersionedSourceFiles(pub BTreeMap<String, Vec<VersionedSourceFile>>);
impl VersionedSourceFiles {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
/// Returns an iterator over all files
pub fn files(&self) -> impl Iterator<Item = &String> + '_ {
self.0.keys()
}
/// Returns an iterator over the source files' ids and path
///
/// ```
/// use std::collections::BTreeMap;
/// use ethers_solc::sources::VersionedSourceFiles;
/// # fn demo(files: VersionedSourceFiles) {
/// let sources: BTreeMap<u32,String> = files.into_ids().collect();
/// # }
/// ```
pub fn into_ids(self) -> impl Iterator<Item = (u32, String)> {
self.into_sources().map(|(path, source)| (source.id, path))
}
/// Returns an iterator over the source files' paths and ids
///
/// ```
/// use std::collections::BTreeMap;
/// use ethers_solc::artifacts::SourceFiles;
/// # fn demo(files: SourceFiles) {
/// let sources :BTreeMap<String, u32> = files.into_paths().collect();
/// # }
/// ```
pub fn into_paths(self) -> impl Iterator<Item = (String, u32)> {
self.into_ids().map(|(id, path)| (path, id))
}
/// Returns an iterator over the source files' ids and path
///
/// ```
/// use std::collections::BTreeMap;
/// use semver::Version;
/// use ethers_solc::sources::VersionedSourceFiles;
/// # fn demo(files: VersionedSourceFiles) {
/// let sources: BTreeMap<(u32, Version) ,String> = files.into_ids_with_version().map(|(id, source, version)|((id, version), source)).collect();
/// # }
/// ```
pub fn into_ids_with_version(self) -> impl Iterator<Item = (u32, String, Version)> {
self.into_sources_with_version().map(|(path, source, version)| (source.id, path, version))
}
/// Finds the _first_ source file with the given path
///
/// # Example
///
/// ```
/// use ethers_solc::Project;
/// use ethers_solc::artifacts::*;
/// # fn demo(project: Project) {
/// let output = project.compile().unwrap().output();
/// let source_file = output.sources.find_file("src/Greeter.sol").unwrap();
/// # }
/// ```
pub fn find_file(&self, source_file: impl AsRef<str>) -> Option<&SourceFile> {
let source_file_name = source_file.as_ref();
self.sources().find_map(
|(path, source_file)| {
if path == source_file_name {
Some(source_file)
} else {
None
}
},
)
}
/// Same as [Self::find_file] but also checks for version
pub fn find_file_and_version(&self, path: &str, version: &Version) -> Option<&SourceFile> {
self.0.get(path).and_then(|contracts| {
contracts.iter().find_map(|source| {
if source.version == *version {
Some(&source.source_file)
} else {
None
}
})
})
}
/// Finds the _first_ source file with the given id
///
/// # Example
///
/// ```
/// use ethers_solc::Project;
/// use ethers_solc::artifacts::*;
/// # fn demo(project: Project) {
/// let output = project.compile().unwrap().output();
/// let source_file = output.sources.find_id(0).unwrap();
/// # }
/// ```
pub fn find_id(&self, id: u32) -> Option<&SourceFile> {
self.sources().filter(|(_, source)| source.id == id).map(|(_, source)| source).next()
}
/// Same as [Self::find_id] but also checks for version
pub fn find_id_and_version(&self, id: u32, version: &Version) -> Option<&SourceFile> {
self.sources_with_version()
.filter(|(_, source, v)| source.id == id && *v == version)
.map(|(_, source, _)| source)
.next()
}
/// Removes the _first_ source_file with the given path from the set
///
/// # Example
///
/// ```
/// use ethers_solc::Project;
/// use ethers_solc::artifacts::*;
/// # fn demo(project: Project) {
/// let (mut sources, _) = project.compile().unwrap().output().split();
/// let source_file = sources.remove_by_path("src/Greeter.sol").unwrap();
/// # }
/// ```
pub fn remove_by_path(&mut self, source_file: impl AsRef<str>) -> Option<SourceFile> {
let source_file_path = source_file.as_ref();
self.0.get_mut(source_file_path).and_then(|all_sources| {
if !all_sources.is_empty() {
Some(all_sources.remove(0).source_file)
} else {
None
}
})
}
/// Removes the _first_ source_file with the given id from the set
///
/// # Example
///
/// ```
/// use ethers_solc::Project;
/// use ethers_solc::artifacts::*;
/// # fn demo(project: Project) {
/// let (mut sources, _) = project.compile().unwrap().output().split();
/// let source_file = sources.remove_by_id(0).unwrap();
/// # }
/// ```
pub fn remove_by_id(&mut self, id: u32) -> Option<SourceFile> {
self.0
.values_mut()
.filter_map(|sources| {
sources
.iter()
.position(|source| source.source_file.id == id)
.map(|pos| sources.remove(pos).source_file)
})
.next()
}
/// Iterate over all contracts and their names
pub fn sources(&self) -> impl Iterator<Item = (&String, &SourceFile)> {
self.0.iter().flat_map(|(path, sources)| {
sources.iter().map(move |source| (path, &source.source_file))
})
}
/// Returns an iterator over (`file`, `SourceFile`, `Version`)
pub fn sources_with_version(&self) -> impl Iterator<Item = (&String, &SourceFile, &Version)> {
self.0.iter().flat_map(|(file, sources)| {
sources.iter().map(move |c| (file, &c.source_file, &c.version))
})
}
/// Returns an iterator over all contracts and their source names.
///
/// ```
/// use std::collections::BTreeMap;
/// use ethers_solc::{ artifacts::* };
/// use ethers_solc::sources::VersionedSourceFiles;
/// # fn demo(sources: VersionedSourceFiles) {
/// let sources: BTreeMap<String, SourceFile> = sources
/// .into_sources()
/// .collect();
/// # }
/// ```
pub fn into_sources(self) -> impl Iterator<Item = (String, SourceFile)> {
self.0.into_iter().flat_map(|(path, sources)| {
sources.into_iter().map(move |source| (path.clone(), source.source_file))
})
}
/// Returns an iterator over all contracts and their source names.
///
/// ```
/// use std::collections::BTreeMap;
/// use semver::Version;
/// use ethers_solc::{ artifacts::* };
/// use ethers_solc::sources::VersionedSourceFiles;
/// # fn demo(sources: VersionedSourceFiles) {
/// let sources: BTreeMap<(String,Version), SourceFile> = sources
/// .into_sources_with_version().map(|(path, source, version)|((path,version), source))
/// .collect();
/// # }
/// ```
pub fn into_sources_with_version(self) -> impl Iterator<Item = (String, SourceFile, Version)> {
self.0.into_iter().flat_map(|(path, sources)| {
sources
.into_iter()
.map(move |source| (path.clone(), source.source_file, source.version))
})
}
}
impl AsRef<BTreeMap<String, Vec<VersionedSourceFile>>> for VersionedSourceFiles {
fn as_ref(&self) -> &BTreeMap<String, Vec<VersionedSourceFile>> {
&self.0
}
}
impl AsMut<BTreeMap<String, Vec<VersionedSourceFile>>> for VersionedSourceFiles {
fn as_mut(&mut self) -> &mut BTreeMap<String, Vec<VersionedSourceFile>> {
&mut self.0
}
}
impl IntoIterator for VersionedSourceFiles {
type Item = (String, Vec<VersionedSourceFile>);
type IntoIter = std::collections::btree_map::IntoIter<String, Vec<VersionedSourceFile>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
/// A [SourceFile] and the compiler version used to compile it
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct VersionedSourceFile {
pub source_file: SourceFile,
pub version: Version,
}

View File

@ -700,7 +700,7 @@ mod tests {
let state = state.compile().unwrap(); let state = state.compile().unwrap();
assert_eq!(state.output.sources.len(), 3); assert_eq!(state.output.sources.len(), 3);
for (f, source) in &state.output.sources { for (f, source) in state.output.sources.sources() {
if f.ends_with("A.sol") { if f.ends_with("A.sol") {
assert!(source.ast.is_some()); assert!(source.ast.is_some());
} else { } else {

View File

@ -35,10 +35,11 @@ pub use filter::{FileFilter, TestFileFilter};
use crate::{ use crate::{
artifacts::Sources, artifacts::Sources,
cache::SolFilesCache, cache::SolFilesCache,
contracts::VersionedContracts,
error::{SolcError, SolcIoError}, error::{SolcError, SolcIoError},
sources::VersionedSourceFiles,
}; };
use artifacts::contract::Contract; use artifacts::contract::Contract;
use compile::output::contracts::VersionedContracts;
use error::Result; use error::Result;
use semver::Version; use semver::Version;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
@ -750,7 +751,7 @@ impl<T: ArtifactOutput> ArtifactOutput for Project<T> {
fn on_output( fn on_output(
&self, &self,
contracts: &VersionedContracts, contracts: &VersionedContracts,
sources: &BTreeMap<String, SourceFile>, sources: &VersionedSourceFiles,
layout: &ProjectPathsConfig, layout: &ProjectPathsConfig,
) -> Result<Artifacts<Self::Artifact>> { ) -> Result<Artifacts<Self::Artifact>> {
self.artifacts_handler().on_output(contracts, sources, layout) self.artifacts_handler().on_output(contracts, sources, layout)
@ -825,7 +826,7 @@ impl<T: ArtifactOutput> ArtifactOutput for Project<T> {
fn output_to_artifacts( fn output_to_artifacts(
&self, &self,
contracts: &VersionedContracts, contracts: &VersionedContracts,
sources: &BTreeMap<String, SourceFile>, sources: &VersionedSourceFiles,
) -> Artifacts<Self::Artifact> { ) -> Artifacts<Self::Artifact> {
self.artifacts_handler().output_to_artifacts(contracts, sources) self.artifacts_handler().output_to_artifacts(contracts, sources)
} }