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

View File

@ -46,10 +46,10 @@ pub type Contracts = FileToContractsMap<Contract>;
pub type Sources = BTreeMap<PathBuf, Source>;
/// 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
pub type VersionedFilteredSources = BTreeMap<Solc, (Version, FilteredSources)>;
pub(crate) type VersionedFilteredSources = BTreeMap<Solc, (Version, FilteredSources)>;
const SOLIDITY: &str = "Solidity";

View File

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

View File

@ -3,14 +3,18 @@
use crate::{
artifacts::{
contract::{CompactContractBytecode, CompactContractRef, Contract},
Error, SourceFile, SourceFiles,
Error,
},
contracts::{VersionedContract, VersionedContracts},
sources::{VersionedSourceFile, VersionedSourceFiles},
ArtifactId, ArtifactOutput, Artifacts, CompilerOutput, ConfigurableArtifacts,
};
use contracts::{VersionedContract, VersionedContracts};
use semver::Version;
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
/// need to be compiled.
#[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.
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;
(
@ -77,7 +83,7 @@ impl<T: ArtifactOutput> ProjectCompileOutput<T> {
.into_artifacts::<T>()
.chain(compiled_artifacts.into_artifacts::<T>())
.collect(),
SourceFiles(compiler_output.sources),
compiler_output.sources,
)
}
@ -228,8 +234,8 @@ impl<T: ArtifactOutput> fmt::Display for ProjectCompileOutput<T> {
pub struct AggregatedCompilerOutput {
/// all errors from all `CompilerOutput`
pub errors: Vec<Error>,
/// All source files
pub sources: BTreeMap<String, SourceFile>,
/// All source files combined with the solc version used to compile them
pub sources: VersionedSourceFiles,
/// All compiled contracts combined with the solc version used to compile them
pub contracts: VersionedContracts,
}
@ -274,10 +280,15 @@ impl AggregatedCompilerOutput {
/// adds a new `CompilerOutput` to the aggregated output
pub fn extend(&mut self, version: Version, output: CompilerOutput) {
self.errors.extend(output.errors);
self.sources.extend(output.sources);
let CompilerOutput { errors, sources, contracts } = output;
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();
for (contract_name, contract) in new_contracts {
let versioned = contracts.entry(contract_name).or_default();
@ -346,8 +357,8 @@ impl AggregatedCompilerOutput {
/// let (sources, contracts) = output.split();
/// # }
/// ```
pub fn split(self) -> (SourceFiles, VersionedContracts) {
(SourceFiles(self.sources), self.contracts)
pub fn split(self) -> (VersionedSourceFiles, VersionedContracts) {
(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();
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") {
assert!(source.ast.is_some());
} else {

View File

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