From 65b1751cf845ac5764a93e6ad14d4ac7368fb31b Mon Sep 17 00:00:00 2001 From: Matthias Seitz Date: Thu, 17 Mar 2022 15:51:43 +0100 Subject: [PATCH] style: split artifacts mod and create contract and bytecode mods (#1052) * style: split artifacts mod and create contract and bytecode mods * chore: rustfmt --- .../src/artifact_output/configurable.rs | 5 +- ethers-solc/src/artifact_output/mod.rs | 7 +- ethers-solc/src/artifacts/bytecode.rs | 404 ++++++++ ethers-solc/src/artifacts/contract.rs | 478 ++++++++++ ethers-solc/src/artifacts/mod.rs | 879 +----------------- ethers-solc/src/cache.rs | 6 +- ethers-solc/src/compile/contracts.rs | 6 +- ethers-solc/src/compile/output.rs | 9 +- ethers-solc/src/hh.rs | 5 +- ethers-solc/src/lib.rs | 3 +- 10 files changed, 914 insertions(+), 888 deletions(-) create mode 100644 ethers-solc/src/artifacts/bytecode.rs create mode 100644 ethers-solc/src/artifacts/contract.rs diff --git a/ethers-solc/src/artifact_output/configurable.rs b/ethers-solc/src/artifact_output/configurable.rs index dbd56b89..b537e6a9 100644 --- a/ethers-solc/src/artifact_output/configurable.rs +++ b/ethers-solc/src/artifact_output/configurable.rs @@ -2,12 +2,13 @@ use crate::{ artifacts::{ + bytecode::{CompactBytecode, CompactDeployedBytecode}, + contract::{CompactContract, CompactContractBytecode, Contract}, output_selection::{ContractOutputSelection, EvmOutputSelection, EwasmOutputSelection}, - CompactBytecode, CompactContract, CompactContractBytecode, CompactDeployedBytecode, CompactEvm, DevDoc, Ewasm, GasEstimates, LosslessAbi, Metadata, Offsets, Settings, StorageLayout, UserDoc, }, - ArtifactOutput, Contract, SolcConfig, SolcError, + ArtifactOutput, SolcConfig, SolcError, }; use serde::{Deserialize, Serialize}; use std::{collections::BTreeMap, fs, path::Path}; diff --git a/ethers-solc/src/artifact_output/mod.rs b/ethers-solc/src/artifact_output/mod.rs index 0025ae2c..25d9790b 100644 --- a/ethers-solc/src/artifact_output/mod.rs +++ b/ethers-solc/src/artifact_output/mod.rs @@ -1,10 +1,8 @@ //! Output artifact handling use crate::{ - artifacts::{CompactContract, CompactContractBytecode, Contract, FileToContractsMap}, - contracts::VersionedContracts, - error::Result, - utils, HardhatArtifact, ProjectPathsConfig, SolcError, + artifacts::FileToContractsMap, contracts::VersionedContracts, error::Result, utils, + HardhatArtifact, ProjectPathsConfig, SolcError, }; use ethers_core::{abi::Abi, types::Bytes}; use semver::Version; @@ -16,6 +14,7 @@ use std::{ }; mod configurable; +use crate::artifacts::contract::{CompactContract, CompactContractBytecode, Contract}; pub use configurable::*; /// Represents unique artifact metadata for identifying artifacts on output diff --git a/ethers-solc/src/artifacts/bytecode.rs b/ethers-solc/src/artifacts/bytecode.rs new file mode 100644 index 00000000..e38951fa --- /dev/null +++ b/ethers-solc/src/artifacts/bytecode.rs @@ -0,0 +1,404 @@ +//! Bytecode related types + +use crate::{ + artifacts::{serde_helpers, FunctionDebugData, GeneratedSource, Offsets}, + sourcemap::{self, SourceMap, SyntaxError}, + utils, +}; +use ethers_core::{abi::Address, types::Bytes}; +use serde::{Deserialize, Serialize}; +use std::collections::BTreeMap; + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Bytecode { + /// Debugging information at function level + #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")] + pub function_debug_data: BTreeMap, + /// The bytecode as a hex string. + pub object: BytecodeObject, + /// Opcodes list (string) + #[serde(default, skip_serializing_if = "Option::is_none")] + pub opcodes: Option, + /// The source mapping as a string. See the source mapping definition. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source_map: Option, + /// Array of sources generated by the compiler. Currently only contains a + /// single Yul file. + #[serde(default, skip_serializing_if = "Vec::is_empty")] + pub generated_sources: Vec, + /// If given, this is an unlinked object. + #[serde(default)] + pub link_references: BTreeMap>>, +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct CompactBytecode { + /// The bytecode as a hex string. + pub object: BytecodeObject, + /// The source mapping as a string. See the source mapping definition. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub source_map: Option, + /// If given, this is an unlinked object. + #[serde(default)] + pub link_references: BTreeMap>>, +} + +impl CompactBytecode { + /// Tries to link the bytecode object with the `file` and `library` name. + /// Replaces all library placeholders with the given address. + /// + /// Returns true if the bytecode object is fully linked, false otherwise + /// This is a noop if the bytecode object is already fully linked. + pub fn link( + &mut self, + file: impl AsRef, + library: impl AsRef, + address: Address, + ) -> bool { + if !self.object.is_unlinked() { + return true + } + + let file = file.as_ref(); + let library = library.as_ref(); + if let Some((key, mut contracts)) = self.link_references.remove_entry(file) { + if contracts.remove(library).is_some() { + self.object.link(file, library, address); + } + if !contracts.is_empty() { + self.link_references.insert(key, contracts); + } + if self.link_references.is_empty() { + return self.object.resolve().is_some() + } + } + false + } +} + +impl From for CompactBytecode { + fn from(bcode: Bytecode) -> CompactBytecode { + CompactBytecode { + object: bcode.object, + source_map: bcode.source_map, + link_references: bcode.link_references, + } + } +} + +impl From for Bytecode { + fn from(bcode: CompactBytecode) -> Bytecode { + Bytecode { + object: bcode.object, + source_map: bcode.source_map, + link_references: bcode.link_references, + function_debug_data: Default::default(), + opcodes: Default::default(), + generated_sources: Default::default(), + } + } +} + +impl From for Bytecode { + fn from(object: BytecodeObject) -> Bytecode { + Bytecode { + object, + function_debug_data: Default::default(), + opcodes: Default::default(), + source_map: Default::default(), + generated_sources: Default::default(), + link_references: Default::default(), + } + } +} + +impl Bytecode { + /// Returns the parsed source map + /// + /// See also https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html + pub fn source_map(&self) -> Option> { + self.source_map.as_ref().map(|map| sourcemap::parse(map)) + } + + /// Same as `Bytecode::link` but with fully qualified name (`file.sol:Math`) + pub fn link_fully_qualified(&mut self, name: impl AsRef, addr: Address) -> bool { + if let Some((file, lib)) = name.as_ref().split_once(':') { + self.link(file, lib, addr) + } else { + false + } + } + + /// Tries to link the bytecode object with the `file` and `library` name. + /// Replaces all library placeholders with the given address. + /// + /// Returns true if the bytecode object is fully linked, false otherwise + /// This is a noop if the bytecode object is already fully linked. + pub fn link( + &mut self, + file: impl AsRef, + library: impl AsRef, + address: Address, + ) -> bool { + if !self.object.is_unlinked() { + return true + } + + let file = file.as_ref(); + let library = library.as_ref(); + if let Some((key, mut contracts)) = self.link_references.remove_entry(file) { + if contracts.remove(library).is_some() { + self.object.link(file, library, address); + } + if !contracts.is_empty() { + self.link_references.insert(key, contracts); + } + if self.link_references.is_empty() { + return self.object.resolve().is_some() + } + } + false + } + + /// Links the bytecode object with all provided `(file, lib, addr)` + pub fn link_all(&mut self, libs: I) -> bool + where + I: IntoIterator, + S: AsRef, + T: AsRef, + { + for (file, lib, addr) in libs.into_iter() { + if self.link(file, lib, addr) { + return true + } + } + false + } + + /// Links the bytecode object with all provided `(fully_qualified, addr)` + pub fn link_all_fully_qualified(&mut self, libs: I) -> bool + where + I: IntoIterator, + S: AsRef, + { + for (name, addr) in libs.into_iter() { + if self.link_fully_qualified(name, addr) { + return true + } + } + false + } +} + +/// Represents the bytecode of a contracts that might be not fully linked yet. +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(untagged)] +pub enum BytecodeObject { + /// Fully linked bytecode object + #[serde(deserialize_with = "serde_helpers::deserialize_bytes")] + Bytecode(Bytes), + /// Bytecode as hex string that's not fully linked yet and contains library placeholders + #[serde(with = "serde_helpers::string_bytes")] + Unlinked(String), +} + +impl BytecodeObject { + /// Returns the underlying `Bytes` if the object is a valid bytecode, and not empty + pub fn into_bytes(self) -> Option { + match self { + BytecodeObject::Bytecode(bytes) => Some(bytes), + BytecodeObject::Unlinked(_) => None, + } + } + + /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode, and not + /// empty + pub fn as_bytes(&self) -> Option<&Bytes> { + match self { + BytecodeObject::Bytecode(bytes) => Some(bytes), + BytecodeObject::Unlinked(_) => None, + } + } + /// Returns a reference to the underlying `String` if the object is unlinked + pub fn as_str(&self) -> Option<&str> { + match self { + BytecodeObject::Bytecode(_) => None, + BytecodeObject::Unlinked(s) => Some(s.as_str()), + } + } + + /// Returns the unlinked `String` if the object is unlinked or empty + pub fn into_unlinked(self) -> Option { + match self { + BytecodeObject::Bytecode(_) => None, + BytecodeObject::Unlinked(code) => Some(code), + } + } + + /// Whether this object is still unlinked + pub fn is_unlinked(&self) -> bool { + matches!(self, BytecodeObject::Unlinked(_)) + } + + /// Whether this object a valid bytecode + pub fn is_bytecode(&self) -> bool { + matches!(self, BytecodeObject::Bytecode(_)) + } + + /// Returns `true` if the object is a valid bytecode and not empty. + /// Returns false the object is a valid but empty bytecode or unlinked. + pub fn is_non_empty_bytecode(&self) -> bool { + self.as_bytes().map(|c| !c.0.is_empty()).unwrap_or_default() + } + + /// Tries to resolve the unlinked string object a valid bytecode object in place + /// + /// Returns the string if it is a valid + pub fn resolve(&mut self) -> Option<&Bytes> { + if let BytecodeObject::Unlinked(unlinked) = self { + if let Ok(linked) = hex::decode(unlinked) { + *self = BytecodeObject::Bytecode(linked.into()); + } + } + self.as_bytes() + } + + /// Link using the fully qualified name of a library + /// The fully qualified library name is the path of its source file and the library name + /// separated by `:` like `file.sol:Math` + /// + /// This will replace all occurrences of the library placeholder with the given address. + /// + /// See also: https://docs.soliditylang.org/en/develop/using-the-compiler.html#library-linking + pub fn link_fully_qualified(&mut self, name: impl AsRef, addr: Address) -> &mut Self { + if let BytecodeObject::Unlinked(ref mut unlinked) = self { + let name = name.as_ref(); + let place_holder = utils::library_hash_placeholder(name); + // the address as hex without prefix + let hex_addr = hex::encode(addr); + + // the library placeholder used to be the fully qualified name of the library instead of + // the hash. This is also still supported by `solc` so we handle this as well + let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name); + + *unlinked = unlinked + .replace(&format!("__{}__", fully_qualified_placeholder), &hex_addr) + .replace(&format!("__{}__", place_holder), &hex_addr) + } + self + } + + /// Link using the `file` and `library` names as fully qualified name `:` + /// See `BytecodeObject::link_fully_qualified` + pub fn link( + &mut self, + file: impl AsRef, + library: impl AsRef, + addr: Address, + ) -> &mut Self { + self.link_fully_qualified(format!("{}:{}", file.as_ref(), library.as_ref(),), addr) + } + + /// Links the bytecode object with all provided `(file, lib, addr)` + pub fn link_all(&mut self, libs: I) -> &mut Self + where + I: IntoIterator, + S: AsRef, + T: AsRef, + { + for (file, lib, addr) in libs.into_iter() { + self.link(file, lib, addr); + } + self + } + + /// Whether the bytecode contains a matching placeholder using the qualified name + pub fn contains_fully_qualified_placeholder(&self, name: impl AsRef) -> bool { + if let BytecodeObject::Unlinked(unlinked) = self { + let name = name.as_ref(); + unlinked.contains(&utils::library_hash_placeholder(name)) || + unlinked.contains(&utils::library_fully_qualified_placeholder(name)) + } else { + false + } + } + + /// Whether the bytecode contains a matching placeholder + pub fn contains_placeholder(&self, file: impl AsRef, library: impl AsRef) -> bool { + self.contains_fully_qualified_placeholder(format!("{}:{}", file.as_ref(), library.as_ref())) + } +} + +// Returns a not deployable bytecode by default as empty +impl Default for BytecodeObject { + fn default() -> Self { + BytecodeObject::Unlinked("".to_string()) + } +} + +impl AsRef<[u8]> for BytecodeObject { + fn as_ref(&self) -> &[u8] { + match self { + BytecodeObject::Bytecode(code) => code.as_ref(), + BytecodeObject::Unlinked(code) => code.as_bytes(), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +pub struct DeployedBytecode { + #[serde(flatten)] + pub bytecode: Option, + #[serde( + default, + rename = "immutableReferences", + skip_serializing_if = "::std::collections::BTreeMap::is_empty" + )] + pub immutable_references: BTreeMap>, +} + +impl DeployedBytecode { + /// Returns the underlying `Bytes` if the object is a valid bytecode, and not empty + pub fn into_bytes(self) -> Option { + self.bytecode?.object.into_bytes() + } +} + +impl From for DeployedBytecode { + fn from(bcode: Bytecode) -> DeployedBytecode { + DeployedBytecode { bytecode: Some(bcode), immutable_references: Default::default() } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct CompactDeployedBytecode { + #[serde(flatten)] + pub bytecode: Option, + #[serde( + default, + rename = "immutableReferences", + skip_serializing_if = "::std::collections::BTreeMap::is_empty" + )] + pub immutable_references: BTreeMap>, +} + +impl From for CompactDeployedBytecode { + fn from(bcode: DeployedBytecode) -> CompactDeployedBytecode { + CompactDeployedBytecode { + bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()), + immutable_references: bcode.immutable_references, + } + } +} + +impl From for DeployedBytecode { + fn from(bcode: CompactDeployedBytecode) -> DeployedBytecode { + DeployedBytecode { + bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()), + immutable_references: bcode.immutable_references, + } + } +} diff --git a/ethers-solc/src/artifacts/contract.rs b/ethers-solc/src/artifacts/contract.rs new file mode 100644 index 00000000..f1b6b9bf --- /dev/null +++ b/ethers-solc/src/artifacts/contract.rs @@ -0,0 +1,478 @@ +//! Contract related types + +use crate::artifacts::{ + bytecode::{ + Bytecode, BytecodeObject, CompactBytecode, CompactDeployedBytecode, DeployedBytecode, + }, + serde_helpers, DevDoc, Evm, Ewasm, LosslessAbi, Metadata, Offsets, StorageLayout, UserDoc, +}; +use ethers_core::{abi::Contract as Abi, types::Bytes}; +use serde::{Deserialize, Serialize}; +use std::{collections::BTreeMap, convert::TryFrom}; + +/// Represents a compiled solidity contract +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Contract { + /// The Ethereum Contract Metadata. + /// See https://docs.soliditylang.org/en/develop/metadata.html + pub abi: Option, + #[serde( + default, + skip_serializing_if = "Option::is_none", + with = "serde_helpers::json_string_opt" + )] + pub metadata: Option, + #[serde(default)] + pub userdoc: UserDoc, + #[serde(default)] + pub devdoc: DevDoc, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ir: Option, + #[serde(default, skip_serializing_if = "StorageLayout::is_empty")] + pub storage_layout: StorageLayout, + /// EVM-related outputs + #[serde(default, skip_serializing_if = "Option::is_none")] + pub evm: Option, + /// Ewasm related outputs + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ewasm: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub ir_optimized: Option, +} + +/// Minimal representation of a contract with a present abi and bytecode. +/// +/// Unlike `CompactContractSome` which contains the `BytecodeObject`, this holds the whole +/// `Bytecode` object. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ContractBytecode { + /// 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, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bytecode: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub deployed_bytecode: Option, +} + +impl ContractBytecode { + /// Returns the `ContractBytecodeSome` if all fields are `Some` + /// + /// # Panics + /// + /// Panics if any of the fields equal `None` + /// + /// # Example + /// + /// ``` + /// use ethers_solc::Project; + /// use ethers_solc::artifacts::*; + /// # fn demo(project: Project) { + /// let mut output = project.compile().unwrap().output(); + /// let contract: ContractBytecode = output.remove("Greeter").unwrap().into(); + /// let contract = contract.unwrap(); + /// # } + /// ``` + pub fn unwrap(self) -> ContractBytecodeSome { + ContractBytecodeSome { + abi: self.abi.unwrap(), + bytecode: self.bytecode.unwrap(), + deployed_bytecode: self.deployed_bytecode.unwrap(), + } + } + + /// 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 + } +} + +impl From for ContractBytecode { + fn from(c: Contract) -> Self { + let (bytecode, deployed_bytecode) = if let Some(evm) = c.evm { + (evm.bytecode, evm.deployed_bytecode) + } else { + (None, None) + }; + + Self { abi: c.abi.map(Into::into), bytecode, deployed_bytecode } + } +} + +/// Minimal representation of a contract with a present abi and bytecode. +/// +/// 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 + 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, +} + +impl CompactContractBytecode { + /// 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 + } +} + +impl From for CompactContractBytecode { + fn from(c: Contract) -> Self { + let (bytecode, deployed_bytecode) = if let Some(evm) = c.evm { + let evm = evm.into_compact(); + (evm.bytecode, evm.deployed_bytecode) + } else { + (None, None) + }; + + Self { abi: c.abi.map(Into::into), bytecode, deployed_bytecode } + } +} + +impl From for CompactContractBytecode { + fn from(c: ContractBytecode) -> Self { + let (maybe_bcode, maybe_runtime) = match (c.bytecode, c.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), + }; + Self { abi: c.abi, bytecode: maybe_bcode, deployed_bytecode: maybe_runtime } + } +} + +impl From for ContractBytecode { + fn from(c: CompactContractBytecode) -> Self { + let (maybe_bcode, maybe_runtime) = match (c.bytecode, c.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), + }; + Self { abi: c.abi, bytecode: maybe_bcode, deployed_bytecode: maybe_runtime } + } +} + +/// Minimal representation of a contract with a present abi and bytecode. +/// +/// Unlike `CompactContractSome` which contains the `BytecodeObject`, this holds the whole +/// `Bytecode` object. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] +pub struct ContractBytecodeSome { + pub abi: Abi, + pub bytecode: Bytecode, + pub deployed_bytecode: DeployedBytecode, +} + +impl TryFrom for ContractBytecodeSome { + type Error = ContractBytecode; + + fn try_from(value: ContractBytecode) -> Result { + if value.abi.is_none() || value.bytecode.is_none() || value.deployed_bytecode.is_none() { + return Err(value) + } + Ok(value.unwrap()) + } +} + +/// Minimal representation of a contract's artifact with a present abi and bytecode. +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +pub struct CompactContractSome { + /// 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: Abi, + pub bin: BytecodeObject, + #[serde(rename = "bin-runtime")] + pub bin_runtime: BytecodeObject, +} + +impl TryFrom for CompactContractSome { + type Error = CompactContract; + + fn try_from(value: CompactContract) -> Result { + if value.abi.is_none() || value.bin.is_none() || value.bin_runtime.is_none() { + return Err(value) + } + Ok(value.unwrap()) + } +} + +/// The general purpose minimal representation of a contract's abi with bytecode +/// Unlike `CompactContractSome` all fields are optional so that every possible compiler output can +/// be represented by it +#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] +pub struct CompactContract { + /// 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, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bin: Option, + #[serde(default, rename = "bin-runtime", skip_serializing_if = "Option::is_none")] + pub bin_runtime: Option, +} + +impl CompactContract { + /// Returns the contents of this type as a single tuple of abi, bytecode and deployed bytecode + pub fn into_parts(self) -> (Option, Option, Option) { + ( + self.abi, + self.bin.and_then(|bin| bin.into_bytes()), + self.bin_runtime.and_then(|bin| bin.into_bytes()), + ) + } + + /// Returns the individual parts of this contract. + /// + /// If the values are `None`, then `Default` is returned. + pub fn into_parts_or_default(self) -> (Abi, Bytes, Bytes) { + ( + self.abi.unwrap_or_default(), + self.bin.and_then(|bin| bin.into_bytes()).unwrap_or_default(), + self.bin_runtime.and_then(|bin| bin.into_bytes()).unwrap_or_default(), + ) + } + + /// Returns the `CompactContractSome` if all fields are `Some` + /// + /// # Panics + /// + /// Panics if any of the fields euqal `None` + /// + /// # Example + /// + /// ``` + /// use ethers_solc::Project; + /// use ethers_solc::artifacts::*; + /// # fn demo(project: Project) { + /// let mut output = project.compile().unwrap().output(); + /// let contract: CompactContract = output.remove("Greeter").unwrap().into(); + /// let contract = contract.unwrap(); + /// # } + /// ``` + pub fn unwrap(self) -> CompactContractSome { + CompactContractSome { + abi: self.abi.unwrap(), + bin: self.bin.unwrap(), + bin_runtime: self.bin_runtime.unwrap(), + } + } + + /// Returns the `CompactContractSome` if any if the field equals `None` the `Default` value is + /// returned + /// + /// Unlike `unwrap`, this function does _not_ panic + pub fn unwrap_or_default(self) -> CompactContractSome { + CompactContractSome { + abi: self.abi.unwrap_or_default(), + bin: self.bin.unwrap_or_default(), + bin_runtime: self.bin_runtime.unwrap_or_default(), + } + } +} + +impl From for CompactContract { + fn from(mut val: serde_json::Value) -> Self { + if let Some(map) = val.as_object_mut() { + let abi = map.remove("abi").and_then(|val| serde_json::from_value(val).ok()); + let bin = map.remove("bin").and_then(|val| serde_json::from_value(val).ok()); + let bin_runtime = + map.remove("bin-runtime").and_then(|val| serde_json::from_value(val).ok()); + Self { abi, bin, bin_runtime } + } else { + CompactContract::default() + } + } +} + +impl From for CompactContractBytecode { + fn from(val: serde_json::Value) -> Self { + serde_json::from_value(val).unwrap_or_default() + } +} + +impl From for CompactContract { + fn from(c: ContractBytecode) -> Self { + let ContractBytecode { abi, bytecode, deployed_bytecode } = c; + Self { + abi, + bin: bytecode.map(|c| c.object), + bin_runtime: deployed_bytecode + .and_then(|deployed| deployed.bytecode.map(|code| code.object)), + } + } +} + +impl From for CompactContract { + fn from(c: CompactContractBytecode) -> Self { + let c: ContractBytecode = c.into(); + c.into() + } +} + +impl From for CompactContract { + fn from(c: ContractBytecodeSome) -> Self { + Self { + abi: Some(c.abi), + bin: Some(c.bytecode.object), + bin_runtime: c.deployed_bytecode.bytecode.map(|code| code.object), + } + } +} + +impl From for CompactContract { + fn from(c: Contract) -> Self { + ContractBytecode::from(c).into() + } +} + +impl From for CompactContract { + fn from(c: CompactContractSome) -> Self { + Self { abi: Some(c.abi), bin: Some(c.bin), bin_runtime: Some(c.bin_runtime) } + } +} + +impl<'a> From> for CompactContract { + fn from(c: CompactContractRef<'a>) -> Self { + Self { abi: c.abi.cloned(), bin: c.bin.cloned(), bin_runtime: c.bin_runtime.cloned() } + } +} + +impl<'a> From> for CompactContract { + fn from(c: CompactContractRefSome<'a>) -> Self { + Self { + abi: Some(c.abi.clone()), + bin: Some(c.bin.clone()), + bin_runtime: Some(c.bin_runtime.clone()), + } + } +} + +/// Minimal representation of a contract with a present abi and bytecode that borrows. +#[derive(Copy, Clone, Debug, Serialize)] +pub struct CompactContractRefSome<'a> { + pub abi: &'a Abi, + pub bin: &'a BytecodeObject, + #[serde(rename = "bin-runtime")] + pub bin_runtime: &'a BytecodeObject, +} + +impl<'a> CompactContractRefSome<'a> { + /// Returns the individual parts of this contract. + /// + /// If the values are `None`, then `Default` is returned. + pub fn into_parts(self) -> (Abi, Bytes, Bytes) { + CompactContract::from(self).into_parts_or_default() + } +} + +impl<'a> TryFrom> for CompactContractRefSome<'a> { + type Error = CompactContractRef<'a>; + + fn try_from(value: CompactContractRef<'a>) -> Result { + if value.abi.is_none() || value.bin.is_none() || value.bin_runtime.is_none() { + return Err(value) + } + Ok(value.unwrap()) + } +} + +/// Helper type to serialize while borrowing from `Contract` +#[derive(Copy, Clone, Debug, Serialize)] +pub struct CompactContractRef<'a> { + pub abi: Option<&'a Abi>, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bin: Option<&'a BytecodeObject>, + #[serde(default, rename = "bin-runtime", skip_serializing_if = "Option::is_none")] + pub bin_runtime: Option<&'a BytecodeObject>, +} + +impl<'a> CompactContractRef<'a> { + /// Clones the referenced values and returns as tuples + pub fn into_parts(self) -> (Option, Option, Option) { + CompactContract::from(self).into_parts() + } + + /// Returns the individual parts of this contract. + /// + /// If the values are `None`, then `Default` is returned. + pub fn into_parts_or_default(self) -> (Abi, Bytes, Bytes) { + CompactContract::from(self).into_parts_or_default() + } + + pub fn bytecode(&self) -> Option<&Bytes> { + self.bin.as_ref().and_then(|bin| bin.as_bytes()) + } + + pub fn runtime_bytecode(&self) -> Option<&Bytes> { + self.bin_runtime.as_ref().and_then(|bin| bin.as_bytes()) + } + + /// Returns the `CompactContractRefSome` if all fields are `Some` + /// + /// # Panics + /// + /// Panics if any of the fields equal `None` + /// + /// # Example + /// + /// ``` + /// use ethers_solc::Project; + /// use ethers_solc::artifacts::*; + /// # fn demo(project: Project) { + /// let output = project.compile().unwrap().output(); + /// let contract = output.find("Greeter").unwrap(); + /// let contract = contract.unwrap(); + /// # } + /// ``` + pub fn unwrap(self) -> CompactContractRefSome<'a> { + CompactContractRefSome { + abi: self.abi.unwrap(), + bin: self.bin.unwrap(), + bin_runtime: self.bin_runtime.unwrap(), + } + } +} + +impl<'a> From<&'a Contract> for CompactContractRef<'a> { + fn from(c: &'a Contract) -> Self { + let (bin, bin_runtime) = if let Some(ref evm) = c.evm { + ( + evm.bytecode.as_ref().map(|c| &c.object), + evm.deployed_bytecode + .as_ref() + .and_then(|deployed| deployed.bytecode.as_ref().map(|evm| &evm.object)), + ) + } else { + (None, None) + }; + + Self { abi: c.abi.as_ref().map(|abi| &abi.abi), bin, bin_runtime } + } +} diff --git a/ethers-solc/src/artifacts/mod.rs b/ethers-solc/src/artifacts/mod.rs index 79b82dd8..a263b2aa 100644 --- a/ethers-solc/src/artifacts/mod.rs +++ b/ethers-solc/src/artifacts/mod.rs @@ -1,33 +1,30 @@ //! Solc artifact types -use ethers_core::{abi::Abi, types::Bytes}; +use ethers_core::abi::Abi; use colored::Colorize; use md5::Digest; use semver::Version; use std::{ collections::{BTreeMap, HashSet}, - convert::TryFrom, fmt, fs, path::{Path, PathBuf}, str::FromStr, }; -use crate::{ - compile::*, - error::SolcIoError, - remappings::Remapping, - sourcemap::{self, SourceMap, SyntaxError}, - utils, -}; -use ethers_core::abi::Address; +use crate::{compile::*, error::SolcIoError, remappings::Remapping, utils}; + use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer}; +pub mod bytecode; +pub mod contract; pub mod output_selection; pub mod serde_helpers; use crate::{ artifacts::output_selection::{ContractOutputSelection, OutputSelection}, filter::FilteredSources, }; +pub use bytecode::*; +pub use contract::*; 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 @@ -809,37 +806,6 @@ impl OutputContracts { } } -/// Represents a compiled solidity contract -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Contract { - /// The Ethereum Contract Metadata. - /// See https://docs.soliditylang.org/en/develop/metadata.html - pub abi: Option, - #[serde( - default, - skip_serializing_if = "Option::is_none", - with = "serde_helpers::json_string_opt" - )] - pub metadata: Option, - #[serde(default)] - pub userdoc: UserDoc, - #[serde(default)] - pub devdoc: DevDoc, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub ir: Option, - #[serde(default, skip_serializing_if = "StorageLayout::is_empty")] - pub storage_layout: StorageLayout, - /// EVM-related outputs - #[serde(default, skip_serializing_if = "Option::is_none")] - pub evm: Option, - /// Ewasm related outputs - #[serde(default, skip_serializing_if = "Option::is_none")] - pub ewasm: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub ir_optimized: Option, -} - /// A helper type that ensures lossless (de)serialisation unlike [`ethabi::Contract`] which omits /// some information of (nested) components in a serde roundtrip. This is a problem for /// abienconderv2 structs because `ethabi::Contract`'s representation of those are [`ethabi::Param`] @@ -882,442 +848,6 @@ impl<'de> Deserialize<'de> for LosslessAbi { } } -/// Minimal representation of a contract with a present abi and bytecode. -/// -/// Unlike `CompactContractSome` which contains the `BytecodeObject`, this holds the whole -/// `Bytecode` object. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct ContractBytecode { - /// 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, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub bytecode: Option, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub deployed_bytecode: Option, -} - -impl ContractBytecode { - /// Returns the `ContractBytecodeSome` if all fields are `Some` - /// - /// # Panics - /// - /// Panics if any of the fields euqal `None` - /// - /// # Example - /// - /// ``` - /// use ethers_solc::Project; - /// use ethers_solc::artifacts::*; - /// # fn demo(project: Project) { - /// let mut output = project.compile().unwrap().output(); - /// let contract: ContractBytecode = output.remove("Greeter").unwrap().into(); - /// let contract = contract.unwrap(); - /// # } - /// ``` - pub fn unwrap(self) -> ContractBytecodeSome { - ContractBytecodeSome { - abi: self.abi.unwrap(), - bytecode: self.bytecode.unwrap(), - deployed_bytecode: self.deployed_bytecode.unwrap(), - } - } - - /// 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 - } -} - -impl From for ContractBytecode { - fn from(c: Contract) -> Self { - let (bytecode, deployed_bytecode) = if let Some(evm) = c.evm { - (evm.bytecode, evm.deployed_bytecode) - } else { - (None, None) - }; - - Self { abi: c.abi.map(Into::into), bytecode, deployed_bytecode } - } -} - -/// Minimal representation of a contract with a present abi and bytecode. -/// -/// 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 - 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, -} - -impl CompactContractBytecode { - /// 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 - } -} - -impl From for CompactContractBytecode { - fn from(c: Contract) -> Self { - let (bytecode, deployed_bytecode) = if let Some(evm) = c.evm { - let evm = evm.into_compact(); - (evm.bytecode, evm.deployed_bytecode) - } else { - (None, None) - }; - - Self { abi: c.abi.map(Into::into), bytecode, deployed_bytecode } - } -} - -impl From for CompactContractBytecode { - fn from(c: ContractBytecode) -> Self { - let (maybe_bcode, maybe_runtime) = match (c.bytecode, c.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), - }; - Self { abi: c.abi, bytecode: maybe_bcode, deployed_bytecode: maybe_runtime } - } -} - -impl From for ContractBytecode { - fn from(c: CompactContractBytecode) -> Self { - let (maybe_bcode, maybe_runtime) = match (c.bytecode, c.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), - }; - Self { abi: c.abi, bytecode: maybe_bcode, deployed_bytecode: maybe_runtime } - } -} - -/// Minimal representation of a contract with a present abi and bytecode. -/// -/// Unlike `CompactContractSome` which contains the `BytecodeObject`, this holds the whole -/// `Bytecode` object. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] -pub struct ContractBytecodeSome { - pub abi: Abi, - pub bytecode: Bytecode, - pub deployed_bytecode: DeployedBytecode, -} - -impl TryFrom for ContractBytecodeSome { - type Error = ContractBytecode; - - fn try_from(value: ContractBytecode) -> Result { - if value.abi.is_none() || value.bytecode.is_none() || value.deployed_bytecode.is_none() { - return Err(value) - } - Ok(value.unwrap()) - } -} - -/// Minimal representation of a contract's artifact with a present abi and bytecode. -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] -pub struct CompactContractSome { - /// 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: Abi, - pub bin: BytecodeObject, - #[serde(rename = "bin-runtime")] - pub bin_runtime: BytecodeObject, -} - -impl TryFrom for CompactContractSome { - type Error = CompactContract; - - fn try_from(value: CompactContract) -> Result { - if value.abi.is_none() || value.bin.is_none() || value.bin_runtime.is_none() { - return Err(value) - } - Ok(value.unwrap()) - } -} - -/// The general purpose minimal representation of a contract's abi with bytecode -/// Unlike `CompactContractSome` all fields are optional so that every possible compiler output can -/// be represented by it -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Default)] -pub struct CompactContract { - /// 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, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub bin: Option, - #[serde(default, rename = "bin-runtime", skip_serializing_if = "Option::is_none")] - pub bin_runtime: Option, -} - -impl CompactContract { - /// Returns the contents of this type as a single tuple of abi, bytecode and deployed bytecode - pub fn into_parts(self) -> (Option, Option, Option) { - ( - self.abi, - self.bin.and_then(|bin| bin.into_bytes()), - self.bin_runtime.and_then(|bin| bin.into_bytes()), - ) - } - - /// Returns the individual parts of this contract. - /// - /// If the values are `None`, then `Default` is returned. - pub fn into_parts_or_default(self) -> (Abi, Bytes, Bytes) { - ( - self.abi.unwrap_or_default(), - self.bin.and_then(|bin| bin.into_bytes()).unwrap_or_default(), - self.bin_runtime.and_then(|bin| bin.into_bytes()).unwrap_or_default(), - ) - } - - /// Returns the `CompactContractSome` if all fields are `Some` - /// - /// # Panics - /// - /// Panics if any of the fields euqal `None` - /// - /// # Example - /// - /// ``` - /// use ethers_solc::Project; - /// use ethers_solc::artifacts::*; - /// # fn demo(project: Project) { - /// let mut output = project.compile().unwrap().output(); - /// let contract: CompactContract = output.remove("Greeter").unwrap().into(); - /// let contract = contract.unwrap(); - /// # } - /// ``` - pub fn unwrap(self) -> CompactContractSome { - CompactContractSome { - abi: self.abi.unwrap(), - bin: self.bin.unwrap(), - bin_runtime: self.bin_runtime.unwrap(), - } - } - - /// Returns the `CompactContractSome` if any if the field equals `None` the `Default` value is - /// returned - /// - /// Unlike `unwrap`, this function does _not_ panic - pub fn unwrap_or_default(self) -> CompactContractSome { - CompactContractSome { - abi: self.abi.unwrap_or_default(), - bin: self.bin.unwrap_or_default(), - bin_runtime: self.bin_runtime.unwrap_or_default(), - } - } -} - -impl From for CompactContract { - fn from(mut val: serde_json::Value) -> Self { - if let Some(map) = val.as_object_mut() { - let abi = map.remove("abi").and_then(|val| serde_json::from_value(val).ok()); - let bin = map.remove("bin").and_then(|val| serde_json::from_value(val).ok()); - let bin_runtime = - map.remove("bin-runtime").and_then(|val| serde_json::from_value(val).ok()); - Self { abi, bin, bin_runtime } - } else { - CompactContract::default() - } - } -} - -impl From for CompactContractBytecode { - fn from(val: serde_json::Value) -> Self { - serde_json::from_value(val).unwrap_or_default() - } -} - -impl From for CompactContract { - fn from(c: ContractBytecode) -> Self { - let ContractBytecode { abi, bytecode, deployed_bytecode } = c; - Self { - abi, - bin: bytecode.map(|c| c.object), - bin_runtime: deployed_bytecode - .and_then(|deployed| deployed.bytecode.map(|code| code.object)), - } - } -} - -impl From for CompactContract { - fn from(c: CompactContractBytecode) -> Self { - let c: ContractBytecode = c.into(); - c.into() - } -} - -impl From for CompactContract { - fn from(c: ContractBytecodeSome) -> Self { - Self { - abi: Some(c.abi), - bin: Some(c.bytecode.object), - bin_runtime: c.deployed_bytecode.bytecode.map(|code| code.object), - } - } -} - -impl From for CompactContract { - fn from(c: Contract) -> Self { - ContractBytecode::from(c).into() - } -} - -impl From for CompactContract { - fn from(c: CompactContractSome) -> Self { - Self { abi: Some(c.abi), bin: Some(c.bin), bin_runtime: Some(c.bin_runtime) } - } -} - -impl<'a> From> for CompactContract { - fn from(c: CompactContractRef<'a>) -> Self { - Self { abi: c.abi.cloned(), bin: c.bin.cloned(), bin_runtime: c.bin_runtime.cloned() } - } -} - -impl<'a> From> for CompactContract { - fn from(c: CompactContractRefSome<'a>) -> Self { - Self { - abi: Some(c.abi.clone()), - bin: Some(c.bin.clone()), - bin_runtime: Some(c.bin_runtime.clone()), - } - } -} - -/// Minimal representation of a contract with a present abi and bytecode that borrows. -#[derive(Copy, Clone, Debug, Serialize)] -pub struct CompactContractRefSome<'a> { - pub abi: &'a Abi, - pub bin: &'a BytecodeObject, - #[serde(rename = "bin-runtime")] - pub bin_runtime: &'a BytecodeObject, -} - -impl<'a> CompactContractRefSome<'a> { - /// Returns the individual parts of this contract. - /// - /// If the values are `None`, then `Default` is returned. - pub fn into_parts(self) -> (Abi, Bytes, Bytes) { - CompactContract::from(self).into_parts_or_default() - } -} - -impl<'a> TryFrom> for CompactContractRefSome<'a> { - type Error = CompactContractRef<'a>; - - fn try_from(value: CompactContractRef<'a>) -> Result { - if value.abi.is_none() || value.bin.is_none() || value.bin_runtime.is_none() { - return Err(value) - } - Ok(value.unwrap()) - } -} - -/// Helper type to serialize while borrowing from `Contract` -#[derive(Copy, Clone, Debug, Serialize)] -pub struct CompactContractRef<'a> { - pub abi: Option<&'a Abi>, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub bin: Option<&'a BytecodeObject>, - #[serde(default, rename = "bin-runtime", skip_serializing_if = "Option::is_none")] - pub bin_runtime: Option<&'a BytecodeObject>, -} - -impl<'a> CompactContractRef<'a> { - /// Clones the referenced values and returns as tuples - pub fn into_parts(self) -> (Option, Option, Option) { - CompactContract::from(self).into_parts() - } - - /// Returns the individual parts of this contract. - /// - /// If the values are `None`, then `Default` is returned. - pub fn into_parts_or_default(self) -> (Abi, Bytes, Bytes) { - CompactContract::from(self).into_parts_or_default() - } - - pub fn bytecode(&self) -> Option<&Bytes> { - self.bin.as_ref().and_then(|bin| bin.as_bytes()) - } - - pub fn runtime_bytecode(&self) -> Option<&Bytes> { - self.bin_runtime.as_ref().and_then(|bin| bin.as_bytes()) - } - - /// Returns the `CompactContractRefSome` if all fields are `Some` - /// - /// # Panics - /// - /// Panics if any of the fields equal `None` - /// - /// # Example - /// - /// ``` - /// use ethers_solc::Project; - /// use ethers_solc::artifacts::*; - /// # fn demo(project: Project) { - /// let output = project.compile().unwrap().output(); - /// let contract = output.find("Greeter").unwrap(); - /// let contract = contract.unwrap(); - /// # } - /// ``` - pub fn unwrap(self) -> CompactContractRefSome<'a> { - CompactContractRefSome { - abi: self.abi.unwrap(), - bin: self.bin.unwrap(), - bin_runtime: self.bin_runtime.unwrap(), - } - } -} - -impl<'a> From<&'a Contract> for CompactContractRef<'a> { - fn from(c: &'a Contract) -> Self { - let (bin, bin_runtime) = if let Some(ref evm) = c.evm { - ( - evm.bytecode.as_ref().map(|c| &c.object), - evm.deployed_bytecode - .as_ref() - .and_then(|deployed| deployed.bytecode.as_ref().map(|evm| &evm.object)), - ) - } else { - (None, None) - }; - - Self { abi: c.abi.as_ref().map(|abi| &abi.abi), bin, bin_runtime } - } -} - #[derive(Clone, Debug, Default, Serialize, Deserialize, Eq, PartialEq)] pub struct UserDoc { #[serde(default, skip_serializing_if = "Option::is_none")] @@ -1425,344 +955,6 @@ pub(crate) struct CompactEvm { pub gas_estimates: Option, } -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct Bytecode { - /// Debugging information at function level - #[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")] - pub function_debug_data: BTreeMap, - /// The bytecode as a hex string. - pub object: BytecodeObject, - /// Opcodes list (string) - #[serde(default, skip_serializing_if = "Option::is_none")] - pub opcodes: Option, - /// The source mapping as a string. See the source mapping definition. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub source_map: Option, - /// Array of sources generated by the compiler. Currently only contains a - /// single Yul file. - #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub generated_sources: Vec, - /// If given, this is an unlinked object. - #[serde(default)] - pub link_references: BTreeMap>>, -} - -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct CompactBytecode { - /// The bytecode as a hex string. - pub object: BytecodeObject, - /// The source mapping as a string. See the source mapping definition. - #[serde(default, skip_serializing_if = "Option::is_none")] - pub source_map: Option, - /// If given, this is an unlinked object. - #[serde(default)] - pub link_references: BTreeMap>>, -} - -impl CompactBytecode { - /// Tries to link the bytecode object with the `file` and `library` name. - /// Replaces all library placeholders with the given address. - /// - /// Returns true if the bytecode object is fully linked, false otherwise - /// This is a noop if the bytecode object is already fully linked. - pub fn link( - &mut self, - file: impl AsRef, - library: impl AsRef, - address: Address, - ) -> bool { - if !self.object.is_unlinked() { - return true - } - - let file = file.as_ref(); - let library = library.as_ref(); - if let Some((key, mut contracts)) = self.link_references.remove_entry(file) { - if contracts.remove(library).is_some() { - self.object.link(file, library, address); - } - if !contracts.is_empty() { - self.link_references.insert(key, contracts); - } - if self.link_references.is_empty() { - return self.object.resolve().is_some() - } - } - false - } -} - -impl From for CompactBytecode { - fn from(bcode: Bytecode) -> CompactBytecode { - CompactBytecode { - object: bcode.object, - source_map: bcode.source_map, - link_references: bcode.link_references, - } - } -} - -impl From for Bytecode { - fn from(bcode: CompactBytecode) -> Bytecode { - Bytecode { - object: bcode.object, - source_map: bcode.source_map, - link_references: bcode.link_references, - function_debug_data: Default::default(), - opcodes: Default::default(), - generated_sources: Default::default(), - } - } -} - -impl From for Bytecode { - fn from(object: BytecodeObject) -> Bytecode { - Bytecode { - object, - function_debug_data: Default::default(), - opcodes: Default::default(), - source_map: Default::default(), - generated_sources: Default::default(), - link_references: Default::default(), - } - } -} - -impl Bytecode { - /// Returns the parsed source map - /// - /// See also https://docs.soliditylang.org/en/v0.8.10/internals/source_mappings.html - pub fn source_map(&self) -> Option> { - self.source_map.as_ref().map(|map| sourcemap::parse(map)) - } - - /// Same as `Bytecode::link` but with fully qualified name (`file.sol:Math`) - pub fn link_fully_qualified(&mut self, name: impl AsRef, addr: Address) -> bool { - if let Some((file, lib)) = name.as_ref().split_once(':') { - self.link(file, lib, addr) - } else { - false - } - } - - /// Tries to link the bytecode object with the `file` and `library` name. - /// Replaces all library placeholders with the given address. - /// - /// Returns true if the bytecode object is fully linked, false otherwise - /// This is a noop if the bytecode object is already fully linked. - pub fn link( - &mut self, - file: impl AsRef, - library: impl AsRef, - address: Address, - ) -> bool { - if !self.object.is_unlinked() { - return true - } - - let file = file.as_ref(); - let library = library.as_ref(); - if let Some((key, mut contracts)) = self.link_references.remove_entry(file) { - if contracts.remove(library).is_some() { - self.object.link(file, library, address); - } - if !contracts.is_empty() { - self.link_references.insert(key, contracts); - } - if self.link_references.is_empty() { - return self.object.resolve().is_some() - } - } - false - } - - /// Links the bytecode object with all provided `(file, lib, addr)` - pub fn link_all(&mut self, libs: I) -> bool - where - I: IntoIterator, - S: AsRef, - T: AsRef, - { - for (file, lib, addr) in libs.into_iter() { - if self.link(file, lib, addr) { - return true - } - } - false - } - - /// Links the bytecode object with all provided `(fully_qualified, addr)` - pub fn link_all_fully_qualified(&mut self, libs: I) -> bool - where - I: IntoIterator, - S: AsRef, - { - for (name, addr) in libs.into_iter() { - if self.link_fully_qualified(name, addr) { - return true - } - } - false - } -} - -/// Represents the bytecode of a contracts that might be not fully linked yet. -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -#[serde(untagged)] -pub enum BytecodeObject { - /// Fully linked bytecode object - #[serde(deserialize_with = "serde_helpers::deserialize_bytes")] - Bytecode(Bytes), - /// Bytecode as hex string that's not fully linked yet and contains library placeholders - #[serde(with = "serde_helpers::string_bytes")] - Unlinked(String), -} - -impl BytecodeObject { - /// Returns the underlying `Bytes` if the object is a valid bytecode, and not empty - pub fn into_bytes(self) -> Option { - match self { - BytecodeObject::Bytecode(bytes) => Some(bytes), - BytecodeObject::Unlinked(_) => None, - } - } - - /// Returns a reference to the underlying `Bytes` if the object is a valid bytecode, and not - /// empty - pub fn as_bytes(&self) -> Option<&Bytes> { - match self { - BytecodeObject::Bytecode(bytes) => Some(bytes), - BytecodeObject::Unlinked(_) => None, - } - } - /// Returns a reference to the underlying `String` if the object is unlinked - pub fn as_str(&self) -> Option<&str> { - match self { - BytecodeObject::Bytecode(_) => None, - BytecodeObject::Unlinked(s) => Some(s.as_str()), - } - } - - /// Returns the unlinked `String` if the object is unlinked or empty - pub fn into_unlinked(self) -> Option { - match self { - BytecodeObject::Bytecode(_) => None, - BytecodeObject::Unlinked(code) => Some(code), - } - } - - /// Whether this object is still unlinked - pub fn is_unlinked(&self) -> bool { - matches!(self, BytecodeObject::Unlinked(_)) - } - - /// Whether this object a valid bytecode - pub fn is_bytecode(&self) -> bool { - matches!(self, BytecodeObject::Bytecode(_)) - } - - /// Returns `true` if the object is a valid bytecode and not empty. - /// Returns false the object is a valid but empty bytecode or unlinked. - pub fn is_non_empty_bytecode(&self) -> bool { - self.as_bytes().map(|c| !c.0.is_empty()).unwrap_or_default() - } - - /// Tries to resolve the unlinked string object a valid bytecode object in place - /// - /// Returns the string if it is a valid - pub fn resolve(&mut self) -> Option<&Bytes> { - if let BytecodeObject::Unlinked(unlinked) = self { - if let Ok(linked) = hex::decode(unlinked) { - *self = BytecodeObject::Bytecode(linked.into()); - } - } - self.as_bytes() - } - - /// Link using the fully qualified name of a library - /// The fully qualified library name is the path of its source file and the library name - /// separated by `:` like `file.sol:Math` - /// - /// This will replace all occurrences of the library placeholder with the given address. - /// - /// See also: https://docs.soliditylang.org/en/develop/using-the-compiler.html#library-linking - pub fn link_fully_qualified(&mut self, name: impl AsRef, addr: Address) -> &mut Self { - if let BytecodeObject::Unlinked(ref mut unlinked) = self { - let name = name.as_ref(); - let place_holder = utils::library_hash_placeholder(name); - // the address as hex without prefix - let hex_addr = hex::encode(addr); - - // the library placeholder used to be the fully qualified name of the library instead of - // the hash. This is also still supported by `solc` so we handle this as well - let fully_qualified_placeholder = utils::library_fully_qualified_placeholder(name); - - *unlinked = unlinked - .replace(&format!("__{}__", fully_qualified_placeholder), &hex_addr) - .replace(&format!("__{}__", place_holder), &hex_addr) - } - self - } - - /// Link using the `file` and `library` names as fully qualified name `:` - /// See `BytecodeObject::link_fully_qualified` - pub fn link( - &mut self, - file: impl AsRef, - library: impl AsRef, - addr: Address, - ) -> &mut Self { - self.link_fully_qualified(format!("{}:{}", file.as_ref(), library.as_ref(),), addr) - } - - /// Links the bytecode object with all provided `(file, lib, addr)` - pub fn link_all(&mut self, libs: I) -> &mut Self - where - I: IntoIterator, - S: AsRef, - T: AsRef, - { - for (file, lib, addr) in libs.into_iter() { - self.link(file, lib, addr); - } - self - } - - /// Whether the bytecode contains a matching placeholder using the qualified name - pub fn contains_fully_qualified_placeholder(&self, name: impl AsRef) -> bool { - if let BytecodeObject::Unlinked(unlinked) = self { - let name = name.as_ref(); - unlinked.contains(&utils::library_hash_placeholder(name)) || - unlinked.contains(&utils::library_fully_qualified_placeholder(name)) - } else { - false - } - } - - /// Whether the bytecode contains a matching placeholder - pub fn contains_placeholder(&self, file: impl AsRef, library: impl AsRef) -> bool { - self.contains_fully_qualified_placeholder(format!("{}:{}", file.as_ref(), library.as_ref())) - } -} - -// Returns a not deployable bytecode by default as empty -impl Default for BytecodeObject { - fn default() -> Self { - BytecodeObject::Unlinked("".to_string()) - } -} - -impl AsRef<[u8]> for BytecodeObject { - fn as_ref(&self) -> &[u8] { - match self { - BytecodeObject::Bytecode(code) => code.as_ref(), - BytecodeObject::Unlinked(code) => code.as_bytes(), - } - } -} - #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] #[serde(rename_all = "camelCase")] pub struct FunctionDebugData { @@ -1789,62 +981,6 @@ pub struct Offsets { pub length: u32, } -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -pub struct DeployedBytecode { - #[serde(flatten)] - pub bytecode: Option, - #[serde( - default, - rename = "immutableReferences", - skip_serializing_if = "::std::collections::BTreeMap::is_empty" - )] - pub immutable_references: BTreeMap>, -} - -impl DeployedBytecode { - /// Returns the underlying `Bytes` if the object is a valid bytecode, and not empty - pub fn into_bytes(self) -> Option { - self.bytecode?.object.into_bytes() - } -} - -impl From for DeployedBytecode { - fn from(bcode: Bytecode) -> DeployedBytecode { - DeployedBytecode { bytecode: Some(bcode), immutable_references: Default::default() } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "camelCase")] -pub struct CompactDeployedBytecode { - #[serde(flatten)] - pub bytecode: Option, - #[serde( - default, - rename = "immutableReferences", - skip_serializing_if = "::std::collections::BTreeMap::is_empty" - )] - pub immutable_references: BTreeMap>, -} - -impl From for CompactDeployedBytecode { - fn from(bcode: DeployedBytecode) -> CompactDeployedBytecode { - CompactDeployedBytecode { - bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()), - immutable_references: bcode.immutable_references, - } - } -} - -impl From for DeployedBytecode { - fn from(bcode: CompactDeployedBytecode) -> DeployedBytecode { - DeployedBytecode { - bytecode: bcode.bytecode.map(|d_bcode| d_bcode.into()), - immutable_references: bcode.immutable_references, - } - } -} - #[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)] pub struct GasEstimates { pub creation: Creation, @@ -2073,6 +1209,7 @@ impl SourceFiles { mod tests { use super::*; use crate::AggregatedCompilerOutput; + use ethers_core::types::Address; use std::{fs, path::PathBuf}; #[test] diff --git a/ethers-solc/src/cache.rs b/ethers-solc/src/cache.rs index f043b0c2..40faf46f 100644 --- a/ethers-solc/src/cache.rs +++ b/ethers-solc/src/cache.rs @@ -181,7 +181,7 @@ impl SolFilesCache { /// /// ``` /// fn t() { - /// use ethers_solc::artifacts::CompactContract; + /// use ethers_solc::artifacts::contract::CompactContract; /// use ethers_solc::cache::SolFilesCache; /// use ethers_solc::Project; /// let project = Project::builder().build().unwrap(); @@ -234,7 +234,7 @@ impl SolFilesCache { /// fn t() { /// use ethers_solc::cache::SolFilesCache; /// use ethers_solc::Project; - /// use ethers_solc::artifacts::CompactContract; + /// use ethers_solc::artifacts::contract::CompactContract; /// /// let project = Project::builder().build().unwrap(); /// let cache = SolFilesCache::read_joined(&project.paths).unwrap(); @@ -267,7 +267,7 @@ impl SolFilesCache { /// ``` /// use ethers_solc::cache::SolFilesCache; /// use ethers_solc::Project; - /// use ethers_solc::artifacts::CompactContractBytecode; + /// use ethers_solc::artifacts::contract::CompactContractBytecode; /// # fn t() { /// let project = Project::builder().build().unwrap(); /// let cache = SolFilesCache::read_joined(&project.paths).unwrap(); diff --git a/ethers-solc/src/compile/contracts.rs b/ethers-solc/src/compile/contracts.rs index 78655cc9..f90f931b 100644 --- a/ethers-solc/src/compile/contracts.rs +++ b/ethers-solc/src/compile/contracts.rs @@ -1,4 +1,7 @@ -use crate::artifacts::{CompactContractRef, Contract, FileToContractsMap}; +use crate::artifacts::{ + contract::{CompactContractRef, Contract}, + FileToContractsMap, +}; use semver::Version; use serde::{Deserialize, Serialize}; use std::collections::BTreeMap; @@ -112,6 +115,7 @@ impl VersionedContracts { /// ``` /// use std::collections::BTreeMap; /// use ethers_solc::{ artifacts::*, Artifact }; + /// use ethers_solc::artifacts::contract::CompactContractSome; /// # fn demo(contracts: OutputContracts) { /// let contracts: BTreeMap = contracts /// .into_contracts() diff --git a/ethers-solc/src/compile/output.rs b/ethers-solc/src/compile/output.rs index 037a0885..a45e36b3 100644 --- a/ethers-solc/src/compile/output.rs +++ b/ethers-solc/src/compile/output.rs @@ -2,7 +2,8 @@ use crate::{ artifacts::{ - CompactContractBytecode, CompactContractRef, Contract, Error, SourceFile, SourceFiles, + contract::{CompactContractBytecode, CompactContractRef, Contract}, + Error, SourceFile, SourceFiles, }, contracts::{VersionedContract, VersionedContracts}, ArtifactId, ArtifactOutput, Artifacts, CompilerOutput, ConfigurableArtifacts, @@ -77,7 +78,7 @@ impl ProjectCompileOutput { /// Make all artifact files relative tot the project's root directory /// /// ```no_run - /// use ethers_solc::artifacts::CompactContractBytecode; + /// use ethers_solc::artifacts::contract::CompactContractBytecode; /// use ethers_solc::Project; /// /// let project = Project::builder().build().unwrap(); @@ -93,7 +94,7 @@ impl ProjectCompileOutput { /// Get the (merged) solc compiler output /// ```no_run /// use std::collections::btree_map::BTreeMap; - /// use ethers_solc::artifacts::Contract; + /// use ethers_solc::artifacts::contract::Contract; /// use ethers_solc::Project; /// /// let project = Project::builder().build().unwrap(); @@ -182,7 +183,7 @@ impl ProjectCompileOutput { /// /// ```no_run /// use std::collections::btree_map::BTreeMap; - /// use ethers_solc::artifacts::CompactContractBytecode; + /// use ethers_solc::artifacts::contract::CompactContractBytecode; /// use ethers_solc::{ArtifactId, Project}; /// /// let project = Project::builder().build().unwrap(); diff --git a/ethers-solc/src/hh.rs b/ethers-solc/src/hh.rs index 8657aac6..dca4f95a 100644 --- a/ethers-solc/src/hh.rs +++ b/ethers-solc/src/hh.rs @@ -2,8 +2,9 @@ use crate::{ artifacts::{ - Bytecode, BytecodeObject, CompactContract, CompactContractBytecode, Contract, - ContractBytecode, DeployedBytecode, LosslessAbi, Offsets, + bytecode::{Bytecode, BytecodeObject, DeployedBytecode}, + contract::{CompactContract, CompactContractBytecode, Contract, ContractBytecode}, + LosslessAbi, Offsets, }, ArtifactOutput, }; diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index b82f00ba..d4da28e5 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -32,11 +32,12 @@ pub mod utils; pub use filter::{FileFilter, TestFileFilter}; use crate::{ - artifacts::{Contract, Sources}, + artifacts::Sources, cache::SolFilesCache, contracts::VersionedContracts, error::{SolcError, SolcIoError}, }; +use artifacts::contract::Contract; use error::Result; use semver::Version; use std::path::{Path, PathBuf};