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

feat(compiler:zk): zksolc linking #711

Merged
merged 18 commits into from
Nov 20, 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
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ impl ProjectCompiler {
let is_target_file =
self.files.is_empty() || self.files.iter().any(|f| artifact_id.path == *f);
if is_target_file {
if let Some(mls) = &artifact.missing_libraries {
if let Some(mls) = artifact.missing_libraries() {
missing_libs_unique.extend(mls.clone());
}
}
Expand Down Expand Up @@ -422,7 +422,7 @@ impl ProjectCompiler {
ZkMissingLibrary {
contract_path: contract_path.to_string(),
contract_name: contract_name.to_string(),
missing_libraries: art.missing_libraries.clone().unwrap_or_default(),
missing_libraries: art.missing_libraries().cloned().unwrap_or_default(),
}
})
.collect();
Expand Down
30 changes: 24 additions & 6 deletions crates/forge/bin/cmd/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,28 @@ impl CreateArgs {
let ZkContractArtifact { bytecode, hash, factory_dependencies, abi, .. } = artifact;

let abi = abi.expect("Abi not found");

let bin = bytecode.expect("Bytecode not found");

let bytecode = match bin.object() {
BytecodeObject::Bytecode(bytes) => bytes.to_vec(),
_ => {
let link_refs = bin
hedgar2017 marked this conversation as resolved.
Show resolved Hide resolved
.missing_libraries
.iter()
.map(|library| {
let mut parts = library.split(':');
let path = parts.next().unwrap();
let name = parts.next().unwrap();
format!("\t{name}: {path}")
})
.collect::<HashSet<String>>()
.into_iter()
.collect::<Vec<String>>()
.join("\n");
eyre::bail!("Dynamic linking not supported in `create` command - deploy the following library contracts first, then provide the address to link at compile time\n{}", link_refs)
hedgar2017 marked this conversation as resolved.
Show resolved Hide resolved
}
};
let bytecode_hash = H256::from_str(&hash.expect("Contract hash not found"))?;
let bytecode = bin.object.clone().into_bytes().unwrap().to_vec();

// Add arguments to constructor
let config = self.eth.try_load_config_emit_warnings()?;
Expand Down Expand Up @@ -221,12 +239,12 @@ impl CreateArgs {
queue.push_back(dep.clone())
}

// TODO(zk): ensure factory deps are also linked
let fdep_bytecode = fdep_art
.bytecode
.clone()
.expect("Bytecode not found for factory dependency")
.object
.clone()
.object()
.into_bytes()
.unwrap()
.to_vec();
Expand All @@ -243,7 +261,7 @@ impl CreateArgs {
let sender = self.eth.wallet.from.expect("required");
self.deploy_zk(
abi,
bin.object,
bin.object(),
params,
provider,
chain_id,
Expand All @@ -262,7 +280,7 @@ impl CreateArgs {
let provider = ProviderBuilder::<_, _, AnyNetwork>::default().on_provider(provider);
self.deploy_zk(
abi,
bin.object,
bin.object(),
params,
provider,
chain_id,
Expand Down
3 changes: 2 additions & 1 deletion crates/forge/src/multi_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,7 +467,8 @@ impl MultiContractRunnerBuilder {
if let Some(abi) = contract.abi {
let bytecode = contract.bytecode.as_ref();

if let Some(bytecode_object) = bytecode.map(|b| b.object.clone()) {
// TODO(zk): retrieve link_references
if let Some(bytecode_object) = bytecode.map(|b| b.object()) {
let compact_bytecode = CompactBytecode {
object: bytecode_object.clone(),
source_map: None,
Expand Down
13 changes: 13 additions & 0 deletions crates/forge/tests/fixtures/zk/Libraries.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.8.7 <0.9.0;

import {UsesFoo} from "../src/WithLibraries.sol";
import "forge-std/Script.sol";

contract DeployUsesFoo is Script {
function run () external {
// should fail because `UsesFoo` is unlinked
bytes memory _code = vm.getCode("UsesFoo");
}
}
2 changes: 1 addition & 1 deletion crates/forge/tests/it/test_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ impl ForgeTestProfile {
zk_config.zksync.startup = true;
zk_config.zksync.fallback_oz = true;
zk_config.zksync.optimizer_mode = '3';
zk_config.zksync.zksolc = Some(foundry_config::SolcReq::Version(Version::new(1, 5, 4)));
zk_config.zksync.zksolc = Some(foundry_config::SolcReq::Version(Version::new(1, 5, 7)));
zk_config.fuzz.no_zksync_reserved_addresses = true;

zk_config
Expand Down
54 changes: 54 additions & 0 deletions crates/forge/tests/it/zk/linking.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use foundry_test_utils::{forgetest_async, util, TestProject};

use crate::test_helpers::{deploy_zk_contract, run_zk_script_test};

// TODO(zk): add test that actually does the deployment
// of the unlinked contract via script, once recursive linking is supported
// and once we also support doing deploy-time linking

forgetest_async!(
#[should_panic = "no bytecode for contract; is it abstract or unlinked?"]
script_using_unlinked_fails,
|prj, cmd| {
setup_libs_prj(&mut prj);
run_zk_script_test(
prj.root(),
&mut cmd,
"./script/Libraries.s.sol",
"DeployUsesFoo",
None,
1,
Some(&["-vvvvv"]),
);
}
);

forgetest_async!(
#[should_panic = "Dynamic linking not supported"]
create_using_unlinked_fails,
|prj, cmd| {
setup_libs_prj(&mut prj);

// we don't really connect to the rpc because
// we expect to fail before that point
let foo_address = deploy_zk_contract(
&mut cmd,
"127.0.0.1:1234",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"./src/WithLibraries.sol:UsesFoo",
)
.expect("Failed to deploy UsesFoo contract");

assert!(!foo_address.is_empty(), "Deployed address should not be empty");
}
);

fn setup_libs_prj(prj: &mut TestProject) {
util::initialize(prj.root());
prj.add_script("Libraries.s.sol", include_str!("../../fixtures/zk/Libraries.s.sol")).unwrap();
prj.add_source(
"WithLibraries.sol",
include_str!("../../../../../testdata/zk/WithLibraries.sol"),
)
.unwrap();
}
1 change: 1 addition & 0 deletions crates/forge/tests/it/zk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ mod factory_deps;
mod fork;
mod fuzz;
mod invariant;
mod linking;
mod logs;
mod nft;
mod ownership;
Expand Down
3 changes: 2 additions & 1 deletion crates/script/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ impl LinkedBuildData {
for (id, contract) in zk_contracts {
if let Some(abi) = contract.abi {
let bytecode = contract.bytecode.as_ref();
if let Some(bytecode_object) = bytecode.map(|b| b.object.clone()) {
// TODO(zk): retrieve link_references
if let Some(bytecode_object) = bytecode.map(|b| b.object()) {
let compact_bytecode = CompactBytecode {
object: bytecode_object.clone(),
source_map: None,
Expand Down
2 changes: 1 addition & 1 deletion crates/zksync/compiler/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub fn config_create_project(
{
zksolc
} else if !config.offline {
let default_version = semver::Version::new(1, 5, 4);
let default_version = semver::Version::new(1, 5, 7);
let mut zksolc = ZkSolc::find_installed_version(&default_version)?;
if zksolc.is_none() {
ZkSolc::blocking_install(&default_version)?;
Expand Down
4 changes: 2 additions & 2 deletions crates/zksync/compiler/src/zksolc/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ impl DualCompiledContracts {
// at this stage for zksolc, and BytecodeObject as ref will get the bytecode bytes.
// We should be careful however and check/handle errors in
// case an Unlinked BytecodeObject gets here somehow
let bytes = bytecode.object.clone().into_bytes().unwrap();
let bytes = bytecode.object().into_bytes().unwrap();
zksolc_all_bytecodes.insert(hash.clone(), bytes.to_vec());
}
}
Expand Down Expand Up @@ -167,7 +167,7 @@ impl DualCompiledContracts {
// bytes. However, we should check and
// handle errors in case an Unlinked BytecodeObject gets
// here somehow
let bytecode_vec = bytecode.object.clone().into_bytes().unwrap().to_vec();
let bytecode_vec = bytecode.object().into_bytes().unwrap().to_vec();
let mut factory_deps_vec: Vec<Vec<u8>> = factory_deps_map
.keys()
.map(|factory_hash| {
Expand Down
17 changes: 17 additions & 0 deletions testdata/zk/WithLibraries.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity >=0.8.7 <0.9.0;

library Foo {
function add(uint256 a, uint256 b) external pure returns (uint256 c) {
c = a + b;
}
}

contract UsesFoo {
uint256 number;

constructor() {
number = Foo.add(42, 0);
}
}