506 lines
17 KiB
Rust
506 lines
17 KiB
Rust
//! A configurable artifacts handler implementation
|
|
|
|
use crate::{
|
|
artifacts::{
|
|
bytecode::{CompactBytecode, CompactDeployedBytecode},
|
|
contract::{CompactContract, CompactContractBytecode, Contract},
|
|
output_selection::{ContractOutputSelection, EvmOutputSelection, EwasmOutputSelection},
|
|
CompactContractBytecodeCow, CompactEvm, DevDoc, Ewasm, GasEstimates, LosslessAbi, Metadata,
|
|
Offsets, Settings, StorageLayout, UserDoc,
|
|
},
|
|
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 <https://docs.soliditylang.org/en/develop/abi-spec.html>
|
|
pub abi: Option<LosslessAbi>,
|
|
#[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>,
|
|
#[serde(default, skip_serializing_if = "serde_json::Value::is_null")]
|
|
pub ast: serde_json::Value,
|
|
}
|
|
|
|
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.map(Into::into),
|
|
bytecode: artifact.bytecode,
|
|
deployed_bytecode: artifact.deployed_bytecode,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<ConfigurableContractArtifact> 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": {...}
|
|
/// // 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,
|
|
source_file: Option<&SourceFile>,
|
|
) -> 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,
|
|
ast: source_file.map(|s| s.ast.clone()).unwrap_or_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,
|
|
|
|
/// 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(())
|
|
}
|
|
}
|