diff --git a/Cargo.lock b/Cargo.lock index 423ff3a..dda29d6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,6 +76,22 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "assert_cmd" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bd389a4b2970a01282ee455294913c0a43724daedcd1a24c3eb0ec1c1320b66" +dependencies = [ + "anstyle", + "bstr", + "doc-comment", + "libc", + "predicates", + "predicates-core", + "predicates-tree", + "wait-timeout", +] + [[package]] name = "atty" version = "0.2.14" @@ -87,6 +103,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + [[package]] name = "backtrace" version = "0.3.75" @@ -106,8 +128,10 @@ dependencies = [ name = "bargo" version = "0.1.0" dependencies = [ + "assert_cmd", "bargo-core", "color-eyre", + "predicates", "tempfile", ] @@ -135,6 +159,17 @@ version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "bstr" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" +dependencies = [ + "memchr", + "regex-automata 0.4.9", + "serde", +] + [[package]] name = "cfg-if" version = "1.0.1" @@ -214,6 +249,18 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "difflib" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" + +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "dotenv" version = "0.15.0" @@ -258,6 +305,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "float-cmp" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09cf3155332e944990140d967ff5eceb70df778b34f77d8075db46e4704e6d8" +dependencies = [ + "num-traits", +] + [[package]] name = "getrandom" version = "0.3.3" @@ -394,6 +450,12 @@ dependencies = [ "adler2", ] +[[package]] +name = "normalize-line-endings" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be" + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -404,6 +466,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.7" @@ -443,6 +514,36 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "predicates" +version = "3.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d19ee57562043d37e82899fade9a22ebab7be9cef5026b07fda9cdd4293573" +dependencies = [ + "anstyle", + "difflib", + "float-cmp", + "normalize-line-endings", + "predicates-core", + "regex", +] + +[[package]] +name = "predicates-core" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "727e462b119fe9c93fd0eb1429a5f7647394014cf3c04ab2c0350eeb09095ffa" + +[[package]] +name = "predicates-tree" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72dd2d6d381dfb73a193c7fca536518d7caee39fc8503f74e7dc0be0531b425c" +dependencies = [ + "predicates-core", + "termtree", +] + [[package]] name = "proc-macro2" version = "1.0.95" @@ -635,6 +736,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "termtree" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" + [[package]] name = "thread_local" version = "1.1.9" @@ -774,6 +881,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + [[package]] name = "wasi" version = "0.14.2+wasi-0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 0fe2f09..9a6077f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,3 +12,5 @@ color-eyre = "0.6.5" [dev-dependencies] tempfile = "3.8" +assert_cmd = "2" +predicates = "3" diff --git a/crates/bargo-core/src/cli.rs b/crates/bargo-core/src/cli.rs new file mode 100644 index 0000000..29c5c92 --- /dev/null +++ b/crates/bargo-core/src/cli.rs @@ -0,0 +1,159 @@ +use clap::{Parser, Subcommand, ValueEnum}; + +/// A developer-friendly CLI wrapper for Noir ZK development +#[derive(Parser)] +#[command( + name = "bargo", + about = "A developer-friendly CLI wrapper for Noir ZK development", + long_about = "bargo consolidates nargo and bb workflows into a single, opinionated tool that 'just works' in a standard Noir workspace.", + version +)] +pub struct Cli { + /// Enable verbose logging (shows underlying commands) + #[arg(short, long, global = true)] + pub verbose: bool, + + /// Print commands without executing them + #[arg(long, global = true)] + pub dry_run: bool, + + /// Override package name (auto-detected from Nargo.toml) + #[arg(long, global = true)] + pub pkg: Option, + + /// Minimize output + #[arg(short, long, global = true)] + pub quiet: bool, + + #[command(subcommand)] + pub command: Commands, +} + +#[derive(Subcommand)] +pub enum Commands { + /// Check circuit syntax and dependencies + #[command(about = "Run nargo check to validate circuit syntax and dependencies")] + Check, + + /// Build circuit (compile + execute to generate bytecode and witness) + #[command(about = "Run nargo execute to generate bytecode and witness files")] + Build, + + /// Clean build artifacts + #[command(about = "Remove target directory and all build artifacts")] + Clean { + /// Backend to clean (defaults to all) + #[arg(long, value_enum)] + backend: Option, + }, + + /// Clean and rebuild (equivalent to clean + build) + #[command(about = "Remove target directory and rebuild from scratch")] + Rebuild { + /// Backend to clean (defaults to all) + #[arg(long, value_enum)] + backend: Option, + }, + + /// Cairo/Starknet operations + #[command(about = "Generate Cairo verifiers and interact with Starknet")] + Cairo { + #[command(subcommand)] + command: CairoCommands, + }, + + /// EVM/Foundry operations + #[command(about = "Generate Solidity verifiers and interact with EVM networks")] + Evm { + #[command(subcommand)] + command: EvmCommands, + }, + + /// Check system dependencies + #[command(about = "Verify that all required tools are installed and available")] + Doctor, +} + +#[derive(Subcommand)] +pub enum CairoCommands { + /// Generate Cairo verifier contract + #[command(about = "Generate Cairo verifier contract for Starknet deployment")] + Gen, + + /// Generate Starknet oracle proof + #[command(about = "Generate proof using bb with Starknet oracle hash")] + Prove, + + /// Verify Starknet oracle proof + #[command(about = "Verify proof generated with Starknet oracle hash")] + Verify, + + /// Generate calldata for proof verification + #[command(about = "Generate calldata JSON for latest proof")] + Calldata, + + /// Declare verifier contract on Starknet + #[command(about = "Declare verifier contract on Starknet")] + Declare { + /// Network to declare on (sepolia or mainnet) + #[arg(long, default_value = "sepolia")] + network: String, + }, + + /// Deploy declared verifier contract + #[command(about = "Deploy declared verifier contract")] + Deploy { + /// Class hash of the declared contract + #[arg(long)] + class_hash: Option, + }, + + /// Verify proof on-chain + #[command(about = "Verify proof on Starknet using deployed verifier")] + VerifyOnchain { + /// Address of deployed verifier contract + #[arg(short = 'a', long)] + address: Option, + }, +} + +#[derive(Subcommand)] +pub enum EvmCommands { + /// Generate Solidity verifier contract and Foundry project + #[command(about = "Generate Solidity verifier contract with complete Foundry project setup")] + Gen, + + /// Generate Keccak oracle proof + #[command(about = "Generate proof using bb with Keccak oracle hash")] + Prove, + + /// Verify Keccak oracle proof + #[command(about = "Verify proof generated with Keccak oracle hash")] + Verify, + + /// Deploy verifier contract to EVM network + #[command(about = "Deploy verifier contract using Foundry")] + Deploy { + /// Network to deploy to (mainnet or sepolia) + #[arg(long, default_value = "sepolia")] + network: String, + }, + + /// Generate calldata for proof verification + #[command(about = "Generate calldata for proof verification using cast")] + Calldata, + + /// Verify proof on-chain + #[command(about = "Verify proof on EVM network using deployed verifier")] + VerifyOnchain, +} + +#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)] +pub enum Backend { + /// Barretenberg backend (EVM/Solidity) + Bb, + /// Starknet backend (Cairo) + Starknet, + /// All backends + All, +} diff --git a/crates/bargo-core/src/commands/build.rs b/crates/bargo-core/src/commands/build.rs index 4906f14..2a56967 100644 --- a/crates/bargo-core/src/commands/build.rs +++ b/crates/bargo-core/src/commands/build.rs @@ -3,45 +3,51 @@ use tracing::info; use crate::{ backends, - util::{self, Flavour, Timer, format_operation_result, success}, - Cli, + commands::build_nargo_args, + config::Config, + util::{self, format_operation_result, success, Flavour, Timer}, }; /// Determine whether a rebuild is needed based on source timestamps -pub fn should_rebuild(pkg: &str, cli: &Cli) -> Result { - if cli.dry_run { return Ok(true); } +pub fn should_rebuild(pkg: &str, cfg: &Config) -> Result { + if cfg.dry_run { return Ok(true); } util::needs_rebuild(pkg) } -/// Run `nargo execute` with the provided arguments +/// Run `nargo execute` with the provided arguments. +/// +/// The slice is typically produced by [`build_nargo_args`]. pub fn run_nargo_execute(args: &[&str]) -> Result<()> { backends::nargo::run(args) } /// Execute the build workflow -pub fn run(cli: &Cli) -> Result<()> { - let pkg_name = util::get_package_name(cli.pkg.as_ref())?; +pub fn run(cfg: &Config) -> Result<()> { + let args = build_nargo_args(cfg, &["execute"])?; + let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); + if cfg.verbose { + info!("Running: nargo {}", args.join(" ")); + } - if !should_rebuild(&pkg_name, cli)? { - if !cli.quiet { - println!("{}", success("Build is up to date")); - } + if cfg.dry_run { + println!("Would run: nargo {}", args.join(" ")); return Ok(()); } - let args = ["execute"]; - if cli.verbose { info!("Running: nargo execute {:?}", args); } + let pkg_name = util::get_package_name(cfg.pkg.as_ref())?; - if cli.dry_run { - println!("Would run: nargo execute {}", args.join(" ")); + if !should_rebuild(&pkg_name, cfg)? { + if !cfg.quiet { + println!("{}", success("Build is up to date")); + } return Ok(()); } let timer = Timer::start(); - run_nargo_execute(&args)?; + run_nargo_execute(&arg_refs)?; util::organize_build_artifacts(&pkg_name, Flavour::Bb)?; - if !cli.quiet { + if !cfg.quiet { let bytecode_path = util::get_bytecode_path(&pkg_name, Flavour::Bb); let witness_path = util::get_witness_path(&pkg_name, Flavour::Bb); println!( diff --git a/crates/bargo-core/src/commands/cairo/workflow.rs b/crates/bargo-core/src/commands/cairo/workflow.rs index 9a6c9d5..fab0a5d 100644 --- a/crates/bargo-core/src/commands/cairo/workflow.rs +++ b/crates/bargo-core/src/commands/cairo/workflow.rs @@ -7,7 +7,7 @@ use color_eyre::Result; use tracing::info; use crate::{ - Cli, + config::Config, util::{ self, Flavour, OperationSummary, Timer, create_smart_error, enhance_error_with_suggestions, format_operation_result, success, @@ -28,11 +28,11 @@ use super::{bb_operations, directories, garaga, load_env_vars}; /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_gen(cli: &Cli) -> Result<()> { - let pkg_name = util::get_package_name(cli.pkg.as_ref())?; +pub fn run_gen(cfg: &Config) -> Result<()> { + let pkg_name = util::get_package_name(cfg.pkg.as_ref())?; load_env_vars(); - if cli.verbose { + if cfg.verbose { info!("Starting Cairo verifier generation workflow"); } @@ -42,13 +42,13 @@ pub fn run_gen(cli: &Cli) -> Result<()> { util::get_witness_path(&pkg_name, Flavour::Bb), ]; - if !cli.dry_run { + if !cfg.dry_run { util::validate_files_exist(&required_files).map_err(enhance_error_with_suggestions)?; directories::validate_cairo_directory_structure() .map_err(enhance_error_with_suggestions)?; } - if cli.dry_run { + if cfg.dry_run { print_dry_run_commands(&pkg_name)?; return Ok(()); } @@ -56,13 +56,13 @@ pub fn run_gen(cli: &Cli) -> Result<()> { let mut summary = OperationSummary::new(); // Step 1: Generate Starknet proof - if cli.verbose { + if cfg.verbose { info!("Generating Starknet proof"); } let proof_timer = Timer::start(); bb_operations::generate_starknet_proof(&pkg_name).map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { let proof_path = util::get_proof_path(Flavour::Starknet); println!( "{}", @@ -79,13 +79,13 @@ pub fn run_gen(cli: &Cli) -> Result<()> { } // Step 2: Generate Starknet VK - if cli.verbose { + if cfg.verbose { info!("Generating Starknet verification key"); } let vk_timer = Timer::start(); bb_operations::generate_starknet_vk(&pkg_name).map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { let vk_path = util::get_vk_path(Flavour::Starknet); println!( "{}", @@ -102,13 +102,13 @@ pub fn run_gen(cli: &Cli) -> Result<()> { } // Step 3: Generate Cairo verifier contract - if cli.verbose { + if cfg.verbose { info!("Generating Cairo verifier contract"); } let contract_timer = Timer::start(); garaga::generate_cairo_contract_from_starknet_vk().map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { let cairo_dir = directories::get_cairo_contracts_dir(); println!( "{}", @@ -136,9 +136,9 @@ pub fn run_gen(cli: &Cli) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_prove(cli: &Cli) -> Result<()> { +pub fn run_prove(cfg: &Config) -> Result<()> { let pkg_name = - util::get_package_name(cli.pkg.as_ref()).map_err(enhance_error_with_suggestions)?; + util::get_package_name(cfg.pkg.as_ref()).map_err(enhance_error_with_suggestions)?; // Validate that required build files exist let required_files = vec![ @@ -146,12 +146,12 @@ pub fn run_prove(cli: &Cli) -> Result<()> { util::get_witness_path(&pkg_name, Flavour::Bb), ]; - if !cli.dry_run { + if !cfg.dry_run { util::validate_files_exist(&required_files).map_err(enhance_error_with_suggestions)?; directories::ensure_starknet_target_dir().map_err(enhance_error_with_suggestions)?; } - if cli.dry_run { + if cfg.dry_run { println!( "Would run: bb prove --scheme ultra_honk --oracle_hash starknet --zk -b ./target/bb/{}.json -w ./target/bb/{}.gz -o ./target/starknet/", pkg_name, pkg_name @@ -167,7 +167,7 @@ pub fn run_prove(cli: &Cli) -> Result<()> { bb_operations::generate_starknet_proof_and_vk(&pkg_name) .map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { let proof_path = util::get_proof_path(Flavour::Starknet); let vk_path = util::get_vk_path(Flavour::Starknet); println!( @@ -192,9 +192,9 @@ pub fn run_prove(cli: &Cli) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_verify(cli: &Cli) -> Result<()> { +pub fn run_verify(cfg: &Config) -> Result<()> { let pkg_name = - util::get_package_name(cli.pkg.as_ref()).map_err(enhance_error_with_suggestions)?; + util::get_package_name(cfg.pkg.as_ref()).map_err(enhance_error_with_suggestions)?; // Validate that required Starknet artifacts exist let required_files = vec![ @@ -203,11 +203,11 @@ pub fn run_verify(cli: &Cli) -> Result<()> { util::get_public_inputs_path(Flavour::Starknet), ]; - if !cli.dry_run { + if !cfg.dry_run { util::validate_files_exist(&required_files).map_err(enhance_error_with_suggestions)?; } - if cli.dry_run { + if cfg.dry_run { println!( "Would run: bb verify -p ./target/starknet/proof -k ./target/starknet/vk -j ./target/starknet/public_inputs" ); @@ -217,7 +217,7 @@ pub fn run_verify(cli: &Cli) -> Result<()> { let timer = Timer::start(); bb_operations::verify_starknet_proof(&pkg_name).map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { println!( "{}", success(&format!( @@ -237,14 +237,14 @@ pub fn run_verify(cli: &Cli) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error -pub fn run_calldata(cli: &Cli) -> Result<()> { +pub fn run_calldata(cfg: &Config) -> Result<()> { let mut summary = OperationSummary::new(); - if !cli.dry_run { + if !cfg.dry_run { garaga::validate_starknet_artifacts().map_err(enhance_error_with_suggestions)?; } - if cli.dry_run { + if cfg.dry_run { let proof_path = util::get_proof_path(Flavour::Starknet); let vk_path = util::get_vk_path(Flavour::Starknet); let public_inputs_path = util::get_public_inputs_path(Flavour::Starknet); @@ -257,7 +257,7 @@ pub fn run_calldata(cli: &Cli) -> Result<()> { return Ok(()); } - if cli.verbose { + if cfg.verbose { info!("Generating calldata for Starknet proof verification"); } @@ -265,7 +265,7 @@ pub fn run_calldata(cli: &Cli) -> Result<()> { let calldata_path = garaga::generate_calldata_from_starknet_artifacts() .map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { println!( "{}", success(&format_operation_result( @@ -295,7 +295,7 @@ pub fn run_calldata(cli: &Cli) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_declare(cli: &Cli, network: &str) -> Result<()> { +pub fn run_declare(cfg: &Config, network: &str) -> Result<()> { load_env_vars(); let cairo_dir = directories::get_cairo_contracts_dir(); @@ -309,12 +309,12 @@ pub fn run_declare(cli: &Cli, network: &str) -> Result<()> { )); } - if cli.dry_run { + if cfg.dry_run { println!("Would declare contract on network: {}", network); return Ok(()); } - if cli.verbose { + if cfg.verbose { info!("Declaring Cairo verifier contract on {}", network); } @@ -335,7 +335,7 @@ pub fn run_declare(cli: &Cli, network: &str) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_deploy(cli: &Cli, class_hash: Option<&str>) -> Result<()> { +pub fn run_deploy(cfg: &Config, class_hash: Option<&str>) -> Result<()> { load_env_vars(); let hash = match class_hash { @@ -357,12 +357,12 @@ pub fn run_deploy(cli: &Cli, class_hash: Option<&str>) -> Result<()> { } }; - if cli.dry_run { + if cfg.dry_run { println!("Would deploy contract with class hash: {}", hash); return Ok(()); } - if cli.verbose { + if cfg.verbose { info!("Deploying Cairo verifier contract"); } @@ -382,7 +382,7 @@ pub fn run_deploy(cli: &Cli, class_hash: Option<&str>) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_verify_onchain(cli: &Cli, address: Option<&str>) -> Result<()> { +pub fn run_verify_onchain(cfg: &Config, address: Option<&str>) -> Result<()> { load_env_vars(); let contract_address = match address { @@ -406,7 +406,7 @@ pub fn run_verify_onchain(cli: &Cli, address: Option<&str>) -> Result<()> { // Validate calldata exists let calldata_path = std::path::PathBuf::from("./target/starknet/calldata.json"); - if !cli.dry_run && !calldata_path.exists() { + if !cfg.dry_run && !calldata_path.exists() { return Err(create_smart_error( "Calldata file not found", &[ @@ -416,7 +416,7 @@ pub fn run_verify_onchain(cli: &Cli, address: Option<&str>) -> Result<()> { )); } - if cli.dry_run { + if cfg.dry_run { println!( "Would verify proof on-chain at address: {}", contract_address @@ -424,7 +424,7 @@ pub fn run_verify_onchain(cli: &Cli, address: Option<&str>) -> Result<()> { return Ok(()); } - if cli.verbose { + if cfg.verbose { info!("Verifying proof on-chain at address: {}", contract_address); } diff --git a/crates/bargo-core/src/commands/check.rs b/crates/bargo-core/src/commands/check.rs new file mode 100644 index 0000000..69c7c31 --- /dev/null +++ b/crates/bargo-core/src/commands/check.rs @@ -0,0 +1,20 @@ +use color_eyre::Result; +use tracing::info; + +use crate::{backends, commands::build_nargo_args, config::Config}; + +pub fn run(cfg: &Config) -> Result<()> { + let args = build_nargo_args(cfg, &["check"])?; + let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); + + if cfg.verbose { + info!("Running: nargo {}", args.join(" ")); + } + + if cfg.dry_run { + println!("Would run: nargo {}", args.join(" ")); + return Ok(()); + } + + backends::nargo::run(&arg_refs) +} diff --git a/crates/bargo-core/src/commands/clean.rs b/crates/bargo-core/src/commands/clean.rs new file mode 100644 index 0000000..2c97264 --- /dev/null +++ b/crates/bargo-core/src/commands/clean.rs @@ -0,0 +1,60 @@ +use color_eyre::Result; +use tracing::info; + +use crate::{config::Config, cli::Backend, util::{info as info_msg, success}}; + +pub fn run(cfg: &Config, backend: Backend) -> Result<()> { + if cfg.verbose { + info!("Cleaning artifacts for backend: {:?}", backend); + } + + match backend { + Backend::All => { + if cfg.dry_run { + println!("Would run: rm -rf target/"); + return Ok(()); + } + + if std::path::Path::new("target").exists() { + std::fs::remove_dir_all("target")?; + if !cfg.quiet { + println!("{}", success("Removed target/")); + } + } else if !cfg.quiet { + println!("{}", info_msg("target/ already clean")); + } + } + Backend::Bb => { + if cfg.dry_run { + println!("Would run: rm -rf target/bb/"); + return Ok(()); + } + + if std::path::Path::new("target/bb").exists() { + std::fs::remove_dir_all("target/bb")?; + if !cfg.quiet { + println!("{}", success("Removed target/bb/")); + } + } else if !cfg.quiet { + println!("{}", info_msg("target/bb/ already clean")); + } + } + Backend::Starknet => { + if cfg.dry_run { + println!("Would run: rm -rf target/starknet/"); + return Ok(()); + } + + if std::path::Path::new("target/starknet").exists() { + std::fs::remove_dir_all("target/starknet")?; + if !cfg.quiet { + println!("{}", success("Removed target/starknet/")); + } + } else if !cfg.quiet { + println!("{}", info_msg("target/starknet/ already clean")); + } + } + } + + Ok(()) +} diff --git a/crates/bargo-core/src/commands/common.rs b/crates/bargo-core/src/commands/common.rs new file mode 100644 index 0000000..8861809 --- /dev/null +++ b/crates/bargo-core/src/commands/common.rs @@ -0,0 +1,15 @@ +use color_eyre::Result; + +use crate::config::Config; + +/// Build argument list for nargo commands based on global config +pub fn build_nargo_args(cfg: &Config, base_args: &[&str]) -> Result> { + let mut args = base_args.iter().map(|s| s.to_string()).collect::>(); + + if let Some(pkg) = &cfg.pkg { + args.push("--package".to_string()); + args.push(pkg.clone()); + } + + Ok(args) +} diff --git a/crates/bargo-core/src/commands/doctor.rs b/crates/bargo-core/src/commands/doctor.rs new file mode 100644 index 0000000..8acb6c7 --- /dev/null +++ b/crates/bargo-core/src/commands/doctor.rs @@ -0,0 +1,107 @@ +use color_eyre::Result; + +use crate::config::Config; + +pub fn run(cfg: &Config) -> Result<()> { + if !cfg.quiet { + println!("๐Ÿ” Checking system dependencies...\n"); + } + + let mut all_good = true; + + match which::which("nargo") { + Ok(path) => { + if !cfg.quiet { + println!("โœ… nargo: {}", path.display()); + } + } + Err(_) => { + if !cfg.quiet { + println!("โŒ nargo: not found"); + println!( + " Install from: https://noir-lang.org/docs/getting_started/installation/" + ); + } + all_good = false; + } + } + + match which::which("bb") { + Ok(path) => { + if !cfg.quiet { + println!("โœ… bb: {}", path.display()); + } + } + Err(_) => { + if !cfg.quiet { + println!("โŒ bb: not found"); + println!(" Install from: https://github.com/AztecProtocol/aztec-packages"); + } + all_good = false; + } + } + + match which::which("garaga") { + Ok(path) => { + if !cfg.quiet { + println!("โœ… garaga: {}", path.display()); + } + } + Err(_) => { + if !cfg.quiet { + println!("โš ๏ธ garaga: not found (optional - needed for Cairo features)"); + println!(" Install with: pipx install garaga"); + println!(" Requires Python 3.10+"); + } + } + } + + match which::which("forge") { + Ok(path) => { + if !cfg.quiet { + println!("โœ… forge: {}", path.display()); + } + } + Err(_) => { + if !cfg.quiet { + println!("โš ๏ธ forge: not found (optional - needed for EVM features)"); + println!(" Install with: curl -L https://foundry.paradigm.xyz | bash"); + println!(" Then run: foundryup"); + } + } + } + + match which::which("cast") { + Ok(path) => { + if !cfg.quiet { + println!("โœ… cast: {}", path.display()); + } + } + Err(_) => { + if !cfg.quiet { + println!("โš ๏ธ cast: not found (optional - needed for EVM features)"); + println!(" Install with: curl -L https://foundry.paradigm.xyz | bash"); + println!(" Then run: foundryup"); + } + } + } + + if !cfg.quiet { + println!(); + if all_good { + println!("๐ŸŽ‰ All required dependencies are available!"); + println!(" You can use all bargo features."); + } else { + println!("๐Ÿšจ Some required dependencies are missing."); + println!(" Core features require: nargo + bb"); + println!(" EVM deployment features also require: forge + cast"); + println!(" Cairo features also require: garaga"); + } + } + + if !all_good { + std::process::exit(1); + } + + Ok(()) +} diff --git a/crates/bargo-core/src/commands/evm/workflow.rs b/crates/bargo-core/src/commands/evm/workflow.rs index 4bee249..06c9e86 100644 --- a/crates/bargo-core/src/commands/evm/workflow.rs +++ b/crates/bargo-core/src/commands/evm/workflow.rs @@ -7,7 +7,7 @@ use color_eyre::Result; use tracing::info; use crate::{ - Cli, + config::Config, util::{ self, Flavour, OperationSummary, Timer, create_smart_error, enhance_error_with_suggestions, format_operation_result, success, @@ -29,11 +29,11 @@ use super::{bb_operations, directories, foundry, load_env_vars}; /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_gen(cli: &Cli) -> Result<()> { - let pkg_name = util::get_package_name(cli.pkg.as_ref())?; +pub fn run_gen(cfg: &Config) -> Result<()> { + let pkg_name = util::get_package_name(cfg.pkg.as_ref())?; load_env_vars(); - if cli.verbose { + if cfg.verbose { info!("Starting EVM verifier generation workflow"); } @@ -43,12 +43,12 @@ pub fn run_gen(cli: &Cli) -> Result<()> { util::get_witness_path(&pkg_name, Flavour::Bb), ]; - if !cli.dry_run { + if !cfg.dry_run { util::validate_files_exist(&required_files).map_err(enhance_error_with_suggestions)?; directories::validate_evm_directory_structure().map_err(enhance_error_with_suggestions)?; } - if cli.dry_run { + if cfg.dry_run { print_dry_run_commands(&pkg_name)?; return Ok(()); } @@ -56,13 +56,13 @@ pub fn run_gen(cli: &Cli) -> Result<()> { let mut summary = OperationSummary::new(); // Step 1: Initialize Foundry project - if cli.verbose { + if cfg.verbose { info!("Initializing Foundry project"); } let foundry_timer = Timer::start(); foundry::init_default_foundry_project().map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { let foundry_dir = directories::get_evm_contracts_dir(); println!( "{}", @@ -76,13 +76,13 @@ pub fn run_gen(cli: &Cli) -> Result<()> { } // Step 2: Generate EVM proof - if cli.verbose { + if cfg.verbose { info!("Generating EVM proof with keccak oracle"); } let proof_timer = Timer::start(); bb_operations::generate_evm_proof(&pkg_name).map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { let proof_path = util::get_proof_path(Flavour::Evm); println!( "{}", @@ -99,13 +99,13 @@ pub fn run_gen(cli: &Cli) -> Result<()> { } // Step 3: Generate EVM VK - if cli.verbose { + if cfg.verbose { info!("Generating EVM verification key"); } let vk_timer = Timer::start(); bb_operations::generate_evm_vk(&pkg_name).map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { let vk_path = util::get_vk_path(Flavour::Evm); println!( "{}", @@ -122,7 +122,7 @@ pub fn run_gen(cli: &Cli) -> Result<()> { } // Step 4: Generate Solidity verifier contract - if cli.verbose { + if cfg.verbose { info!("Generating Solidity verifier contract"); } let contract_timer = Timer::start(); @@ -130,7 +130,7 @@ pub fn run_gen(cli: &Cli) -> Result<()> { bb_operations::write_solidity_verifier_from_evm_vk(&verifier_path.to_string_lossy()) .map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { println!( "{}", success(&format_operation_result( @@ -160,9 +160,9 @@ pub fn run_gen(cli: &Cli) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_prove(cli: &Cli) -> Result<()> { +pub fn run_prove(cfg: &Config) -> Result<()> { let pkg_name = - util::get_package_name(cli.pkg.as_ref()).map_err(enhance_error_with_suggestions)?; + util::get_package_name(cfg.pkg.as_ref()).map_err(enhance_error_with_suggestions)?; // Validate that required build files exist let required_files = vec![ @@ -170,12 +170,12 @@ pub fn run_prove(cli: &Cli) -> Result<()> { util::get_witness_path(&pkg_name, Flavour::Bb), ]; - if !cli.dry_run { + if !cfg.dry_run { util::validate_files_exist(&required_files).map_err(enhance_error_with_suggestions)?; directories::ensure_evm_target_dir().map_err(enhance_error_with_suggestions)?; } - if cli.dry_run { + if cfg.dry_run { println!( "Would run: bb prove -b ./target/bb/{}.json -w ./target/bb/{}.gz -o ./target/evm/ --oracle_hash keccak --output_format bytes_and_fields", pkg_name, pkg_name @@ -190,7 +190,7 @@ pub fn run_prove(cli: &Cli) -> Result<()> { let timer = Timer::start(); bb_operations::generate_evm_proof_and_vk(&pkg_name).map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { let proof_path = util::get_proof_path(Flavour::Evm); let vk_path = util::get_vk_path(Flavour::Evm); println!( @@ -215,16 +215,16 @@ pub fn run_prove(cli: &Cli) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_verify(cli: &Cli) -> Result<()> { +pub fn run_verify(cfg: &Config) -> Result<()> { let pkg_name = - util::get_package_name(cli.pkg.as_ref()).map_err(enhance_error_with_suggestions)?; + util::get_package_name(cfg.pkg.as_ref()).map_err(enhance_error_with_suggestions)?; // Validate that required EVM artifacts exist - if !cli.dry_run { + if !cfg.dry_run { bb_operations::validate_evm_artifacts().map_err(enhance_error_with_suggestions)?; } - if cli.dry_run { + if cfg.dry_run { println!( "Would run: bb verify -p ./target/evm/proof -k ./target/evm/vk -j ./target/evm/public_inputs" ); @@ -234,7 +234,7 @@ pub fn run_verify(cli: &Cli) -> Result<()> { let timer = Timer::start(); bb_operations::verify_evm_proof(&pkg_name).map_err(enhance_error_with_suggestions)?; - if !cli.quiet { + if !cfg.quiet { println!( "{}", success(&format!( @@ -255,16 +255,16 @@ pub fn run_verify(cli: &Cli) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_deploy(cli: &Cli, network: &str) -> Result<()> { +pub fn run_deploy(cfg: &Config, network: &str) -> Result<()> { load_env_vars(); // Validate Foundry installation - if !cli.dry_run { + if !cfg.dry_run { foundry::validate_foundry_installation().map_err(enhance_error_with_suggestions)?; } // Check that verifier contract exists - if !cli.dry_run && !directories::verifier_contract_exists() { + if !cfg.dry_run && !directories::verifier_contract_exists() { return Err(create_smart_error( "Verifier contract not found", &[ @@ -296,13 +296,13 @@ pub fn run_deploy(cli: &Cli, network: &str) -> Result<()> { ) })?; - if cli.dry_run { + if cfg.dry_run { println!("Would deploy Verifier contract to network: {}", network); println!("Would use RPC URL: {}", rpc_url); return Ok(()); } - if cli.verbose { + if cfg.verbose { info!("Deploying Verifier contract to {}", network); } @@ -317,7 +317,7 @@ pub fn run_deploy(cli: &Cli, network: &str) -> Result<()> { } std::fs::write(address_file, &contract_address).ok(); - if !cli.quiet { + if !cfg.quiet { println!( "{}", success(&format!( @@ -349,17 +349,17 @@ pub fn run_deploy(cli: &Cli, network: &str) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_calldata(cli: &Cli) -> Result<()> { +pub fn run_calldata(cfg: &Config) -> Result<()> { load_env_vars(); // Validate Foundry installation - if !cli.dry_run { + if !cfg.dry_run { foundry::validate_foundry_installation().map_err(enhance_error_with_suggestions)?; } // Check that proof fields JSON exists (BB output for EVM) let proof_fields_path = std::path::PathBuf::from("./target/evm/proof_fields.json"); - if !cli.dry_run && !proof_fields_path.exists() { + if !cfg.dry_run && !proof_fields_path.exists() { return Err(create_smart_error( "Proof fields file not found", &[ @@ -369,13 +369,13 @@ pub fn run_calldata(cli: &Cli) -> Result<()> { )); } - if cli.dry_run { + if cfg.dry_run { println!("Would generate calldata from proof fields JSON"); println!("Would read: {}", proof_fields_path.display()); return Ok(()); } - if cli.verbose { + if cfg.verbose { info!("Generating calldata for EVM proof verification"); } @@ -388,7 +388,7 @@ pub fn run_calldata(cli: &Cli) -> Result<()> { std::fs::write(&calldata_path, &proof_fields_content) .map_err(|e| color_eyre::eyre::eyre!("Failed to write calldata file: {}", e))?; - if !cli.quiet { + if !cfg.quiet { let calldata_timer = Timer::start(); println!( "{}", @@ -420,11 +420,11 @@ pub fn run_calldata(cli: &Cli) -> Result<()> { /// /// # Returns /// * `Result<()>` - Success or error from workflow -pub fn run_verify_onchain(cli: &Cli) -> Result<()> { +pub fn run_verify_onchain(cfg: &Config) -> Result<()> { load_env_vars(); // Validate Foundry installation - if !cli.dry_run { + if !cfg.dry_run { foundry::validate_foundry_installation().map_err(enhance_error_with_suggestions)?; } @@ -445,7 +445,7 @@ pub fn run_verify_onchain(cli: &Cli) -> Result<()> { // Check that calldata exists let calldata_path = std::path::PathBuf::from("./target/evm/calldata.json"); - if !cli.dry_run && !calldata_path.exists() { + if !cfg.dry_run && !calldata_path.exists() { return Err(create_smart_error( "Calldata file not found", &[ @@ -465,7 +465,7 @@ pub fn run_verify_onchain(cli: &Cli) -> Result<()> { ) })?; - if cli.dry_run { + if cfg.dry_run { println!( "Would verify proof on-chain at contract: {}", contract_address @@ -474,7 +474,7 @@ pub fn run_verify_onchain(cli: &Cli) -> Result<()> { return Ok(()); } - if cli.verbose { + if cfg.verbose { info!("Verifying proof on-chain at contract: {}", contract_address); } @@ -482,7 +482,7 @@ pub fn run_verify_onchain(cli: &Cli) -> Result<()> { let calldata_content = std::fs::read_to_string(&calldata_path) .map_err(|e| color_eyre::eyre::eyre!("Failed to read calldata file: {}", e))?; - if cli.verbose { + if cfg.verbose { info!("Using calldata: {}", calldata_content.trim()); } @@ -493,7 +493,7 @@ pub fn run_verify_onchain(cli: &Cli) -> Result<()> { println!("RPC URL: {}", rpc_url); println!("Calldata: {}", calldata_path.display()); - if !cli.quiet { + if !cfg.quiet { let mut summary = OperationSummary::new(); summary.add_operation(&format!( "On-chain verification prepared for contract: {}", diff --git a/crates/bargo-core/src/commands/mod.rs b/crates/bargo-core/src/commands/mod.rs index 2401571..15b4382 100644 --- a/crates/bargo-core/src/commands/mod.rs +++ b/crates/bargo-core/src/commands/mod.rs @@ -1,5 +1,11 @@ pub mod build; - +pub mod cairo; pub mod evm; -pub mod cairo; +pub mod check; +pub mod clean; +pub mod rebuild; +pub mod doctor; +pub mod common; + +pub use common::build_nargo_args; diff --git a/crates/bargo-core/src/commands/rebuild.rs b/crates/bargo-core/src/commands/rebuild.rs new file mode 100644 index 0000000..f70d4a9 --- /dev/null +++ b/crates/bargo-core/src/commands/rebuild.rs @@ -0,0 +1,99 @@ +use color_eyre::Result; +use tracing::info; + +use crate::{ + backends, + cli::Backend, + config::Config, + commands::build_nargo_args, + util::{self, Flavour, OperationSummary, Timer, format_operation_result, path, success}, +}; + +use super::clean; + +pub fn run(cfg: &Config, backend: Backend) -> Result<()> { + let mut summary = OperationSummary::new(); + + // Step 1: Clean + if cfg.verbose { + info!("Step 1/2: Cleaning artifacts for backend: {:?}", backend); + } + + if !cfg.quiet { + println!("๐Ÿงน Cleaning build artifacts..."); + } + + clean::run(cfg, backend)?; + if backend != Backend::Starknet { + summary.add_operation("Build artifacts cleaned"); + } + + // Step 2: Build + if cfg.verbose { + info!("Step 2/2: Building from scratch"); + } + + if !cfg.quiet { + println!("\n๐Ÿ”จ Building circuit..."); + } + + let pkg_name = + util::get_package_name(cfg.pkg.as_ref()).map_err(util::enhance_error_with_suggestions)?; + let args = build_nargo_args(cfg, &["execute"])?; + let arg_refs: Vec<&str> = args.iter().map(|s| s.as_str()).collect(); + + if cfg.verbose { + info!("Running: nargo {}", args.join(" ")); + } + + if cfg.dry_run { + println!("Would run: nargo {}", args.join(" ")); + return Ok(()); + } + + let timer = Timer::start(); + let result = backends::nargo::run(&arg_refs); + + match result { + Ok(()) => { + util::organize_build_artifacts(&pkg_name, Flavour::Bb)?; + + if !cfg.quiet { + let bytecode_path = util::get_bytecode_path(&pkg_name, Flavour::Bb); + let witness_path = util::get_witness_path(&pkg_name, Flavour::Bb); + + println!( + "{}", + success(&format_operation_result( + "Bytecode generated", + &bytecode_path, + &timer + )) + ); + + let witness_timer = Timer::start(); + println!( + "{}", + success(&format_operation_result( + "Witness generated", + &witness_path, + &witness_timer + )) + ); + + summary.add_operation(&format!("Circuit rebuilt for {}", path(&pkg_name))); + summary.add_operation(&format!( + "Bytecode generated ({})", + util::format_file_size(&bytecode_path) + )); + summary.add_operation(&format!( + "Witness generated ({})", + util::format_file_size(&witness_path) + )); + summary.print(); + } + Ok(()) + } + Err(e) => Err(util::enhance_error_with_suggestions(e)), + } +} diff --git a/crates/bargo-core/src/config.rs b/crates/bargo-core/src/config.rs new file mode 100644 index 0000000..79dcb6d --- /dev/null +++ b/crates/bargo-core/src/config.rs @@ -0,0 +1,20 @@ +use crate::cli::Cli; + +#[derive(Clone, Debug)] +pub struct Config { + pub verbose: bool, + pub dry_run: bool, + pub pkg: Option, + pub quiet: bool, +} + +impl From<&Cli> for Config { + fn from(cli: &Cli) -> Self { + Self { + verbose: cli.verbose, + dry_run: cli.dry_run, + pkg: cli.pkg.clone(), + quiet: cli.quiet, + } + } +} diff --git a/crates/bargo-core/src/lib.rs b/crates/bargo-core/src/lib.rs index 7f2e61e..1335ab3 100644 --- a/crates/bargo-core/src/lib.rs +++ b/crates/bargo-core/src/lib.rs @@ -1,184 +1,22 @@ -use clap::{Parser, Subcommand, ValueEnum}; +use clap::Parser; use color_eyre::Result; use tracing::{info, warn}; mod backends; -mod commands; mod util; -use util::{ - Flavour, OperationSummary, Timer, enhance_error_with_suggestions, format_operation_result, - info, path, print_banner, success, -}; +pub mod cli; +pub mod commands; +pub mod config; -/// A developer-friendly CLI wrapper for Noir ZK development -#[derive(Parser)] -#[command( - name = "bargo", - about = "A developer-friendly CLI wrapper for Noir ZK development", - long_about = "bargo consolidates nargo and bb workflows into a single, opinionated tool that 'just works' in a standard Noir workspace.", - version -)] -struct Cli { - /// Enable verbose logging (shows underlying commands) - #[arg(short, long, global = true)] - verbose: bool, - - /// Print commands without executing them - #[arg(long, global = true)] - dry_run: bool, - - /// Override package name (auto-detected from Nargo.toml) - #[arg(long, global = true)] - pkg: Option, - - /// Minimize output - #[arg(short, long, global = true)] - quiet: bool, - - #[command(subcommand)] - command: Commands, -} - -#[derive(Subcommand)] -enum Commands { - /// Check circuit syntax and dependencies - #[command(about = "Run nargo check to validate circuit syntax and dependencies")] - Check, - - /// Build circuit (compile + execute to generate bytecode and witness) - #[command(about = "Run nargo execute to generate bytecode and witness files")] - Build, - - /// Clean build artifacts - #[command(about = "Remove target directory and all build artifacts")] - Clean { - /// Backend to clean (defaults to all) - #[arg(long, value_enum)] - backend: Option, - }, - - /// Clean and rebuild (equivalent to clean + build) - #[command(about = "Remove target directory and rebuild from scratch")] - Rebuild { - /// Backend to clean (defaults to all) - #[arg(long, value_enum)] - backend: Option, - }, - - /// Cairo/Starknet operations - #[command(about = "Generate Cairo verifiers and interact with Starknet")] - Cairo { - #[command(subcommand)] - command: CairoCommands, - }, - - /// EVM/Foundry operations - #[command(about = "Generate Solidity verifiers and interact with EVM networks")] - Evm { - #[command(subcommand)] - command: EvmCommands, - }, - - /// Check system dependencies - #[command(about = "Verify that all required tools are installed and available")] - Doctor, -} - -#[derive(Subcommand)] -enum CairoCommands { - /// Generate Cairo verifier contract - #[command(about = "Generate Cairo verifier contract for Starknet deployment")] - Gen, - - /// Generate Starknet oracle proof - #[command(about = "Generate proof using bb with Starknet oracle hash")] - Prove, - - /// Verify Starknet oracle proof - #[command(about = "Verify proof generated with Starknet oracle hash")] - Verify, - - /// Generate calldata for proof verification - #[command(about = "Generate calldata JSON for latest proof")] - Calldata, - - /// Declare verifier contract on Starknet - #[command(about = "Declare verifier contract on Starknet")] - Declare { - /// Network to declare on (sepolia or mainnet) - #[arg(long, default_value = "sepolia")] - network: String, - }, - - /// Deploy declared verifier contract - #[command(about = "Deploy declared verifier contract")] - Deploy { - /// Class hash of the declared contract - #[arg(long)] - class_hash: Option, - }, - - /// Verify proof on-chain - #[command(about = "Verify proof on Starknet using deployed verifier")] - VerifyOnchain { - /// Address of deployed verifier contract - #[arg(short = 'a', long)] - address: Option, - }, -} - -#[derive(Subcommand)] -enum EvmCommands { - /// Generate Solidity verifier contract and Foundry project - #[command(about = "Generate Solidity verifier contract with complete Foundry project setup")] - Gen, - - /// Generate Keccak oracle proof - #[command(about = "Generate proof using bb with Keccak oracle hash")] - Prove, - - /// Verify Keccak oracle proof - #[command(about = "Verify proof generated with Keccak oracle hash")] - Verify, - - /// Deploy verifier contract to EVM network - #[command(about = "Deploy verifier contract using Foundry")] - Deploy { - /// Network to deploy to (mainnet or sepolia) - #[arg(long, default_value = "sepolia")] - network: String, - }, - - /// Generate calldata for proof verification - #[command(about = "Generate calldata for proof verification using cast")] - Calldata, - - /// Verify proof on-chain - #[command(about = "Verify proof on EVM network using deployed verifier")] - VerifyOnchain, -} - -#[derive(ValueEnum, Clone, Copy, Debug, PartialEq, Eq)] -enum Backend { - /// Barretenberg backend (EVM/Solidity) - Bb, - /// Starknet backend (Cairo) - Starknet, - /// All backends - All, -} +pub use cli::Cli; +pub use config::Config; pub fn run() -> Result<()> { - // Install color-eyre for pretty error reporting color_eyre::install()?; - - // Load .env file if present (for EVM environment variables) - dotenv::dotenv().ok(); // .ok() means don't fail if .env doesn't exist + dotenv::dotenv().ok(); let cli = Cli::parse(); - - // Initialize logging based on verbosity setup_logging(cli.verbose, cli.quiet)?; if cli.verbose { @@ -188,135 +26,140 @@ pub fn run() -> Result<()> { } } - // Route to appropriate command handler - match cli.command { + let cfg = Config::from(&cli); + dispatch(&cli, &cfg)?; + + if cli.verbose { + info!("โœจ bargo completed successfully"); + } + + Ok(()) +} + +fn dispatch(cli: &Cli, cfg: &Config) -> Result<()> { + use cli::{Backend, CairoCommands, Commands, EvmCommands}; + use util::print_banner; + + match &cli.command { Commands::Check => { - if !cli.quiet { + if !cfg.quiet { print_banner("check"); } - handle_check(&cli)?; + commands::check::run(cfg) } Commands::Build => { - if !cli.quiet { + if !cfg.quiet { print_banner("build"); } - handle_build(&cli)?; + commands::build::run(cfg) } - - Commands::Clean { ref backend } => { - if !cli.quiet { + Commands::Clean { backend } => { + if !cfg.quiet { print_banner("clean"); } - handle_clean(&cli, (*backend).unwrap_or(Backend::All))?; + commands::clean::run(cfg, backend.unwrap_or(Backend::All)) } - Commands::Rebuild { ref backend } => { - if !cli.quiet { + Commands::Rebuild { backend } => { + if !cfg.quiet { print_banner("rebuild"); } - handle_rebuild(&cli, (*backend).unwrap_or(Backend::All))?; + commands::rebuild::run(cfg, backend.unwrap_or(Backend::All)) } - Commands::Cairo { ref command } => match command { + Commands::Cairo { command } => match command { CairoCommands::Gen => { - if !cli.quiet { + if !cfg.quiet { print_banner("cairo gen"); } - handle_cairo_gen(&cli)?; + commands::cairo::run_gen(cfg) } CairoCommands::Prove => { - if !cli.quiet { + if !cfg.quiet { print_banner("cairo prove"); } - handle_cairo_prove(&cli)?; + commands::cairo::run_prove(cfg) } CairoCommands::Verify => { - if !cli.quiet { + if !cfg.quiet { print_banner("cairo verify"); } - handle_cairo_verify(&cli)?; + commands::cairo::run_verify(cfg) } CairoCommands::Calldata => { - if !cli.quiet { + if !cfg.quiet { print_banner("cairo calldata"); } - handle_cairo_calldata(&cli)?; + commands::cairo::run_calldata(cfg) } CairoCommands::Declare { network } => { - if !cli.quiet { + if !cfg.quiet { print_banner("cairo declare"); } - handle_cairo_declare(&cli, network)?; + commands::cairo::run_declare(cfg, network) } CairoCommands::Deploy { class_hash } => { - if !cli.quiet { + if !cfg.quiet { print_banner("cairo deploy"); } - handle_cairo_deploy(&cli, class_hash.as_deref())?; + commands::cairo::run_deploy(cfg, class_hash.as_deref()) } CairoCommands::VerifyOnchain { address } => { - if !cli.quiet { + if !cfg.quiet { print_banner("cairo verify-onchain"); } - handle_cairo_verify_onchain(&cli, address.as_deref())?; + commands::cairo::run_verify_onchain(cfg, address.as_deref()) } }, - Commands::Evm { ref command } => match command { + Commands::Evm { command } => match command { EvmCommands::Gen => { - if !cli.quiet { + if !cfg.quiet { print_banner("evm gen"); } - handle_evm_gen(&cli)?; + commands::evm::run_gen(cfg) } EvmCommands::Prove => { - if !cli.quiet { + if !cfg.quiet { print_banner("evm prove"); } - handle_evm_prove(&cli)?; + commands::evm::run_prove(cfg) } EvmCommands::Verify => { - if !cli.quiet { + if !cfg.quiet { print_banner("evm verify"); } - handle_evm_verify(&cli)?; + commands::evm::run_verify(cfg) } EvmCommands::Deploy { network } => { - if !cli.quiet { + if !cfg.quiet { print_banner("evm deploy"); } - handle_evm_deploy(&cli, network)?; + commands::evm::run_deploy(cfg, network) } EvmCommands::Calldata => { - if !cli.quiet { + if !cfg.quiet { print_banner("evm calldata"); } - handle_evm_calldata(&cli)?; + commands::evm::run_calldata(cfg) } EvmCommands::VerifyOnchain => { - if !cli.quiet { + if !cfg.quiet { print_banner("evm verify-onchain"); } - handle_evm_verify_onchain(&cli)?; + commands::evm::run_verify_onchain(cfg) } }, Commands::Doctor => { - if !cli.quiet { + if !cfg.quiet { print_banner("doctor"); } - handle_doctor(&cli)?; + commands::doctor::run(cfg) } } - - if cli.verbose { - info!("โœจ bargo completed successfully"); - } - - Ok(()) } fn setup_logging(verbose: bool, quiet: bool) -> Result<()> { - use tracing_subscriber::{EnvFilter, fmt}; + use tracing_subscriber::{fmt, EnvFilter}; if quiet { - // Only show errors let subscriber = fmt() .with_max_level(tracing::Level::ERROR) .with_target(false) @@ -324,18 +167,14 @@ fn setup_logging(verbose: bool, quiet: bool) -> Result<()> { .finish(); tracing::subscriber::set_global_default(subscriber)?; } else if verbose { - // Show info and above, plus set RUST_LOG environment - unsafe { - std::env::set_var("RUST_LOG", "info"); - } + let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")); let subscriber = fmt() - .with_env_filter(EnvFilter::from_default_env()) + .with_env_filter(filter) .with_target(false) .with_level(true) .finish(); tracing::subscriber::set_global_default(subscriber)?; } else { - // Default: only show warnings and errors let subscriber = fmt() .with_max_level(tracing::Level::WARN) .with_target(false) @@ -346,345 +185,3 @@ fn setup_logging(verbose: bool, quiet: bool) -> Result<()> { Ok(()) } - -fn handle_check(cli: &Cli) -> Result<()> { - let args = build_nargo_args(cli, &[])?; - - if cli.verbose { - info!("Running: nargo check {}", args.join(" ")); - } - - if cli.dry_run { - println!("Would run: nargo check {}", args.join(" ")); - return Ok(()); - } - - backends::nargo::run(&["check"]) -} - -fn handle_build(cli: &Cli) -> Result<()> { - commands::build::run(cli).map_err(enhance_error_with_suggestions) -} - -fn handle_cairo_prove(cli: &Cli) -> Result<()> { - commands::cairo::run_prove(cli).map_err(enhance_error_with_suggestions) -} - -fn handle_cairo_verify(cli: &Cli) -> Result<()> { - commands::cairo::run_verify(cli).map_err(enhance_error_with_suggestions) -} - -fn handle_evm_prove(cli: &Cli) -> Result<()> { - commands::evm::run_prove(cli).map_err(enhance_error_with_suggestions) -} - -fn handle_evm_verify(cli: &Cli) -> Result<()> { - commands::evm::run_verify(cli).map_err(enhance_error_with_suggestions) -} - -fn handle_clean(cli: &Cli, backend: Backend) -> Result<()> { - if cli.verbose { - info!("Cleaning artifacts for backend: {:?}", backend); - } - - match backend { - Backend::All => { - if cli.dry_run { - println!("Would run: rm -rf target/"); - return Ok(()); - } - - if std::path::Path::new("target").exists() { - std::fs::remove_dir_all("target")?; - if !cli.quiet { - println!("{}", success("Removed target/")); - } - } else if !cli.quiet { - println!("{}", info("target/ already clean")); - } - } - Backend::Bb => { - if cli.dry_run { - println!("Would run: rm -rf target/bb/"); - return Ok(()); - } - - if std::path::Path::new("target/bb").exists() { - std::fs::remove_dir_all("target/bb")?; - if !cli.quiet { - println!("{}", success("Removed target/bb/")); - } - } else if !cli.quiet { - println!("{}", info("target/bb/ already clean")); - } - } - Backend::Starknet => { - if cli.dry_run { - println!("Would run: rm -rf target/starknet/"); - return Ok(()); - } - - if std::path::Path::new("target/starknet").exists() { - std::fs::remove_dir_all("target/starknet")?; - if !cli.quiet { - println!("{}", success("Removed target/starknet/")); - } - } else if !cli.quiet { - println!("{}", info("target/starknet/ already clean")); - } - } - } - - Ok(()) -} - -fn build_nargo_args(cli: &Cli, base_args: &[&str]) -> Result> { - let mut args = base_args.iter().map(|s| s.to_string()).collect::>(); - - // Add package-specific args if needed - if let Some(pkg) = &cli.pkg { - args.push("--package".to_string()); - args.push(pkg.clone()); - } - - Ok(args) -} - -fn handle_rebuild(cli: &Cli, backend: Backend) -> Result<()> { - let mut summary = OperationSummary::new(); - - // Step 1: Clean - if cli.verbose { - info!("Step 1/2: Cleaning artifacts for backend: {:?}", backend); - } - - if !cli.quiet { - println!("๐Ÿงน Cleaning build artifacts..."); - } - - handle_clean(cli, backend)?; - if backend != Backend::Starknet { - summary.add_operation("Build artifacts cleaned"); - } - - // Step 2: Build - if cli.verbose { - info!("Step 2/2: Building from scratch"); - } - - if !cli.quiet { - println!("\n๐Ÿ”จ Building circuit..."); - } - - let pkg_name = get_package_name(cli)?; - let args = build_nargo_args(cli, &[])?; - - if cli.verbose { - info!("Running: nargo execute {}", args.join(" ")); - } - - if cli.dry_run { - println!("Would run: nargo execute {}", args.join(" ")); - return Ok(()); - } - - let timer = Timer::start(); - let result = backends::nargo::run(&["execute"]); - - match result { - Ok(()) => { - // Organize build artifacts into flavour-specific directories - util::organize_build_artifacts(&pkg_name, Flavour::Bb)?; - - if !cli.quiet { - let bytecode_path = util::get_bytecode_path(&pkg_name, Flavour::Bb); - let witness_path = util::get_witness_path(&pkg_name, Flavour::Bb); - - println!( - "{}", - success(&format_operation_result( - "Bytecode generated", - &bytecode_path, - &timer - )) - ); - - // Create a new timer for witness (they're generated together but we show separate timing) - let witness_timer = Timer::start(); - println!( - "{}", - success(&format_operation_result( - "Witness generated", - &witness_path, - &witness_timer - )) - ); - - summary.add_operation(&format!("Circuit rebuilt for {}", path(&pkg_name))); - summary.add_operation(&format!( - "Bytecode generated ({})", - util::format_file_size(&bytecode_path) - )); - summary.add_operation(&format!( - "Witness generated ({})", - util::format_file_size(&witness_path) - )); - summary.print(); - } - Ok(()) - } - Err(e) => Err(enhance_error_with_suggestions(e)), - } -} - -fn handle_cairo_gen(cli: &Cli) -> Result<()> { - commands::cairo::run_gen(cli).map_err(enhance_error_with_suggestions) -} - -fn handle_cairo_calldata(cli: &Cli) -> Result<()> { - commands::cairo::run_calldata(cli).map_err(enhance_error_with_suggestions) -} - -fn handle_cairo_declare(cli: &Cli, network: &str) -> Result<()> { - commands::cairo::run_declare(cli, network).map_err(enhance_error_with_suggestions) -} - -fn handle_cairo_deploy(cli: &Cli, class_hash: Option<&str>) -> Result<()> { - commands::cairo::run_deploy(cli, class_hash).map_err(enhance_error_with_suggestions) -} - -fn handle_cairo_verify_onchain(cli: &Cli, address: Option<&str>) -> Result<()> { - commands::cairo::run_verify_onchain(cli, address).map_err(enhance_error_with_suggestions) -} - -fn handle_doctor(cli: &Cli) -> Result<()> { - if !cli.quiet { - println!("๐Ÿ” Checking system dependencies...\n"); - } - - let mut all_good = true; - - // Check nargo - match which::which("nargo") { - Ok(path) => { - if !cli.quiet { - println!("โœ… nargo: {}", path.display()); - } - } - Err(_) => { - if !cli.quiet { - println!("โŒ nargo: not found"); - println!( - " Install from: https://noir-lang.org/docs/getting_started/installation/" - ); - } - all_good = false; - } - } - - // Check bb - match which::which("bb") { - Ok(path) => { - if !cli.quiet { - println!("โœ… bb: {}", path.display()); - } - } - Err(_) => { - if !cli.quiet { - println!("โŒ bb: not found"); - println!(" Install from: https://github.com/AztecProtocol/aztec-packages"); - } - all_good = false; - } - } - - // Check garaga (optional for Cairo features) - match which::which("garaga") { - Ok(path) => { - if !cli.quiet { - println!("โœ… garaga: {}", path.display()); - } - } - Err(_) => { - if !cli.quiet { - println!("โš ๏ธ garaga: not found (optional - needed for Cairo features)"); - println!(" Install with: pipx install garaga"); - println!(" Requires Python 3.10+"); - } - } - } - - // Check forge (optional for EVM features) - match which::which("forge") { - Ok(path) => { - if !cli.quiet { - println!("โœ… forge: {}", path.display()); - } - } - Err(_) => { - if !cli.quiet { - println!("โš ๏ธ forge: not found (optional - needed for EVM features)"); - println!(" Install with: curl -L https://foundry.paradigm.xyz | bash"); - println!(" Then run: foundryup"); - } - } - } - - // Check cast (optional for EVM features) - match which::which("cast") { - Ok(path) => { - if !cli.quiet { - println!("โœ… cast: {}", path.display()); - } - } - Err(_) => { - if !cli.quiet { - println!("โš ๏ธ cast: not found (optional - needed for EVM features)"); - println!(" Install with: curl -L https://foundry.paradigm.xyz | bash"); - println!(" Then run: foundryup"); - } - } - } - - if !cli.quiet { - println!(); - if all_good { - println!("๐ŸŽ‰ All required dependencies are available!"); - println!(" You can use all bargo features."); - } else { - println!("๐Ÿšจ Some required dependencies are missing."); - println!(" Core features require: nargo + bb"); - println!(" EVM deployment features also require: forge + cast"); - println!(" Cairo features also require: garaga"); - } - } - - if !all_good { - std::process::exit(1); - } - - Ok(()) -} - -fn get_package_name(cli: &Cli) -> Result { - util::get_package_name(cli.pkg.as_ref()).map_err(enhance_error_with_suggestions) -} - -/// Handle `evm gen` command - Generate Solidity verifier contract and Foundry project -fn handle_evm_gen(cli: &Cli) -> Result<()> { - commands::evm::run_gen(cli).map_err(enhance_error_with_suggestions) -} -/// Handle `evm deploy` command - Deploy verifier contract to EVM network -fn handle_evm_deploy(cli: &Cli, network: &str) -> Result<()> { - commands::evm::run_deploy(cli, network).map_err(enhance_error_with_suggestions) -} - -/// Handle `evm calldata` command - Generate calldata for proof verification -fn handle_evm_calldata(cli: &Cli) -> Result<()> { - commands::evm::run_calldata(cli).map_err(enhance_error_with_suggestions) -} - -/// Handle `evm verify-onchain` command - Verify proof on EVM network -fn handle_evm_verify_onchain(cli: &Cli) -> Result<()> { - commands::evm::run_verify_onchain(cli).map_err(enhance_error_with_suggestions) -} diff --git a/tests/cli_smoke.rs b/tests/cli_smoke.rs new file mode 100644 index 0000000..ea67810 --- /dev/null +++ b/tests/cli_smoke.rs @@ -0,0 +1,21 @@ +use assert_cmd::Command; + +#[test] +fn cli_still_builds_and_parses() { + Command::cargo_bin("bargo") + .unwrap() + .args(["--dry-run", "build"]) + .assert() + .success(); +} + +#[test] +fn pkg_flag_is_propagated() { + use predicates::str::contains; + + Command::cargo_bin("bargo") + .unwrap() + .args(["--dry-run", "--pkg", "my_pkg", "build"]) + .assert() + .stdout(contains("--package my_pkg")); +}