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(zk): zksolc linking #800

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d6489ae
feat(zk): zksolc linking
Karrq Dec 20, 2024
e5b805d
fix(zk:libs): calculate addresses w/ proper nonce
Karrq Jan 7, 2025
44aebad
Merge remote-tracking branch 'origin/main' into feat/zksolc-link
Karrq Jan 7, 2025
f5dcfa1
Merge remote-tracking branch 'origin/main' into feat/zksolc-link
Karrq Jan 8, 2025
262d2a7
fix: don't always assume DCC is present
Karrq Jan 8, 2025
b79cada
feat(executor): `deploy_library` in strategy
Karrq Jan 9, 2025
1941e33
fix(zk): create address computation
Karrq Jan 10, 2025
93babb9
chore: cleanup unused imports
Karrq Jan 10, 2025
5ed5154
test(zk): deploy-time linking (script/test)
Karrq Jan 10, 2025
018f3f0
chore: default zksolc to 1.5.8
Karrq Jan 13, 2025
c7f4f35
chore: lints
Karrq Jan 13, 2025
c1c2615
refactor: allow multiple lib deployments
Karrq Jan 13, 2025
1ec71e4
refactor(link): move to executor strategy
Karrq Jan 13, 2025
624119d
fix: compilation
Karrq Jan 13, 2025
8ba1e1c
feat(strategy:link): pass config
Karrq Jan 14, 2025
98c56fc
feat(zk:link): dedicated linker module
Karrq Jan 14, 2025
a81ee0c
chore: more lints
Karrq Jan 14, 2025
2152648
feat(zk:link): version check
Karrq Jan 14, 2025
da6caf6
Merge remote-tracking branch 'origin/main' into feat/zksolc-link
Karrq Jan 14, 2025
71b78bd
chore: lints & fmt
Karrq Jan 14, 2025
14b2b0a
chore: more formatting
Karrq Jan 14, 2025
51590b9
fix(zk:link): retrieve factory dep hash
Karrq Jan 14, 2025
ef6b750
fix(zk:compilers): remove dead `libraries` module
Karrq Jan 14, 2025
119b94d
feat(link:zk): create2 linking
Karrq Jan 14, 2025
b3de768
chore: formatting
Karrq Jan 14, 2025
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
11 changes: 7 additions & 4 deletions Cargo.lock

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

76 changes: 6 additions & 70 deletions crates/common/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,14 @@ use foundry_compilers::{
solc::SolcSettings,
Artifact, Project, ProjectBuilder, ProjectCompileOutput, ProjectPathsConfig, SolcConfig,
};
use foundry_zksync_compilers::{
compilers::{
artifact_output::zk::ZkArtifactOutput,
zksolc::{ZkSolc, ZkSolcCompiler},
},
libraries::{self, ZkMissingLibrary},
use foundry_zksync_compilers::compilers::{
artifact_output::zk::ZkArtifactOutput,
zksolc::{ZkSolc, ZkSolcCompiler},
};

use num_format::{Locale, ToFormattedString};
use std::{
collections::{BTreeMap, HashSet},
collections::BTreeMap,
fmt::Display,
io::IsTerminal,
path::{Path, PathBuf},
Expand Down Expand Up @@ -336,7 +333,7 @@ impl ProjectCompiler {
let zksolc_version = ZkSolc::get_version_for_path(&project.compiler.zksolc)?;
Report::new(SpinnerReporter::spawn_with(format!("Using zksolc-{zksolc_version}")));
}
self.zksync_compile_with(&project.paths.root, || {
self.zksync_compile_with(|| {
let files_to_compile =
if !files.is_empty() { files } else { project.paths.input_files() };
let sources = Source::read_all(files_to_compile)?;
Expand All @@ -349,7 +346,6 @@ impl ProjectCompiler {
#[instrument(target = "forge::compile", skip_all)]
fn zksync_compile_with<F>(
self,
root_path: impl AsRef<Path>,
f: F,
) -> Result<ProjectCompileOutput<ZkSolcCompiler, ZkArtifactOutput>>
where
Expand Down Expand Up @@ -394,7 +390,7 @@ impl ProjectCompiler {
sh_println!("{output}")?;
}

self.zksync_handle_output(root_path, &output)?;
self.zksync_handle_output(&output)?;
}

Ok(output)
Expand All @@ -403,71 +399,11 @@ impl ProjectCompiler {
/// If configured, this will print sizes or names
fn zksync_handle_output(
&self,
root_path: impl AsRef<Path>,
output: &ProjectCompileOutput<ZkSolcCompiler, ZkArtifactOutput>,
) -> Result<()> {
let print_names = self.print_names.unwrap_or(false);
let print_sizes = self.print_sizes.unwrap_or(false);

// Process missing libraries
// TODO: skip this if project was not compiled using --detect-missing-libraries
let mut missing_libs_unique: HashSet<String> = HashSet::new();
for (artifact_id, artifact) in output.artifact_ids() {
// TODO: when compiling specific files, the output might still add cached artifacts
// that are not part of the file list to the output, which may cause missing libraries
// error to trigger for files that were not intended to be compiled.
// This behaviour needs to be investigated better on the foundry-compilers side.
// For now we filter, checking only the files passed to compile.
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() {
missing_libs_unique.extend(mls.clone());
}
}
}

let missing_libs: Vec<ZkMissingLibrary> = missing_libs_unique
.into_iter()
.map(|ml| {
let mut split = ml.split(':');
let contract_path =
split.next().expect("Failed to extract contract path for missing library");
let contract_name =
split.next().expect("Failed to extract contract name for missing library");

let mut abs_path_buf = PathBuf::new();
abs_path_buf.push(root_path.as_ref());
abs_path_buf.push(contract_path);

let art = output.find(abs_path_buf.as_path(), contract_name).unwrap_or_else(|| {
panic!(
"Could not find contract {contract_name} at path {contract_path} for compilation output"
)
});

ZkMissingLibrary {
contract_path: contract_path.to_string(),
contract_name: contract_name.to_string(),
missing_libraries: art.missing_libraries().cloned().unwrap_or_default(),
}
})
.collect();

if !missing_libs.is_empty() {
libraries::add_dependencies_to_missing_libraries_cache(
root_path,
missing_libs.as_slice(),
)
.expect("Error while adding missing libraries");
let missing_libs_list = missing_libs
.iter()
.map(|ml| format!("{}:{}", ml.contract_path, ml.contract_name))
.collect::<Vec<String>>()
.join(", ");
eyre::bail!("Missing libraries detected: {missing_libs_list}\n\nRun the following command in order to deploy each missing library:\n\nforge create <LIBRARY> --private-key <PRIVATE_KEY> --rpc-url <RPC_URL> --chain <CHAIN_ID> --zksync\n\nThen pass the library addresses using the --libraries option");
}

// print any sizes or names
if print_names {
let mut artifacts: BTreeMap<_, Vec<_>> = BTreeMap::new();
Expand Down
2 changes: 1 addition & 1 deletion crates/config/src/zksync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ pub fn config_create_project(
{
zksolc
} else if !config.offline {
let default_version = semver::Version::new(1, 5, 7);
let default_version = semver::Version::new(1, 5, 8);
Karrq marked this conversation as resolved.
Show resolved Hide resolved
let mut zksolc = ZkSolc::find_installed_version(&default_version)?;
if zksolc.is_none() {
ZkSolc::blocking_install(&default_version)?;
Expand Down
16 changes: 15 additions & 1 deletion crates/evm/evm/src/executors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,20 @@ impl Executor {
self.deploy_with_env(env, rd)
}

/// Deploys a library contract and commits the new state to the underlying database.
///
/// Executes a CREATE transaction with the contract `code` and persistent database state
/// modifications.
pub fn deploy_library(
&mut self,
from: Address,
code: Bytes,
value: U256,
rd: Option<&RevertDecoder>,
) -> Result<DeployResult, EvmError> {
self.strategy.runner.deploy_library(self, from, code, value, rd)
}

/// Deploys a contract using the given `env` and commits the new state to the underlying
/// database.
///
Expand Down Expand Up @@ -672,7 +686,7 @@ impl Executor {
///
/// If using a backend with cheatcodes, `tx.gas_price` and `block.number` will be overwritten by
/// the cheatcode state in between calls.
fn build_test_env(
pub fn build_test_env(
&self,
caller: Address,
transact_to: TxKind,
Expand Down
50 changes: 47 additions & 3 deletions crates/evm/evm/src/executors/strategy.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::{any::Any, fmt::Debug};

use alloy_primitives::{Address, U256};
use alloy_primitives::{Address, Bytes, U256};
use alloy_serde::OtherFields;
use eyre::Result;
use foundry_cheatcodes::strategy::{
CheatcodeInspectorStrategy, EvmCheatcodeInspectorStrategyRunner,
};
use foundry_evm_core::backend::{strategy::BackendStrategy, Backend, BackendResult, CowBackend};
use foundry_evm_core::{
backend::{strategy::BackendStrategy, Backend, BackendResult, CowBackend},
decode::RevertDecoder,
};
use foundry_zksync_compilers::dual_compiled_contracts::DualCompiledContracts;
use revm::{
primitives::{Env, EnvWithHandlerCfg, ResultAndState},
Expand All @@ -15,7 +18,7 @@ use revm::{

use crate::inspectors::InspectorStack;

use super::Executor;
use super::{DeployResult, EvmError, Executor};

pub trait ExecutorStrategyContext: Debug + Send + Sync + Any {
/// Clone the strategy context.
Expand Down Expand Up @@ -68,9 +71,23 @@ pub trait ExecutorStrategyRunner: Debug + Send + Sync + ExecutorStrategyExt {
amount: U256,
) -> BackendResult<()>;

fn get_balance(&self, executor: &mut Executor, address: Address) -> BackendResult<U256>;

fn set_nonce(&self, executor: &mut Executor, address: Address, nonce: u64)
-> BackendResult<()>;

fn get_nonce(&self, executor: &mut Executor, address: Address) -> BackendResult<u64>;

/// Deploys a library, applying state changes
fn deploy_library(
&self,
executor: &mut Executor,
from: Address,
code: Bytes,
value: U256,
rd: Option<&RevertDecoder>,
) -> Result<DeployResult, EvmError>;

/// Execute a transaction and *WITHOUT* applying state changes.
fn call(
&self,
Expand Down Expand Up @@ -110,6 +127,13 @@ pub trait ExecutorStrategyExt {
) {
}

fn zksync_get_mut_dual_compiled_contracts<'a>(
&self,
_ctx: &'a mut dyn ExecutorStrategyContext,
) -> Option<&'a mut DualCompiledContracts> {
None
}

Karrq marked this conversation as resolved.
Show resolved Hide resolved
/// Set the fork environment on the context.
fn zksync_set_fork_env(
&self,
Expand Down Expand Up @@ -153,6 +177,10 @@ impl ExecutorStrategyRunner for EvmExecutorStrategyRunner {
Ok(())
}

fn get_balance(&self, executor: &mut Executor, address: Address) -> BackendResult<U256> {
executor.get_balance(address)
}

fn set_nonce(
&self,
executor: &mut Executor,
Expand All @@ -166,6 +194,22 @@ impl ExecutorStrategyRunner for EvmExecutorStrategyRunner {
Ok(())
}

fn get_nonce(&self, executor: &mut Executor, address: Address) -> BackendResult<u64> {
executor.get_nonce(address)
}

/// Deploys a library, applying state changes
fn deploy_library(
&self,
executor: &mut Executor,
from: Address,
code: Bytes,
value: U256,
rd: Option<&RevertDecoder>,
) -> Result<DeployResult, EvmError> {
executor.deploy(from, code, value, rd)
}

fn call(
&self,
_ctx: &dyn ExecutorStrategyContext,
Expand Down
12 changes: 6 additions & 6 deletions crates/forge/bin/cmd/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -170,10 +170,10 @@ impl CreateArgs {
let (artifact, id) =
remove_zk_contract(&mut zk_output, &target_path, &self.contract.name)?;

let ZkContractArtifact { bytecode, factory_dependencies, abi, .. } = artifact;
let ZkContractArtifact { bytecode, abi, factory_dependencies, .. } = &artifact;

let abi = abi.expect("Abi not found");
let bin = bytecode.expect("Bytecode not found");
let abi = abi.clone().expect("Abi not found");
let bin = bytecode.as_ref().expect("Bytecode not found");

let bytecode = match bin.object() {
BytecodeObject::Bytecode(bytes) => bytes.to_vec(),
Expand Down Expand Up @@ -220,7 +220,7 @@ impl CreateArgs {

let factory_deps: Vec<Vec<u8>> = {
let factory_dependencies_map =
factory_dependencies.expect("factory deps not found");
factory_dependencies.as_ref().expect("factory deps not found");
let mut visited_paths = HashSet::new();
let mut visited_bytecodes = HashSet::new();
let mut queue = VecDeque::new();
Expand Down Expand Up @@ -248,12 +248,12 @@ impl CreateArgs {
)
});
let fdep_fdeps_map =
fdep_art.factory_dependencies.clone().expect("factory deps not found");
fdep_art.factory_dependencies.as_ref().expect("factory deps not found");
for dep in fdep_fdeps_map.values() {
queue.push_back(dep.clone())
}

// TODO(zk): ensure factory deps are also linked
// NOTE(zk): unlinked factory deps don't show up in `factory_dependencies`
let fdep_bytecode = fdep_art
.bytecode
.clone()
Expand Down
Loading
Loading