Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(zk): properly mark unlinked contract #40

Merged
merged 4 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 0 additions & 28 deletions crates/artifacts/zksolc/src/bytecode.rs

This file was deleted.

106 changes: 81 additions & 25 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;
use alloy_json_abi::JsonAbi;
use foundry_compilers_artifacts_solc::{
CompactContractBytecode, CompactContractBytecodeCow, CompactContractRef, DevDoc, StorageLayout,
UserDoc,
Bytecode, BytecodeObject, 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,70 @@ 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.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub missing_libraries: Option<Vec<String>>,
/// EVM-related outputs
#[serde(default, skip_serializing_if = "Option::is_none")]
pub evm: Option<Evm>,
pub eravm: Option<EraVM>,
/// 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.as_ref())
.map(|object| {
match (self.is_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())
}
(true, BytecodeObject::Unlinked(_)) | (false, BytecodeObject::Bytecode(_)) => {
object.to_owned()
}
}
})
.map(|object| {
let mut bytecode: Bytecode = object.into();
bytecode.link_references = self.link_references();
bytecode
})
}
}

// CompactContract variants
Expand All @@ -48,37 +106,35 @@ 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 {
(evm.bytecode.as_ref().map(|c| &c.object), evm.bytecode.as_ref().map(|c| &c.object))
let (bin, bin_runtime) = if let Some(ref eravm) = c.eravm {
(eravm.bytecode.as_ref(), eravm.bytecode.as_ref())
} else {
(None, None)
};
Expand Down
17 changes: 4 additions & 13 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,
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 @@ -91,22 +90,14 @@ impl CompilerOutput {

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct Evm {
pub struct EraVM {
/// The contract EraVM assembly code.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub assembly: Option<String>,
/// The contract EVM legacy assembly code.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub legacy_assembly: Option<serde_json::Value>,
/// The contract bytecode.
/// Is reset by that of EraVM before yielding the compiled project artifacts.
pub bytecode: Option<Bytecode>,
/// The list of function hashes
#[serde(default, skip_serializing_if = "::std::collections::BTreeMap::is_empty")]
pub method_identifiers: BTreeMap<String, String>,
elfedy marked this conversation as resolved.
Show resolved Hide resolved
/// The extra EVMLA metadata.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub extra_metadata: Option<ExtraMetadata>,
pub bytecode: Option<BytecodeObject>,
}

///
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.eravm.unwrap().bytecode.unwrap();
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.eravm.unwrap().bytecode.unwrap();
assert!(!bytecode.is_unlinked());
}
}
47 changes: 17 additions & 30 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 @@ -132,46 +135,30 @@ 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 Contract {
abi,
metadata,
userdoc,
devdoc,
storage_layout,
evm,
eravm,
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, eravm.assembly)).unwrap_or_else(|| (None, None));
let bytecode = bytecode.map(|object| ZkArtifactBytecode { object, 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
35 changes: 35 additions & 0 deletions crates/compilers/src/zksync/artifact_output/zk/bytecode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
use std::collections::BTreeMap;

use foundry_compilers_artifacts::{
zksolc::contract::Contract, BytecodeObject, CompactBytecode, CompactDeployedBytecode, Offsets,
};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct ZkArtifactBytecode {
pub object: BytecodeObject,

#[serde(default)]
pub missing_libraries: Vec<String>,
}

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

// NOTE: distinction between bytecode and deployed bytecode makes no sense of zkEvm, but
// we implement these conversions in order to be able to use the Artifacts trait.
impl From<ZkArtifactBytecode> for CompactBytecode {
fn from(bcode: ZkArtifactBytecode) -> Self {
let link_references = bcode.link_references();
Self { object: bcode.object, source_map: None, link_references }
}
}

impl From<ZkArtifactBytecode> for CompactDeployedBytecode {
fn from(bcode: ZkArtifactBytecode) -> Self {
Self { bytecode: Some(bcode.into()), immutable_references: BTreeMap::default() }
}
}