//! 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::output_selection::OutputSelection). In addition to //! that some output values can also be emitted as standalone files. use crate::{ artifacts::{ bytecode::{CompactBytecode, CompactDeployedBytecode}, contract::{CompactContract, CompactContractBytecode, Contract}, output_selection::{ BytecodeOutputSelection, ContractOutputSelection, EvmOutputSelection, EwasmOutputSelection, }, Ast, CompactContractBytecodeCow, DevDoc, Evm, Ewasm, FunctionDebugData, GasEstimates, GeneratedSource, LosslessAbi, LosslessMetadata, Metadata, Offsets, Settings, StorageLayout, UserDoc, }, sources::VersionedSourceFile, ArtifactOutput, SolcConfig, SolcError, SourceFile, }; use serde::{Deserialize, Serialize}; use std::{borrow::Cow, 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 pub abi: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub bytecode: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub deployed_bytecode: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub assembly: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub opcodes: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub method_identifiers: Option>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub generated_sources: Vec, #[serde(default, skip_serializing_if = "Option::is_none")] pub function_debug_data: Option>, #[serde(default, skip_serializing_if = "Option::is_none")] pub gas_estimates: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub raw_metadata: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub metadata: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub storage_layout: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub userdoc: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub devdoc: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub ir: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub ir_optimized: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub ewasm: Option, #[serde(default, skip_serializing_if = "Option::is_none")] pub ast: Option, /// The identifier of the source file #[serde(default, skip_serializing_if = "Option::is_none")] pub id: Option, } 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>> { 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 } /// Returns the source file of this artifact's contract pub fn source_file(&self) -> Option { self.id.map(|id| SourceFile { id, ast: self.ast.clone() }) } } impl From for CompactContractBytecode { fn from(artifact: ConfigurableContractArtifact) -> Self { CompactContractBytecode { abi: artifact.abi.map(Into::into), bytecode: artifact.bytecode, deployed_bytecode: artifact.deployed_bytecode, } } } impl From for CompactContract { fn from(artifact: ConfigurableContractArtifact) -> Self { CompactContractBytecode::from(artifact).into() } } impl<'a> From<&'a ConfigurableContractArtifact> for CompactContractBytecodeCow<'a> { fn from(artifact: &'a ConfigurableContractArtifact) -> Self { CompactContractBytecodeCow { abi: artifact.abi.as_ref().map(|abi| Cow::Borrowed(&abi.abi)), bytecode: artifact.bytecode.as_ref().map(Cow::Borrowed), deployed_bytecode: artifact.deployed_bytecode.as_ref().map(Cow::Borrowed), } } } /// 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": {...}, /// "methodIdentifiers": {...}, /// // 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, extra_files: impl IntoIterator, ) -> 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 { 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()); } if self.additional_values.function_debug_data { selection.push(BytecodeOutputSelection::FunctionDebugData.into()); } if self.additional_values.method_identifiers { selection.push(EvmOutputSelection::MethodIdentifiers.into()); } if self.additional_values.generated_sources { selection.push( EvmOutputSelection::ByteCode(BytecodeOutputSelection::GeneratedSources).into(), ); } if self.additional_values.source_map { selection.push(EvmOutputSelection::ByteCode(BytecodeOutputSelection::SourceMap).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, source_file: Option<&SourceFile>, ) -> Self::Artifact { let mut artifact_userdoc = None; let mut artifact_devdoc = None; let mut artifact_raw_metadata = 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_function_debug_data = None; let mut artifact_method_identifiers = None; let mut artifact_assembly = None; let mut artifact_storage_layout = None; let mut generated_sources = None; let mut opcodes = None; let Contract { abi, metadata, userdoc, devdoc, ir, storage_layout, evm, ewasm, ir_optimized, } = contract; if self.additional_values.metadata { if let Some(LosslessMetadata { raw_metadata, metadata }) = metadata { artifact_raw_metadata = Some(raw_metadata); artifact_metadata = Some(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 Evm { assembly, mut bytecode, deployed_bytecode, method_identifiers, gas_estimates, .. } = evm; if self.additional_values.function_debug_data { artifact_function_debug_data = 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); if self.additional_values.gas_estimates { artifact_gas_estimates = gas_estimates; } if self.additional_values.assembly { artifact_assembly = assembly; } } ConfigurableContractArtifact { abi, 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, raw_metadata: artifact_raw_metadata, 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, id: source_file.as_ref().map(|s| s.id), ast: source_file.and_then(|s| s.ast.clone()), generated_sources: generated_sources.unwrap_or_default(), } } fn standalone_source_file_to_artifact( &self, _path: &str, file: &VersionedSourceFile, ) -> Option { file.source_file.ast.clone().map(|ast| ConfigurableContractArtifact { abi: Some(LosslessAbi::default()), id: Some(file.source_file.id), ast: Some(ast), bytecode: Some(CompactBytecode::empty()), deployed_bytecode: Some(CompactDeployedBytecode::empty()), ..Default::default() }) } } /// 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, 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: /// /// ```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, function_debug_data: true, generated_sources: true, source_map: true, opcodes: true, __non_exhaustive: (), } } /// Sets the values based on a set of `ContractOutputSelection` pub fn from_output_selection( settings: impl IntoIterator, ) -> 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; config.generated_sources = true; config.source_map = true; config.opcodes = true; } EvmOutputSelection::Assembly => { config.assembly = true; } EvmOutputSelection::MethodIdentifiers => { config.method_identifiers = true; } EvmOutputSelection::GasEstimates => { config.gas_estimates = true; } EvmOutputSelection::ByteCode(BytecodeOutputSelection::FunctionDebugData) => { config.function_debug_data = true; } EvmOutputSelection::ByteCode(BytecodeOutputSelection::Opcodes) => { config.opcodes = true; } EvmOutputSelection::ByteCode(BytecodeOutputSelection::GeneratedSources) => { config.generated_sources = true; } EvmOutputSelection::ByteCode(BytecodeOutputSelection::SourceMap) => { config.source_map = 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 abi: bool, pub metadata: bool, pub ir_optimized: bool, pub ewasm: bool, pub assembly: bool, pub source_map: bool, pub generated_sources: 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 { abi: true, metadata: true, ir_optimized: true, ewasm: true, assembly: true, source_map: true, generated_sources: true, __non_exhaustive: (), } } /// Sets the values based on a set of `ContractOutputSelection` pub fn from_output_selection( settings: impl IntoIterator, ) -> Self { let mut config = Self::default(); for value in settings.into_iter() { match value { ContractOutputSelection::Abi => { config.abi = true; } ContractOutputSelection::Metadata => { config.metadata = true; } ContractOutputSelection::IrOptimized => { config.ir_optimized = true; } ContractOutputSelection::Evm(evm) => match evm { EvmOutputSelection::All => { config.assembly = true; config.generated_sources = true; config.source_map = true; } EvmOutputSelection::Assembly => { config.assembly = true; } EvmOutputSelection::ByteCode(BytecodeOutputSelection::GeneratedSources) => { config.generated_sources = true; } EvmOutputSelection::ByteCode(BytecodeOutputSelection::SourceMap) => { config.source_map = 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.abi { if let Some(ref abi) = contract.abi { let file = file.with_extension("abi.json"); fs::write(&file, serde_json::to_string_pretty(abi)?) .map_err(|err| SolcError::io(err, file))? } } 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.raw_json()?)?) .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))? } } } if self.generated_sources { if let Some(ref evm) = contract.evm { if let Some(ref bytecode) = evm.bytecode { let file = file.with_extension("gensources"); fs::write(&file, serde_json::to_vec_pretty(&bytecode.generated_sources)?) .map_err(|err| SolcError::io(err, file))?; } } } if self.source_map { if let Some(ref evm) = contract.evm { if let Some(ref bytecode) = evm.bytecode { if let Some(ref sourcemap) = bytecode.source_map { let file = file.with_extension("sourcemap"); fs::write(&file, sourcemap).map_err(|err| SolcError::io(err, file))? } } } } Ok(()) } }