Skip to content

Commit

Permalink
feat(zk_toolbox): Add contract verifier support for zk toolbox (#2420)
Browse files Browse the repository at this point in the history
## What ❔
Add contract verifier support for zk toolbox

---------

Signed-off-by: Danil <[email protected]>
Co-authored-by: Danil <[email protected]>
  • Loading branch information
matias-gonz and Deniallugo authored Jul 12, 2024
1 parent 6cc3f17 commit d10a24b
Show file tree
Hide file tree
Showing 10 changed files with 529 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -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<String>,
/// Version of zkvyper to install
#[clap(long)]
pub zkvyper_version: Option<String>,
/// Version of solc to install
#[clap(long)]
pub solc_version: Option<String>,
/// Version of vyper to install
#[clap(long)]
pub vyper_version: Option<String>,
}

#[derive(Debug, Clone)]
pub struct InitContractVerifierArgsFinal {
pub zksolc_releases: Vec<Version>,
pub zkvyper_releases: Vec<Version>,
pub solc_releases: Vec<Version>,
pub vyper_releases: Vec<Version>,
}

impl InitContractVerifierArgs {
pub fn fill_values_with_prompt(
self,
shell: &Shell,
) -> anyhow::Result<InitContractVerifierArgsFinal> {
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<Arch> {
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<String>,
versions: Vec<Version>,
prompt_msg: &str,
) -> anyhow::Result<Version> {
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: Version,
) -> anyhow::Result<Vec<Version>> {
let pos = releases
.iter()
.position(|r| r.version == version.version)
.context(MSG_NO_VERSION_FOUND_ERR)?;

Ok(releases[..=pos].to_vec())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod init;
pub mod releases;
Original file line number Diff line number Diff line change
@@ -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<GitHubAsset>,
}

#[derive(Deserialize)]
struct GitHubAsset {
name: String,
browser_download_url: String,
}

#[derive(Deserialize)]
struct SolcList {
builds: Vec<SolcBuild>,
}

#[derive(Deserialize)]
struct SolcBuild {
path: String,
version: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Version {
pub version: String,
pub arch: Vec<Arch>,
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<Self, Self::Err> {
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<Vec<Arch>> {
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<Vec<Version>> {
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<GitHubRelease> = 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<Vec<Version>> {
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<Vec<Version>> {
let spinner = Spinner::new(message);
let releases = get_releases(shell, repo, arch)?;
let releases = releases
.into_iter()
.filter(|r| r.arch.contains(&arch))
.collect::<Vec<_>>();
if releases.is_empty() {
anyhow::bail!(MSG_NO_RELEASES_FOUND_ERR);
}
spinner.finish();
Ok(releases)
}
Loading

0 comments on commit d10a24b

Please sign in to comment.