From 6f79d93d0cb7fdd62a71152c54d9b1079c6a1d49 Mon Sep 17 00:00:00 2001 From: Karrq Date: Thu, 20 Jun 2024 18:00:30 +0200 Subject: [PATCH 1/3] refactor(config): split zk-related configuration (#430) --- crates/cli/src/opts/build/core.rs | 9 +- crates/cli/src/opts/build/mod.rs | 76 ++-------------- crates/cli/src/opts/build/zksync.rs | 105 ++++++++++++++++++++++ crates/config/src/lib.rs | 86 +++--------------- crates/config/src/zksync.rs | 111 ++++++++++++++++++++++++ crates/forge/bin/cmd/build.rs | 2 +- crates/forge/bin/cmd/create.rs | 2 +- crates/forge/bin/cmd/script/executor.rs | 7 +- crates/forge/bin/cmd/test/mod.rs | 4 +- crates/forge/tests/cli/config.rs | 13 +-- 10 files changed, 247 insertions(+), 168 deletions(-) create mode 100644 crates/cli/src/opts/build/zksync.rs create mode 100644 crates/config/src/zksync.rs diff --git a/crates/cli/src/opts/build/core.rs b/crates/cli/src/opts/build/core.rs index dd01710bd..cdb0dd0f4 100644 --- a/crates/cli/src/opts/build/core.rs +++ b/crates/cli/src/opts/build/core.rs @@ -6,8 +6,8 @@ use foundry_compilers::{ artifacts::RevertStrings, remappings::Remapping, utils::canonicalized, Project, }; use foundry_config::{ - figment, figment::{ + self, error::Kind::InvalidType, value::{Dict, Map, Value}, Figment, Metadata, Profile, Provider, @@ -157,7 +157,12 @@ impl<'a> From<&'a CoreBuildArgs> for Figment { let mut remappings = Remappings::new_with_remappings(args.project_paths.get_remappings()); remappings .extend(figment.extract_inner::>("remappings").unwrap_or_default()); - figment.merge(("remappings", remappings.into_inner())).merge(args) + + // override only set values from the CLI for zksync + let zksync = + args.compiler.zk.apply_overrides(figment.extract_inner("zksync").unwrap_or_default()); + + figment.merge(("remappings", remappings.into_inner())).merge(args).merge(("zksync", zksync)) } } diff --git a/crates/cli/src/opts/build/mod.rs b/crates/cli/src/opts/build/mod.rs index 42a7cb8df..af44c99b0 100644 --- a/crates/cli/src/opts/build/mod.rs +++ b/crates/cli/src/opts/build/mod.rs @@ -1,5 +1,3 @@ -use std::path::PathBuf; - use clap::Parser; use foundry_compilers::{artifacts::output_selection::ContractOutputSelection, EvmVersion}; use serde::Serialize; @@ -10,6 +8,9 @@ pub use self::core::CoreBuildArgs; mod paths; pub use self::paths::ProjectPathsArgs; +mod zksync; +pub use self::zksync::ZkSyncArgs; + // A set of solc compiler settings that can be set via command line arguments, which are intended // to be merged into an existing `foundry_config::Config`. // @@ -53,76 +54,9 @@ pub struct CompilerArgs { #[serde(skip_serializing_if = "Vec::is_empty")] pub extra_output_files: Vec, - // @zksync - /// Use ZKSync era vm. - #[clap(help_heading = "Use ZKSync era vm", long)] - pub zksync: bool, - - #[clap( - help_heading = "zkSync Compiler options", - help = "Solc compiler path to use when compiling with zksolc", - long = "zk-solc-path", - value_name = "ZK_SOLC_PATH" - )] - #[serde(skip_serializing_if = "Option::is_none")] - pub zk_solc_path: Option, - - /// A flag indicating whether to enable the system contract compilation mode. - #[clap( - help_heading = "zkSync Compiler options", - help = "Enable the system contract compilation mode.", - long = "is-system", - value_name = "SYSTEM_MODE" - )] - #[serde(skip_serializing_if = "Option::is_none")] - pub is_system: Option, - - /// A flag indicating whether to forcibly switch to the EVM legacy assembly pipeline. - #[clap( - help_heading = "zkSync Compiler options", - help = "Forcibly switch to the EVM legacy assembly pipeline.", - long = "force-evmla", - value_name = "FORCE_EVMLA" - )] - #[serde(skip_serializing_if = "Option::is_none")] - pub force_evmla: Option, - - /// Try to recompile with -Oz if the bytecode is too large. - #[clap( - help_heading = "zkSync Compiler options", - long = "fallback-oz", - value_name = "FALLBACK_OZ" - )] - #[serde(skip_serializing_if = "Option::is_none")] - pub fallback_oz: Option, - - /// Path to cache missing library dependencies, used for compiling and deploying libraries. - #[clap(help_heading = "zkSync Compiler options", long = "detect-missing-libraries")] - pub detect_missing_libraries: bool, - - /// Set the LLVM optimization parameter `-O[0 | 1 | 2 | 3 | s | z]`. - /// Use `3` for best performance and `z` for minimal size. - #[clap( - help_heading = "zkSync Compiler options", - short = 'O', - long = "optimization", - value_name = "LEVEL" - )] - #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, - - /// Enables optimizations - #[clap(help_heading = "zkSync Compiler options", long = "zk-optimizer")] + #[clap(flatten)] #[serde(skip)] - pub zk_optimizer: bool, - - /// Contracts to avoid compiling on zkSync - #[clap( - long, - help_heading = "Contracts to avoid during zkSync compilation", - value_delimiter = ',' - )] - pub avoid_contracts: Option>, + pub zk: ZkSyncArgs, } #[cfg(test)] diff --git a/crates/cli/src/opts/build/zksync.rs b/crates/cli/src/opts/build/zksync.rs new file mode 100644 index 000000000..77e33dd1e --- /dev/null +++ b/crates/cli/src/opts/build/zksync.rs @@ -0,0 +1,105 @@ +use std::path::PathBuf; + +use clap::Parser; +use foundry_config::ZkSyncConfig; +use serde::Serialize; + +#[derive(Clone, Debug, Default, Serialize, Parser)] +#[clap(next_help_heading = "ZKSync configuration")] +pub struct ZkSyncArgs { + /// Use ZKSync era vm. + #[clap(long = "zksync", visible_alias = "zk")] + pub enable: bool, + + #[clap( + help = "Solc compiler path to use when compiling with zksolc", + long = "zk-solc-path", + value_name = "ZK_SOLC_PATH" + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub solc_path: Option, + + /// A flag indicating whether to enable the system contract compilation mode. + #[clap( + help = "Enable the system contract compilation mode.", + long = "zk-eravm-extensions", + visible_alias = "enable-eravm-extensions", + visible_alias = "system-mode", + value_name = "ENABLE_ERAVM_EXTENSIONS" + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub eravm_extensions: Option, + + /// A flag indicating whether to forcibly switch to the EVM legacy assembly pipeline. + #[clap( + help = "Forcibly switch to the EVM legacy assembly pipeline.", + long = "zk-force-evmla", + visible_alias = "force-evmla", + value_name = "FORCE_EVMLA" + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub force_evmla: Option, + + /// Try to recompile with -Oz if the bytecode is too large. + #[clap(long = "zk-fallback-oz", visible_alias = "fallback-oz", value_name = "FALLBACK_OZ")] + #[serde(skip_serializing_if = "Option::is_none")] + pub fallback_oz: Option, + + /// Detect missing libraries, instead of erroring + /// + /// Currently unused + #[clap(long = "zk-detect-missing-libraries")] + pub detect_missing_libraries: bool, + + /// Set the LLVM optimization parameter `-O[0 | 1 | 2 | 3 | s | z]`. + /// Use `3` for best performance and `z` for minimal size. + #[clap( + short = 'O', + long = "zk-optimizer-mode", + visible_alias = "zk-optimization", + value_name = "LEVEL" + )] + #[serde(skip_serializing_if = "Option::is_none")] + pub optimizer_mode: Option, + + /// Enables optimizations + #[clap(long = "zk-optimizer")] + #[serde(skip)] + pub optimizer: bool, + + /// Contracts to avoid compiling on zkSync + #[clap(long = "zk-avoid-contracts", visible_alias = "avoid-contracts", value_delimiter = ',')] + pub avoid_contracts: Option>, +} + +impl ZkSyncArgs { + /// Merge the current cli arguments into the specified zksync configuration + pub(crate) fn apply_overrides(&self, mut zksync: ZkSyncConfig) -> ZkSyncConfig { + macro_rules! set_if_some { + ($src:expr, $dst:expr) => { + if let Some(src) = $src { + $dst = src.into(); + } + }; + } + + set_if_some!(self.enable.then_some(true), zksync.enable); + set_if_some!(self.solc_path.clone(), zksync.solc_path); + set_if_some!(self.eravm_extensions, zksync.eravm_extensions); + set_if_some!(self.force_evmla, zksync.force_evmla); + set_if_some!(self.fallback_oz, zksync.fallback_oz); + set_if_some!( + self.detect_missing_libraries.then_some(true), + zksync.detect_missing_libraries + ); + set_if_some!(self.avoid_contracts.clone(), zksync.avoid_contracts); + + set_if_some!(self.optimizer.then_some(true), zksync.optimizer); + set_if_some!( + self.optimizer_mode.as_ref().and_then(|mode| mode.parse::().ok()), + zksync.optimizer_mode + ); + + zksync + } +} diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index f58275410..e2c86ec89 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -22,15 +22,7 @@ use foundry_compilers::{ cache::SOLIDITY_FILES_CACHE_FILENAME, error::SolcError, remappings::{RelativeRemapping, Remapping}, - zksync::{ - artifacts::{ - BytecodeHash as ZkSolcBytecodeHash, Optimizer as ZkSolcOptimizer, - OptimizerDetails as ZkSolcOptimizerDetails, Settings as ZkSolcSettings, - SettingsMetadata as ZkSolcSettingsMetadata, - }, - compile::ZkSolc, - config::ZkSolcConfig, - }, + zksync::{artifacts::Settings as ZkSolcSettings, compile::ZkSolc, config::ZkSolcConfig}, ConfigurableArtifacts, EvmVersion, Project, ProjectPathsConfig, Solc, SolcConfig, }; use inflector::Inflector; @@ -106,6 +98,9 @@ mod inline; use crate::etherscan::EtherscanEnvProvider; pub use inline::{validate_profiles, InlineConfig, InlineConfigError, InlineConfigParser, NatSpec}; +mod zksync; +pub use zksync::*; + /// Foundry configuration /// /// # Defaults @@ -414,31 +409,7 @@ pub struct Config { pub __warnings: Vec, /// @zkSync zkSolc configuration and settings - /// - /// Use zksync era - pub zksync: bool, - /// The zkSolc instance to use if any. - pub zksolc: Option, - /// solc path to use along the zksolc compiler - pub zk_solc_path: Option, - /// Optimizer settings for zkSync - pub zk_optimizer: bool, - /// The optimization mode string. - pub mode: char, - /// zksolc optimizer details remain the same - pub zk_optimizer_details: Option, - /// Whether to include the metadata hash for zksolc compiled bytecode. - pub zk_bytecode_hash: ZkSolcBytecodeHash, - /// Whether to try to recompile with -Oz if the bytecode is too large. - pub fallback_oz: bool, - /// Whether to support compilation of zkSync-specific simulations - pub is_system: bool, - /// Force evmla for zkSync - pub force_evmla: bool, - /// Path to cache missing library dependencies, used for compiling and deploying libraries. - pub detect_missing_libraries: bool, - /// Source files to avoid compiling on zksolc - pub avoid_contracts: Option>, + pub zksync: ZkSyncConfig, } /// Mapping of fallback standalone sections. See [`FallbackProfileProvider`] @@ -461,7 +432,7 @@ impl Config { /// Standalone sections in the config which get integrated into the selected profile pub const STANDALONE_SECTIONS: &'static [&'static str] = - &["rpc_endpoints", "etherscan", "fmt", "doc", "fuzz", "invariant", "labels"]; + &["rpc_endpoints", "etherscan", "fmt", "doc", "fuzz", "invariant", "labels", "zksync"]; /// File name of config toml file pub const FILE_NAME: &'static str = "foundry.toml"; @@ -728,7 +699,7 @@ impl Config { // when setting up the builder for the sake of consistency (requires dedicated // builder methods) project.zksync_zksolc_config = ZkSolcConfig { settings: self.zksync_zksolc_settings()? }; - project.zksync_avoid_contracts = self.avoid_contracts.clone().map(|patterns| { + project.zksync_avoid_contracts = self.zksync.avoid_contracts.clone().map(|patterns| { patterns .into_iter() .map(|pat| globset::Glob::new(&pat).expect("invalid pattern").compile_matcher()) @@ -802,7 +773,7 @@ impl Config { /// /// If `zksolc` is [`SolcReq::Local`] then this will ensure that the path exists. fn zksync_ensure_zksolc(&self) -> Result, SolcError> { - if let Some(ref zksolc) = self.zksolc { + if let Some(ref zksolc) = self.zksync.zksolc { let zksolc = match zksolc { SolcReq::Version(version) => { let mut zksolc = ZkSolc::find_installed_version(version)?; @@ -1218,32 +1189,7 @@ impl Config { Err(e) => return Err(SolcError::msg(format!("Failed to parse libraries: {}", e))), }; - let optimizer = ZkSolcOptimizer { - enabled: Some(self.zk_optimizer), - mode: Some(self.mode), - fallback_to_optimizing_for_size: Some(self.fallback_oz), - disable_system_request_memoization: Some(true), - details: self.zk_optimizer_details.clone(), - jump_table_density_threshold: None, - }; - - let settings = ZkSolcSettings { - libraries, - optimizer, - evm_version: Some(self.evm_version), - metadata: Some(ZkSolcSettingsMetadata { bytecode_hash: Some(self.zk_bytecode_hash) }), - via_ir: Some(self.via_ir), - // Set in project paths. - remappings: Vec::new(), - detect_missing_libraries: self.detect_missing_libraries, - system_mode: self.is_system, - force_evmla: self.force_evmla, - // TODO: See if we need to set this from here - output_selection: Default::default(), - solc: self.zk_solc_path.clone(), - }; - - Ok(settings) + Ok(self.zksync.settings(libraries, self.evm_version, self.via_ir)) } /// Returns the default figment @@ -2070,19 +2016,7 @@ impl Default for Config { labels: Default::default(), __non_exhaustive: (), __warnings: vec![], - // @zkSync - zksolc: None, - zk_bytecode_hash: ZkSolcBytecodeHash::None, - zk_optimizer: true, - mode: '3', - zksync: false, - zk_solc_path: None, - zk_optimizer_details: None, - fallback_oz: false, - force_evmla: false, - is_system: false, - detect_missing_libraries: false, - avoid_contracts: None, + zksync: Default::default(), } } } diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs new file mode 100644 index 000000000..1f498f47e --- /dev/null +++ b/crates/config/src/zksync.rs @@ -0,0 +1,111 @@ +use foundry_compilers::{ + artifacts::Libraries, + zksync::artifacts::{BytecodeHash, Optimizer, OptimizerDetails, Settings, SettingsMetadata}, + EvmVersion, +}; + +use serde::{Deserialize, Serialize}; +use std::path::PathBuf; + +use crate::SolcReq; + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +/// ZkSync configuration +pub struct ZkSyncConfig { + /// Enable zksync mode + pub enable: bool, + + /// The zkSolc instance to use if any. + pub zksolc: Option, + + /// solc path to use along the zksolc compiler + pub solc_path: Option, + + /// Whether to include the metadata hash for zksolc compiled bytecode. + pub bytecode_hash: BytecodeHash, + + /// Whether to try to recompile with -Oz if the bytecode is too large. + pub fallback_oz: bool, + + /// Whether to support compilation of zkSync-specific simulations + pub eravm_extensions: bool, + + /// Force evmla for zkSync + pub force_evmla: bool, + + /// Detect missing libraries, instead of erroring + /// + /// Currently unused + pub detect_missing_libraries: bool, + + /// Source files to avoid compiling on zksolc + pub avoid_contracts: Option>, + + /// Enable optimizer for zkSync + pub optimizer: bool, + + /// The optimization mode string for zkSync + pub optimizer_mode: char, + + /// zkSolc optimizer details + pub optimizer_details: Option, +} + +impl Default for ZkSyncConfig { + fn default() -> Self { + Self { + enable: Default::default(), + zksolc: Default::default(), + solc_path: Default::default(), + bytecode_hash: Default::default(), + fallback_oz: Default::default(), + eravm_extensions: Default::default(), + force_evmla: Default::default(), + detect_missing_libraries: Default::default(), + avoid_contracts: Default::default(), + optimizer: true, + optimizer_mode: '3', + optimizer_details: Default::default(), + } + } +} + +impl ZkSyncConfig { + /// Returns true if zk mode is enabled and it if tests should be run in zk mode + pub fn run_in_zk_mode(&self) -> bool { + self.enable + } + + /// Convert the zksync config to a foundry_compilers zksync Settings + pub fn settings( + &self, + libraries: Libraries, + evm_version: EvmVersion, + via_ir: bool, + ) -> Settings { + let optimizer = Optimizer { + enabled: Some(self.optimizer), + mode: Some(self.optimizer_mode), + fallback_to_optimizing_for_size: Some(self.fallback_oz), + disable_system_request_memoization: Some(true), + details: self.optimizer_details.clone(), + jump_table_density_threshold: None, + }; + + Settings { + libraries, + optimizer, + evm_version: Some(evm_version), + metadata: Some(SettingsMetadata { bytecode_hash: Some(self.bytecode_hash) }), + via_ir: Some(via_ir), + // Set in project paths. + remappings: Vec::new(), + detect_missing_libraries: self.detect_missing_libraries, + system_mode: self.eravm_extensions, + force_evmla: self.force_evmla, + // TODO: See if we need to set this from here + output_selection: Default::default(), + solc: self.solc_path.clone(), + } + } +} diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index 0c23dc2a7..49e1fa788 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -87,7 +87,7 @@ impl BuildArgs { project = config.project()?; } - if !config.zksync { + if !config.zksync.enable { let mut compiler = ProjectCompiler::new() .print_names(self.names) .print_sizes(self.sizes) diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 751104fc5..7e441433b 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -117,7 +117,7 @@ impl CreateArgs { pub async fn run(self) -> Result<()> { let mut config = self.eth.try_load_config_emit_warnings()?; let project_root = config.project_paths().root; - let zksync = self.opts.compiler.zksync; + let zksync = self.opts.compiler.zk.enable; // Resolve missing libraries let libs_batches = if zksync && self.deploy_missing_libraries { diff --git a/crates/forge/bin/cmd/script/executor.rs b/crates/forge/bin/cmd/script/executor.rs index 108cdf286..c89b78919 100644 --- a/crates/forge/bin/cmd/script/executor.rs +++ b/crates/forge/bin/cmd/script/executor.rs @@ -159,7 +159,7 @@ impl ScriptArgs { tx.to, tx.input.clone().into_input(), tx.value, - (script_config.config.zksync, zk.clone()), + (script_config.config.zksync.run_in_zk_mode(), zk.clone()), ) .wrap_err("Internal EVM error during simulation")?; @@ -323,6 +323,7 @@ impl ScriptArgs { .spec(script_config.config.evm_spec_id()) .gas_limit(script_config.evm_opts.gas_limit()); + let use_zk = script_config.config.zksync.run_in_zk_mode(); if let SimulationStage::Local = stage { builder = builder.inspectors(|stack| { stack @@ -333,7 +334,7 @@ impl ScriptArgs { script_config.evm_opts.clone(), script_wallets, dual_compiled_contracts.unwrap_or_default(), - script_config.config.zksync, + use_zk, ) .into(), ) @@ -342,7 +343,7 @@ impl ScriptArgs { } let mut executor = builder.build(env, db); - executor.use_zk = script_config.config.zksync; + executor.use_zk = use_zk; Ok(ScriptRunner::new(executor, script_config.evm_opts.initial_balance, sender)) } } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 4677da088..069e72677 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -210,7 +210,7 @@ impl TestArgs { evm_opts.clone(), None, dual_compiled_contracts, - config.zksync, + config.zksync.run_in_zk_mode(), )) .with_test_options(test_options.clone()) .enable_isolation(evm_opts.isolate) @@ -226,7 +226,7 @@ impl TestArgs { } *test_pattern = Some(debug_test_pattern.clone()); } - runner.use_zk = config.zksync; + runner.use_zk = config.zksync.run_in_zk_mode(); let outcome = self.run_tests(runner, config, verbosity, &filter, test_options).await?; diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index 94e10ac21..7ddf44a3e 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -124,18 +124,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { isolate: true, __non_exhaustive: (), __warnings: vec![], - zk_optimizer: Default::default(), - mode: Default::default(), - zksync: false, - zk_optimizer_details: Default::default(), - fallback_oz: Default::default(), - is_system: Default::default(), - force_evmla: Default::default(), - detect_missing_libraries: Default::default(), - zksolc: Default::default(), - zk_solc_path: None, - zk_bytecode_hash: Default::default(), - avoid_contracts: Default::default(), + zksync: Default::default(), }; prj.write_config(input.clone()); let config = cmd.config(); From 56b536b728b2ca1c841343ac6d3964b93d05b97a Mon Sep 17 00:00:00 2001 From: Karrq Date: Sat, 22 Jun 2024 18:27:58 +0200 Subject: [PATCH 2/3] docs: zksync config (#437) --- README.md | 7 +++---- crates/config/README.md | 32 ++++++++++++++++++++++++++++++++ crates/config/src/lib.rs | 2 +- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 3154db303..7f868ec7e 100644 --- a/README.md +++ b/README.md @@ -240,11 +240,10 @@ src = 'src' out = 'out' libs = ['lib'] -[profile.zksync] -src = 'src' -libs = ['lib'] +[profile.default.zksync] +enable = true fallback_oz = true -mode = "3" +mode = '3' ``` ### Additional Configuration diff --git a/crates/config/README.md b/crates/config/README.md index 75979464b..5f18f6133 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -204,6 +204,38 @@ tab_width = 2 bracket_spacing = true ``` +#### ZkSync settings + +ZkSync configuration can be tweaked with the [`ZkSyncConfig`](./src/zksync.rs) object. +The `zksync` settings must be prefixed with the profile they correspond to: +`[profile.default.zksync]` belongs to the `[profile.default]` profile + +``` toml +[profile.default.zksync] +# Overridden by `--zksync` flag +enable = false +# By default the latest version is used +zksolc = "1.5.0" +# By default the corresponding solc patched version from matter-labs is used +solc_path = "./solc-0.8.23-1.0.1" +bytecode_hash = "none" +# Allow compiler to use mode 'z' if contracts won't fit in the EraVM bytecode +# size limitations +fallback_oz = false +# Enable EraVM extensions (ex system-mode) +eravm_extensions = false +# Force compilation via EVMLA instead of Yul codegen pipeline +force_evmla = false +# List of globs that match contracts to avoid compiling with zksolc +avoid_contracts = [] +# Enable optimizer on zksolc (defaults to true) +optimizer = true +# zksolc optimizer mode (0 | 1 | 2 | 3 | s | z) +optimizer_mode = '3' +# zksolc optimizer details +optimizer_details = { ... } +``` + #### Additional Optimizer settings Optimizer components can be tweaked with the `OptimizerDetails` object: diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index e2c86ec89..f1c4374bc 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -432,7 +432,7 @@ impl Config { /// Standalone sections in the config which get integrated into the selected profile pub const STANDALONE_SECTIONS: &'static [&'static str] = - &["rpc_endpoints", "etherscan", "fmt", "doc", "fuzz", "invariant", "labels", "zksync"]; + &["rpc_endpoints", "etherscan", "fmt", "doc", "fuzz", "invariant", "labels"]; /// File name of config toml file pub const FILE_NAME: &'static str = "foundry.toml"; From f505a536a8f6ddae2b8618a701f66c047b1a2b0c Mon Sep 17 00:00:00 2001 From: Karrq Date: Tue, 25 Jun 2024 22:32:52 +0200 Subject: [PATCH 3/3] refactor: only `script`/`test` evm or zksync (#435) --- README.md | 2 +- crates/cheatcodes/src/inspector.rs | 2 ++ crates/cli/src/opts/build/zksync.rs | 53 +++++++++++++++++++++++----- crates/config/README.md | 6 ++-- crates/config/src/zksync.rs | 17 ++++++--- crates/forge/bin/cmd/build.rs | 2 +- crates/forge/bin/cmd/create.rs | 2 +- crates/forge/bin/cmd/script/build.rs | 23 +++++++----- crates/forge/bin/cmd/test/mod.rs | 13 ++++--- zk-tests/test.sh | 26 +++++++------- 10 files changed, 101 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 7f868ec7e..1b3882c08 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ out = 'out' libs = ['lib'] [profile.default.zksync] -enable = true +compile = true fallback_oz = true mode = '3' ``` diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 6f4c20bcf..d0c026033 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -1609,6 +1609,7 @@ impl Inspector for Cheatcodes { let constructor_input = call.init_code[contract.evm_bytecode.len()..].to_vec(); + let create_input = foundry_zksync_core::encode_create_params( &call.scheme, contract.zk_bytecode_hash, @@ -1712,6 +1713,7 @@ impl Inspector for Cheatcodes { let factory_deps = self.dual_compiled_contracts.fetch_all_factory_deps(zk_contract); tracing::debug!(contract = zk_contract.name, "using dual compiled contract"); + let ccx = foundry_zksync_core::vm::CheatcodeTracerContext { mocked_calls: self.mocked_calls.clone(), expected_calls: Some(&mut self.expected_calls), diff --git a/crates/cli/src/opts/build/zksync.rs b/crates/cli/src/opts/build/zksync.rs index 77e33dd1e..1c60b3248 100644 --- a/crates/cli/src/opts/build/zksync.rs +++ b/crates/cli/src/opts/build/zksync.rs @@ -7,9 +7,27 @@ use serde::Serialize; #[derive(Clone, Debug, Default, Serialize, Parser)] #[clap(next_help_heading = "ZKSync configuration")] pub struct ZkSyncArgs { - /// Use ZKSync era vm. - #[clap(long = "zksync", visible_alias = "zk")] - pub enable: bool, + /// Compile for zkVM + #[clap( + long = "zk-compile", + value_name = "COMPILE_FOR_ZKVM", + num_args = 0..=1, + require_equals = true, + default_missing_value = "true", + default_value_if("startup", "true", "true"))] + pub compile: Option, + + /// Enable zkVM at startup + #[clap( + long = "zk-startup", + visible_alias = "zksync", + display_order = 0, + value_name = "ENABLE_ZKVM_AT_STARTUP", + num_args = 0..=1, + require_equals = true, + default_missing_value = "true", + )] + pub startup: Option, #[clap( help = "Solc compiler path to use when compiling with zksolc", @@ -25,7 +43,10 @@ pub struct ZkSyncArgs { long = "zk-eravm-extensions", visible_alias = "enable-eravm-extensions", visible_alias = "system-mode", - value_name = "ENABLE_ERAVM_EXTENSIONS" + value_name = "ENABLE_ERAVM_EXTENSIONS", + num_args = 0..=1, + require_equals = true, + default_missing_value = "true" )] #[serde(skip_serializing_if = "Option::is_none")] pub eravm_extensions: Option, @@ -35,14 +56,23 @@ pub struct ZkSyncArgs { help = "Forcibly switch to the EVM legacy assembly pipeline.", long = "zk-force-evmla", visible_alias = "force-evmla", - value_name = "FORCE_EVMLA" + value_name = "FORCE_EVMLA", + num_args = 0..=1, + require_equals = true, + default_missing_value = "true" )] #[serde(skip_serializing_if = "Option::is_none")] pub force_evmla: Option, /// Try to recompile with -Oz if the bytecode is too large. - #[clap(long = "zk-fallback-oz", visible_alias = "fallback-oz", value_name = "FALLBACK_OZ")] - #[serde(skip_serializing_if = "Option::is_none")] + #[clap( + long = "zk-fallback-oz", + visible_alias = "fallback-oz", + value_name = "FALLBACK_OZ", + num_args = 0..=1, + require_equals = true, + default_missing_value = "true" + )] pub fallback_oz: Option, /// Detect missing libraries, instead of erroring @@ -64,7 +94,6 @@ pub struct ZkSyncArgs { /// Enables optimizations #[clap(long = "zk-optimizer")] - #[serde(skip)] pub optimizer: bool, /// Contracts to avoid compiling on zkSync @@ -73,6 +102,11 @@ pub struct ZkSyncArgs { } impl ZkSyncArgs { + /// Returns true if zksync mode is enabled + pub fn enabled(&self) -> bool { + self.compile.unwrap_or_default() + } + /// Merge the current cli arguments into the specified zksync configuration pub(crate) fn apply_overrides(&self, mut zksync: ZkSyncConfig) -> ZkSyncConfig { macro_rules! set_if_some { @@ -83,7 +117,8 @@ impl ZkSyncArgs { }; } - set_if_some!(self.enable.then_some(true), zksync.enable); + set_if_some!(self.compile, zksync.compile); + set_if_some!(self.startup, zksync.startup); set_if_some!(self.solc_path.clone(), zksync.solc_path); set_if_some!(self.eravm_extensions, zksync.eravm_extensions); set_if_some!(self.force_evmla, zksync.force_evmla); diff --git a/crates/config/README.md b/crates/config/README.md index 5f18f6133..e96040ab4 100644 --- a/crates/config/README.md +++ b/crates/config/README.md @@ -212,8 +212,10 @@ The `zksync` settings must be prefixed with the profile they correspond to: ``` toml [profile.default.zksync] -# Overridden by `--zksync` flag -enable = false +# Compile contracts for zkVM +compile = false +# Enable zkVM at startup, needs `compile = true` to have effect +startup = true # By default the latest version is used zksolc = "1.5.0" # By default the corresponding solc patched version from matter-labs is used diff --git a/crates/config/src/zksync.rs b/crates/config/src/zksync.rs index 1f498f47e..b8265418e 100644 --- a/crates/config/src/zksync.rs +++ b/crates/config/src/zksync.rs @@ -12,8 +12,11 @@ use crate::SolcReq; #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] /// ZkSync configuration pub struct ZkSyncConfig { - /// Enable zksync mode - pub enable: bool, + /// Compile for zkVM + pub compile: bool, + + /// Start VM in zkVM mode + pub startup: bool, /// The zkSolc instance to use if any. pub zksolc: Option, @@ -54,7 +57,8 @@ pub struct ZkSyncConfig { impl Default for ZkSyncConfig { fn default() -> Self { Self { - enable: Default::default(), + compile: Default::default(), + startup: true, zksolc: Default::default(), solc_path: Default::default(), bytecode_hash: Default::default(), @@ -73,7 +77,12 @@ impl Default for ZkSyncConfig { impl ZkSyncConfig { /// Returns true if zk mode is enabled and it if tests should be run in zk mode pub fn run_in_zk_mode(&self) -> bool { - self.enable + self.compile && self.startup + } + + /// Returns true if contracts should be compiled for zk + pub fn should_compile(&self) -> bool { + self.compile } /// Convert the zksync config to a foundry_compilers zksync Settings diff --git a/crates/forge/bin/cmd/build.rs b/crates/forge/bin/cmd/build.rs index 49e1fa788..6155dc25a 100644 --- a/crates/forge/bin/cmd/build.rs +++ b/crates/forge/bin/cmd/build.rs @@ -87,7 +87,7 @@ impl BuildArgs { project = config.project()?; } - if !config.zksync.enable { + if !config.zksync.should_compile() { let mut compiler = ProjectCompiler::new() .print_names(self.names) .print_sizes(self.sizes) diff --git a/crates/forge/bin/cmd/create.rs b/crates/forge/bin/cmd/create.rs index 7e441433b..041706208 100644 --- a/crates/forge/bin/cmd/create.rs +++ b/crates/forge/bin/cmd/create.rs @@ -117,7 +117,7 @@ impl CreateArgs { pub async fn run(self) -> Result<()> { let mut config = self.eth.try_load_config_emit_warnings()?; let project_root = config.project_paths().root; - let zksync = self.opts.compiler.zk.enable; + let zksync = self.opts.compiler.zk.enabled(); // Resolve missing libraries let libs_batches = if zksync && self.deploy_missing_libraries { diff --git a/crates/forge/bin/cmd/script/build.rs b/crates/forge/bin/cmd/script/build.rs index 2067cfe81..3d645d76b 100644 --- a/crates/forge/bin/cmd/script/build.rs +++ b/crates/forge/bin/cmd/script/build.rs @@ -28,14 +28,19 @@ impl ScriptArgs { let root = project.root(); let output = output.with_stripped_file_prefixes(root); - // ZK - // TODO: see if we need to support the `get_project_and_output` flow - // seems it verifies script is part of the project - let zk_compiler = ProjectCompiler::new().quiet(self.opts.args.silent); - let zk_output = zk_compiler.zksync_compile(&project)?; - - let dual_compiled_contracts = - DualCompiledContracts::new(&output, &zk_output, &project.paths); + // TODO: find a way to not compile _all_ contracts but only the ones necessary + // in each mode + let dual_compiled_contracts = if script_config.config.zksync.should_compile() { + // ZK + // TODO: see if we need to support the `get_project_and_output` flow + // seems it verifies script is part of the project + let zk_compiler = ProjectCompiler::new().quiet(self.opts.args.silent); + let zk_output = zk_compiler.zksync_compile(&project)?; + + Some(DualCompiledContracts::new(&output, &zk_output, &project.paths)) + } else { + None + }; let sources = ContractSources::from_project_output(&output, root)?; let contracts = output.into_artifacts().collect(); @@ -64,7 +69,7 @@ impl ScriptArgs { libraries, predeploy_libraries, sources, - dual_compiled_contracts: Some(dual_compiled_contracts), + dual_compiled_contracts, }) } diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 069e72677..662295e0d 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -169,10 +169,13 @@ impl TestArgs { } let output = compiler.compile(&project)?; - let zk_compiler = ProjectCompiler::new().quiet_if(self.json || self.opts.silent); - let zk_output = zk_compiler.zksync_compile(&project)?; - let dual_compiled_contracts = - DualCompiledContracts::new(&output, &zk_output, &project.paths); + let dual_compiled_contracts = if config.zksync.should_compile() { + let zk_compiler = ProjectCompiler::new().quiet_if(self.json || self.opts.silent); + let zk_output = zk_compiler.zksync_compile(&project)?; + Some(DualCompiledContracts::new(&output, &zk_output, &project.paths)) + } else { + None + }; // Create test options from general project settings and compiler output. let project_root = &project.paths.root; @@ -209,7 +212,7 @@ impl TestArgs { &config, evm_opts.clone(), None, - dual_compiled_contracts, + dual_compiled_contracts.unwrap_or_default(), config.zksync.run_in_zk_mode(), )) .with_test_options(test_options.clone()) diff --git a/zk-tests/test.sh b/zk-tests/test.sh index 65233bbff..62659b106 100755 --- a/zk-tests/test.sh +++ b/zk-tests/test.sh @@ -128,41 +128,41 @@ build_forge "${REPO_ROOT}" start_era_test_node # Test missing libraries detection and deploy -output=$(RUST_LOG=warn "${FORGE}" build --zksync 2>&1 || true) +output=$(RUST_LOG=warn "${FORGE}" build --zk-compile 2>&1 || true) if echo "$output" | grep -q "Missing libraries detected"; then - RUST_LOG=warn "${FORGE}" create --deploy-missing-libraries --rpc-url $RPC_URL --private-key $PRIVATE_KEY --zksync - RUST_LOG=warn "${FORGE}" script ./src/MissingLibraries.sol:MathematicianScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --zksync --chain 260 --use "./${SOLC}" -vvv || fail "forge script with libs failed" - RUST_LOG=warn "${FORGE}" script ./src/NestedMissingLibraries.sol:NestedMathematicianScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --zksync --chain 260 --use "./${SOLC}" -vvv || fail "forge script with nested libs failed" + RUST_LOG=warn "${FORGE}" create --deploy-missing-libraries --rpc-url $RPC_URL --private-key $PRIVATE_KEY --zk-compile + RUST_LOG=warn "${FORGE}" script ./src/MissingLibraries.sol:MathematicianScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --zk-startup --chain 260 --use "./${SOLC}" -vvv || fail "forge script with libs failed" + RUST_LOG=warn "${FORGE}" script ./src/NestedMissingLibraries.sol:NestedMathematicianScript --rpc-url $RPC_URL --private-key $PRIVATE_KEY --zk-startup --chain 260 --use "./${SOLC}" -vvv || fail "forge script with nested libs failed" else echo "No missing libraries detected." fi echo "Running tests..." -RUST_LOG=warn "${FORGE}" test --use "./${SOLC}" --chain 300 -vvv || fail "forge test failed" +RUST_LOG=warn "${FORGE}" test --use "./${SOLC}" --chain 300 -vvv --zk-compile || fail "forge test failed" -echo "Running tests with '--zksync'..." -RUST_LOG=warn "${FORGE}" test --use "./${SOLC}" --chain 300 -vvv --zksync || fail "forge test --zksync failed" +echo "Running tests with '--zk-startup'..." +RUST_LOG=warn "${FORGE}" test --use "./${SOLC}" --chain 300 -vvv --zk-startup || fail "forge test --zk-startup failed" echo "Running script..." -RUST_LOG=warn "${FORGE}" script ./script/Deploy.s.sol:DeployScript --broadcast --private-key "$PRIVATE_KEY" --chain 260 --gas-estimate-multiplier 310 --rpc-url "$RPC_URL" --use "./${SOLC}" --slow -vvv || fail "forge script failed" -RUST_LOG=warn "${FORGE}" script ./script/Deploy.s.sol:DeployScript --broadcast --private-key "$PRIVATE_KEY" --chain 260 --gas-estimate-multiplier 310 --rpc-url "$RPC_URL" --use "./${SOLC}" --slow -vvv || fail "forge script failed on 2nd deploy" +RUST_LOG=warn "${FORGE}" script ./script/Deploy.s.sol:DeployScript --broadcast --private-key "$PRIVATE_KEY" --chain 260 --gas-estimate-multiplier 310 --rpc-url "$RPC_URL" --use "./${SOLC}" --slow -vvv --zk-compile || fail "forge script failed" +RUST_LOG=warn "${FORGE}" script ./script/Deploy.s.sol:DeployScript --broadcast --private-key "$PRIVATE_KEY" --chain 260 --gas-estimate-multiplier 310 --rpc-url "$RPC_URL" --use "./${SOLC}" --slow -vvv --zk-compile || fail "forge script failed on 2nd deploy" echo "Running NFT script" -RUST_LOG=warn "${FORGE}" script ./script/NFT.s.sol:MyScript --broadcast --private-key $PRIVATE_KEY --rpc-url $RPC_URL --use 0.8.20 --zksync || fail "forge script failed" +RUST_LOG=warn "${FORGE}" script ./script/NFT.s.sol:MyScript --broadcast --private-key $PRIVATE_KEY --rpc-url $RPC_URL --use 0.8.20 --zk-startup || fail "forge script failed" echo "Running Proxy script" -RUST_LOG=warn "${FORGE}" script ./script/Proxy.s.sol:ProxyScript --broadcast --private-key $PRIVATE_KEY --rpc-url $RPC_URL --use 0.8.20 --zksync || fail "forge proxy script failed" +RUST_LOG=warn "${FORGE}" script ./script/Proxy.s.sol:ProxyScript --broadcast --private-key $PRIVATE_KEY --rpc-url $RPC_URL --use 0.8.20 --zk-startup || fail "forge proxy script failed" # Deploy ERC20 echo "Deploying MyToken..." -MYTOKEN_DEPLOYMENT=$(RUST_LOG=warn "${FORGE}" create ./src/ERC20.sol:MyToken --rpc-url $RPC_URL --private-key $PRIVATE_KEY --use 0.8.20 --zksync) || fail "forge script failed" +MYTOKEN_DEPLOYMENT=$(RUST_LOG=warn "${FORGE}" create ./src/ERC20.sol:MyToken --rpc-url $RPC_URL --private-key $PRIVATE_KEY --use 0.8.20 --zk-startup) || fail "forge script failed" MYTOKEN_ADDRESS=$(echo $MYTOKEN_DEPLOYMENT | awk '/Deployed to:/ {for (i=1; i<=NF; i++) if ($i == "to:") print $(i+1)}') echo "MyToken deployed at: $MYTOKEN_ADDRESS" # Deploy TokenReceiver echo "Deploying TokenReceiver..." -TOKENRECEIVER_DEPLOYMENT=$(RUST_LOG=warn "${FORGE}" create ./src/TokenReceiver.sol:TokenReceiver --rpc-url $RPC_URL --private-key $PRIVATE_KEY --use "./${SOLC}" --zksync) || fail "forge script failed" +TOKENRECEIVER_DEPLOYMENT=$(RUST_LOG=warn "${FORGE}" create ./src/TokenReceiver.sol:TokenReceiver --rpc-url $RPC_URL --private-key $PRIVATE_KEY --use "./${SOLC}" --zk-startup) || fail "forge script failed" TOKENRECEIVER_ADDRESS=$(echo $TOKENRECEIVER_DEPLOYMENT | awk '/Deployed to:/ {for (i=1; i<=NF; i++) if ($i == "to:") print $(i+1)}') echo "TokenReceiver deployed at: $TOKENRECEIVER_ADDRESS"