From 24c39bd32af37f7906bf0d5288a4daa7c83ac095 Mon Sep 17 00:00:00 2001 From: brockelmore <31553173+brockelmore@users.noreply.github.com> Date: Fri, 28 Jan 2022 02:23:28 -0500 Subject: [PATCH] write `CompactContractBytecode` instead of `CompactContract` (#833) * write `CompactContractBytecode` instead of `CompactContract` * chore: fix doctest Co-authored-by: Georgios Konstantopoulos --- ethers-solc/src/artifacts.rs | 215 +++++++++++++++++++++++++++++++++++ ethers-solc/src/config.rs | 24 ++-- ethers-solc/src/hh.rs | 31 ++++- ethers-solc/src/lib.rs | 4 +- 4 files changed, 264 insertions(+), 10 deletions(-) diff --git a/ethers-solc/src/artifacts.rs b/ethers-solc/src/artifacts.rs index e671b9fc..8a154da9 100644 --- a/ethers-solc/src/artifacts.rs +++ b/ethers-solc/src/artifacts.rs @@ -814,6 +814,21 @@ impl ContractBytecode { 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 { @@ -828,6 +843,80 @@ impl From for ContractBytecode { } } +/// 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 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 (maybe_bcode, maybe_runtime) = match (evm.bytecode, evm.deployed_bytecode) { + (Some(bcode), Some(dbcode)) => (Some(bcode.into()), Some(dbcode.into())), + (None, Some(dbcode)) => (None, Some(dbcode.into())), + (Some(bcode), None) => (Some(bcode.into()), None), + (None, None) => (None, None), + }; + (maybe_bcode, maybe_runtime) + } else { + (None, None) + }; + + Self { abi: c.abi, 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 @@ -971,6 +1060,13 @@ impl From for CompactContract { } } +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 { @@ -1183,6 +1279,88 @@ pub struct Bytecode { 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 /// @@ -1453,6 +1631,43 @@ impl DeployedBytecode { } } +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, diff --git a/ethers-solc/src/config.rs b/ethers-solc/src/config.rs index bc810501..93cad0de 100644 --- a/ethers-solc/src/config.rs +++ b/ethers-solc/src/config.rs @@ -1,5 +1,5 @@ use crate::{ - artifacts::{CompactContract, CompactContractRef, Contract, Settings}, + artifacts::{CompactContract, CompactContractBytecode, Contract, Settings}, cache::SOLIDITY_FILES_CACHE_FILENAME, error::{Result, SolcError, SolcIoError}, hh::HardhatArtifact, @@ -503,14 +503,20 @@ pub trait Artifact { /// Returns the artifact's `Abi` and bytecode fn into_inner(self) -> (Option, Option); - /// Turns the artifact into a container type for abi, bytecode and deployed bytecode + /// Turns the artifact into a container type for abi, compact bytecode and deployed bytecode fn into_compact_contract(self) -> CompactContract; + /// Turns the artifact into a container type for abi, full bytecode and deployed bytecode + fn into_contract_bytecode(self) -> CompactContractBytecode; + /// Returns the contents of this type as a single tuple of abi, bytecode and deployed bytecode fn into_parts(self) -> (Option, Option, Option); } -impl> Artifact for T { +impl Artifact for T +where + T: Into + Into, +{ fn into_inner(self) -> (Option, Option) { let artifact = self.into_compact_contract(); (artifact.abi, artifact.bin.and_then(|bin| bin.into_bytes())) @@ -520,6 +526,10 @@ impl> Artifact for T { self.into() } + fn into_contract_bytecode(self) -> CompactContractBytecode { + self.into() + } + fn into_parts(self) -> (Option, Option, Option) { self.into_compact_contract().into_parts() } @@ -625,7 +635,7 @@ pub trait ArtifactOutput { pub struct MinimalCombinedArtifacts; impl ArtifactOutput for MinimalCombinedArtifacts { - type Artifact = CompactContract; + type Artifact = CompactContractBytecode; fn on_output(output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()> { fs::create_dir_all(&layout.artifacts) @@ -643,7 +653,7 @@ impl ArtifactOutput for MinimalCombinedArtifacts { )) })?; } - let min = CompactContractRef::from(contract); + let min = CompactContractBytecode::from(contract.clone()); fs::write(&file, serde_json::to_vec_pretty(&min)?) .map_err(|err| SolcError::io(err, file))? } @@ -662,7 +672,7 @@ impl ArtifactOutput for MinimalCombinedArtifacts { pub struct MinimalCombinedArtifactsHardhatFallback; impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback { - type Artifact = CompactContract; + type Artifact = CompactContractBytecode; fn on_output(output: &CompilerOutput, layout: &ProjectPathsConfig) -> Result<()> { MinimalCombinedArtifacts::on_output(output, layout) @@ -678,7 +688,7 @@ impl ArtifactOutput for MinimalCombinedArtifactsHardhatFallback { tracing::trace!("Fallback to hardhat artifact deserialization"); let artifact = serde_json::from_str::(&content)?; tracing::trace!("successfully deserialized hardhat artifact"); - Ok(artifact.into_compact_contract()) + Ok(artifact.into_contract_bytecode()) } } diff --git a/ethers-solc/src/hh.rs b/ethers-solc/src/hh.rs index dd79f75a..8a7e3d78 100644 --- a/ethers-solc/src/hh.rs +++ b/ethers-solc/src/hh.rs @@ -1,7 +1,10 @@ //! Hardhat support use crate::{ - artifacts::{BytecodeObject, CompactContract, Contract, Offsets}, + artifacts::{ + Bytecode, BytecodeObject, CompactContract, CompactContractBytecode, Contract, + ContractBytecode, DeployedBytecode, Offsets, + }, error::{Result, SolcError}, ArtifactOutput, CompilerOutput, ProjectPathsConfig, }; @@ -49,6 +52,32 @@ impl From for CompactContract { } } +impl From for ContractBytecode { + fn from(artifact: HardhatArtifact) -> Self { + let bytecode: Option = artifact.bytecode.as_ref().map(|t| { + let mut bcode: Bytecode = t.clone().into(); + bcode.link_references = artifact.link_references.clone(); + bcode + }); + + let deployed_bytecode: Option = artifact.bytecode.as_ref().map(|t| { + let mut bcode: Bytecode = t.clone().into(); + bcode.link_references = artifact.deployed_link_references.clone(); + bcode.into() + }); + + ContractBytecode { abi: Some(artifact.abi), bytecode, deployed_bytecode } + } +} + +impl From for CompactContractBytecode { + fn from(artifact: HardhatArtifact) -> Self { + let c: ContractBytecode = artifact.into(); + + c.into() + } +} + /// Hardhat style artifacts handler #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct HardhatArtifacts; diff --git a/ethers-solc/src/lib.rs b/ethers-solc/src/lib.rs index 1bcae07c..92e4eb83 100644 --- a/ethers-solc/src/lib.rs +++ b/ethers-solc/src/lib.rs @@ -918,11 +918,11 @@ impl ProjectCompileOutput { /// /// ```no_run /// use std::collections::BTreeMap; - /// use ethers_solc::artifacts::CompactContract; + /// use ethers_solc::artifacts::CompactContractBytecode; /// use ethers_solc::Project; /// /// let project = Project::builder().build().unwrap(); - /// let contracts: BTreeMap = project.compile().unwrap().into_artifacts().collect(); + /// let contracts: BTreeMap = project.compile().unwrap().into_artifacts().collect(); /// ``` pub fn into_artifacts(mut self) -> Box> { let artifacts = self.artifacts.into_iter().filter_map(|(path, art)| {