diff --git a/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/args/init.rs b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/args/init.rs new file mode 100644 index 000000000000..c74e4a4f765e --- /dev/null +++ b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/args/init.rs @@ -0,0 +1,169 @@ +use anyhow::Context; +use clap::Parser; +use common::PromptSelect; +use xshell::Shell; + +use super::releases::{get_releases_with_arch, Arch, Version}; +use crate::messages::{ + MSG_ARCH_NOT_SUPPORTED_ERR, MSG_FETCHING_VYPER_RELEASES_SPINNER, + MSG_FETCHING_ZKSOLC_RELEASES_SPINNER, MSG_FETCHING_ZKVYPER_RELEASES_SPINNER, + MSG_FETCH_SOLC_RELEASES_SPINNER, MSG_GET_SOLC_RELEASES_ERR, MSG_GET_VYPER_RELEASES_ERR, + MSG_GET_ZKSOLC_RELEASES_ERR, MSG_GET_ZKVYPER_RELEASES_ERR, MSG_NO_VERSION_FOUND_ERR, + MSG_OS_NOT_SUPPORTED_ERR, MSG_SOLC_VERSION_PROMPT, MSG_VYPER_VERSION_PROMPT, + MSG_ZKSOLC_VERSION_PROMPT, MSG_ZKVYPER_VERSION_PROMPT, +}; + +#[derive(Debug, Clone, Parser, Default)] +pub struct InitContractVerifierArgs { + /// Version of zksolc to install + #[clap(long)] + pub zksolc_version: Option, + /// Version of zkvyper to install + #[clap(long)] + pub zkvyper_version: Option, + /// Version of solc to install + #[clap(long)] + pub solc_version: Option, + /// Version of vyper to install + #[clap(long)] + pub vyper_version: Option, +} + +#[derive(Debug, Clone)] +pub struct InitContractVerifierArgsFinal { + pub zksolc_releases: Vec, + pub zkvyper_releases: Vec, + pub solc_releases: Vec, + pub vyper_releases: Vec, +} + +impl InitContractVerifierArgs { + pub fn fill_values_with_prompt( + self, + shell: &Shell, + ) -> anyhow::Result { + let arch = get_arch()?; + + let zksolc_releases = get_releases_with_arch( + shell, + "matter-labs/zksolc-bin", + arch, + MSG_FETCHING_ZKSOLC_RELEASES_SPINNER, + ) + .context(MSG_GET_ZKSOLC_RELEASES_ERR)?; + + let zkvyper_releases = get_releases_with_arch( + shell, + "matter-labs/zkvyper-bin", + arch, + MSG_FETCHING_ZKVYPER_RELEASES_SPINNER, + ) + .context(MSG_GET_ZKVYPER_RELEASES_ERR)?; + + let solc_releases = get_releases_with_arch( + shell, + "ethereum/solc-bin", + arch, + MSG_FETCH_SOLC_RELEASES_SPINNER, + ) + .context(MSG_GET_SOLC_RELEASES_ERR)?; + + let vyper_releases = get_releases_with_arch( + shell, + "vyperlang/vyper", + arch, + MSG_FETCHING_VYPER_RELEASES_SPINNER, + ) + .context(MSG_GET_VYPER_RELEASES_ERR)?; + + let zksolc_version = select_min_version( + self.zksolc_version, + zksolc_releases.clone(), + MSG_ZKSOLC_VERSION_PROMPT, + )?; + let zksolc_releases = get_releases_above_version(zksolc_releases, zksolc_version)?; + + let zkvyper_version = select_min_version( + self.zkvyper_version, + zkvyper_releases.clone(), + MSG_ZKVYPER_VERSION_PROMPT, + )?; + let zkvyper_releases = get_releases_above_version(zkvyper_releases, zkvyper_version)?; + + let solc_version = select_min_version( + self.solc_version, + solc_releases.clone(), + MSG_SOLC_VERSION_PROMPT, + )?; + let solc_releases = get_releases_above_version(solc_releases, solc_version)?; + + let vyper_version = select_min_version( + self.vyper_version, + vyper_releases.clone(), + MSG_VYPER_VERSION_PROMPT, + )?; + let vyper_releases = get_releases_above_version(vyper_releases, vyper_version)?; + + Ok(InitContractVerifierArgsFinal { + zksolc_releases, + zkvyper_releases, + solc_releases, + vyper_releases, + }) + } +} + +fn get_arch() -> anyhow::Result { + let os = std::env::consts::OS; + let arch = std::env::consts::ARCH; + + let arch = match os { + "linux" => match arch { + "x86_64" => Arch::LinuxAmd, + "aarch64" => Arch::LinuxArm, + "arm" => Arch::LinuxArm, + _ => anyhow::bail!(MSG_ARCH_NOT_SUPPORTED_ERR), + }, + "macos" => match arch { + "x86_64" => Arch::MacosAmd, + "aarch64" => Arch::MacosArm, + "arm" => Arch::MacosArm, + _ => anyhow::bail!(MSG_ARCH_NOT_SUPPORTED_ERR), + }, + _ => anyhow::bail!(MSG_OS_NOT_SUPPORTED_ERR), + }; + + Ok(arch) +} + +fn select_min_version( + selected: Option, + versions: Vec, + prompt_msg: &str, +) -> anyhow::Result { + let selected = selected.unwrap_or_else(|| { + PromptSelect::new(prompt_msg, versions.iter().map(|r| &r.version)) + .ask() + .into() + }); + + let selected = versions + .iter() + .find(|r| r.version == selected) + .context(MSG_NO_VERSION_FOUND_ERR)? + .to_owned(); + + Ok(selected) +} + +fn get_releases_above_version( + releases: Vec, + version: Version, +) -> anyhow::Result> { + let pos = releases + .iter() + .position(|r| r.version == version.version) + .context(MSG_NO_VERSION_FOUND_ERR)?; + + Ok(releases[..=pos].to_vec()) +} diff --git a/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/args/mod.rs b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/args/mod.rs new file mode 100644 index 000000000000..7f5df830d114 --- /dev/null +++ b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/args/mod.rs @@ -0,0 +1,2 @@ +pub mod init; +pub mod releases; diff --git a/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/args/releases.rs b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/args/releases.rs new file mode 100644 index 000000000000..6f7eae4c1685 --- /dev/null +++ b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/args/releases.rs @@ -0,0 +1,159 @@ +use std::str::FromStr; + +use common::{cmd::Cmd, spinner::Spinner}; +use serde::Deserialize; +use xshell::{cmd, Shell}; + +use crate::messages::{MSG_INVALID_ARCH_ERR, MSG_NO_RELEASES_FOUND_ERR}; + +#[derive(Deserialize)] +struct GitHubRelease { + tag_name: String, + assets: Vec, +} + +#[derive(Deserialize)] +struct GitHubAsset { + name: String, + browser_download_url: String, +} + +#[derive(Deserialize)] +struct SolcList { + builds: Vec, +} + +#[derive(Deserialize)] +struct SolcBuild { + path: String, + version: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Version { + pub version: String, + pub arch: Vec, + pub url: String, +} + +#[derive(Debug, Clone, PartialEq, Eq, Copy)] +pub enum Arch { + LinuxAmd, + LinuxArm, + MacosAmd, + MacosArm, +} + +impl std::str::FromStr for Arch { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + if s.contains("linux-amd64") { + Ok(Arch::LinuxAmd) + } else if s.contains("linux-arm64") { + Ok(Arch::LinuxArm) + } else if s.contains("macosx-amd64") { + Ok(Arch::MacosAmd) + } else if s.contains("macosx-arm64") { + Ok(Arch::MacosArm) + } else { + Err(anyhow::anyhow!(MSG_INVALID_ARCH_ERR)) + } + } +} + +fn get_compatible_archs(asset_name: &str) -> anyhow::Result> { + if let Ok(arch) = Arch::from_str(asset_name) { + Ok(vec![arch]) + } else if asset_name.contains(".linux") { + Ok(vec![Arch::LinuxAmd, Arch::LinuxArm]) + } else if asset_name.contains(".darwin") { + Ok(vec![Arch::MacosAmd, Arch::MacosArm]) + } else { + Err(anyhow::anyhow!(MSG_INVALID_ARCH_ERR)) + } +} + +fn get_releases(shell: &Shell, repo: &str, arch: Arch) -> anyhow::Result> { + if repo == "ethereum/solc-bin" { + return get_solc_releases(shell, arch); + } + + let response: std::process::Output = Cmd::new(cmd!( + shell, + "curl https://api.github.com/repos/{repo}/releases" + )) + .run_with_output()?; + + let response = String::from_utf8(response.stdout)?; + let releases: Vec = serde_json::from_str(&response)?; + + let mut versions = vec![]; + + for release in releases { + let version = release.tag_name; + for asset in release.assets { + let arch = match get_compatible_archs(&asset.name) { + Ok(arch) => arch, + Err(_) => continue, + }; + let url = asset.browser_download_url; + versions.push(Version { + version: version.clone(), + arch, + url, + }); + } + } + + Ok(versions) +} + +fn get_solc_releases(shell: &Shell, arch: Arch) -> anyhow::Result> { + let (arch_str, compatible_archs) = match arch { + Arch::LinuxAmd => ("linux-amd64", vec![Arch::LinuxAmd, Arch::LinuxArm]), + Arch::LinuxArm => ("linux-amd64", vec![Arch::LinuxAmd, Arch::LinuxArm]), + Arch::MacosAmd => ("macosx-amd64", vec![Arch::MacosAmd, Arch::MacosArm]), + Arch::MacosArm => ("macosx-amd64", vec![Arch::MacosAmd, Arch::MacosArm]), + }; + + let response: std::process::Output = Cmd::new(cmd!( + shell, + "curl https://raw.githubusercontent.com/ethereum/solc-bin/gh-pages/{arch_str}/list.json" + )) + .run_with_output()?; + + let response = String::from_utf8(response.stdout)?; + let solc_list: SolcList = serde_json::from_str(&response)?; + + let mut versions = vec![]; + for build in solc_list.builds { + let path = build.path; + versions.push(Version { + version: build.version, + arch: compatible_archs.clone(), + url: format!("https://github.com/ethereum/solc-bin/raw/gh-pages/{arch_str}/{path}"), + }); + } + versions.reverse(); + Ok(versions) +} + +pub fn get_releases_with_arch( + shell: &Shell, + repo: &str, + arch: Arch, + message: &str, +) -> anyhow::Result> { + let spinner = Spinner::new(message); + let releases = get_releases(shell, repo, arch)?; + let releases = releases + .into_iter() + .filter(|r| r.arch.contains(&arch)) + .collect::>(); + if releases.is_empty() { + anyhow::bail!(MSG_NO_RELEASES_FOUND_ERR); + } + spinner.finish(); + Ok(releases) +} diff --git a/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/init.rs b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/init.rs new file mode 100644 index 000000000000..5fd482ae5fff --- /dev/null +++ b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/init.rs @@ -0,0 +1,107 @@ +use std::path::{Path, PathBuf}; + +use common::{cmd::Cmd, logger, spinner::Spinner}; +use config::EcosystemConfig; +use xshell::{cmd, Shell}; + +use super::args::{init::InitContractVerifierArgs, releases::Version}; +use crate::messages::{msg_binary_already_exists, msg_downloading_binary_spinner}; + +pub(crate) async fn run(shell: &Shell, args: InitContractVerifierArgs) -> anyhow::Result<()> { + let args = args.fill_values_with_prompt(shell)?; + let ecosystem = EcosystemConfig::from_file(shell)?; + let link_to_code = ecosystem.link_to_code; + + download_binaries( + shell, + args.zksolc_releases, + get_zksolc_path, + &link_to_code, + "zksolc", + )?; + + download_binaries( + shell, + args.zkvyper_releases, + get_zkvyper_path, + &link_to_code, + "zkvyper", + )?; + + download_binaries( + shell, + args.solc_releases, + get_solc_path, + &link_to_code, + "solc", + )?; + + download_binaries( + shell, + args.vyper_releases, + get_vyper_path, + &link_to_code, + "vyper", + )?; + + Ok(()) +} + +fn download_binaries( + shell: &Shell, + releases: Vec, + get_path: fn(&Path, &str) -> PathBuf, + link_to_code: &Path, + name: &str, +) -> anyhow::Result<()> { + for release in releases { + download_binary( + shell, + &release.url, + &get_path(link_to_code, &release.version), + name, + &release.version, + )?; + } + Ok(()) +} + +fn download_binary( + shell: &Shell, + url: &str, + path: &Path, + name: &str, + version: &str, +) -> anyhow::Result<()> { + let binary_path = path.join(name); + if shell.path_exists(binary_path.clone()) { + logger::info(msg_binary_already_exists(name, version)); + return Ok(()); + } + + let spinner = Spinner::new(&msg_downloading_binary_spinner(name, version)); + Cmd::new(cmd!(shell, "mkdir -p {path}")).run()?; + Cmd::new(cmd!(shell, "wget {url} -O {binary_path}")).run()?; + Cmd::new(cmd!(shell, "chmod +x {binary_path}")).run()?; + spinner.finish(); + + Ok(()) +} + +fn get_zksolc_path(link_to_code: &Path, version: &str) -> PathBuf { + link_to_code.join("etc/zksolc-bin/").join(version) +} + +fn get_zkvyper_path(link_to_code: &Path, version: &str) -> PathBuf { + link_to_code.join("etc/zkvyper-bin/").join(version) +} + +fn get_vyper_path(link_to_code: &Path, version: &str) -> PathBuf { + link_to_code + .join("etc/vyper-bin/") + .join(version.replace('v', "")) +} + +fn get_solc_path(link_to_code: &Path, version: &str) -> PathBuf { + link_to_code.join("etc/solc-bin/").join(version) +} diff --git a/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/mod.rs b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/mod.rs new file mode 100644 index 000000000000..78bdc5fae7ec --- /dev/null +++ b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/mod.rs @@ -0,0 +1,22 @@ +use args::init::InitContractVerifierArgs; +use clap::Subcommand; +use xshell::Shell; + +pub mod args; +pub mod init; +pub mod run; + +#[derive(Subcommand, Debug)] +pub enum ContractVerifierCommands { + /// Run contract verifier + Run, + /// Download required binaries for contract verifier + Init(InitContractVerifierArgs), +} + +pub(crate) async fn run(shell: &Shell, args: ContractVerifierCommands) -> anyhow::Result<()> { + match args { + ContractVerifierCommands::Run => run::run(shell).await, + ContractVerifierCommands::Init(args) => init::run(shell, args).await, + } +} diff --git a/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/run.rs b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/run.rs new file mode 100644 index 000000000000..1ae06c810ba1 --- /dev/null +++ b/zk_toolbox/crates/zk_inception/src/commands/contract_verifier/run.rs @@ -0,0 +1,29 @@ +use anyhow::Context; +use common::{cmd::Cmd, logger}; +use config::EcosystemConfig; +use xshell::{cmd, Shell}; + +use crate::messages::{ + MSG_CHAIN_NOT_FOUND_ERR, MSG_FAILED_TO_RUN_CONTRACT_VERIFIER_ERR, MSG_RUNNING_CONTRACT_VERIFIER, +}; + +pub(crate) async fn run(shell: &Shell) -> anyhow::Result<()> { + let ecosystem = EcosystemConfig::from_file(shell)?; + let chain = ecosystem + .load_chain(Some(ecosystem.default_chain.clone())) + .context(MSG_CHAIN_NOT_FOUND_ERR)?; + + let config_path = chain.path_to_general_config(); + let secrets_path = chain.path_to_secrets_config(); + + let _dir_guard = shell.push_dir(&chain.link_to_code); + + logger::info(MSG_RUNNING_CONTRACT_VERIFIER); + + let mut cmd = Cmd::new(cmd!( + shell, + "cargo run --bin zksync_contract_verifier -- --config-path={config_path} --secrets-path={secrets_path}" + )); + cmd = cmd.with_force_run(); + cmd.run().context(MSG_FAILED_TO_RUN_CONTRACT_VERIFIER_ERR) +} diff --git a/zk_toolbox/crates/zk_inception/src/commands/mod.rs b/zk_toolbox/crates/zk_inception/src/commands/mod.rs index db34e1d8647d..5cba51265981 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/mod.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/mod.rs @@ -1,6 +1,7 @@ pub mod args; pub mod chain; pub mod containers; +pub mod contract_verifier; pub mod ecosystem; pub mod external_node; pub mod prover; diff --git a/zk_toolbox/crates/zk_inception/src/commands/prover/run.rs b/zk_toolbox/crates/zk_inception/src/commands/prover/run.rs index f91e992f1fdc..898cf0e45d66 100644 --- a/zk_toolbox/crates/zk_inception/src/commands/prover/run.rs +++ b/zk_toolbox/crates/zk_inception/src/commands/prover/run.rs @@ -16,6 +16,7 @@ use crate::messages::{ }; pub(crate) async fn run(args: ProverRunArgs, shell: &Shell) -> anyhow::Result<()> { + check_prover_prequisites(shell); let args = args.fill_values_with_prompt()?; let ecosystem_config = EcosystemConfig::from_file(shell)?; let chain = ecosystem_config @@ -42,7 +43,6 @@ pub(crate) async fn run(args: ProverRunArgs, shell: &Shell) -> anyhow::Result<() } fn run_gateway(shell: &Shell, chain: &ChainConfig) -> anyhow::Result<()> { - check_prover_prequisites(shell); logger::info(MSG_RUNNING_PROVER_GATEWAY); let config_path = chain.path_to_general_config(); let secrets_path = chain.path_to_secrets_config(); diff --git a/zk_toolbox/crates/zk_inception/src/main.rs b/zk_toolbox/crates/zk_inception/src/main.rs index 741d6df12e4e..63a2884195a5 100644 --- a/zk_toolbox/crates/zk_inception/src/main.rs +++ b/zk_toolbox/crates/zk_inception/src/main.rs @@ -1,4 +1,5 @@ use clap::{command, Parser, Subcommand}; +use commands::contract_verifier::ContractVerifierCommands; use common::{ check_general_prerequisites, config::{global_config, init_global_config, GlobalConfig}, @@ -49,6 +50,9 @@ pub enum InceptionSubcommands { /// Run containers for local development #[command(subcommand, alias = "up")] Containers, + /// Run contract verifier + #[command(subcommand)] + ContractVerifier(ContractVerifierCommands), } #[derive(Parser, Debug)] @@ -103,6 +107,9 @@ async fn run_subcommand(inception_args: Inception, shell: &Shell) -> anyhow::Res InceptionSubcommands::ExternalNode(args) => { commands::external_node::run(shell, args).await? } + InceptionSubcommands::ContractVerifier(args) => { + commands::contract_verifier::run(shell, args).await? + } } Ok(()) } diff --git a/zk_toolbox/crates/zk_inception/src/messages.rs b/zk_toolbox/crates/zk_inception/src/messages.rs index d0b146c9a4c5..af40b48e5795 100644 --- a/zk_toolbox/crates/zk_inception/src/messages.rs +++ b/zk_toolbox/crates/zk_inception/src/messages.rs @@ -278,3 +278,35 @@ pub(super) const MSG_BELLMAN_CUDA_SELECTION_PATH: &str = "I have the code alread pub(super) fn msg_bucket_created(bucket_name: &str) -> String { format!("Bucket created successfully with url: gs://{bucket_name}") } + +/// Contract verifier related messages +pub(super) const MSG_RUNNING_CONTRACT_VERIFIER: &str = "Running contract verifier"; +pub(super) const MSG_FAILED_TO_RUN_CONTRACT_VERIFIER_ERR: &str = "Failed to run contract verifier"; +pub(super) const MSG_INVALID_ARCH_ERR: &str = "Invalid arch"; +pub(super) const MSG_GET_ZKSOLC_RELEASES_ERR: &str = "Failed to get zksolc releases"; +pub(super) const MSG_FETCHING_ZKSOLC_RELEASES_SPINNER: &str = "Fetching zksolc releases..."; +pub(super) const MSG_FETCHING_ZKVYPER_RELEASES_SPINNER: &str = "Fetching zkvyper releases..."; +pub(super) const MSG_FETCH_SOLC_RELEASES_SPINNER: &str = "Fetching solc releases..."; +pub(super) const MSG_FETCHING_VYPER_RELEASES_SPINNER: &str = "Fetching vyper releases..."; +pub(super) const MSG_ZKSOLC_VERSION_PROMPT: &str = "Select the minimal zksolc version:"; +pub(super) const MSG_ZKVYPER_VERSION_PROMPT: &str = "Select the minimal zkvyper version:"; +pub(super) const MSG_SOLC_VERSION_PROMPT: &str = "Select the minimal solc version:"; +pub(super) const MSG_VYPER_VERSION_PROMPT: &str = "Select the minimal vyper version:"; +pub(super) const MSG_NO_RELEASES_FOUND_ERR: &str = "No releases found for current architecture"; +pub(super) const MSG_NO_VERSION_FOUND_ERR: &str = "No version found"; +pub(super) const MSG_ARCH_NOT_SUPPORTED_ERR: &str = "Architecture not supported"; +pub(super) const MSG_OS_NOT_SUPPORTED_ERR: &str = "OS not supported"; +pub(super) const MSG_GET_VYPER_RELEASES_ERR: &str = "Failed to get vyper releases"; +pub(super) const MSG_GET_SOLC_RELEASES_ERR: &str = "Failed to get solc releases"; +pub(super) const MSG_GET_ZKVYPER_RELEASES_ERR: &str = "Failed to get zkvyper releases"; + +pub(super) fn msg_binary_already_exists(name: &str, version: &str) -> String { + format!( + "{} {} binary already exists. Skipping download.", + name, version + ) +} + +pub(super) fn msg_downloading_binary_spinner(name: &str, version: &str) -> String { + format!("Downloading {} {} binary", name, version) +}