Skip to content

Commit

Permalink
feat(zk): properly mark unlinked contract (#40,#41,#43,#44)
Browse files Browse the repository at this point in the history
feat(zk): mark bytecode as unlinked
refactor(zk): get output from `eravm` object
feat: retain pre-1.5.7 zksolc compatibility
test(artifact:zk): unprefixed bytecode test
  • Loading branch information
Karrq committed Nov 25, 2024
1 parent 02e8404 commit 87c2e83
Show file tree
Hide file tree
Showing 7 changed files with 229 additions and 88 deletions.
28 changes: 0 additions & 28 deletions crates/artifacts/zksolc/src/bytecode.rs

This file was deleted.

97 changes: 74 additions & 23 deletions crates/artifacts/zksolc/src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! Contract related types.
use crate::Evm;
use crate::{EraVM, Evm};
use alloy_json_abi::JsonAbi;
use foundry_compilers_artifacts_solc::{
CompactContractBytecode, CompactContractBytecodeCow, CompactContractRef, DevDoc, StorageLayout,
UserDoc,
Bytecode, CompactBytecode, CompactContractBytecode, CompactContractBytecodeCow,
CompactContractRef, CompactDeployedBytecode, DevDoc, Offsets, StorageLayout, UserDoc,
};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, collections::BTreeMap};
Expand Down Expand Up @@ -31,12 +31,63 @@ pub struct Contract {
/// The contract factory dependencies.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub factory_dependencies: Option<BTreeMap<String, String>>,
/// The contract missing libraries.
/// EraVM-related outputs
#[serde(default, skip_serializing_if = "Option::is_none")]
pub missing_libraries: Option<Vec<String>>,
/// EVM-related outputs
pub eravm: Option<EraVM>,
/// EVM-related outputs (deprecated)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub evm: Option<Evm>,
/// The contract's unlinked libraries
#[serde(default)]
pub missing_libraries: Vec<String>,
}

impl Contract {
pub fn is_unlinked(&self) -> bool {
self.hash.is_none() || !self.missing_libraries.is_empty()
}

pub fn missing_libs_to_link_references(
missing_libraries: &[String],
) -> BTreeMap<String, BTreeMap<String, Vec<Offsets>>> {
missing_libraries
.iter()
.map(|file_and_lib| {
let mut parts = file_and_lib.split(':');
let filename = parts.next().expect("missing library contract file (<file>:<name>)");
let contract = parts.next().expect("missing library contract name (<file>:<name>)");
(filename.to_owned(), contract.to_owned())
})
.fold(BTreeMap::default(), |mut acc, (filename, contract)| {
acc.entry(filename)
.or_default()
//empty offsets since we can't patch it anyways
.insert(contract, vec![]);
acc
})
}

fn link_references(&self) -> BTreeMap<String, BTreeMap<String, Vec<Offsets>>> {
Self::missing_libs_to_link_references(self.missing_libraries.as_slice())
}

pub fn bytecode(&self) -> Option<Bytecode> {
self.eravm
.as_ref()
.and_then(|eravm| eravm.bytecode(self.is_unlinked()))
.or_else(|| {
self.evm
.as_ref()
.and_then(|evm| evm.bytecode.as_ref())
.map(|bytecode| &bytecode.object)
.cloned()
})
.map(|object| {
let mut bytecode: Bytecode = object.into();
bytecode.link_references = self.link_references();
bytecode
})
}
}

// CompactContract variants
Expand All @@ -48,36 +99,36 @@ pub struct Contract {
// Ideally the Artifacts trait would not be coupled to a specific Contract type
impl<'a> From<&'a Contract> for CompactContractBytecodeCow<'a> {
fn from(artifact: &'a Contract) -> Self {
let (bytecode, deployed_bytecode) = if let Some(ref evm) = artifact.evm {
(
evm.bytecode.clone().map(Into::into).map(Cow::Owned),
evm.bytecode.clone().map(Into::into).map(Cow::Owned),
)
} else {
(None, None)
};
let bc = artifact.bytecode();
let bytecode = bc.clone().map(|bc| CompactBytecode {
object: bc.object,
source_map: None,
link_references: bc.link_references,
});
let deployed_bytecode = bytecode.clone().map(|bytecode| CompactDeployedBytecode {
bytecode: Some(bytecode),
immutable_references: Default::default(),
});

CompactContractBytecodeCow {
abi: artifact.abi.as_ref().map(Cow::Borrowed),
bytecode,
deployed_bytecode,
bytecode: bytecode.map(Cow::Owned),
deployed_bytecode: deployed_bytecode.map(Cow::Owned),
}
}
}

impl From<Contract> for CompactContractBytecode {
fn from(c: Contract) -> Self {
let bytecode = if let Some(evm) = c.evm { evm.bytecode } else { None };
Self {
abi: c.abi.map(Into::into),
deployed_bytecode: bytecode.clone().map(|b| b.into()),
bytecode: bytecode.clone().map(|b| b.into()),
}
CompactContractBytecodeCow::from(&c).into()
}
}

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 {
let (bin, bin_runtime) = if let Some(ref eravm) = c.eravm {
(eravm.bytecode.as_ref(), eravm.bytecode.as_ref())
} else if let Some(ref evm) = c.evm {
(evm.bytecode.as_ref().map(|c| &c.object), evm.bytecode.as_ref().map(|c| &c.object))
} else {
(None, None)
Expand Down
35 changes: 32 additions & 3 deletions crates/artifacts/zksolc/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use foundry_compilers_artifacts_solc::{
CompactContractRef, FileToContractsMap, SourceFile, SourceFiles,
Bytecode, BytecodeObject, CompactContractRef, FileToContractsMap, SourceFile, SourceFiles,
};

use semver::Version;
Expand All @@ -9,12 +9,11 @@ use std::{
path::{Path, PathBuf},
};

pub mod bytecode;
pub mod contract;
pub mod error;
pub mod output_selection;

use self::{bytecode::Bytecode, contract::Contract, error::Error};
use self::{contract::Contract, error::Error};

/// file -> (contract name -> Contract)
pub type Contracts = FileToContractsMap<Contract>;
Expand Down Expand Up @@ -109,6 +108,36 @@ pub struct Evm {
pub extra_metadata: Option<ExtraMetadata>,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct EraVM {
/// The contract EraVM assembly code.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub assembly: Option<String>,
/// The contract bytecode.
/// Is reset by that of EraVM before yielding the compiled project artifacts.
#[serde(default, skip_serializing_if = "Option::is_none")]
bytecode: Option<BytecodeObject>,
}

impl EraVM {
pub fn bytecode(&self, should_be_unlinked: bool) -> Option<BytecodeObject> {
self.bytecode.as_ref().map(|object| match (should_be_unlinked, object) {
(true, BytecodeObject::Bytecode(bc)) => {
// convert to unlinked
let encoded = alloy_primitives::hex::encode(bc);
BytecodeObject::Unlinked(encoded)
}
(false, BytecodeObject::Unlinked(bc)) => {
// convert to linked
let bytecode = alloy_primitives::hex::decode(bc).expect("valid bytecode");
BytecodeObject::Bytecode(bytecode.into())
}
_ => object.to_owned(),
})
}
}

///
/// The `solc --standard-json` output contract EVM extra metadata.
#[derive(Debug, Default, Serialize, Deserialize, Clone, Eq, PartialEq)]
Expand Down
5 changes: 3 additions & 2 deletions crates/compilers/src/compilers/zksolc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ impl ZkSolc {
let output = std::str::from_utf8(&output).map_err(|_| SolcError::InvalidUtf8)?;

let mut compiler_output: CompilerOutput = serde_json::from_str(output)?;

// Add zksync version so that there's some way to identify if zksync solc was used
// by looking at build info
compiler_output.zksync_solc_version = self.solc_version_info.zksync_version.clone();
Expand Down Expand Up @@ -728,7 +729,7 @@ mod tests {
let out = zksolc().compile(&input).unwrap();
let (_, mut contracts) = out.split();
let contract = contracts.remove("LinkTest").unwrap();
let bytecode = &contract.evm.unwrap().bytecode.unwrap().object;
let bytecode = contract.bytecode().unwrap().object;
assert!(!bytecode.is_unlinked());
}

Expand All @@ -741,7 +742,7 @@ mod tests {
let out = zksolc().compile(&input).unwrap();
let (_, mut contracts) = out.split();
let contract = contracts.remove("LinkTest").unwrap();
let bytecode = &contract.evm.unwrap().bytecode.unwrap().object;
let bytecode = contract.bytecode().unwrap().object;
assert!(!bytecode.is_unlinked());
}
}
54 changes: 23 additions & 31 deletions crates/compilers/src/zksync/artifact_output/zk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use foundry_compilers_artifacts::{
CompactBytecode, CompactContract, CompactContractBytecode, CompactContractBytecodeCow,
CompactDeployedBytecode,
},
zksolc::{bytecode::Bytecode, contract::Contract, Evm},
zksolc::contract::Contract,
SolcLanguage,
};
use path_slash::PathBufExt;
Expand All @@ -24,19 +24,18 @@ use std::{
path::Path,
};

mod bytecode;
pub use bytecode::ZkArtifactBytecode;

#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct ZkContractArtifact {
pub abi: Option<JsonAbi>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub bytecode: Option<Bytecode>,
pub bytecode: Option<ZkArtifactBytecode>,
#[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 = "Vec::is_empty")]
//pub generated_sources: Vec<GeneratedSource>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub metadata: Option<serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub storage_layout: Option<StorageLayout>,
Expand All @@ -50,13 +49,17 @@ pub struct ZkContractArtifact {
pub hash: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub factory_dependencies: Option<BTreeMap<String, String>>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub missing_libraries: Option<Vec<String>>,
/// The identifier of the source file
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id: Option<u32>,
}

impl ZkContractArtifact {
pub fn missing_libraries(&self) -> Option<&Vec<String>> {
self.bytecode.as_ref().map(|bc| &bc.missing_libraries)
}
}

// CompactContract variants
// TODO: for zkEvm, the distinction between bytecode and deployed_bytecode makes little sense,
// and there some fields that the ouptut doesn't provide (e.g: source_map)
Expand Down Expand Up @@ -92,8 +95,8 @@ impl From<ZkContractArtifact> for CompactContract {
fn from(c: ZkContractArtifact) -> Self {
// TODO: c.abi might have None, we need to get this field from solc_metadata
Self {
bin: c.bytecode.clone().map(|b| b.object),
bin_runtime: c.bytecode.clone().map(|b| b.object),
bin: c.bytecode.clone().map(|b| b.object()),
bin_runtime: c.bytecode.clone().map(|b| b.object()),
abi: c.abi,
}
}
Expand Down Expand Up @@ -132,46 +135,35 @@ impl ZkArtifactOutput {
contract: Contract,
source_file: Option<&SourceFile>,
) -> ZkContractArtifact {
let mut artifact_bytecode = None;
let mut artifact_method_identifiers = None;
let mut artifact_assembly = None;

let is_unlinked = contract.is_unlinked();
let Contract {
abi,
metadata,
userdoc,
devdoc,
storage_layout,
eravm,
evm,
ir_optimized,
hash,
factory_dependencies,
missing_libraries,
} = contract;

if let Some(evm) = evm {
let Evm {
assembly,
bytecode,
method_identifiers,
extra_metadata: _,
legacy_assembly: _,
} = evm;

artifact_bytecode = bytecode.map(Into::into);
artifact_method_identifiers = Some(method_identifiers);
artifact_assembly = assembly;
}
let (bytecode, assembly) = eravm
.map(|eravm| (eravm.bytecode(is_unlinked), eravm.assembly))
.or_else(|| evm.map(|evm| (evm.bytecode.map(|bc| bc.object), evm.assembly)))
.unwrap_or_else(|| (None, None));
let bytecode = bytecode
.map(|object| ZkArtifactBytecode::with_object(object, is_unlinked, missing_libraries));

ZkContractArtifact {
abi,
hash,
factory_dependencies,
missing_libraries,
storage_layout: Some(storage_layout),
bytecode: artifact_bytecode,
assembly: artifact_assembly,
method_identifiers: artifact_method_identifiers,
bytecode,
assembly,
metadata,
userdoc: Some(userdoc),
devdoc: Some(devdoc),
Expand Down
Loading

0 comments on commit 87c2e83

Please sign in to comment.