diff --git a/Cargo.lock b/Cargo.lock index f6857a33d..881ed75fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4110,12 +4110,16 @@ dependencies = [ name = "foundry-common" version = "0.2.0" dependencies = [ + "ansi_term", + "anyhow", "async-trait", "auto_impl", "clap 4.4.6", "comfy-table", "const-hex", + "dirs 5.0.1", "dunce", + "era_revm", "ethers-core", "ethers-etherscan", "ethers-middleware", @@ -12162,9 +12166,7 @@ name = "zkforge" version = "0.2.0" dependencies = [ "alloy-primitives", - "ansi_term", "anvil", - "anyhow", "async-trait", "clap 4.4.6", "clap_complete", @@ -12173,9 +12175,7 @@ dependencies = [ "const-hex", "criterion", "dialoguer", - "dirs 5.0.1", "dunce", - "era_revm", "ethers", "eyre", "forge-doc", diff --git a/crates/common/Cargo.toml b/crates/common/Cargo.toml index 110c0b5a2..e43cf1c91 100644 --- a/crates/common/Cargo.toml +++ b/crates/common/Cargo.toml @@ -23,6 +23,10 @@ ethers-etherscan = { workspace = true, features = ["ethers-solc"] } # zksync zksync-web3-rs = {git = "https://github.com/lambdaclass/zksync-web3-rs.git", rev = "70327ae5413c517bd4d27502507cdd96ee40cd22"} +era_revm = { git = "https://github.com/matter-labs/era-revm.git", tag = "v0.0.1-alpha" } +anyhow = {version = "1.0.70"} +dirs = {version = "5.0.0"} +ansi_term = "0.12.1" # io reqwest = { version = "0.11", default-features = false } diff --git a/crates/common/src/lib.rs b/crates/common/src/lib.rs index d03afb9c1..c65688744 100644 --- a/crates/common/src/lib.rs +++ b/crates/common/src/lib.rs @@ -20,7 +20,9 @@ pub mod shell; pub mod term; pub mod traits; pub mod transactions; +pub mod zk_compile; pub mod zk_utils; +pub mod zksolc_manager; pub use constants::*; pub use contracts::*; diff --git a/crates/zkforge/bin/cmd/zk_solc.rs b/crates/common/src/zk_compile.rs similarity index 94% rename from crates/zkforge/bin/cmd/zk_solc.rs rename to crates/common/src/zk_compile.rs index 90334b9f7..e18c794f9 100644 --- a/crates/zkforge/bin/cmd/zk_solc.rs +++ b/crates/common/src/zk_compile.rs @@ -1,4 +1,5 @@ -/// This module provides the implementation of the ZkSolc compiler for Solidity contracts. +#![allow(missing_docs)] +//! This module provides the implementation of the ZkSolc compiler for Solidity contracts. /// ZkSolc is a specialized compiler that supports zero-knowledge (ZK) proofs for smart /// contracts. /// @@ -29,18 +30,17 @@ /// /// - Artifact Path Generation: The `build_artifacts_path` and `build_artifacts_file` methods /// construct the path and file for saving the compiler output artifacts. +use crate::zksolc_manager::ZkSolcManager; use ansi_term::Colour::{Red, Yellow}; -use ethers::{ - prelude::{artifacts::Source, remappings::RelativeRemapping, Solc}, - solc::{ - artifacts::{ - output_selection::FileOutputSelection, CompactBytecode, CompactDeployedBytecode, - LosslessAbi, StandardJsonCompilerInput, - }, - ArtifactFile, Artifacts, ConfigurableContractArtifact, Graph, Project, - ProjectCompileOutput, +use ethers_core::types::Bytes; +use ethers_solc::{ + artifacts::{ + output_selection::FileOutputSelection, CompactBytecode, CompactDeployedBytecode, + LosslessAbi, Source, StandardJsonCompilerInput, }, - types::Bytes, + remappings::RelativeRemapping, + ArtifactFile, Artifacts, ConfigurableContractArtifact, Graph, Project, ProjectCompileOutput, + Solc, }; use eyre::{Context, ContextCompat, Result}; use regex::Regex; @@ -138,6 +138,42 @@ impl fmt::Display for ZkSolc { } } +/// The `compile_smart_contracts` function initiates the contract compilation process. +/// +/// It follows these steps: +/// 1. Create an instance of `ZkSolcOpts` with the appropriate options. +/// 2. Instantiate `ZkSolc` with the created options and the project. +/// 3. Initiate the contract compilation process. +/// +/// The function returns `Ok(())` if the compilation process completes successfully, or an error +/// if it fails. +pub fn compile_smart_contracts( + is_system: bool, + is_legacy: bool, + zksolc_manager: ZkSolcManager, + project: Project, + remappings: Vec, +) -> eyre::Result<()> { + let zksolc_opts = ZkSolcOpts { + compiler_path: zksolc_manager.get_full_compiler_path(), + is_system, + force_evmla: is_legacy, + remappings, + }; + + let mut zksolc = ZkSolc::new(zksolc_opts, project); + + match zksolc.compile() { + Ok(_) => { + println!("Compiled Successfully"); + Ok(()) + } + Err(err) => { + eyre::bail!("Failed to compile smart contracts with zksolc: {}", err); + } + } +} + impl ZkSolc { pub fn new(opts: ZkSolcOpts, project: Project) -> Self { Self { @@ -448,7 +484,7 @@ impl ZkSolc { let mut art = ConfigurableContractArtifact { bytecode: Some(CompactBytecode { - object: ethers::solc::artifacts::BytecodeObject::Bytecode( + object: ethers_solc::artifacts::BytecodeObject::Bytecode( packed_bytecode.clone(), ), source_map: None, @@ -456,7 +492,7 @@ impl ZkSolc { }), deployed_bytecode: Some(CompactDeployedBytecode { bytecode: Some(CompactBytecode { - object: ethers::solc::artifacts::BytecodeObject::Bytecode( + object: ethers_solc::artifacts::BytecodeObject::Bytecode( packed_bytecode, ), source_map: None, @@ -941,9 +977,8 @@ mod tests { /// Basic test to analyze the single Counter.sol artifact. #[test] pub fn test_artifacts_extraction() { - let data = include_str!("../../../../testdata/artifacts-counter/artifacts.json") - .as_bytes() - .to_vec(); + let data = + include_str!("../../../testdata/artifacts-counter/artifacts.json").as_bytes().to_vec(); let mut displayed_warnings = HashSet::new(); let source = "src/Counter.sol".to_owned(); let result = ZkSolc::handle_output(data, &source, &mut displayed_warnings, None); @@ -958,14 +993,13 @@ mod tests { } #[test] pub fn test_json_parsing() { - let data = include_str!("../../../../testdata/artifacts-counter/artifacts.json") - .as_bytes() - .to_vec(); + let data = + include_str!("../../../testdata/artifacts-counter/artifacts.json").as_bytes().to_vec(); let _parsed: ZkSolcCompilerOutput = serde_json::from_slice(&data).unwrap(); // Contract that has almost no data (and many fields missing). let almost_empty_data = - include_str!("../../../../testdata/artifacts-counter/empty.json").as_bytes().to_vec(); + include_str!("../../../testdata/artifacts-counter/empty.json").as_bytes().to_vec(); let _parsed_empty: ZkSolcCompilerOutput = serde_json::from_slice(&almost_empty_data).unwrap(); } diff --git a/crates/zkforge/bin/cmd/zksolc_manager.rs b/crates/common/src/zksolc_manager.rs similarity index 93% rename from crates/zkforge/bin/cmd/zksolc_manager.rs rename to crates/common/src/zksolc_manager.rs index 0ea910467..7f44d29a3 100644 --- a/crates/zkforge/bin/cmd/zksolc_manager.rs +++ b/crates/common/src/zksolc_manager.rs @@ -1,4 +1,5 @@ -/// The `ZkSolcManager` module manages the downloading and setup of the `zksolc` Solidity +#![allow(missing_docs)] +//! The `ZkSolcManager` module manages the downloading and setup of the `zksolc` Solidity /// compiler. This module provides functionalities to interact with different versions of the /// zksolc compiler as well as supporting different operating systems. /// @@ -409,6 +410,42 @@ impl fmt::Display for ZkSolcManager { } } +/// The `setup_zksolc_manager` function creates and prepares an instance of `ZkSolcManager`. +/// +/// It follows these steps: +/// 1. Instantiate `ZkSolcManagerOpts` and `ZkSolcManagerBuilder` with the specified zkSync Solidity +/// compiler. +/// 2. Create a `ZkSolcManager` using the builder. +/// 3. Check if the setup compilers directory is properly set up. If not, it raises an error. +/// 4. If the zkSync Solidity compiler does not exist in the compilers directory, it triggers its +/// download. +/// +/// The function returns the `ZkSolcManager` if all steps are successful, or an error if any +/// step fails. +pub fn setup_zksolc_manager(zksolc_version: String) -> eyre::Result { + let zksolc_manager_opts = ZkSolcManagerOpts::new(zksolc_version); + let zksolc_manager_builder = ZkSolcManagerBuilder::new(zksolc_manager_opts); + let zksolc_manager = zksolc_manager_builder + .build() + .map_err(|e| eyre::eyre!("Error building zksolc_manager: {}", e))?; + + if let Err(err) = zksolc_manager.check_setup_compilers_dir() { + eyre::bail!("Failed to setup compilers directory: {}", err); + } + + if !zksolc_manager.exists() { + println!( + "Downloading zksolc compiler from {:?}", + zksolc_manager.get_full_download_url().unwrap().to_string() + ); + zksolc_manager + .download() + .map_err(|err| eyre::eyre!("Failed to download the file: {}", err))?; + } + + Ok(zksolc_manager) +} + impl ZkSolcManager { /// Constructs a new instance of `ZkSolcManager` with the specified configuration options. /// diff --git a/crates/zkforge/Cargo.toml b/crates/zkforge/Cargo.toml index b67eada6f..34347d7cc 100644 --- a/crates/zkforge/Cargo.toml +++ b/crates/zkforge/Cargo.toml @@ -35,11 +35,7 @@ tracing.workspace = true yansi = "0.5" # zksync -era_revm = { git = "https://github.com/matter-labs/era-revm.git", tag = "v0.0.1-alpha" } zksync-web3-rs = {git = "https://github.com/lambdaclass/zksync-web3-rs.git", rev = "70327ae5413c517bd4d27502507cdd96ee40cd22"} -ansi_term = "0.12.1" -anyhow = {version = "1.0.70"} -dirs = {version = "5.0.0"} url = "2.3.1" # bin diff --git a/crates/zkforge/bin/cmd/mod.rs b/crates/zkforge/bin/cmd/mod.rs index ecbd08855..c4ef66c49 100644 --- a/crates/zkforge/bin/cmd/mod.rs +++ b/crates/zkforge/bin/cmd/mod.rs @@ -68,5 +68,3 @@ pub mod verify; pub mod watch; pub mod zk_build; pub mod zk_create; -pub mod zk_solc; -pub mod zksolc_manager; diff --git a/crates/zkforge/bin/cmd/test/mod.rs b/crates/zkforge/bin/cmd/test/mod.rs index 574145c19..48121c4fa 100644 --- a/crates/zkforge/bin/cmd/test/mod.rs +++ b/crates/zkforge/bin/cmd/test/mod.rs @@ -1,8 +1,4 @@ use super::{install, test::filter::ProjectPathsAwareFilter, watch::WatchArgs}; -use crate::cmd::{ - zk_solc::{ZkSolc, ZkSolcOpts}, - zksolc_manager::{ZkSolcManagerBuilder, ZkSolcManagerOpts, DEFAULT_ZKSOLC_VERSION}, -}; use alloy_primitives::U256; use clap::Parser; use eyre::Result; @@ -11,8 +7,12 @@ use foundry_cli::{ utils::{self, LoadConfig}, }; use foundry_common::{ - compact_to_contract, compile::ContractSources, evm::EvmArgs, get_contract_name, get_file_name, - shell, + compact_to_contract, + compile::ContractSources, + evm::EvmArgs, + get_contract_name, get_file_name, shell, + zk_compile::{ZkSolc, ZkSolcOpts}, + zksolc_manager::{ZkSolcManagerBuilder, ZkSolcManagerOpts, DEFAULT_ZKSOLC_VERSION}, }; use foundry_config::{ figment, diff --git a/crates/zkforge/bin/cmd/watch.rs b/crates/zkforge/bin/cmd/watch.rs index d61cbbe32..6bdf09edd 100644 --- a/crates/zkforge/bin/cmd/watch.rs +++ b/crates/zkforge/bin/cmd/watch.rs @@ -1,4 +1,5 @@ use super::{snapshot::SnapshotArgs, test::TestArgs}; +use crate::cmd::zk_build::ZkBuildArgs; use clap::Parser; use eyre::Result; use foundry_cli::utils::{self, FoundryPathExt}; @@ -82,22 +83,22 @@ impl WatchArgs { /// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge /// build` -// pub async fn watch_build(args: ZkBuildArgs) -> Result<()> { -// let (init, mut runtime) = args.watchexec_config()?; -// let cmd = cmd_args(args.watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default()); +pub async fn watch_build(args: ZkBuildArgs) -> Result<()> { + let (init, mut runtime) = args.watchexec_config()?; + let cmd = cmd_args(args.watch.watch.as_ref().map(|paths| paths.len()).unwrap_or_default()); -// trace!("watch build cmd={:?}", cmd); -// runtime.command(watch_command(cmd.clone())); + trace!("watch build cmd={:?}", cmd); + runtime.command(watch_command(cmd.clone())); -// let wx = Watchexec::new(init, runtime.clone())?; -// on_action(args.watch, runtime, Arc::clone(&wx), cmd, (), |_| {}); + let wx = Watchexec::new(init, runtime.clone())?; + on_action(args.watch, runtime, Arc::clone(&wx), cmd, (), |_| {}); -// // start executing the command immediately -// wx.send_event(Event::default(), Priority::default()).await?; -// wx.main().await??; + // start executing the command immediately + wx.send_event(Event::default(), Priority::default()).await?; + wx.main().await??; -// Ok(()) -// } + Ok(()) +} /// Executes a [`Watchexec`] that listens for changes in the project's src dir and reruns `forge /// snapshot` diff --git a/crates/zkforge/bin/cmd/zk_build.rs b/crates/zkforge/bin/cmd/zk_build.rs index 703986f1e..2d353b615 100644 --- a/crates/zkforge/bin/cmd/zk_build.rs +++ b/crates/zkforge/bin/cmd/zk_build.rs @@ -25,16 +25,11 @@ /// zkSync contracts. It is designed to provide a seamless experience for developers, providing /// an easy-to-use interface for contract compilation while taking care of the underlying /// complexities. -use super::watch::WatchArgs; -use super::{ - zk_solc::{ZkSolc, ZkSolcOpts}, - zksolc_manager::{ - ZkSolcManager, ZkSolcManagerBuilder, ZkSolcManagerOpts, DEFAULT_ZKSOLC_VERSION, - }, -}; +use super::{install, watch::WatchArgs}; use clap::Parser; -use ethers::{prelude::Project, solc::remappings::RelativeRemapping}; +use ethers::prelude::Project; use foundry_cli::{opts::CoreBuildArgs, utils::LoadConfig}; +use foundry_common::zksolc_manager::{setup_zksolc_manager, DEFAULT_ZKSOLC_VERSION}; use foundry_config::{ figment::{ self, @@ -46,6 +41,7 @@ use foundry_config::{ }; use serde::Serialize; use std::fmt::Debug; +use watchexec::config::{InitConfig, RuntimeConfig}; foundry_config::merge_impl_figment_convert!(ZkBuildArgs, args); @@ -80,6 +76,16 @@ foundry_config::merge_impl_figment_convert!(ZkBuildArgs, args); #[derive(Debug, Clone, Parser, Serialize, Default)] #[clap(next_help_heading = "ZkBuild options", about = None)] pub struct ZkBuildArgs { + /// Print compiled contract names. + #[clap(long)] + #[serde(skip)] + pub names: bool, + + /// Print compiled contract sizes. + #[clap(long)] + #[serde(skip)] + pub sizes: bool, + /// Specify the solc version, or a path to a local solc, to build with. /// /// Valid values are in the format `x.y.z`, `solc:x.y.z` or `path/to/solc`. @@ -147,101 +153,55 @@ impl ZkBuildArgs { /// involved in the zkSync contract compilation process in a single method, allowing for /// easy invocation of the process with a single function call. pub fn run(self) -> eyre::Result<()> { - let config = self.try_load_config_emit_warnings()?; + let mut config = self.try_load_config_emit_warnings()?; let mut project = config.project()?; //set zk out path let zk_out_path = project.paths.root.join("zkout"); project.paths.artifacts = zk_out_path; - let zksolc_manager = self.setup_zksolc_manager()?; + if install::install_missing_dependencies(&mut config, self.args.silent) && + config.auto_detect_remappings + { + // need to re-configure here to also catch additional remappings + config = self.load_config(); + project = config.project()?; + } + let zksolc_manager = setup_zksolc_manager(self.use_zksolc.clone())?; let remappings = config.remappings; - self.compile_smart_contracts(zksolc_manager, project, remappings) + // TODO: add filter support + + foundry_common::zk_compile::compile_smart_contracts( + self.is_system, + self.force_evmla, + zksolc_manager, + project, + remappings, + ) + } + /// Returns the `Project` for the current workspace + /// + /// This loads the `foundry_config::Config` for the current workspace (see + /// [`utils::find_project_root_path`] and merges the cli `BuildArgs` into it before returning + /// [`foundry_config::Config::project()`] + pub fn project(&self) -> eyre::Result { + self.args.project() } + /// Returns whether `ZkBuildArgs` was configured with `--watch` - pub fn _is_watch(&self) -> bool { + pub fn is_watch(&self) -> bool { self.watch.watch.is_some() } /// Returns the [`watchexec::InitConfig`] and [`watchexec::RuntimeConfig`] necessary to /// bootstrap a new [`watchexe::Watchexec`] loop. - // pub(crate) fn watchexec_config(&self) -> Result<(InitConfig, RuntimeConfig)> { - // // use the path arguments or if none where provided the `src` dir - // self.watch.watchexec_config(|| { - // let config = Config::from(self); - // vec![config.src, config.test, config.script] - // }) - // } - /// The `setup_zksolc_manager` function creates and prepares an instance of `ZkSolcManager`. - /// - /// It follows these steps: - /// 1. Instantiate `ZkSolcManagerOpts` and `ZkSolcManagerBuilder` with the specified zkSync - /// Solidity compiler. - /// 2. Create a `ZkSolcManager` using the builder. - /// 3. Check if the setup compilers directory is properly set up. If not, it raises an error. - /// 4. If the zkSync Solidity compiler does not exist in the compilers directory, it triggers - /// its download. - /// - /// The function returns the `ZkSolcManager` if all steps are successful, or an error if any - /// step fails. - fn setup_zksolc_manager(&self) -> eyre::Result { - let zksolc_manager_opts = ZkSolcManagerOpts::new(self.use_zksolc.clone()); - let zksolc_manager_builder = ZkSolcManagerBuilder::new(zksolc_manager_opts); - let zksolc_manager = zksolc_manager_builder - .build() - .map_err(|e| eyre::eyre!("Error building zksolc_manager: {}", e))?; - - if let Err(err) = zksolc_manager.check_setup_compilers_dir() { - eyre::bail!("Failed to setup compilers directory: {}", err); - } - - if !zksolc_manager.exists() { - println!( - "Downloading zksolc compiler from {:?}", - zksolc_manager.get_full_download_url().unwrap().to_string() - ); - zksolc_manager - .download() - .map_err(|err| eyre::eyre!("Failed to download the file: {}", err))?; - } - - Ok(zksolc_manager) - } - - /// The `compile_smart_contracts` function initiates the contract compilation process. - /// - /// It follows these steps: - /// 1. Create an instance of `ZkSolcOpts` with the appropriate options. - /// 2. Instantiate `ZkSolc` with the created options and the project. - /// 3. Initiate the contract compilation process. - /// - /// The function returns `Ok(())` if the compilation process completes successfully, or an error - /// if it fails. - fn compile_smart_contracts( - &self, - zksolc_manager: ZkSolcManager, - project: Project, - remappings: Vec, - ) -> eyre::Result<()> { - let zksolc_opts = ZkSolcOpts { - compiler_path: zksolc_manager.get_full_compiler_path(), - is_system: self.is_system, - force_evmla: self.force_evmla, - remappings, - }; - - let mut zksolc = ZkSolc::new(zksolc_opts, project); - - match zksolc.compile() { - Ok(_) => { - println!("Compiled Successfully"); - Ok(()) - } - Err(err) => { - eyre::bail!("Failed to compile smart contracts with zksolc: {}", err); - } - } + pub(crate) fn watchexec_config(&self) -> eyre::Result<(InitConfig, RuntimeConfig)> { + // use the path arguments or if none where provided the `src` dir + self.watch.watchexec_config(|| { + let config = Config::from(self); + vec![config.src, config.test, config.script] + }) } } @@ -254,15 +214,15 @@ impl Provider for ZkBuildArgs { fn data(&self) -> Result, figment::Error> { let value = Value::serialize(self)?; let error = InvalidType(value.to_actual(), "map".into()); - let dict = value.into_dict().ok_or(error)?; + let mut dict = value.into_dict().ok_or(error)?; - // if self.names { - // dict.insert("names".to_string(), true.into()); - // } + if self.names { + dict.insert("names".to_string(), true.into()); + } - // if self.sizes { - // dict.insert("sizes".to_string(), true.into()); - // } + if self.sizes { + dict.insert("sizes".to_string(), true.into()); + } Ok(Map::from([(Config::selected_profile(), dict)])) } diff --git a/crates/zkforge/bin/main.rs b/crates/zkforge/bin/main.rs index 9519ca7f3..34391096a 100644 --- a/crates/zkforge/bin/main.rs +++ b/crates/zkforge/bin/main.rs @@ -35,7 +35,13 @@ fn main() -> Result<()> { } Subcommands::Coverage(cmd) => utils::block_on(cmd.run()), Subcommands::Bind(cmd) => cmd.run(), - Subcommands::ZkBuild(cmd) => cmd.run(), + Subcommands::ZkBuild(cmd) => { + if cmd.is_watch() { + utils::block_on(watch::watch_build(cmd)) + } else { + cmd.run().map(|_| ()) + } + } Subcommands::Debug(cmd) => utils::block_on(cmd.run()), Subcommands::VerifyContract(args) => utils::block_on(args.run()), Subcommands::VerifyCheck(args) => utils::block_on(args.run()), @@ -57,7 +63,7 @@ fn main() -> Result<()> { clap_complete::generate( clap_complete_fig::Fig, &mut Opts::command(), - "forge", + "zkforge", &mut std::io::stdout(), ); Ok(()) diff --git a/deny.toml b/deny.toml index 021da25ed..f395e30de 100644 --- a/deny.toml +++ b/deny.toml @@ -1,6 +1,6 @@ # Temporarily exclude rusoto and ethers-providers from bans since we've yet to transition to the # Rust AWS SDK. -exclude = ["rusoto_core", "rusoto_kms", "rusoto_credential", "ethers-providers", "tungstenite"] +exclude = ["rusoto_core", "rusoto_kms", "rusoto_credential", "ethers-providers", "tungstenite", "const_format"] # This section is considered when running `cargo deny check advisories` # More documentation for the advisories section can be found here: @@ -23,10 +23,8 @@ multiple-versions = "warn" # Lint level for when a crate version requirement is `*` wildcards = "allow" highlight = "all" -# List of crates to deny -deny = [ - { name = "openssl" }, -] + + # Certain crates/versions that will be skipped when doing duplicate detection. skip = [] # Similarly to `skip` allows you to skip certain crates during duplicate @@ -53,6 +51,7 @@ allow = [ "WTFPL", "BSL-1.0", "0BSD", + "Zlib" ] # Allow 1 or more licenses on a per-crate basis, so that particular licenses