feat(solc): add configurable Artifact type (#907)

* refactor: make artifacts a sub mod

* feat: more options for output selection

* feat(solc): make ArtifactOutput struct functions

* fix: migrate all features

* feat: add configurable artifacts type

* refactor: move configurable to separate file

* feat: impl ArtifactOutput

* refactor: write extras

* simplify write extra

* feat: more helper functions

* feat: implement delegate

* fix: failing doc test

* fix: rustfmt

* chore: update CHANGELOG

* feat: add helper functions

* refactor: remove flatten

* feat: add link function

* feat: replace default type

* fix: doc tests

* feat: more utility functions

* fix: failing tests

* chore: rename types

* chore: bump ethers-solc 0.3.0

* fix: set metadata file extension properly
This commit is contained in:
Matthias Seitz 2022-02-17 16:31:35 +01:00 committed by GitHub
parent 8ce58bfcf3
commit 19d2fd1155
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 935 additions and 152 deletions

View File

@ -54,6 +54,8 @@
### Unreleased
- Let `Project` take ownership of `ArtifactOutput` and change trait interface
[#907](https://github.com/gakonst/ethers-rs/pull/907)
- Total revamp of the `Project::compile` pipeline
[#802](https://github.com/gakonst/ethers-rs/pull/802)
- Support multiple versions of compiled contracts

2
Cargo.lock generated
View File

@ -1375,7 +1375,7 @@ dependencies = [
[[package]]
name = "ethers-solc"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"colored",
"criterion",

View File

@ -88,7 +88,7 @@ ethers-core = { version = "^0.6.0", default-features = false, path = "./ethers-c
ethers-providers = { version = "^0.6.0", default-features = false, path = "./ethers-providers" }
ethers-signers = { version = "^0.6.0", default-features = false, path = "./ethers-signers" }
ethers-middleware = { version = "^0.6.0", default-features = false, path = "./ethers-middleware" }
ethers-solc = { version = "^0.2.0", default-features = false, path = "./ethers-solc" }
ethers-solc = { version = "^0.3.0", default-features = false, path = "./ethers-solc" }
ethers-etherscan = { version = "^0.2.0", default-features = false, path = "./ethers-etherscan" }
[dev-dependencies]

View File

@ -32,7 +32,7 @@ ethers-contract-abigen = { version = "^0.6.0", path = "ethers-contract-abigen" }
ethers-contract-derive = { version = "^0.6.0", path = "ethers-contract-derive" }
ethers-core = { version = "^0.6.0", path = "../ethers-core", default-features = false, features = ["eip712"]}
ethers-derive-eip712 = { version = "^0.2.0", path = "../ethers-core/ethers-derive-eip712"}
ethers-solc = { version = "^0.2.0", path = "../ethers-solc", default-features = false }
ethers-solc = { version = "^0.3.0", path = "../ethers-solc", default-features = false }
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
tokio = { version = "1.5", default-features = false, features = ["macros"] }

View File

@ -15,7 +15,7 @@ keywords = ["ethereum", "web3", "etherscan", "ethers"]
[dependencies]
ethers-core = { version = "^0.6.0", path = "../ethers-core", default-features = false }
ethers-solc = { version = "^0.2.0", path = "../ethers-solc", default-features = false }
ethers-solc = { version = "^0.3.0", path = "../ethers-solc", default-features = false }
reqwest = { version = "0.11.9", default-features = false, features = ["json"] }
serde = { version = "1.0.124", default-features = false, features = ["derive"] }
serde_json = { version = "1.0.64", default-features = false }

View File

@ -311,7 +311,7 @@ mod tests {
.sources(&root)
.build()
.expect("failed to resolve project paths");
let project = Project::<MinimalCombinedArtifacts>::builder()
let project = Project::builder()
.paths(paths)
.build()
.expect("failed to build the project");

View File

@ -42,7 +42,7 @@ hex = { version = "0.4.3", default-features = false, features = ["std"] }
rand = { version = "0.8.5", default-features = false }
ethers-providers = { version = "^0.6.0", path = "../ethers-providers", default-features = false, features = ["ws", "rustls"] }
once_cell = "1.8.0"
ethers-solc = { version = "^0.2.0", path = "../ethers-solc", default-features = false }
ethers-solc = { version = "^0.3.0", path = "../ethers-solc", default-features = false }
serial_test = "0.5.1"
[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]

View File

@ -1,6 +1,6 @@
[package]
name = "ethers-solc"
version = "0.2.0"
version = "0.3.0"
authors = ["Matthias Seitz <matthias.seitz@outlook.de>", "Georgios Konstantopoulos <me@gakonst.com>"]
license = "MIT OR Apache-2.0"
edition = "2018"

View File

@ -0,0 +1,486 @@
//! A configurable artifacts handler implementation
use crate::{
artifacts::{
output_selection::{ContractOutputSelection, EvmOutputSelection, EwasmOutputSelection},
CompactBytecode, CompactContract, CompactContractBytecode, CompactDeployedBytecode,
CompactEvm, DevDoc, Ewasm, GasEstimates, Metadata, Offsets, Settings, StorageLayout,
UserDoc,
},
ArtifactOutput, Contract, SolcConfig, SolcError,
};
use ethers_core::abi::Abi;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, fs, path::Path};
/// Represents the `Artifact` that `ConfigurableArtifacts` emits.
///
/// This is essentially a superset of [`CompactContractBytecode`].
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ConfigurableContractArtifact {
/// The Ethereum Contract ABI. If empty, it is represented as an empty
/// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
pub abi: Option<Abi>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bytecode: Option<CompactBytecode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deployed_bytecode: Option<CompactDeployedBytecode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub assembly: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub method_identifiers: Option<BTreeMap<String, String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gas_estimates: Option<GasEstimates>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<Metadata>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub storage_layout: Option<StorageLayout>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub userdoc: Option<UserDoc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub devdoc: Option<DevDoc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ir: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ir_optimized: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub ewasm: Option<Ewasm>,
}
impl ConfigurableContractArtifact {
/// Returns the inner element that contains the core bytecode related information
pub fn into_contract_bytecode(self) -> CompactContractBytecode {
self.into()
}
/// Looks for all link references in deployment and runtime bytecodes
pub fn all_link_references(&self) -> BTreeMap<String, BTreeMap<String, Vec<Offsets>>> {
let mut links = BTreeMap::new();
if let Some(bcode) = &self.bytecode {
links.extend(bcode.link_references.clone());
}
if let Some(d_bcode) = &self.deployed_bytecode {
if let Some(bcode) = &d_bcode.bytecode {
links.extend(bcode.link_references.clone());
}
}
links
}
}
impl From<ConfigurableContractArtifact> for CompactContractBytecode {
fn from(artifact: ConfigurableContractArtifact) -> Self {
CompactContractBytecode {
abi: artifact.abi,
bytecode: artifact.bytecode,
deployed_bytecode: artifact.deployed_bytecode,
}
}
}
impl From<ConfigurableContractArtifact> for CompactContract {
fn from(artifact: ConfigurableContractArtifact) -> Self {
CompactContractBytecode::from(artifact).into()
}
}
/// An `Artifact` implementation that can be configured to include additional content and emit
/// additional files
///
/// Creates a single json artifact with
/// ```json
/// {
/// "abi": [],
/// "bytecode": {...},
/// "deployedBytecode": {...}
/// // additional values
/// }
/// ```
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub struct ConfigurableArtifacts {
/// A set of additional values to include in the contract's artifact file
pub additional_values: ExtraOutputValues,
/// A set of values that should be written to a separate file
pub additional_files: ExtraOutputFiles,
/// PRIVATE: This structure may grow, As such, constructing this structure should
/// _always_ be done using a public constructor or update syntax:
///
/// ```rust
///
/// use ethers_solc::{ExtraOutputFiles, ConfigurableArtifacts};
/// let config = ConfigurableArtifacts {
/// additional_files: ExtraOutputFiles { metadata: true, ..Default::default() },
/// ..Default::default()
/// };
/// ```
#[doc(hidden)]
pub __non_exhaustive: (),
}
impl ConfigurableArtifacts {
pub fn new(
extra_values: impl IntoIterator<Item = ContractOutputSelection>,
extra_files: impl IntoIterator<Item = ContractOutputSelection>,
) -> Self {
Self {
additional_values: ExtraOutputValues::from_output_selection(extra_values),
additional_files: ExtraOutputFiles::from_output_selection(extra_files),
..Default::default()
}
}
/// Returns the `Settings` this configuration corresponds to
pub fn settings(&self) -> Settings {
SolcConfig::builder().additional_outputs(self.output_selection()).build().into()
}
/// Returns the output selection corresponding to this configuration
pub fn output_selection(&self) -> Vec<ContractOutputSelection> {
let mut selection = ContractOutputSelection::basic();
if self.additional_values.ir {
selection.push(ContractOutputSelection::Ir);
}
if self.additional_values.ir_optimized || self.additional_files.ir_optimized {
selection.push(ContractOutputSelection::IrOptimized);
}
if self.additional_values.metadata || self.additional_files.metadata {
selection.push(ContractOutputSelection::Metadata);
}
if self.additional_values.storage_layout {
selection.push(ContractOutputSelection::StorageLayout);
}
if self.additional_values.devdoc {
selection.push(ContractOutputSelection::DevDoc);
}
if self.additional_values.userdoc {
selection.push(ContractOutputSelection::UserDoc);
}
if self.additional_values.gas_estimates {
selection.push(EvmOutputSelection::GasEstimates.into());
}
if self.additional_values.assembly || self.additional_files.assembly {
selection.push(EvmOutputSelection::Assembly.into());
}
if self.additional_values.ewasm || self.additional_files.ewasm {
selection.push(EwasmOutputSelection::All.into());
}
selection
}
}
impl ArtifactOutput for ConfigurableArtifacts {
type Artifact = ConfigurableContractArtifact;
fn write_contract_extras(&self, contract: &Contract, file: &Path) -> Result<(), SolcError> {
self.additional_files.write_extras(contract, file)
}
fn contract_to_artifact(&self, _file: &str, _name: &str, contract: Contract) -> Self::Artifact {
let mut artifact_userdoc = None;
let mut artifact_devdoc = None;
let mut artifact_metadata = None;
let mut artifact_ir = None;
let mut artifact_ir_optimized = None;
let mut artifact_ewasm = None;
let mut artifact_bytecode = None;
let mut artifact_deployed_bytecode = None;
let mut artifact_gas_estimates = None;
let mut artifact_method_identifiers = None;
let mut artifact_assembly = None;
let mut artifact_storage_layout = None;
let Contract {
abi,
metadata,
userdoc,
devdoc,
ir,
storage_layout,
evm,
ewasm,
ir_optimized,
} = contract;
if self.additional_values.metadata {
artifact_metadata = metadata;
}
if self.additional_values.userdoc {
artifact_userdoc = Some(userdoc);
}
if self.additional_values.devdoc {
artifact_devdoc = Some(devdoc);
}
if self.additional_values.ewasm {
artifact_ewasm = ewasm;
}
if self.additional_values.ir {
artifact_ir = ir;
}
if self.additional_values.ir_optimized {
artifact_ir_optimized = ir_optimized;
}
if self.additional_values.storage_layout {
artifact_storage_layout = Some(storage_layout);
}
if let Some(evm) = evm {
let CompactEvm {
assembly,
bytecode,
deployed_bytecode,
method_identifiers,
gas_estimates,
..
} = evm.into_compact();
artifact_bytecode = bytecode;
artifact_deployed_bytecode = deployed_bytecode;
if self.additional_values.gas_estimates {
artifact_gas_estimates = gas_estimates;
}
if self.additional_values.method_identifiers {
artifact_method_identifiers = Some(method_identifiers);
}
if self.additional_values.assembly {
artifact_assembly = assembly;
}
}
ConfigurableContractArtifact {
abi,
bytecode: artifact_bytecode,
deployed_bytecode: artifact_deployed_bytecode,
assembly: artifact_assembly,
method_identifiers: artifact_method_identifiers,
gas_estimates: artifact_gas_estimates,
metadata: artifact_metadata,
storage_layout: artifact_storage_layout,
userdoc: artifact_userdoc,
devdoc: artifact_devdoc,
ir: artifact_ir,
ir_optimized: artifact_ir_optimized,
ewasm: artifact_ewasm,
}
}
}
/// Determines the additional values to include in the contract's artifact file
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub struct ExtraOutputValues {
pub ast: bool,
pub userdoc: bool,
pub devdoc: bool,
pub method_identifiers: bool,
pub storage_layout: bool,
pub assembly: bool,
pub gas_estimates: bool,
pub compact_format: bool,
pub metadata: bool,
pub ir: bool,
pub ir_optimized: bool,
pub ewasm: bool,
/// PRIVATE: This structure may grow, As such, constructing this structure should
/// _always_ be done using a public constructor or update syntax:
///
/// ```rust
///
/// use ethers_solc::ExtraOutputValues;
/// let config = ExtraOutputValues {
/// ir: true,
/// ..Default::default()
/// };
/// ```
#[doc(hidden)]
pub __non_exhaustive: (),
}
impl ExtraOutputValues {
/// Returns an instance where all values are set to `true`
pub fn all() -> Self {
Self {
ast: true,
userdoc: true,
devdoc: true,
method_identifiers: true,
storage_layout: true,
assembly: true,
gas_estimates: true,
compact_format: true,
metadata: true,
ir: true,
ir_optimized: true,
ewasm: true,
__non_exhaustive: (),
}
}
/// Sets the values based on a set of `ContractOutputSelection`
pub fn from_output_selection(
settings: impl IntoIterator<Item = ContractOutputSelection>,
) -> Self {
let mut config = Self::default();
for value in settings.into_iter() {
match value {
ContractOutputSelection::DevDoc => {
config.devdoc = true;
}
ContractOutputSelection::UserDoc => {
config.userdoc = true;
}
ContractOutputSelection::Metadata => {
config.metadata = true;
}
ContractOutputSelection::Ir => {
config.ir = true;
}
ContractOutputSelection::IrOptimized => {
config.ir_optimized = true;
}
ContractOutputSelection::StorageLayout => {
config.storage_layout = true;
}
ContractOutputSelection::Evm(evm) => match evm {
EvmOutputSelection::All => {
config.assembly = true;
config.gas_estimates = true;
config.method_identifiers = true;
}
EvmOutputSelection::Assembly => {
config.assembly = true;
}
EvmOutputSelection::MethodIdentifiers => {
config.method_identifiers = true;
}
EvmOutputSelection::GasEstimates => {
config.gas_estimates = true;
}
_ => {}
},
ContractOutputSelection::Ewasm(_) => {
config.ewasm = true;
}
_ => {}
}
}
config
}
}
/// Determines what to emit as additional file
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub struct ExtraOutputFiles {
pub metadata: bool,
pub ir_optimized: bool,
pub ewasm: bool,
pub assembly: bool,
/// PRIVATE: This structure may grow, As such, constructing this structure should
/// _always_ be done using a public constructor or update syntax:
///
/// ```rust
///
/// use ethers_solc::ExtraOutputFiles;
/// let config = ExtraOutputFiles {
/// metadata: true,
/// ..Default::default()
/// };
/// ```
#[doc(hidden)]
pub __non_exhaustive: (),
}
impl ExtraOutputFiles {
/// Returns an instance where all values are set to `true`
pub fn all() -> Self {
Self {
metadata: true,
ir_optimized: true,
ewasm: true,
assembly: true,
__non_exhaustive: (),
}
}
/// Sets the values based on a set of `ContractOutputSelection`
pub fn from_output_selection(
settings: impl IntoIterator<Item = ContractOutputSelection>,
) -> Self {
let mut config = Self::default();
for value in settings.into_iter() {
match value {
ContractOutputSelection::Metadata => {
config.metadata = true;
}
ContractOutputSelection::IrOptimized => {
config.ir_optimized = true;
}
ContractOutputSelection::Evm(evm) => match evm {
EvmOutputSelection::All => {
config.assembly = true;
}
EvmOutputSelection::Assembly => {
config.assembly = true;
}
_ => {}
},
ContractOutputSelection::Ewasm(_) => {
config.ewasm = true;
}
_ => {}
}
}
config
}
/// Write the set values as separate files
pub fn write_extras(&self, contract: &Contract, file: &Path) -> Result<(), SolcError> {
if self.metadata {
if let Some(ref metadata) = contract.metadata {
let file = file.with_extension("metadata.json");
fs::write(&file, serde_json::to_string_pretty(metadata)?)
.map_err(|err| SolcError::io(err, file))?
}
}
if self.ir_optimized {
if let Some(ref iropt) = contract.ir_optimized {
let file = file.with_extension("iropt");
fs::write(&file, iropt).map_err(|err| SolcError::io(err, file))?
}
}
if self.ewasm {
if let Some(ref ir) = contract.ir {
let file = file.with_extension("ir");
fs::write(&file, ir).map_err(|err| SolcError::io(err, file))?
}
}
if self.ewasm {
if let Some(ref ewasm) = contract.ewasm {
let file = file.with_extension("ewasm");
fs::write(&file, serde_json::to_vec_pretty(ewasm)?)
.map_err(|err| SolcError::io(err, file))?;
}
}
if self.assembly {
if let Some(ref evm) = contract.evm {
if let Some(ref asm) = evm.assembly {
let file = file.with_extension("asm");
fs::write(&file, asm).map_err(|err| SolcError::io(err, file))?
}
}
}
Ok(())
}
}

View File

@ -15,6 +15,9 @@ use std::{
path::{Path, PathBuf},
};
mod configurable;
pub use configurable::*;
/// Represents an artifact file representing a [`crate::Contract`]
#[derive(Debug, Clone, PartialEq)]
pub struct ArtifactFile<T> {
@ -314,18 +317,24 @@ pub trait ArtifactOutput {
/// This will be invoked with all aggregated contracts from (multiple) solc `CompilerOutput`.
/// See [`crate::AggregatedCompilerOutput`]
fn on_output(
&self,
contracts: &VersionedContracts,
layout: &ProjectPathsConfig,
) -> Result<Artifacts<Self::Artifact>> {
let mut artifacts = Self::output_to_artifacts(contracts);
let mut artifacts = self.output_to_artifacts(contracts);
artifacts.join_all(&layout.artifacts);
artifacts.write_all()?;
Self::write_extras(contracts, layout)?;
self.write_extras(contracts, layout)?;
Ok(artifacts)
}
/// Write additional files for the contract
fn write_contract_extras(&self, contract: &Contract, file: &Path) -> Result<()> {
ExtraOutputFiles::all().write_extras(contract, file)
}
/// Writes additional files for the contracts if the included in the `Contract`, such as `ir`,
/// `ewasm`, `iropt`.
///
@ -334,7 +343,11 @@ pub trait ArtifactOutput {
/// [`Contract`] will `None`. If they'll be manually added to the `output_selection`, then
/// we're also creating individual files for this output, such as `Greeter.iropt`,
/// `Gretter.ewasm`
fn write_extras(contracts: &VersionedContracts, layout: &ProjectPathsConfig) -> Result<()> {
fn write_extras(
&self,
contracts: &VersionedContracts,
layout: &ProjectPathsConfig,
) -> Result<()> {
for (file, contracts) in contracts.as_ref().iter() {
for (name, versioned_contracts) in contracts {
for c in versioned_contracts {
@ -347,30 +360,7 @@ pub trait ArtifactOutput {
let file = layout.artifacts.join(artifact_path);
utils::create_parent_dir_all(&file)?;
if let Some(iropt) = &c.contract.ir_optimized {
fs::write(&file.with_extension("iropt"), iropt)
.map_err(|err| SolcError::io(err, file.with_extension("iropt")))?
}
if let Some(ir) = &c.contract.ir {
fs::write(&file.with_extension("ir"), ir)
.map_err(|err| SolcError::io(err, file.with_extension("ir")))?
}
if let Some(ewasm) = &c.contract.ewasm {
fs::write(
&file.with_extension("ewasm"),
serde_json::to_vec_pretty(&ewasm)?,
)
.map_err(|err| SolcError::io(err, file.with_extension("ewasm")))?;
}
if let Some(evm) = &c.contract.evm {
if let Some(asm) = &evm.assembly {
fs::write(&file.with_extension("asm"), asm)
.map_err(|err| SolcError::io(err, file.with_extension("asm")))?
}
}
self.write_contract_extras(&c.contract, &file)?;
}
}
}
@ -474,13 +464,13 @@ pub trait ArtifactOutput {
///
/// This is the core conversion function that takes care of converting a `Contract` into the
/// associated `Artifact` type
fn contract_to_artifact(_file: &str, _name: &str, contract: Contract) -> Self::Artifact;
fn contract_to_artifact(&self, _file: &str, _name: &str, contract: Contract) -> Self::Artifact;
/// Convert the compiler output into a set of artifacts
///
/// **Note:** This does only convert, but _NOT_ write the artifacts to disk, See
/// [`Self::on_output()`]
fn output_to_artifacts(contracts: &VersionedContracts) -> Artifacts<Self::Artifact> {
fn output_to_artifacts(&self, contracts: &VersionedContracts) -> Artifacts<Self::Artifact> {
let mut artifacts = ArtifactsMap::new();
for (file, contracts) in contracts.as_ref().iter() {
let mut entries = BTreeMap::new();
@ -493,8 +483,7 @@ pub trait ArtifactOutput {
} else {
Self::output_file(file, name)
};
let artifact =
Self::contract_to_artifact(file, name, contract.contract.clone());
let artifact = self.contract_to_artifact(file, name, contract.contract.clone());
contracts.push(ArtifactFile {
artifact,
@ -511,40 +500,45 @@ pub trait ArtifactOutput {
}
}
/// An Artifacts implementation that uses a compact representation
/// An `Artifact` implementation that uses a compact representation
///
/// Creates a single json artifact with
/// ```json
/// {
/// "abi": [],
/// "bin": "...",
/// "runtime-bin": "..."
/// "bytecode": {...},
/// "deployedBytecode": {...}
/// }
/// ```
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct MinimalCombinedArtifacts;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub struct MinimalCombinedArtifacts {
_priv: (),
}
impl ArtifactOutput for MinimalCombinedArtifacts {
type Artifact = CompactContractBytecode;
fn contract_to_artifact(_file: &str, _name: &str, contract: Contract) -> Self::Artifact {
fn contract_to_artifact(&self, _file: &str, _name: &str, contract: Contract) -> Self::Artifact {
Self::Artifact::from(contract)
}
}
/// An Artifacts handler implementation that works the same as `MinimalCombinedArtifacts` but also
/// supports reading hardhat artifacts if an initial attempt to deserialize an artifact failed
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct MinimalCombinedArtifactsHardhatFallback;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub struct MinimalCombinedArtifactsHardhatFallback {
_priv: (),
}
impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback {
type Artifact = CompactContractBytecode;
fn on_output(
&self,
output: &VersionedContracts,
layout: &ProjectPathsConfig,
) -> Result<Artifacts<Self::Artifact>> {
MinimalCombinedArtifacts::on_output(output, layout)
MinimalCombinedArtifacts::default().on_output(output, layout)
}
fn read_cached_artifact(path: impl AsRef<Path>) -> Result<Self::Artifact> {
@ -561,8 +555,8 @@ impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback {
}
}
fn contract_to_artifact(file: &str, name: &str, contract: Contract) -> Self::Artifact {
MinimalCombinedArtifacts::contract_to_artifact(file, name, contract)
fn contract_to_artifact(&self, file: &str, name: &str, contract: Contract) -> Self::Artifact {
MinimalCombinedArtifacts::default().contract_to_artifact(file, name, contract)
}
}

View File

@ -24,6 +24,7 @@ use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
pub mod output_selection;
pub mod serde_helpers;
use crate::artifacts::output_selection::ContractOutputSelection;
pub use serde_helpers::{deserialize_bytes, deserialize_opt_bytes};
/// Solidity files are made up of multiple `source units`, a solidity contract is such a `source
@ -229,6 +230,25 @@ impl Settings {
)])
}
/// Inserts a set of `ContractOutputSelection`
pub fn push_all(&mut self, settings: impl IntoIterator<Item = ContractOutputSelection>) {
for value in settings {
self.push_output_selection(value)
}
}
/// Inserts a set of `ContractOutputSelection`
#[must_use]
pub fn with_extra_output(
mut self,
settings: impl IntoIterator<Item = ContractOutputSelection>,
) -> Self {
for value in settings {
self.push_output_selection(value)
}
self
}
/// Inserts the value for all files and contracts
///
/// ```
@ -922,6 +942,7 @@ impl From<Contract> for ContractBytecode {
/// Unlike `CompactContractSome` which contains the `BytecodeObject`, this holds the whole
/// `Bytecode` object.
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct CompactContractBytecode {
/// The Ethereum Contract ABI. If empty, it is represented as an empty
/// array. See https://docs.soliditylang.org/en/develop/abi-spec.html
@ -952,13 +973,8 @@ impl CompactContractBytecode {
impl From<Contract> for CompactContractBytecode {
fn from(c: Contract) -> Self {
let (bytecode, deployed_bytecode) = if let Some(evm) = c.evm {
let (maybe_bcode, maybe_runtime) = match (evm.bytecode, evm.deployed_bytecode) {
(Some(bcode), Some(dbcode)) => (Some(bcode.into()), Some(dbcode.into())),
(None, Some(dbcode)) => (None, Some(dbcode.into())),
(Some(bcode), None) => (Some(bcode.into()), None),
(None, None) => (None, None),
};
(maybe_bcode, maybe_runtime)
let evm = evm.into_compact();
(evm.bytecode, evm.deployed_bytecode)
} else {
(None, None)
};
@ -1336,6 +1352,55 @@ pub struct Evm {
pub gas_estimates: Option<GasEstimates>,
}
impl Evm {
/// Crate internal helper do transform the underlying bytecode artifacts into a more convenient
/// structure
pub(crate) fn into_compact(self) -> CompactEvm {
let Evm {
assembly,
legacy_assembly,
bytecode,
deployed_bytecode,
method_identifiers,
gas_estimates,
} = self;
let (bytecode, deployed_bytecode) = match (bytecode, deployed_bytecode) {
(Some(bcode), Some(dbcode)) => (Some(bcode.into()), Some(dbcode.into())),
(None, Some(dbcode)) => (None, Some(dbcode.into())),
(Some(bcode), None) => (Some(bcode.into()), None),
(None, None) => (None, None),
};
CompactEvm {
assembly,
legacy_assembly,
bytecode,
deployed_bytecode,
method_identifiers,
gas_estimates,
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub(crate) struct CompactEvm {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub assembly: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub legacy_assembly: Option<serde_json::Value>,
pub bytecode: Option<CompactBytecode>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub deployed_bytecode: Option<CompactDeployedBytecode>,
/// The list of function hashes
#[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
pub method_identifiers: BTreeMap<String, String>,
/// Function gas estimates
#[serde(default, skip_serializing_if = "Option::is_none")]
pub gas_estimates: Option<GasEstimates>,
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Bytecode {

View File

@ -4,7 +4,7 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::{fmt, str::FromStr};
/// Contract level output selection
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ContractOutputSelection {
Abi,
DevDoc,
@ -17,6 +17,23 @@ pub enum ContractOutputSelection {
Ewasm(EwasmOutputSelection),
}
impl ContractOutputSelection {
/// Returns the basic set of contract level settings that should be included in the `Contract`
/// that solc emits:
/// - "abi"
/// - "evm.bytecode"
/// - "evm.deployedBytecode"
/// - "evm.methodIdentifiers"
pub fn basic() -> Vec<ContractOutputSelection> {
vec![
ContractOutputSelection::Abi,
BytecodeOutputSelection::All.into(),
DeployedBytecodeOutputSelection::All.into(),
EvmOutputSelection::MethodIdentifiers.into(),
]
}
}
impl Serialize for ContractOutputSelection {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -61,8 +78,12 @@ impl FromStr for ContractOutputSelection {
"userdoc" => Ok(ContractOutputSelection::UserDoc),
"metadata" => Ok(ContractOutputSelection::Metadata),
"ir" => Ok(ContractOutputSelection::Ir),
"irOptimized" => Ok(ContractOutputSelection::IrOptimized),
"storageLayout" => Ok(ContractOutputSelection::StorageLayout),
"ir-optimized" | "irOptimized" | "iroptimized" => {
Ok(ContractOutputSelection::IrOptimized)
}
"storage-layout" | "storagelayout" | "storageLayout" => {
Ok(ContractOutputSelection::StorageLayout)
}
s => EvmOutputSelection::from_str(s)
.map(ContractOutputSelection::Evm)
.or_else(|_| EwasmOutputSelection::from_str(s).map(ContractOutputSelection::Ewasm))
@ -71,8 +92,20 @@ impl FromStr for ContractOutputSelection {
}
}
impl<T: Into<EvmOutputSelection>> From<T> for ContractOutputSelection {
fn from(evm: T) -> Self {
ContractOutputSelection::Evm(evm.into())
}
}
impl From<EwasmOutputSelection> for ContractOutputSelection {
fn from(ewasm: EwasmOutputSelection) -> Self {
ContractOutputSelection::Ewasm(ewasm)
}
}
/// Contract level output selection for `evm`
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum EvmOutputSelection {
All,
Assembly,
@ -83,6 +116,18 @@ pub enum EvmOutputSelection {
DeployedByteCode(DeployedBytecodeOutputSelection),
}
impl From<BytecodeOutputSelection> for EvmOutputSelection {
fn from(b: BytecodeOutputSelection) -> Self {
EvmOutputSelection::ByteCode(b)
}
}
impl From<DeployedBytecodeOutputSelection> for EvmOutputSelection {
fn from(b: DeployedBytecodeOutputSelection) -> Self {
EvmOutputSelection::DeployedByteCode(b)
}
}
impl Serialize for EvmOutputSelection {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@ -121,10 +166,12 @@ impl FromStr for EvmOutputSelection {
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"evm" => Ok(EvmOutputSelection::All),
"evm.assembly" => Ok(EvmOutputSelection::Assembly),
"asm" | "evm.assembly" => Ok(EvmOutputSelection::Assembly),
"evm.legacyAssembly" => Ok(EvmOutputSelection::LegacyAssembly),
"evm.methodIdentifiers" => Ok(EvmOutputSelection::MethodIdentifiers),
"evm.gasEstimates" => Ok(EvmOutputSelection::GasEstimates),
"methodidentifiers" | "evm.methodIdentifiers" | "evm.methodidentifiers" => {
Ok(EvmOutputSelection::MethodIdentifiers)
}
"gas" | "evm.gasEstimates" | "evm.gasestimates" => Ok(EvmOutputSelection::GasEstimates),
s => BytecodeOutputSelection::from_str(s)
.map(EvmOutputSelection::ByteCode)
.or_else(|_| {
@ -137,7 +184,7 @@ impl FromStr for EvmOutputSelection {
}
/// Contract level output selection for `evm.bytecode`
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum BytecodeOutputSelection {
All,
FunctionDebugData,
@ -202,7 +249,7 @@ impl FromStr for BytecodeOutputSelection {
}
/// Contract level output selection for `evm.deployedBytecode`
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum DeployedBytecodeOutputSelection {
All,
FunctionDebugData,
@ -277,7 +324,7 @@ impl FromStr for DeployedBytecodeOutputSelection {
}
/// Contract level output selection for `evm.ewasm`
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum EwasmOutputSelection {
All,
Wast,

View File

@ -1,9 +1,11 @@
//! The output of a compiled project
use crate::{
artifacts::{CompactContractRef, Contract, Error, SourceFile, SourceFiles},
artifacts::{
CompactContractBytecode, CompactContractRef, Contract, Error, SourceFile, SourceFiles,
},
contracts::{VersionedContract, VersionedContracts},
ArtifactOutput, Artifacts, CompilerOutput,
ArtifactOutput, Artifacts, CompilerOutput, ConfigurableArtifacts,
};
use semver::Version;
use std::{collections::BTreeMap, fmt, path::Path};
@ -11,7 +13,7 @@ use std::{collections::BTreeMap, fmt, path::Path};
/// 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)]
pub struct ProjectCompileOutput<T: ArtifactOutput> {
pub struct ProjectCompileOutput<T: ArtifactOutput = ConfigurableArtifacts> {
/// contains the aggregated `CompilerOutput`
///
/// See [`CompilerSources::compile`]
@ -33,11 +35,11 @@ impl<T: ArtifactOutput> ProjectCompileOutput<T> {
///
/// ```no_run
/// use std::collections::btree_map::BTreeMap;
/// use ethers_solc::artifacts::CompactContractBytecode;
/// use ethers_solc::ConfigurableContractArtifact;
/// use ethers_solc::Project;
///
/// let project = Project::builder().build().unwrap();
/// let contracts: BTreeMap<String, CompactContractBytecode> = project.compile().unwrap().into_artifacts().collect();
/// let contracts: BTreeMap<String, ConfigurableContractArtifact> = project.compile().unwrap().into_artifacts().collect();
/// ```
pub fn into_artifacts(self) -> impl Iterator<Item = (String, T::Artifact)> {
let Self { cached_artifacts, compiled_artifacts, .. } = self;
@ -53,11 +55,10 @@ impl<T: ArtifactOutput> ProjectCompileOutput<T> {
///
/// ```no_run
/// use std::collections::btree_map::BTreeMap;
/// use ethers_solc::artifacts::CompactContractBytecode;
/// use ethers_solc::Project;
/// use ethers_solc::{ConfigurableContractArtifact, Project};
///
/// let project = Project::builder().build().unwrap();
/// let contracts: Vec<(String, String, CompactContractBytecode)> = project.compile().unwrap().into_artifacts_with_files().collect();
/// let contracts: Vec<(String, String, ConfigurableContractArtifact)> = project.compile().unwrap().into_artifacts_with_files().collect();
/// ```
///
/// **NOTE** the `file` will be returned as is, see also [`Self::with_stripped_file_prefixes()`]
@ -173,6 +174,27 @@ where
}
}
impl ProjectCompileOutput<ConfigurableArtifacts> {
/// A helper functions that extracts the underlying [`CompactContractBytecode`] from the
/// [`ConfigurableContractArtifact`]
///
/// # Example
///
/// ```no_run
/// use std::collections::btree_map::BTreeMap;
/// use ethers_solc::artifacts::CompactContractBytecode;
/// use ethers_solc::Project;
///
/// let project = Project::builder().build().unwrap();
/// let contracts: BTreeMap<String, CompactContractBytecode> = project.compile().unwrap().into_contract_bytecodes().collect();
/// ```
pub fn into_contract_bytecodes(
self,
) -> impl Iterator<Item = (String, CompactContractBytecode)> {
self.into_artifacts().map(|(name, artifact)| (name, artifact.into_contract_bytecode()))
}
}
impl<T: ArtifactOutput> fmt::Display for ProjectCompileOutput<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.compiler_output.is_unchanged() {

View File

@ -219,11 +219,15 @@ impl<'a, T: ArtifactOutput> CompiledState<'a, T> {
/// Writes all output contracts to disk if enabled in the `Project`
fn write_artifacts(self) -> Result<ArtifactsState<'a, T>> {
let CompiledState { output, cache } = self;
// write all artifacts
let compiled_artifacts = if !cache.project().no_artifacts {
T::on_output(&output.contracts, &cache.project().paths)?
cache
.project()
.artifacts_handler()
.on_output(&output.contracts, &cache.project().paths)?
} else {
T::output_to_artifacts(&output.contracts)
cache.project().artifacts_handler().output_to_artifacts(&output.contracts)
};
Ok(ArtifactsState { output, cache, compiled_artifacts })

View File

@ -7,6 +7,7 @@ use crate::{
utils, Source, Sources,
};
use crate::artifacts::output_selection::ContractOutputSelection;
use serde::{Deserialize, Serialize};
use std::{
fmt::{self, Formatter},
@ -465,9 +466,18 @@ impl SolcConfig {
}
}
impl From<SolcConfig> for Settings {
fn from(config: SolcConfig) -> Self {
config.settings
}
}
#[derive(Default)]
pub struct SolcConfigBuilder {
settings: Option<Settings>,
/// additionally selected outputs that should be included in the `Contract` that `solc´ creates
output_selection: Vec<ContractOutputSelection>,
}
impl SolcConfigBuilder {
@ -476,12 +486,34 @@ impl SolcConfigBuilder {
self
}
/// Adds another `ContractOutputSelection` to the set
#[must_use]
pub fn additional_output(mut self, output: impl Into<ContractOutputSelection>) -> Self {
self.output_selection.push(output.into());
self
}
/// Adds multiple `ContractOutputSelection` to the set
#[must_use]
pub fn additional_outputs<I, S>(mut self, outputs: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<ContractOutputSelection>,
{
for out in outputs {
self = self.additional_output(out);
}
self
}
/// Creates the solc config
///
/// If no solc version is configured then it will be determined by calling `solc --version`.
pub fn build(self) -> SolcConfig {
let Self { settings } = self;
SolcConfig { settings: settings.unwrap_or_default() }
let Self { settings, output_selection } = self;
let mut settings = settings.unwrap_or_default();
settings.push_all(output_selection);
SolcConfig { settings }
}
}

View File

@ -78,13 +78,15 @@ impl From<HardhatArtifact> for CompactContractBytecode {
}
/// Hardhat style artifacts handler
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub struct HardhatArtifacts;
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
pub struct HardhatArtifacts {
_priv: (),
}
impl ArtifactOutput for HardhatArtifacts {
type Artifact = HardhatArtifact;
fn contract_to_artifact(file: &str, name: &str, contract: Contract) -> Self::Artifact {
fn contract_to_artifact(&self, file: &str, name: &str, contract: Contract) -> Self::Artifact {
let (bytecode, link_references, deployed_bytecode, deployed_link_references) =
if let Some(evm) = contract.evm {
let (deployed_bytecode, deployed_link_references) =

View File

@ -2,6 +2,7 @@ pub mod artifacts;
pub mod sourcemap;
pub use artifacts::{CompilerInput, CompilerOutput, EvmVersion};
use std::collections::BTreeMap;
mod artifact_output;
pub mod cache;
@ -30,13 +31,12 @@ pub mod utils;
use crate::{
artifacts::{Contract, Sources},
contracts::VersionedContracts,
error::{SolcError, SolcIoError},
};
use error::Result;
use std::{
marker::PhantomData,
path::{Path, PathBuf},
};
use semver::Version;
use std::path::{Path, PathBuf};
/// Utilities for creating, mocking and testing of (temporary) projects
#[cfg(feature = "project-util")]
@ -44,7 +44,7 @@ pub mod project_util;
/// Represents a project workspace and handles `solc` compiling of all contracts in that workspace.
#[derive(Debug)]
pub struct Project<Artifacts: ArtifactOutput = MinimalCombinedArtifacts> {
pub struct Project<T: ArtifactOutput = ConfigurableArtifacts> {
/// The layout of the
pub paths: ProjectPathsConfig,
/// Where to find solc
@ -57,8 +57,8 @@ pub struct Project<Artifacts: ArtifactOutput = MinimalCombinedArtifacts> {
pub no_artifacts: bool,
/// Whether writing artifacts to disk is enabled
pub auto_detect: bool,
/// How to handle compiler output
pub artifacts: PhantomData<Artifacts>,
/// Handles all artifacts related tasks, reading and writing from the artifact dir.
pub artifacts: T,
/// Errors/Warnings which match these error codes are not going to be logged
pub ignored_error_codes: Vec<u64>,
/// The paths which will be allowed for library inclusion
@ -74,7 +74,7 @@ impl Project {
///
/// # Example
///
/// Configure with `MinimalCombinedArtifacts` artifacts output
/// Configure with `ConfigurableArtifacts` artifacts output
///
/// ```rust
/// use ethers_solc::Project;
@ -91,15 +91,15 @@ impl Project {
/// or use the builder directly
///
/// ```rust
/// use ethers_solc::{MinimalCombinedArtifacts, ProjectBuilder};
/// let config = ProjectBuilder::<MinimalCombinedArtifacts>::default().build().unwrap();
/// use ethers_solc::{ConfigurableArtifacts, ProjectBuilder};
/// let config = ProjectBuilder::<ConfigurableArtifacts>::default().build().unwrap();
/// ```
pub fn builder() -> ProjectBuilder {
ProjectBuilder::default()
}
}
impl<Artifacts: ArtifactOutput> Project<Artifacts> {
impl<T: ArtifactOutput> Project<T> {
/// Returns the path to the artifacts directory
pub fn artifacts_path(&self) -> &PathBuf {
&self.paths.artifacts
@ -120,6 +120,11 @@ impl<Artifacts: ArtifactOutput> Project<Artifacts> {
&self.paths.root
}
/// Returns the handler that takes care of processing all artifacts
pub fn artifacts_handler(&self) -> &T {
&self.artifacts
}
/// Applies the configured settings to the given `Solc`
fn configure_solc(&self, mut solc: Solc) -> Solc {
if self.allowed_lib_paths.0.is_empty() {
@ -187,7 +192,7 @@ impl<Artifacts: ArtifactOutput> Project<Artifacts> {
/// # }
/// ```
#[tracing::instrument(skip_all, name = "compile")]
pub fn compile(&self) -> Result<ProjectCompileOutput<Artifacts>> {
pub fn compile(&self) -> Result<ProjectCompileOutput<T>> {
let sources = self.paths.read_input_files()?;
tracing::trace!("found {} sources to compile: {:?}", sources.len(), sources.keys());
@ -226,7 +231,7 @@ impl<Artifacts: ArtifactOutput> Project<Artifacts> {
/// # }
/// ```
#[cfg(all(feature = "svm", feature = "async"))]
pub fn svm_compile(&self, sources: Sources) -> Result<ProjectCompileOutput<Artifacts>> {
pub fn svm_compile(&self, sources: Sources) -> Result<ProjectCompileOutput<T>> {
project::ProjectCompiler::with_sources(self, sources)?.compile()
}
@ -257,7 +262,7 @@ impl<Artifacts: ArtifactOutput> Project<Artifacts> {
&self,
solc: &Solc,
sources: Sources,
) -> Result<ProjectCompileOutput<Artifacts>> {
) -> Result<ProjectCompileOutput<T>> {
project::ProjectCompiler::with_sources_and_solc(
self,
sources,
@ -325,7 +330,7 @@ impl<Artifacts: ArtifactOutput> Project<Artifacts> {
}
}
pub struct ProjectBuilder<Artifacts: ArtifactOutput = MinimalCombinedArtifacts> {
pub struct ProjectBuilder<T: ArtifactOutput = ConfigurableArtifacts> {
/// The layout of the
paths: Option<ProjectPathsConfig>,
/// Where to find solc
@ -340,7 +345,8 @@ pub struct ProjectBuilder<Artifacts: ArtifactOutput = MinimalCombinedArtifacts>
auto_detect: bool,
/// Use offline mode
offline: bool,
artifacts: PhantomData<Artifacts>,
/// handles all artifacts related tasks
artifacts: T,
/// Which error codes to ignore
pub ignored_error_codes: Vec<u64>,
/// All allowed paths
@ -348,7 +354,24 @@ pub struct ProjectBuilder<Artifacts: ArtifactOutput = MinimalCombinedArtifacts>
solc_jobs: Option<usize>,
}
impl<Artifacts: ArtifactOutput> ProjectBuilder<Artifacts> {
impl<T: ArtifactOutput> ProjectBuilder<T> {
/// Create a new builder with the given artifacts handler
pub fn new(artifacts: T) -> Self {
Self {
paths: None,
solc: None,
solc_config: None,
cached: true,
no_artifacts: false,
auto_detect: true,
offline: false,
artifacts,
ignored_error_codes: Vec::new(),
allowed_paths: vec![],
solc_jobs: None,
}
}
#[must_use]
pub fn paths(mut self, paths: ProjectPathsConfig) -> Self {
self.paths = Some(paths);
@ -454,7 +477,7 @@ impl<Artifacts: ArtifactOutput> ProjectBuilder<Artifacts> {
}
/// Set arbitrary `ArtifactOutputHandler`
pub fn artifacts<A: ArtifactOutput>(self) -> ProjectBuilder<A> {
pub fn artifacts<A: ArtifactOutput>(self, artifacts: A) -> ProjectBuilder<A> {
let ProjectBuilder {
paths,
solc,
@ -476,7 +499,7 @@ impl<Artifacts: ArtifactOutput> ProjectBuilder<Artifacts> {
no_artifacts,
auto_detect,
offline,
artifacts: PhantomData::default(),
artifacts,
ignored_error_codes,
allowed_paths,
solc_jobs,
@ -485,7 +508,7 @@ impl<Artifacts: ArtifactOutput> ProjectBuilder<Artifacts> {
/// Adds an allowed-path to the solc executable
#[must_use]
pub fn allowed_path<T: Into<PathBuf>>(mut self, path: T) -> Self {
pub fn allowed_path<P: Into<PathBuf>>(mut self, path: P) -> Self {
self.allowed_paths.push(path.into());
self
}
@ -503,7 +526,7 @@ impl<Artifacts: ArtifactOutput> ProjectBuilder<Artifacts> {
self
}
pub fn build(self) -> Result<Project<Artifacts>> {
pub fn build(self) -> Result<Project<T>> {
let Self {
paths,
solc,
@ -544,29 +567,85 @@ impl<Artifacts: ArtifactOutput> ProjectBuilder<Artifacts> {
}
}
impl<Artifacts: ArtifactOutput> Default for ProjectBuilder<Artifacts> {
impl<T: ArtifactOutput + Default> Default for ProjectBuilder<T> {
fn default() -> Self {
Self {
paths: None,
solc: None,
solc_config: None,
cached: true,
no_artifacts: false,
auto_detect: true,
offline: false,
artifacts: PhantomData::default(),
ignored_error_codes: Vec::new(),
allowed_paths: vec![],
solc_jobs: None,
}
Self::new(T::default())
}
}
impl<Artifacts: ArtifactOutput> ArtifactOutput for Project<Artifacts> {
type Artifact = Artifacts::Artifact;
impl<T: ArtifactOutput> ArtifactOutput for Project<T> {
type Artifact = T::Artifact;
fn contract_to_artifact(file: &str, name: &str, contract: Contract) -> Self::Artifact {
Artifacts::contract_to_artifact(file, name, contract)
fn on_output(
&self,
contracts: &VersionedContracts,
layout: &ProjectPathsConfig,
) -> Result<Artifacts<Self::Artifact>> {
self.artifacts_handler().on_output(contracts, layout)
}
fn write_contract_extras(&self, contract: &Contract, file: &Path) -> Result<()> {
self.artifacts_handler().write_contract_extras(contract, file)
}
fn write_extras(
&self,
contracts: &VersionedContracts,
layout: &ProjectPathsConfig,
) -> Result<()> {
self.artifacts_handler().write_extras(contracts, layout)
}
fn output_file_name(name: impl AsRef<str>) -> PathBuf {
T::output_file_name(name)
}
fn output_file_name_versioned(name: impl AsRef<str>, version: &Version) -> PathBuf {
T::output_file_name_versioned(name, version)
}
fn output_file(contract_file: impl AsRef<Path>, name: impl AsRef<str>) -> PathBuf {
T::output_file(contract_file, name)
}
fn output_file_versioned(
contract_file: impl AsRef<Path>,
name: impl AsRef<str>,
version: &Version,
) -> PathBuf {
T::output_file_versioned(contract_file, name, version)
}
fn contract_name(file: impl AsRef<Path>) -> Option<String> {
T::contract_name(file)
}
fn output_exists(
contract_file: impl AsRef<Path>,
name: impl AsRef<str>,
root: impl AsRef<Path>,
) -> bool {
T::output_exists(contract_file, name, root)
}
fn read_cached_artifact(path: impl AsRef<Path>) -> Result<Self::Artifact> {
T::read_cached_artifact(path)
}
fn read_cached_artifacts<P, I>(files: I) -> Result<BTreeMap<PathBuf, Self::Artifact>>
where
I: IntoIterator<Item = P>,
P: Into<PathBuf>,
{
T::read_cached_artifacts(files)
}
fn contract_to_artifact(&self, file: &str, name: &str, contract: Contract) -> Self::Artifact {
self.artifacts_handler().contract_to_artifact(file, name, contract)
}
fn output_to_artifacts(&self, contracts: &VersionedContracts) -> Artifacts<Self::Artifact> {
self.artifacts_handler().output_to_artifacts(contracts)
}
}

View File

@ -1,10 +1,11 @@
//! Utilities for mocking project workspaces
use crate::{
artifacts::Settings,
config::ProjectPathsConfigBuilder,
error::{Result, SolcError},
hh::HardhatArtifacts,
utils::tempdir,
ArtifactOutput, MinimalCombinedArtifacts, PathStyle, Project, ProjectCompileOutput,
ArtifactOutput, ConfigurableArtifacts, PathStyle, Project, ProjectCompileOutput,
ProjectPathsConfig, SolcIoError,
};
use fs_extra::{dir, file};
@ -17,7 +18,7 @@ use tempfile::TempDir;
/// A [`Project`] wrapper that lives in a new temporary directory
///
/// Once `TempProject` is dropped, the temp dir is automatically removed, see [`TempDir::drop()`]
pub struct TempProject<T: ArtifactOutput = MinimalCombinedArtifacts> {
pub struct TempProject<T: ArtifactOutput = ConfigurableArtifacts> {
/// temporary workspace root
_root: TempDir,
/// actual project workspace with the `root` tempdir as its root
@ -32,26 +33,29 @@ impl<T: ArtifactOutput> TempProject<T> {
Ok(project)
}
/// Creates a new temp project inside a tempdir with a prefixed directory
pub fn prefixed(prefix: &str, paths: ProjectPathsConfigBuilder) -> Result<Self> {
/// Creates a new temp project using the provided paths and artifacts handler.
/// sets the project root to a temp dir
pub fn with_artifacts(paths: ProjectPathsConfigBuilder, artifacts: T) -> Result<Self> {
Self::prefixed_with_artifacts("temp-project", paths, artifacts)
}
/// Creates a new temp project inside a tempdir with a prefixed directory and the given
/// artifacts handler
pub fn prefixed_with_artifacts(
prefix: &str,
paths: ProjectPathsConfigBuilder,
artifacts: T,
) -> Result<Self> {
let tmp_dir = tempdir(prefix)?;
let paths = paths.build_with_root(tmp_dir.path());
let inner = Project::builder().artifacts().paths(paths).build()?;
let inner = Project::builder().artifacts(artifacts).paths(paths).build()?;
Ok(Self::create_new(tmp_dir, inner)?)
}
/// Creates a new temp project for the given `PathStyle`
pub fn with_style(prefix: &str, style: PathStyle) -> Result<Self> {
let tmp_dir = tempdir(prefix)?;
let paths = style.paths(tmp_dir.path())?;
let inner = Project::builder().artifacts().paths(paths).build()?;
Ok(Self::create_new(tmp_dir, inner)?)
}
/// Creates a new temp project using the provided paths and setting the project root to a temp
/// dir
pub fn new(paths: ProjectPathsConfigBuilder) -> Result<Self> {
Self::prefixed("temp-project", paths)
/// Overwrites the settings to pass to `solc`
pub fn with_settings(mut self, settings: impl Into<Settings>) -> Self {
self.inner.solc_config.settings = settings.into();
self
}
pub fn project(&self) -> &Project<T> {
@ -158,6 +162,27 @@ impl<T: ArtifactOutput> TempProject<T> {
}
}
impl<T: ArtifactOutput + Default> TempProject<T> {
/// Creates a new temp project inside a tempdir with a prefixed directory
pub fn prefixed(prefix: &str, paths: ProjectPathsConfigBuilder) -> Result<Self> {
Self::prefixed_with_artifacts(prefix, paths, T::default())
}
/// Creates a new temp project for the given `PathStyle`
pub fn with_style(prefix: &str, style: PathStyle) -> Result<Self> {
let tmp_dir = tempdir(prefix)?;
let paths = style.paths(tmp_dir.path())?;
let inner = Project::builder().artifacts(T::default()).paths(paths).build()?;
Ok(Self::create_new(tmp_dir, inner)?)
}
/// Creates a new temp project using the provided paths and setting the project root to a temp
/// dir
pub fn new(paths: ProjectPathsConfigBuilder) -> Result<Self> {
Self::prefixed("temp-project", paths)
}
}
impl<T: ArtifactOutput> fmt::Debug for TempProject<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("TempProject").field("paths", self.paths()).finish()
@ -189,18 +214,19 @@ impl TempProject<HardhatArtifacts> {
let paths = ProjectPathsConfig::hardhat(tmp_dir.path())?;
let inner = Project::builder().artifacts().paths(paths).build()?;
let inner =
Project::builder().artifacts(HardhatArtifacts::default()).paths(paths).build()?;
Ok(Self::create_new(tmp_dir, inner)?)
}
}
impl TempProject<MinimalCombinedArtifacts> {
impl TempProject<ConfigurableArtifacts> {
/// Creates an empty new dapptools style workspace in a new temporary dir
pub fn dapptools() -> Result<Self> {
let tmp_dir = tempdir("tmp_dapp")?;
let paths = ProjectPathsConfig::dapptools(tmp_dir.path())?;
let inner = Project::builder().artifacts().paths(paths).build()?;
let inner = Project::builder().paths(paths).build()?;
Ok(Self::create_new(tmp_dir, inner)?)
}
}

View File

@ -11,7 +11,8 @@ use ethers_solc::{
cache::{SolFilesCache, SOLIDITY_FILES_CACHE_FILENAME},
project_util::*,
remappings::Remapping,
Graph, MinimalCombinedArtifacts, Project, ProjectCompileOutput, ProjectPathsConfig,
ConfigurableArtifacts, ExtraOutputValues, Graph, Project, ProjectCompileOutput,
ProjectPathsConfig,
};
use pretty_assertions::assert_eq;
@ -28,7 +29,7 @@ fn can_compile_hardhat_sample() {
let paths = ProjectPathsConfig::builder()
.sources(root.join("contracts"))
.lib(root.join("node_modules"));
let project = TempProject::<MinimalCombinedArtifacts>::new(paths).unwrap();
let project = TempProject::<ConfigurableArtifacts>::new(paths).unwrap();
let compiled = project.compile().unwrap();
assert!(compiled.find("Greeter").is_some());
@ -53,7 +54,7 @@ fn can_compile_hardhat_sample() {
fn can_compile_dapp_sample() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample");
let paths = ProjectPathsConfig::builder().sources(root.join("src")).lib(root.join("lib"));
let project = TempProject::<MinimalCombinedArtifacts>::new(paths).unwrap();
let project = TempProject::<ConfigurableArtifacts>::new(paths).unwrap();
let compiled = project.compile().unwrap();
assert!(compiled.find("Dapp").is_some());
@ -76,9 +77,33 @@ fn can_compile_dapp_sample() {
assert_eq!(cache, updated_cache);
}
#[test]
fn can_compile_configured() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample");
let paths = ProjectPathsConfig::builder().sources(root.join("src")).lib(root.join("lib"));
let handler = ConfigurableArtifacts {
additional_values: ExtraOutputValues {
metadata: true,
ir: true,
ir_optimized: true,
..Default::default()
},
..Default::default()
};
let settings = handler.settings();
let project = TempProject::with_artifacts(paths, handler).unwrap().with_settings(settings);
let compiled = project.compile().unwrap();
let artifact = compiled.find("Dapp").unwrap();
assert!(artifact.metadata.is_some());
assert!(artifact.ir.is_some());
assert!(artifact.ir_optimized.is_some());
}
#[test]
fn can_compile_dapp_detect_changes_in_libs() {
let mut project = TempProject::<MinimalCombinedArtifacts>::dapptools().unwrap();
let mut project = TempProject::<ConfigurableArtifacts>::dapptools().unwrap();
let remapping = project.paths().libraries[0].join("remapping");
project
@ -151,8 +176,7 @@ fn can_compile_dapp_detect_changes_in_libs() {
#[test]
fn can_compile_dapp_detect_changes_in_sources() {
init_tracing();
let project = TempProject::<MinimalCombinedArtifacts>::dapptools().unwrap();
let project = TempProject::<ConfigurableArtifacts>::dapptools().unwrap();
let src = project
.add_source(
@ -330,7 +354,7 @@ fn can_flatten_file() {
.sources(root.join("src"))
.lib(root.join("lib1"))
.lib(root.join("lib2"));
let project = TempProject::<MinimalCombinedArtifacts>::new(paths).unwrap();
let project = TempProject::<ConfigurableArtifacts>::new(paths).unwrap();
let result = project.flatten(&target);
assert!(result.is_ok());
@ -346,7 +370,7 @@ fn can_flatten_file_with_external_lib() {
let paths = ProjectPathsConfig::builder()
.sources(root.join("contracts"))
.lib(root.join("node_modules"));
let project = TempProject::<MinimalCombinedArtifacts>::new(paths).unwrap();
let project = TempProject::<ConfigurableArtifacts>::new(paths).unwrap();
let target = root.join("contracts").join("Greeter.sol");
@ -362,7 +386,7 @@ fn can_flatten_file_with_external_lib() {
fn can_flatten_file_in_dapp_sample() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/dapp-sample");
let paths = ProjectPathsConfig::builder().sources(root.join("src")).lib(root.join("lib"));
let project = TempProject::<MinimalCombinedArtifacts>::new(paths).unwrap();
let project = TempProject::<ConfigurableArtifacts>::new(paths).unwrap();
let target = root.join("src/Dapp.t.sol");
@ -379,7 +403,7 @@ fn can_flatten_file_in_dapp_sample() {
fn can_flatten_file_with_duplicates() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("test-data/flatten-sample");
let paths = ProjectPathsConfig::builder().sources(root.join("contracts"));
let project = TempProject::<MinimalCombinedArtifacts>::new(paths).unwrap();
let project = TempProject::<ConfigurableArtifacts>::new(paths).unwrap();
let target = root.join("contracts/FooBar.sol");
@ -395,7 +419,7 @@ fn can_flatten_file_with_duplicates() {
#[test]
fn can_detect_type_error() {
let project = TempProject::<MinimalCombinedArtifacts>::dapptools().unwrap();
let project = TempProject::<ConfigurableArtifacts>::dapptools().unwrap();
project
.add_source(

View File

@ -43,4 +43,4 @@ hex = "0.4.3"
web-sys = "0.3.56"
[dev-dependencies]
wasm-bindgen-test = "0.3.29"
wasm-bindgen-test = "0.3.29"