diff --git a/leo/cli/cli.rs b/leo/cli/cli.rs index f8ff533662..6c408cf193 100644 --- a/leo/cli/cli.rs +++ b/leo/cli/cli.rs @@ -16,7 +16,8 @@ use crate::cli::{commands::*, context::*, helpers::*}; use clap::Parser; -use leo_errors::Result; +use leo_errors::{CliError, Result}; +use serde::Serialize; use std::{path::PathBuf, process::exit}; /// CLI Arguments entry point - includes global parameters and subcommands @@ -29,6 +30,9 @@ pub struct CLI { #[clap(short, global = true, help = "Suppress CLI output")] quiet: bool, + #[clap(long, global = true, help = "Write results as JSON to a file. Defaults to build/json-outputs/.json if no path specified.", num_args = 0..=1, require_equals = true, default_missing_value = "")] + json_output: Option, + #[clap(long, global = true, help = "Disable Leo's daily check for version updates")] disable_update_check: bool, @@ -127,6 +131,29 @@ enum Commands { }, } +impl Commands { + fn name(&self) -> &'static str { + match self { + Commands::Account { .. } => "account", + Commands::New { .. } => "new", + Commands::Run { .. } => "run", + Commands::Test { .. } => "test", + Commands::Execute { .. } => "execute", + Commands::Deploy { .. } => "deploy", + Commands::Devnet { .. } => "devnet", + Commands::Query { .. } => "query", + Commands::Build { .. } => "build", + Commands::Debug { .. } => "debug", + Commands::Add { .. } => "add", + Commands::Remove { .. } => "remove", + Commands::Clean { .. } => "clean", + Commands::Synthesize { .. } => "synthesize", + Commands::Update { .. } => "update", + Commands::Upgrade { .. } => "upgrade", + } + } +} + pub fn handle_error(res: Result) -> T { match res { Ok(t) => t, @@ -137,10 +164,26 @@ pub fn handle_error(res: Result) -> T { } } +/// JSON output types for commands that support `--json`. +#[derive(Serialize)] +#[serde(untagged)] +#[allow(clippy::large_enum_variant)] +enum JsonOutput { + Deploy(DeployOutput), + Run(RunOutput), + Execute(ExecuteOutput), + Test(TestOutput), + Query(serde_json::Value), + Synthesize(SynthesizeOutput), +} + /// Run command with custom build arguments. pub fn run_with_args(cli: CLI) -> Result<()> { + // JSON output mode implies quiet mode. + let quiet = cli.quiet || cli.json_output.is_some(); + // Print the variables found in the `.env` files. - if let Ok(vars) = dotenvy::dotenv_iter().map(|v| v.flatten().collect::>()) { + if !quiet && let Ok(vars) = dotenvy::dotenv_iter().map(|v| v.flatten().collect::>()) { if !vars.is_empty() { println!("πŸ“’ Loading environment variables from a `.env` file in the directory tree."); } @@ -151,7 +194,7 @@ pub fn run_with_args(cli: CLI) -> Result<()> { // Initialize the `.env` file. dotenvy::dotenv().ok(); - if !cli.quiet { + if !quiet { // Init logger with optional debug flag. logger::init_logger("leo", match cli.debug { false => 1, @@ -159,8 +202,9 @@ pub fn run_with_args(cli: CLI) -> Result<()> { })?; } - // Check for updates. If not forced, it checks once per day. - if !cli.disable_update_check + // Check for updates. If not forced, it checks once per day. + if !quiet + && !cli.disable_update_check && let Ok(true) = updater::Updater::check_for_updates(false) { let _ = updater::Updater::print_cli(); @@ -168,26 +212,59 @@ pub fn run_with_args(cli: CLI) -> Result<()> { // Get custom root folder and create context for it. // If not specified, default context will be created in cwd. - let context = handle_error(Context::new(cli.path, cli.home, false)); + let context = handle_error(Context::new(cli.path.clone(), cli.home, false)); + + let command_name = cli.command.name(); + let mut command_output: Option = None; match cli.command { - Commands::Add { command } => command.try_execute(context), - Commands::Account { command } => command.try_execute(context), - Commands::New { command } => command.try_execute(context), - Commands::Build { command } => command.try_execute(context), - Commands::Debug { command } => command.try_execute(context), - Commands::Query { command } => command.try_execute(context), - Commands::Clean { command } => command.try_execute(context), - Commands::Deploy { command } => command.try_execute(context), - Commands::Devnet { command } => command.try_execute(context), - Commands::Run { command } => command.try_execute(context), - Commands::Test { command } => command.try_execute(context), - Commands::Execute { command } => command.try_execute(context), - Commands::Remove { command } => command.try_execute(context), - Commands::Synthesize { command } => command.try_execute(context), - Commands::Update { command } => command.try_execute(context), - Commands::Upgrade { command } => command.try_execute(context), + Commands::Add { command } => command.try_execute(context)?, + Commands::Account { command } => command.try_execute(context)?, + Commands::New { command } => command.try_execute(context)?, + Commands::Build { command } => command.try_execute(context)?, + Commands::Debug { command } => command.try_execute(context)?, + Commands::Query { command } => { + let result = command.execute(context)?; + let data = serde_json::from_str(&result).unwrap_or_else(|_| serde_json::Value::String(result)); + command_output = Some(JsonOutput::Query(data)); + } + Commands::Clean { command } => command.try_execute(context)?, + Commands::Deploy { command } => command_output = Some(JsonOutput::Deploy(command.execute(context)?)), + Commands::Devnet { command } => command.try_execute(context)?, + Commands::Run { command } => command_output = Some(JsonOutput::Run(command.execute(context)?)), + Commands::Test { command } => command_output = Some(JsonOutput::Test(command.execute(context)?)), + Commands::Execute { command } => command_output = Some(JsonOutput::Execute(command.execute(context)?)), + Commands::Remove { command } => command.try_execute(context)?, + Commands::Synthesize { command } => command_output = Some(JsonOutput::Synthesize(command.execute(context)?)), + Commands::Update { command } => command.try_execute(context)?, + Commands::Upgrade { command } => command_output = Some(JsonOutput::Deploy(command.execute(context)?)), + } + + if let Some(json_output_arg) = cli.json_output + && let Some(output) = command_output + { + let json = serde_json::to_string_pretty(&output).expect("JSON serialization failed"); + + // Use provided path or default to build/json-outputs/.json + let path = if json_output_arg.is_empty() { + cli.path + .unwrap_or_else(|| PathBuf::from(".")) + .join("build") + .join("json-outputs") + .join(format!("{command_name}.json")) + } else { + PathBuf::from(json_output_arg) + }; + + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .map_err(|e| CliError::custom(format!("Failed to create directory: {e}")))?; + } + std::fs::write(&path, json) + .map_err(|e| CliError::custom(format!("Failed to write JSON output to {}: {e}", path.display())))?; } + + Ok(()) } #[cfg(test)] @@ -223,6 +300,7 @@ mod tests { let run = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Run { command: crate::cli::commands::LeoRun { @@ -266,6 +344,7 @@ mod tests { let run = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Run { command: crate::cli::commands::LeoRun { @@ -310,6 +389,7 @@ mod tests { let run = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Run { command: crate::cli::commands::LeoRun { @@ -348,6 +428,7 @@ mod tests { let run = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Run { command: crate::cli::commands::LeoRun { @@ -389,6 +470,7 @@ mod test_helpers { let new = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::New { command: LeoNew { @@ -463,6 +545,7 @@ function external_nested_function: let add = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Add { command: LeoAdd { @@ -510,6 +593,7 @@ function external_nested_function: let create_grandparent_project = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::New { command: LeoNew { @@ -525,6 +609,7 @@ function external_nested_function: let create_parent_project = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::New { command: LeoNew { @@ -540,6 +625,7 @@ function external_nested_function: let create_child_project = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::New { command: LeoNew { @@ -597,6 +683,7 @@ program child.aleo { let add_grandparent_dependency_1 = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Add { command: LeoAdd { @@ -613,6 +700,7 @@ program child.aleo { let add_grandparent_dependency_2 = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Add { command: LeoAdd { @@ -629,6 +717,7 @@ program child.aleo { let add_parent_dependency = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Add { command: LeoAdd { @@ -674,6 +763,7 @@ program child.aleo { let create_outer_project = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::New { command: LeoNew { @@ -689,6 +779,7 @@ program child.aleo { let create_inner_1_project = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::New { command: LeoNew { @@ -704,6 +795,7 @@ program child.aleo { let create_inner_2_project = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::New { command: LeoNew { @@ -784,6 +876,7 @@ program outer.aleo { let add_outer_dependency_1 = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Add { command: LeoAdd { @@ -800,6 +893,7 @@ program outer.aleo { let add_outer_dependency_2 = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Add { command: LeoAdd { @@ -844,6 +938,7 @@ program outer.aleo { let create_outer_project = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::New { command: LeoNew { @@ -859,6 +954,7 @@ program outer.aleo { let create_inner_1_project = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::New { command: LeoNew { @@ -874,6 +970,7 @@ program outer.aleo { let create_inner_2_project = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::New { command: LeoNew { @@ -985,6 +1082,7 @@ program outer_2.aleo { let add_outer_dependency_1 = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Add { command: LeoAdd { @@ -1001,6 +1099,7 @@ program outer_2.aleo { let add_outer_dependency_2 = CLI { debug: false, quiet: false, + json_output: None, disable_update_check: false, command: Commands::Add { command: LeoAdd { diff --git a/leo/cli/commands/common/mod.rs b/leo/cli/commands/common/mod.rs index 57fbab24ec..cb3f216932 100644 --- a/leo/cli/commands/common/mod.rs +++ b/leo/cli/commands/common/mod.rs @@ -20,6 +20,9 @@ pub use interactive::*; mod options; pub use options::*; +mod output; +pub use output::*; + mod query; pub use query::*; diff --git a/leo/cli/commands/common/output.rs b/leo/cli/commands/common/output.rs new file mode 100644 index 0000000000..0b84c902b1 --- /dev/null +++ b/leo/cli/commands/common/output.rs @@ -0,0 +1,285 @@ +// Copyright (C) 2019-2025 Provable Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +//! JSON-serializable output types and helpers for CLI commands. + +use serde::Serialize; +use snarkvm::prelude::{Network, ProgramID, block::Transaction}; +use std::fmt; + +/// Cost breakdown in microcredits, convertible to credits for display. +#[derive(Serialize, Clone, Default)] +pub struct CostBreakdown { + pub storage: u64, + pub namespace: Option, + pub synthesis: Option, + pub constructor: Option, + pub execution: Option, + pub priority: u64, + pub total: u64, +} + +impl CostBreakdown { + /// Create a cost breakdown for deployment. + pub fn for_deployment(storage: u64, synthesis: u64, namespace: u64, constructor: u64, priority: u64) -> Self { + Self { + storage, + namespace: Some(namespace), + synthesis: Some(synthesis), + constructor: Some(constructor), + execution: None, + priority, + total: storage + synthesis + namespace + constructor + priority, + } + } + + /// Create a cost breakdown for execution. + pub fn for_execution(storage: u64, execution: u64, priority: u64) -> Self { + Self { + storage, + namespace: None, + synthesis: None, + constructor: None, + execution: Some(execution), + priority, + total: storage + execution + priority, + } + } + + fn microcredits_to_credits(microcredits: u64) -> f64 { + microcredits as f64 / 1_000_000.0 + } +} + +impl fmt::Display for CostBreakdown { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use colored::*; + writeln!(f, "{}", "πŸ’° Cost Breakdown (credits)".bold())?; + writeln!(f, " {:22}{:.6}", "Transaction Storage:".cyan(), Self::microcredits_to_credits(self.storage))?; + if let Some(synthesis) = self.synthesis { + writeln!(f, " {:22}{:.6}", "Program Synthesis:".cyan(), Self::microcredits_to_credits(synthesis))?; + } + if let Some(namespace) = self.namespace { + writeln!(f, " {:22}{:.6}", "Namespace:".cyan(), Self::microcredits_to_credits(namespace))?; + } + if let Some(constructor) = self.constructor { + writeln!(f, " {:22}{:.6}", "Constructor:".cyan(), Self::microcredits_to_credits(constructor))?; + } + if let Some(execution) = self.execution { + writeln!(f, " {:22}{:.6}", "On-chain Execution:".cyan(), Self::microcredits_to_credits(execution))?; + } + writeln!(f, " {:22}{:.6}", "Priority Fee:".cyan(), Self::microcredits_to_credits(self.priority))?; + writeln!(f, " {:22}{:.6}", "Total Fee:".cyan(), Self::microcredits_to_credits(self.total)) + } +} + +/// Statistics for a deployed program. +#[derive(Serialize, Clone, Default)] +pub struct DeploymentStats { + pub program_size_bytes: usize, + pub max_program_size_bytes: usize, + pub total_variables: u64, + pub total_constraints: u64, + pub max_variables: u64, + pub max_constraints: u64, + pub cost: CostBreakdown, +} + +impl fmt::Display for DeploymentStats { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use colored::*; + use num_format::{Locale, ToFormattedString}; + + writeln!( + f, + " {:22}{:.2} KB / {:.2} KB", + "Program Size:".cyan(), + self.program_size_bytes as f64 / 1024.0, + self.max_program_size_bytes as f64 / 1024.0 + )?; + writeln!( + f, + " {:22}{}", + "Total Variables:".cyan(), + self.total_variables.to_formatted_string(&Locale::en).yellow() + )?; + writeln!( + f, + " {:22}{}", + "Total Constraints:".cyan(), + self.total_constraints.to_formatted_string(&Locale::en).yellow() + )?; + writeln!(f, " {:22}{}", "Max Variables:".cyan(), self.max_variables.to_formatted_string(&Locale::en).green())?; + writeln!( + f, + " {:22}{}", + "Max Constraints:".cyan(), + self.max_constraints.to_formatted_string(&Locale::en).green() + )?; + writeln!(f)?; + write!(f, "{}", self.cost) + } +} + +/// Statistics for an execution. +#[derive(Serialize, Clone, Default)] +pub struct ExecutionStats { + pub cost: CostBreakdown, +} + +impl fmt::Display for ExecutionStats { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.cost) + } +} + +/// Broadcast result when a transaction is sent to the network. +#[derive(Serialize, Clone, Default)] +pub struct BroadcastStats { + pub fee_id: String, + pub fee_transaction_id: String, + pub confirmed: bool, +} + +/// Configuration used for the command. +#[derive(Serialize, Clone, Default)] +pub struct Config { + pub address: String, + pub network: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub endpoint: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub consensus_version: Option, +} + +/// Output for `leo deploy` and `leo upgrade`. +#[derive(Serialize, Default)] +pub struct DeployOutput { + #[serde(skip_serializing_if = "Option::is_none")] + pub config: Option, + pub deployments: Vec, +} + +/// A single deployed program. +#[derive(Serialize)] +pub struct DeployedProgram { + pub program_id: String, + pub transaction_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub stats: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub broadcast: Option, +} + +/// Output for `leo run`. +#[derive(Serialize)] +pub struct RunOutput { + pub program: String, + pub function: String, + pub outputs: Vec, +} + +/// Output for `leo execute`. +#[derive(Serialize, Default)] +pub struct ExecuteOutput { + #[serde(skip_serializing_if = "Option::is_none")] + pub config: Option, + pub program: String, + pub function: String, + pub outputs: Vec, + pub transaction_id: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub stats: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub broadcast: Option, +} + +/// Output for `leo test`. +#[derive(Serialize, Default)] +pub struct TestOutput { + pub passed: usize, + pub failed: usize, + pub tests: Vec, +} + +/// A single test result. +#[derive(Serialize)] +pub struct TestResult { + pub name: String, + pub passed: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, +} + +/// Output for `leo synthesize`. +#[derive(Serialize, Default)] +pub struct SynthesizeOutput { + pub program: String, + pub functions: Vec, +} + +/// Prover/verifier key metadata. +#[derive(Serialize, Clone, Default)] +pub struct Metadata { + pub prover_checksum: String, + pub prover_size: usize, + pub verifier_checksum: String, + pub verifier_size: usize, +} + +/// Circuit information from key synthesis. +#[derive(Serialize, Clone, Default)] +pub struct CircuitInfo { + pub num_public_inputs: u64, + pub num_variables: u64, + pub num_constraints: u64, + pub num_non_zero_a: u64, + pub num_non_zero_b: u64, + pub num_non_zero_c: u64, + pub circuit_id: String, +} + +/// A single synthesized function's key metadata. +#[derive(Serialize)] +pub struct SynthesizedFunction { + pub name: String, + pub circuit_info: CircuitInfo, + #[serde(flatten)] + pub metadata: Metadata, +} + +/// Build a `DeployOutput` from transactions, stats, and optional broadcast results. +pub fn build_deploy_output( + config: Option, + transactions: &[(ProgramID, Transaction)], + stats: &[DeploymentStats], + broadcasts: &[BroadcastStats], +) -> DeployOutput { + DeployOutput { + config, + deployments: transactions + .iter() + .zip(stats.iter().map(Some).chain(std::iter::repeat(None))) + .zip(broadcasts.iter().map(Some).chain(std::iter::repeat(None))) + .map(|(((program_id, transaction), stats), broadcast)| DeployedProgram { + program_id: program_id.to_string(), + transaction_id: transaction.id().to_string(), + stats: stats.cloned(), + broadcast: broadcast.cloned(), + }) + .collect(), + } +} diff --git a/leo/cli/commands/deploy.rs b/leo/cli/commands/deploy.rs index c0e2c67d02..a883f160af 100644 --- a/leo/cli/commands/deploy.rs +++ b/leo/cli/commands/deploy.rs @@ -70,7 +70,7 @@ pub struct Task { impl Command for LeoDeploy { type Input = Package; - type Output = (); + type Output = DeployOutput; fn log_span(&self) -> Span { tracing::span!(tracing::Level::INFO, "Leo") @@ -221,6 +221,14 @@ fn handle_deploy( let consensus_version = get_consensus_version(&command.extra.consensus_version, &endpoint, network, &consensus_heights, &context)?; + // Build the config for JSON output. + let config = Some(Config { + address: address.to_string(), + network: network.to_string(), + endpoint: Some(endpoint.clone()), + consensus_version: Some(consensus_version as u8), + }); + // Print a summary of the deployment plan. print_deployment_plan( &private_key, @@ -238,7 +246,7 @@ fn handle_deploy( // Prompt the user to confirm the plan. if !confirm("Do you want to proceed with deployment?", command.extra.yes)? { println!("❌ Deployment aborted."); - return Ok(()); + return Ok(DeployOutput::default()); } // Initialize an RNG. @@ -278,6 +286,8 @@ fn handle_deploy( // For each of the programs, generate a deployment transaction. let mut transactions = Vec::new(); + let mut all_stats = Vec::new(); + let mut all_broadcasts = Vec::new(); for Task { id, program, priority_fee, record, bytecode_size, .. } in local { // If the program is a local dependency that is not skipped, generate a deployment transaction. if !skipped.contains(&id) { @@ -295,7 +305,7 @@ Once it is deployed, it CANNOT be changed. ); if !confirm("Would you like to proceed?", command.extra.yes)? { println!("❌ Deployment aborted."); - return Ok(()); + return Ok(DeployOutput::default()); } } println!("πŸ“¦ Creating deployment transaction for '{}'...\n", id.to_string().bold()); @@ -305,10 +315,18 @@ Once it is deployed, it CANNOT be changed. .map_err(|e| CliError::custom(format!("Failed to generate deployment transaction: {e}")))?; // Get the deployment. let deployment = transaction.deployment().expect("Expected a deployment in the transaction"); - // Print the deployment stats. - print_deployment_stats(&vm, &id.to_string(), deployment, priority_fee, consensus_version, bytecode_size)?; - // Save the transaction. + // Compute and print the deployment stats. + let stats = print_deployment_stats( + &vm, + &id.to_string(), + deployment, + priority_fee, + consensus_version, + bytecode_size, + )?; + // Save the transaction and stats. transactions.push((id, transaction)); + all_stats.push(stats); } // Add the program to the VM. vm.process().write().add_program(&program)?; @@ -359,6 +377,7 @@ Once it is deployed, it CANNOT be changed. continue; } let fee_id = fee.id().to_string(); + let fee_transaction_id = Transaction::from_fee(fee.clone())?.id().to_string(); let id = transaction.id().to_string(); let height_before = check_transaction::current_height(&endpoint, network)?; // Broadcast the transaction to the network. @@ -381,7 +400,7 @@ Once it is deployed, it CANNOT be changed. match status { 200..=299 => { - let status = check_transaction::check_transaction_with_message( + let tx_status = check_transaction::check_transaction_with_message( &id, Some(&fee_id), &endpoint, @@ -390,26 +409,32 @@ Once it is deployed, it CANNOT be changed. command.extra.max_wait, command.extra.blocks_to_check, )?; - if status == Some(TransactionStatus::Accepted) { + let confirmed = tx_status == Some(TransactionStatus::Accepted); + if confirmed { println!("βœ… Deployment confirmed!"); } else if fail_and_prompt("could not find the transaction on the network")? { continue; } else { - return Ok(()); + return Ok(build_deploy_output(config.clone(), &transactions, &all_stats, &all_broadcasts)); } + all_broadcasts.push(BroadcastStats { + fee_id: fee_id.clone(), + fee_transaction_id: fee_transaction_id.clone(), + confirmed, + }); } _ => { if fail_and_prompt(&message)? { continue; } else { - return Ok(()); + return Ok(build_deploy_output(config.clone(), &transactions, &all_stats, &all_broadcasts)); } } } } } - Ok(()) + Ok(build_deploy_output(config, &transactions, &all_stats, &all_broadcasts)) } /// Check the tasks to warn the user about any potential issues. @@ -596,67 +621,53 @@ pub(crate) fn print_deployment_plan( println!("{}", "──────────────────────────────────────────────\n".dimmed()); } -/// Pretty‑print deployment statistics without a table, using the same UI -/// conventions as `print_deployment_plan`. -pub(crate) fn print_deployment_stats( +/// Compute deployment statistics. +pub(crate) fn compute_deployment_stats( vm: &VM>, - program_id: &str, deployment: &Deployment, priority_fee: Option, consensus_version: ConsensusVersion, bytecode_size: usize, -) -> Result<()> { - use colored::*; - use num_format::{Locale, ToFormattedString}; - - // ── Collect statistics ──────────────────────────────────────────────── +) -> Result { let variables = deployment.num_combined_variables()?; let constraints = deployment.num_combined_constraints()?; - let (base_fee, (storage_cost, synthesis_cost, constructor_cost, namespace_cost)) = + let (_, (storage_cost, synthesis_cost, constructor_cost, namespace_cost)) = deployment_cost(&vm.process().read(), deployment, consensus_version)?; - let base_fee_cr = base_fee as f64 / 1_000_000.0; - let prio_fee_cr = priority_fee.unwrap_or(0) as f64 / 1_000_000.0; - let total_fee_cr = base_fee_cr + prio_fee_cr; - - // ── Header ──────────────────────────────────────────────────────────── - println!("\n{} {}", "πŸ“Š Deployment Summary for".bold(), program_id.bold()); - println!("{}", "──────────────────────────────────────────────".dimmed()); + Ok(DeploymentStats { + program_size_bytes: bytecode_size, + max_program_size_bytes: N::MAX_PROGRAM_SIZE, + total_variables: variables, + total_constraints: constraints, + max_variables: N::MAX_DEPLOYMENT_VARIABLES, + max_constraints: N::MAX_DEPLOYMENT_CONSTRAINTS, + cost: CostBreakdown::for_deployment( + storage_cost, + synthesis_cost, + namespace_cost, + constructor_cost, + priority_fee.unwrap_or(0), + ), + }) +} - // ── High‑level metrics ──────────────────────────────────────────────── - let (size_kb, max_kb, warning) = format_program_size(bytecode_size, N::MAX_PROGRAM_SIZE); - println!(" {:22}{size_kb:.2} KB / {max_kb:.2} KB", "Program Size:".cyan()); - if let Some(msg) = warning { - println!(" {} Program is {msg}.", "⚠️ ".bold().yellow()); - } - println!(" {:22}{}", "Total Variables:".cyan(), variables.to_formatted_string(&Locale::en).yellow()); - println!(" {:22}{}", "Total Constraints:".cyan(), constraints.to_formatted_string(&Locale::en).yellow()); - println!( - " {:22}{}", - "Max Variables:".cyan(), - N::MAX_DEPLOYMENT_VARIABLES.to_formatted_string(&Locale::en).green() - ); - println!( - " {:22}{}", - "Max Constraints:".cyan(), - N::MAX_DEPLOYMENT_CONSTRAINTS.to_formatted_string(&Locale::en).green() - ); +/// Pretty-print deployment statistics. +pub(crate) fn print_deployment_stats( + vm: &VM>, + program_id: &str, + deployment: &Deployment, + priority_fee: Option, + consensus_version: ConsensusVersion, + bytecode_size: usize, +) -> Result { + use colored::*; - // ── Cost breakdown ──────────────────────────────────────────────────── - println!("\n{}", "πŸ’° Cost Breakdown (credits)".bold()); - println!( - " {:22}{}{:.6}", - "Transaction Storage:".cyan(), - "".yellow(), // spacer for alignment - storage_cost as f64 / 1_000_000.0 - ); - println!(" {:22}{}{:.6}", "Program Synthesis:".cyan(), "".yellow(), synthesis_cost as f64 / 1_000_000.0); - println!(" {:22}{}{:.6}", "Namespace:".cyan(), "".yellow(), namespace_cost as f64 / 1_000_000.0); - println!(" {:22}{}{:.6}", "Constructor:".cyan(), "".yellow(), constructor_cost as f64 / 1_000_000.0); - println!(" {:22}{}{:.6}", "Priority Fee:".cyan(), "".yellow(), prio_fee_cr); - println!(" {:22}{}{:.6}", "Total Fee:".cyan(), "".yellow(), total_fee_cr); + let stats = compute_deployment_stats(vm, deployment, priority_fee, consensus_version, bytecode_size)?; - // ── Footer rule ─────────────────────────────────────────────────────── + println!("\n{} {}", "πŸ“Š Deployment Summary for".bold(), program_id.bold()); println!("{}", "──────────────────────────────────────────────".dimmed()); - Ok(()) + print!("{stats}"); + println!("{}", "──────────────────────────────────────────────".dimmed()); + + Ok(stats) } diff --git a/leo/cli/commands/execute.rs b/leo/cli/commands/execute.rs index dc31d77bed..c93105e11b 100644 --- a/leo/cli/commands/execute.rs +++ b/leo/cli/commands/execute.rs @@ -72,7 +72,7 @@ pub struct LeoExecute { impl Command for LeoExecute { type Input = Option; - type Output = (); + type Output = ExecuteOutput; fn log_span(&self) -> Span { tracing::span!(tracing::Level::INFO, "Leo") @@ -276,6 +276,14 @@ fn handle_execute( let consensus_version = get_consensus_version(&command.extra.consensus_version, &endpoint, network, &consensus_heights, &context)?; + // Build the config for JSON output. + let config = Some(Config { + address: address.to_string(), + network: network.to_string(), + endpoint: Some(endpoint.clone()), + consensus_version: Some(consensus_version as u8), + }); + // Print the execution plan. print_execution_plan::( &private_key, @@ -295,7 +303,7 @@ fn handle_execute( // Prompt the user to confirm the plan. if !confirm("Do you want to proceed with execution?", command.extra.yes)? { println!("❌ Execution aborted."); - return Ok(()); + return Ok(ExecuteOutput::default()); } // Initialize an RNG. @@ -346,8 +354,8 @@ fn handle_execute( rng, )?; - // Print the execution stats. - print_execution_stats::( + // Compute and print the execution stats. + let stats = print_execution_stats::( &vm, &program_name, transaction.execution().expect("Expected execution"), @@ -379,13 +387,18 @@ fn handle_execute( .map_err(|e| CliError::custom(format!("Failed to write transaction to file: {e}")))?; } - match response.outputs().len() { + let mut broadcast_stats = None; + + // Collect outputs. + let outputs: Vec = response.outputs().iter().map(|o| o.to_string()).collect(); + + match outputs.len() { 0 => (), 1 => println!("\n➑️ Output\n"), _ => println!("\n➑️ Outputs\n"), }; - for output in response.outputs() { - println!(" β€’ {output}"); + for o in &outputs { + println!(" β€’ {o}"); } println!(); @@ -394,13 +407,23 @@ fn handle_execute( println!("πŸ“‘ Broadcasting execution for {program_name}..."); // Get and confirm the fee with the user. let mut fee_id = None; + let mut fee_transaction_id = None; if let Some(fee) = transaction.fee_transition() { // Most transactions will have fees, but some, like credits.aleo/upgrade executions, may not. if !confirm_fee(&fee, &private_key, &address, &endpoint, network, &context, command.extra.yes)? { println!("❌ Execution aborted."); - return Ok(()); + return Ok(ExecuteOutput { + config: config.clone(), + program: program_name.clone(), + function: function_name.clone(), + outputs, + transaction_id: transaction.id().to_string(), + stats: Some(stats), + broadcast: None, + }); } fee_id = Some(fee.id().to_string()); + fee_transaction_id = Some(Transaction::from_fee(fee.clone())?.id().to_string()); } let id = transaction.id().to_string(); let height_before = check_transaction::current_height(&endpoint, network)?; @@ -408,14 +431,9 @@ fn handle_execute( let (message, status) = handle_broadcast(&format!("{endpoint}/{network}/transaction/broadcast"), &transaction, &program_name)?; - let fail = |msg| { - println!("❌ Failed to broadcast execution: {msg}."); - Ok(()) - }; - match status { 200..=299 => { - let status = check_transaction::check_transaction_with_message( + let tx_status = check_transaction::check_transaction_with_message( &id, fee_id.as_deref(), &endpoint, @@ -424,17 +442,31 @@ fn handle_execute( command.extra.max_wait, command.extra.blocks_to_check, )?; - if status == Some(TransactionStatus::Accepted) { + let confirmed = tx_status == Some(TransactionStatus::Accepted); + if confirmed { println!("βœ… Execution confirmed!"); } + broadcast_stats = Some(BroadcastStats { + fee_id: fee_id.unwrap_or_default(), + fee_transaction_id: fee_transaction_id.unwrap_or_default(), + confirmed, + }); } _ => { - return fail(&message); + println!("❌ Failed to broadcast execution: {message}."); } } } - Ok(()) + Ok(ExecuteOutput { + config, + program: program_name.clone(), + function: function_name.clone(), + outputs, + transaction_id: transaction.id().to_string(), + stats: Some(stats), + broadcast: broadcast_stats, + }) } /// Check the execution task for warnings. @@ -545,37 +577,34 @@ fn print_execution_plan( println!("{}", "──────────────────────────────────────────────\n".dimmed()); } -/// Pretty‑print execution statistics without a table, using the same UI -/// conventions as `print_deployment_plan`. +/// Compute execution statistics. +fn compute_execution_stats( + vm: &VM>, + execution: &Execution, + priority_fee: Option, + consensus_version: ConsensusVersion, +) -> Result { + let (_, (storage_cost, exec_cost)) = execution_cost(&vm.process().read(), execution, consensus_version)?; + + Ok(ExecutionStats { cost: CostBreakdown::for_execution(storage_cost, exec_cost, priority_fee.unwrap_or(0)) }) +} + +/// Pretty-print execution statistics. fn print_execution_stats( vm: &VM>, program_name: &str, execution: &Execution, priority_fee: Option, consensus_version: ConsensusVersion, -) -> Result<()> { +) -> Result { use colored::*; - // ── Gather cost components ──────────────────────────────────────────── - let (base_fee, (storage_cost, execution_cost)) = - execution_cost(&vm.process().read(), execution, consensus_version)?; - - let base_cr = base_fee as f64 / 1_000_000.0; - let prio_cr = priority_fee.unwrap_or(0) as f64 / 1_000_000.0; - let total_cr = base_cr + prio_cr; + let stats = compute_execution_stats(vm, execution, priority_fee, consensus_version)?; - // ── Header ──────────────────────────────────────────────────────────── println!("\n{} {}", "πŸ“Š Execution Summary for".bold(), program_name.bold()); println!("{}", "──────────────────────────────────────────────".dimmed()); - - // ── Cost breakdown ──────────────────────────────────────────────────── - println!("{}", "πŸ’° Cost Breakdown (credits)".bold()); - println!(" {:22}{}{:.6}", "Transaction Storage:".cyan(), "".yellow(), storage_cost as f64 / 1_000_000.0); - println!(" {:22}{}{:.6}", "On‑chain Execution:".cyan(), "".yellow(), execution_cost as f64 / 1_000_000.0); - println!(" {:22}{}{:.6}", "Priority Fee:".cyan(), "".yellow(), prio_cr); - println!(" {:22}{}{:.6}", "Total Fee:".cyan(), "".yellow(), total_cr); - - // ── Footer rule ─────────────────────────────────────────────────────── + print!("{stats}"); println!("{}", "──────────────────────────────────────────────".dimmed()); - Ok(()) + + Ok(stats) } diff --git a/leo/cli/commands/run.rs b/leo/cli/commands/run.rs index 1ea0b2666f..d689f55a18 100644 --- a/leo/cli/commands/run.rs +++ b/leo/cli/commands/run.rs @@ -57,7 +57,7 @@ pub struct LeoRun { impl Command for LeoRun { type Input = Option; - type Output = (); + type Output = RunOutput; fn log_span(&self) -> Span { tracing::span!(tracing::Level::INFO, "Leo") @@ -262,15 +262,18 @@ fn handle_run( let authorization = vm.authorize(&private_key, program_id, function_id, inputs.iter(), rng)?; let response = vm.process().read().evaluate::(authorization)?; + // Collect outputs. + let outputs: Vec = response.outputs().iter().map(|o| o.to_string()).collect(); + // Print the response. - match response.outputs().len() { + match outputs.len() { 0 => (), 1 => println!("\n➑️ Output\n"), _ => println!("\n➑️ Outputs\n"), }; - for output in response.outputs() { + for output in &outputs { println!(" β€’ {output}"); } - Ok(()) + Ok(RunOutput { program: program_id.to_string(), function: function_id.to_string(), outputs }) } diff --git a/leo/cli/commands/synthesize.rs b/leo/cli/commands/synthesize.rs index 0a90282f63..5617979a33 100644 --- a/leo/cli/commands/synthesize.rs +++ b/leo/cli/commands/synthesize.rs @@ -36,17 +36,8 @@ use snarkvm::{ }; use clap::Parser; -use serde::Serialize; use std::{fmt::Write, path::PathBuf}; -#[derive(Serialize)] -pub struct Metadata { - pub prover_checksum: String, - pub prover_size: usize, - pub verifier_checksum: String, - pub verifier_size: usize, -} - /// Synthesize proving and verifying keys for a given function. #[derive(Parser, Debug)] pub struct LeoSynthesize { @@ -66,7 +57,7 @@ pub struct LeoSynthesize { impl Command for LeoSynthesize { type Input = Option; - type Output = (); + type Output = SynthesizeOutput; fn log_span(&self) -> Span { tracing::span!(tracing::Level::INFO, "Leo") @@ -97,12 +88,12 @@ impl Command for LeoSynthesize { println!( "❌ `--broadcast` is not a valid option for `leo synthesize`. Please use `--save` and specify a valid directory." ); - return Ok(()); + return Ok(SynthesizeOutput::default()); } else if self.action.print { println!( "❌ `--print` is not a valid option for `leo synthesize`. Please use `--save` and specify a valid directory." ); - return Ok(()); + return Ok(SynthesizeOutput::default()); } // Get the network, accounting for overrides. @@ -243,6 +234,8 @@ fn handle_synthesize( println!(" - {id}"); } + let mut synthesized_functions = Vec::new(); + for function_id in function_ids { stack.synthesize_key::(function_id, rng)?; let proving_key = stack.get_proving_key(function_id)?; @@ -274,6 +267,22 @@ fn handle_synthesize( let metadata_pretty = serde_json::to_string_pretty(&metadata) .map_err(|e| CliError::custom(format!("Failed to serialize metadata: {e}")))?; + let circuit_info = CircuitInfo { + num_public_inputs: verifying_key.circuit_info.num_public_inputs as u64, + num_variables: verifying_key.circuit_info.num_public_and_private_variables as u64, + num_constraints: verifying_key.circuit_info.num_constraints as u64, + num_non_zero_a: verifying_key.circuit_info.num_non_zero_a as u64, + num_non_zero_b: verifying_key.circuit_info.num_non_zero_b as u64, + num_non_zero_c: verifying_key.circuit_info.num_non_zero_c as u64, + circuit_id: verifying_key.id.to_string(), + }; + + synthesized_functions.push(SynthesizedFunction { + name: function_id.to_string(), + circuit_info, + metadata: metadata.clone(), + }); + // A helper to write to a file. let write_to_file = |path: PathBuf, data: &[u8]| -> Result<()> { std::fs::write(path, data).map_err(|e| CliError::custom(format!("Failed to write to file: {e}")))?; @@ -309,5 +318,5 @@ fn handle_synthesize( } } - Ok(()) + Ok(SynthesizeOutput { program: program_id.to_string(), functions: synthesized_functions }) } diff --git a/leo/cli/commands/test.rs b/leo/cli/commands/test.rs index 679f3805a0..362e1b2301 100644 --- a/leo/cli/commands/test.rs +++ b/leo/cli/commands/test.rs @@ -44,7 +44,7 @@ pub struct LeoTest { impl Command for LeoTest { type Input = ::Output; - type Output = (); + type Output = TestOutput; fn log_span(&self) -> Span { tracing::span!(tracing::Level::INFO, "Leo") @@ -61,7 +61,7 @@ impl Command for LeoTest { } } -fn handle_test(command: LeoTest, package: Package) -> Result<()> { +fn handle_test(command: LeoTest, package: Package) -> Result { // Get the private key. let private_key = PrivateKey::::from_str(TEST_PRIVATE_KEY)?; @@ -152,32 +152,39 @@ fn handle_test(command: LeoTest, package: Package) -> Result<()> { let total_passed = interpreter_result.iter().filter(|(_, test_result)| matches!(test_result, Ok(()))).count() + native_results.iter().filter(|(_, _, x)| x.is_none()).count(); + // Build structured output and print results. + let mut tests = Vec::new(); + if total == 0 { println!("No tests run."); - Ok(()) } else { println!("{total_passed} / {total} tests passed."); let failed = "FAILED".bold().red(); let passed = "PASSED".bold().green(); + for (id, id_result) in interpreter_result.iter() { // Wasteful to make this, but fill will work. let str_id = format!("{id}"); if let Err(err) = id_result { println!("{failed}: {str_id:<30} | {err}"); + tests.push(TestResult { name: str_id, passed: false, error: Some(err.to_string()) }); } else { println!("{passed}: {str_id}"); + tests.push(TestResult { name: str_id, passed: true, error: None }); } } - for (program, function, case_result) in native_results { + for (program, function, case_result) in &native_results { let str_id = format!("{program}/{function}"); if let Some(err_str) = case_result { println!("{failed}: {str_id:<30} | {err_str}"); + tests.push(TestResult { name: str_id, passed: false, error: Some(err_str.clone()) }); } else { println!("{passed}: {str_id}"); + tests.push(TestResult { name: str_id, passed: true, error: None }); } } - - Ok(()) } + + Ok(TestOutput { passed: total_passed, failed: total - total_passed, tests }) } diff --git a/leo/cli/commands/upgrade.rs b/leo/cli/commands/upgrade.rs index d30d890ee3..ba3345b216 100644 --- a/leo/cli/commands/upgrade.rs +++ b/leo/cli/commands/upgrade.rs @@ -62,7 +62,7 @@ pub struct LeoUpgrade { impl Command for LeoUpgrade { type Input = Package; - type Output = (); + type Output = DeployOutput; fn log_span(&self) -> Span { tracing::span!(tracing::Level::INFO, "Leo") @@ -216,6 +216,14 @@ fn handle_upgrade( let consensus_version = get_consensus_version(&command.extra.consensus_version, &endpoint, network, &consensus_heights, &context)?; + // Build the config for JSON output. + let config = Some(Config { + address: address.to_string(), + network: network.to_string(), + endpoint: Some(endpoint.clone()), + consensus_version: Some(consensus_version as u8), + }); + // Print a summary of the deployment plan. print_deployment_plan( &private_key, @@ -233,7 +241,7 @@ fn handle_upgrade( // Prompt the user to confirm the plan. if !confirm("Do you want to proceed with upgrade?", command.extra.yes)? { println!("❌ Upgrade aborted."); - return Ok(()); + return Ok(DeployOutput::default()); } // Initialize an RNG. @@ -298,6 +306,8 @@ fn handle_upgrade( // For each of the programs, generate a deployment transaction. let mut transactions = Vec::new(); + let mut all_stats = Vec::new(); + let mut all_broadcasts = Vec::new(); for Task { id, program, priority_fee, record, bytecode_size, .. } in local { // If the program is a local dependency that is not skipped, generate a deployment transaction. if !skipped.contains(&id) { @@ -308,12 +318,20 @@ fn handle_upgrade( .map_err(|e| CliError::custom(format!("Failed to generate deployment transaction: {e}")))?; // Get the deployment. let deployment = transaction.deployment().expect("Expected a deployment in the transaction"); - // Print the deployment stats. - print_deployment_stats(&vm, &id.to_string(), deployment, priority_fee, consensus_version, bytecode_size)?; + // Compute and print the deployment stats. + let stats = print_deployment_stats( + &vm, + &id.to_string(), + deployment, + priority_fee, + consensus_version, + bytecode_size, + )?; // Validate the deployment limits. validate_deployment_limits(deployment, &id, &network)?; - // Save the transaction. + // Save the transaction and stats. transactions.push((id, transaction)); + all_stats.push(stats); } // Add the program to the VM. vm.process().write().add_program(&program)?; @@ -358,6 +376,7 @@ fn handle_upgrade( continue; } let fee_id = fee.id().to_string(); + let fee_transaction_id = Transaction::from_fee(fee.clone())?.id().to_string(); let id = transaction.id().to_string(); let height_before = check_transaction::current_height(&endpoint, network)?; // Broadcast the transaction to the network. @@ -380,7 +399,7 @@ fn handle_upgrade( match status { 200..=299 => { - let status = check_transaction::check_transaction_with_message( + let tx_status = check_transaction::check_transaction_with_message( &id, Some(&fee_id), &endpoint, @@ -389,26 +408,32 @@ fn handle_upgrade( command.extra.max_wait, command.extra.blocks_to_check, )?; - if status == Some(TransactionStatus::Accepted) { + let confirmed = tx_status == Some(TransactionStatus::Accepted); + if confirmed { println!("βœ… Upgrade confirmed!"); } else if fail_and_prompt("could not find the transaction on the network")? { continue; } else { - return Ok(()); + return Ok(build_deploy_output(config.clone(), &transactions, &all_stats, &all_broadcasts)); } + all_broadcasts.push(BroadcastStats { + fee_id: fee_id.clone(), + fee_transaction_id: fee_transaction_id.clone(), + confirmed, + }); } _ => { if fail_and_prompt(&message)? { continue; } else { - return Ok(()); + return Ok(build_deploy_output(config.clone(), &transactions, &all_stats, &all_broadcasts)); } } } } } - Ok(()) + Ok(build_deploy_output(config, &transactions, &all_stats, &all_broadcasts)) } /// Check the tasks to warn the user about any potential issues. diff --git a/leo/tests/integration.rs b/leo/tests/integration.rs index 395625cdc3..27996b5877 100644 --- a/leo/tests/integration.rs +++ b/leo/tests/integration.rs @@ -136,13 +136,25 @@ fn filter_stdout(data: &str) -> String { (Regex::new(" - transaction ID: '[a-zA-Z0-9]*'").unwrap(), " - transaction ID: 'XXXXXX'"), (Regex::new(" - fee ID: '[a-zA-Z0-9]*'").unwrap(), " - fee ID: 'XXXXXX'"), (Regex::new(" - fee transaction ID: '[a-zA-Z0-9]*'").unwrap(), " - fee transaction ID: 'XXXXXX'"), + (Regex::new(r#""transaction_id":\s*"[a-zA-Z0-9]*""#).unwrap(), r#""transaction_id": "XXXXXX""#), + (Regex::new(r#""fee_id":\s*"[a-zA-Z0-9]*""#).unwrap(), r#""fee_id": "XXXXXX""#), + (Regex::new(r#""fee_transaction_id":\s*"[a-zA-Z0-9]*""#).unwrap(), r#""fee_transaction_id": "XXXXXX""#), + (Regex::new(r#""address":\s*"aleo1[a-zA-Z0-9]*""#).unwrap(), r#""address": "XXXXXX""#), ( Regex::new("πŸ’°Your current public balance is [0-9.]* credits.").unwrap(), "πŸ’°Your current public balance is XXXXXX credits.", ), (Regex::new("Explored [0-9]* blocks.").unwrap(), "Explored XXXXXX blocks."), + // Transaction confirmation can vary between environments (timing-dependent) + (Regex::new("Transaction rejected\\.").unwrap(), "Could not find the transaction."), (Regex::new("Max Variables: [0-9,]*").unwrap(), "Max Variables: XXXXXX"), (Regex::new("Max Constraints: [0-9,]*").unwrap(), "Max Constraints: XXXXXX"), + // Synthesize command produces checksums and sizes that may vary. + (Regex::new(r#""prover_checksum":"[a-fA-F0-9]+""#).unwrap(), r#""prover_checksum":"XXXXXX""#), + (Regex::new(r#""verifier_checksum":"[a-fA-F0-9]+""#).unwrap(), r#""verifier_checksum":"XXXXXX""#), + (Regex::new(r#""prover_size":[0-9]+"#).unwrap(), r#""prover_size":0"#), + (Regex::new(r#""verifier_size":[0-9]+"#).unwrap(), r#""verifier_size":0"#), + (Regex::new(r"- Circuit ID: [a-zA-Z0-9]+").unwrap(), "- Circuit ID: XXXXXX"), // These are filtered out since the cache can frequently differ between local and CI runs. (Regex::new("Warning: The cached file.*\n").unwrap(), ""), ( @@ -179,6 +191,32 @@ fn filter_stderr(data: &str) -> String { cow.into_owned() } +/// Filter dynamic values in JSON output files to allow comparison across runs. +fn filter_json_file(data: &str) -> String { + use regex::Regex; + + let regexes = [ + (Regex::new(r#""transaction_id":\s*"[a-zA-Z0-9]*""#).unwrap(), r#""transaction_id": "XXXXXX""#), + (Regex::new(r#""fee_id":\s*"[a-zA-Z0-9]*""#).unwrap(), r#""fee_id": "XXXXXX""#), + (Regex::new(r#""fee_transaction_id":\s*"[a-zA-Z0-9]*""#).unwrap(), r#""fee_transaction_id": "XXXXXX""#), + (Regex::new(r#""address":\s*"aleo1[a-zA-Z0-9]*""#).unwrap(), r#""address": "XXXXXX""#), + (Regex::new(r#""prover_checksum":\s*"[a-fA-F0-9]+""#).unwrap(), r#""prover_checksum": "XXXXXX""#), + (Regex::new(r#""verifier_checksum":\s*"[a-fA-F0-9]+""#).unwrap(), r#""verifier_checksum": "XXXXXX""#), + (Regex::new(r#""circuit_id":\s*"[a-fA-F0-9]+""#).unwrap(), r#""circuit_id": "XXXXXX""#), + (Regex::new(r#""prover_size":\s*[0-9]+"#).unwrap(), r#""prover_size": 0"#), + (Regex::new(r#""verifier_size":\s*[0-9]+"#).unwrap(), r#""verifier_size": 0"#), + ]; + + let mut cow = Cow::Borrowed(data); + for (regex, replacement) in regexes { + if let Cow::Owned(s) = regex.replace_all(&cow, replacement) { + cow = Cow::Owned(s); + } + } + + cow.into_owned() +} + const BINARY_PATH: &str = env!("CARGO_BIN_EXE_leo"); #[test] @@ -260,7 +298,6 @@ fn integration_tests() { } fn copy_recursively(src: &Path, dst: &Path) -> io::Result<()> { - // Ensure destination directory exists if !dst.exists() { fs::create_dir_all(dst)?; } @@ -274,7 +311,14 @@ fn copy_recursively(src: &Path, dst: &Path) -> io::Result<()> { if file_type.is_dir() { copy_recursively(&src_path, &dst_path)?; } else if file_type.is_file() { - fs::copy(&src_path, &dst_path)?; + let in_json_outputs = src_path.components().any(|c| c.as_os_str() == "json-outputs"); + if in_json_outputs && src_path.extension().is_some_and(|ext| ext == "json") { + let content = fs::read_to_string(&src_path)?; + let filtered = filter_json_file(&content); + fs::write(&dst_path, filtered)?; + } else { + fs::copy(&src_path, &dst_path)?; + } } else { panic!("Unexpected file type at {}", src_path.display()) } @@ -305,9 +349,21 @@ fn dirs_equal(actual: &Path, expected: &Path) -> io::Result> { let bytes1 = fs::read(&path1)?; let bytes2 = fs::read(&path2)?; - if bytes1 != bytes2 { - let actual = String::from_utf8_lossy(&bytes1); - let expected = String::from_utf8_lossy(&bytes2); + // Apply filtering to JSON files in json-outputs directory + let is_json_output = relative_path.to_string_lossy().contains("json-outputs/") + && relative_path.extension().is_some_and(|ext| ext == "json"); + + let (content1, content2) = if is_json_output { + let s1 = String::from_utf8_lossy(&bytes1); + let s2 = String::from_utf8_lossy(&bytes2); + (filter_json_file(&s1).into_bytes(), filter_json_file(&s2).into_bytes()) + } else { + (bytes1, bytes2) + }; + + if content1 != content2 { + let actual = String::from_utf8_lossy(&content1); + let expected = String::from_utf8_lossy(&content2); return Ok(Some(format!( "File contents differ: {}\n - Actual: {actual}\n - Expected: {expected}", relative_path.display() diff --git a/tests/expectations/cli/network_dependency/STDOUT b/tests/expectations/cli/network_dependency/STDOUT index a433bc1c48..03b01f19a9 100644 --- a/tests/expectations/cli/network_dependency/STDOUT +++ b/tests/expectations/cli/network_dependency/STDOUT @@ -191,7 +191,7 @@ Attempting to determine the consensus version from the latest block height at ht ────────────────────────────────────────────── πŸ’° Cost Breakdown (credits) Transaction Storage: 0.001994 - On‑chain Execution: 0.000000 + On-chain Execution: 0.000000 Priority Fee: 0.000000 Total Fee: 0.001994 ────────────────────────────────────────────── diff --git a/tests/expectations/cli/test_add/STDOUT b/tests/expectations/cli/test_add/STDOUT index 754c3286c9..b41f764382 100644 --- a/tests/expectations/cli/test_add/STDOUT +++ b/tests/expectations/cli/test_add/STDOUT @@ -54,7 +54,7 @@ Attempting to determine the consensus version from the latest block height at ht ────────────────────────────────────────────── πŸ’° Cost Breakdown (credits) Transaction Storage: 0.001334 - On‑chain Execution: 0.000000 + On-chain Execution: 0.000000 Priority Fee: 0.000000 Total Fee: 0.001334 ────────────────────────────────────────────── @@ -113,7 +113,7 @@ Attempting to determine the consensus version from the latest block height at ht ────────────────────────────────────────────── πŸ’° Cost Breakdown (credits) Transaction Storage: 0.001334 - On‑chain Execution: 0.000000 + On-chain Execution: 0.000000 Priority Fee: 0.000000 Total Fee: 0.001334 ────────────────────────────────────────────── diff --git a/tests/expectations/cli/test_command_shortcuts/contents/program.json b/tests/expectations/cli/test_command_shortcuts/contents/program.json index c939191e70..8ee8dd8c29 100644 --- a/tests/expectations/cli/test_command_shortcuts/contents/program.json +++ b/tests/expectations/cli/test_command_shortcuts/contents/program.json @@ -4,8 +4,5 @@ "description": "", "license": "MIT", "dependencies": null, - "dev_dependencies": null, - "upgrade": { - "mode": "noupgrade" - } + "dev_dependencies": null } diff --git a/tests/expectations/cli/test_json/COMMANDS b/tests/expectations/cli/test_json/COMMANDS new file mode 100755 index 0000000000..551ce05682 --- /dev/null +++ b/tests/expectations/cli/test_json/COMMANDS @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +LEO_BIN=${1} +PRIVATE_KEY="APrivateKey1zkp2RWGDcde3efb89rjhME1VYA8QMxcxep5DShNBR6n8Yjh" +COMMON_OPTS="--disable-update-check --devnet --network testnet --endpoint http://localhost:3030 --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11" + +$LEO_BIN run --json-output main 1u32 2u32 + +$LEO_BIN test --json-output + +$LEO_BIN synthesize --json-output --local test_json_program.aleo $COMMON_OPTS + +$LEO_BIN deploy -y --json-output --broadcast $COMMON_OPTS + +$LEO_BIN execute -y --json-output --broadcast main 1u32 2u32 $COMMON_OPTS + +$LEO_BIN query --json-output program test_json_program.aleo $COMMON_OPTS + +$LEO_BIN upgrade -y --json-output --broadcast $COMMON_OPTS diff --git a/tests/expectations/cli/test_json/STDERR b/tests/expectations/cli/test_json/STDERR new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/expectations/cli/test_json/STDOUT b/tests/expectations/cli/test_json/STDOUT new file mode 100644 index 0000000000..78931f275e --- /dev/null +++ b/tests/expectations/cli/test_json/STDOUT @@ -0,0 +1,240 @@ +⚠️ No network specified, defaulting to 'testnet'. +⚠️ No endpoint specified, defaulting to 'https://api.explorer.provable.com/v1'. +⚠️ No network specified, defaulting to 'testnet'. +⚠️ No valid private key specified, defaulting to 'APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH'. + +βž•Adding programs to the VM in the following order: + - test_json_program.aleo (local) + +➑️ Output + + β€’ 3u32 +⚠️ No network specified, defaulting to 'testnet'. +⚠️ No endpoint specified, defaulting to 'https://api.explorer.provable.com/v1'. +1 / 1 tests passed. +PASSED: test_main.aleo/test_addition + +βž• Adding programs to the VM in the following order: + - test_json_program.aleo (local) + +🌱 Synthesizing the following keys in test_json_program.aleo: + - main + +πŸ”‘ Synthesized keys for test_json_program.aleo/main (edition 1) +ℹ️ Circuit Information: + - Public Inputs: 16 + - Variables: 12940 + - Constraints: 12927 + - Non-Zero Entries in A: 32191 + - Non-Zero Entries in B: 46449 + - Non-Zero Entries in C: 16949 + - Circuit ID: XXXXXX + +πŸ“’ Using the following consensus heights: 0,1,2,3,4,5,6,7,8,9,10,11 + To override, pass in `--consensus-heights` or override the environment variable `CONSENSUS_VERSION_HEIGHTS`. + +Attempting to determine the consensus version from the latest block height at http://localhost:3030... + +πŸ› οΈ Deployment Plan Summary +────────────────────────────────────────────── +πŸ”§ Configuration: + Private Key: APrivateKey1zkp2RWGDcde3... + Address: aleo1s3ws5tra87fjycnjrws... + Endpoint: http://localhost:3030 + Network: testnet + Consensus Version: 12 + +πŸ“¦ Deployment Tasks: + β€’ test_json_program.aleo β”‚ priority fee: 0 β”‚ fee record: no (public fee) + +βš™οΈ Actions: + β€’ Transaction(s) will NOT be printed to the console. + β€’ Transaction(s) will NOT be saved to a file. + β€’ Transaction(s) will be broadcast to http://localhost:3030 +────────────────────────────────────────────── + + +πŸ”§ Your program 'test_json_program.aleo' has the following constructor. +────────────────────────────────────────────── +constructor: + branch.eq edition 0u16 to end; + get expected_checksum[true] into r0; + assert.eq checksum r0; + position end; +────────────────────────────────────────────── +Once it is deployed, it CANNOT be changed. + +πŸ“¦ Creating deployment transaction for 'test_json_program.aleo'... + + +πŸ“Š Deployment Summary for test_json_program.aleo +────────────────────────────────────────────── + Program Size: 0.37 KB / 97.66 KB + Total Variables: 17,276 + Total Constraints: 12,927 + Max Variables: XXXXXX + Max Constraints: XXXXXX + +πŸ’° Cost Breakdown (credits) + Transaction Storage: 0.956000 + Program Synthesis: 0.030203 + Namespace: 1.000000 + Constructor: 0.010440 + Priority Fee: 0.000000 + Total Fee: 1.996643 +────────────────────────────────────────────── + +πŸ“‘ Broadcasting deployment for test_json_program.aleo... +πŸ’°Your current public balance is XXXXXX credits. + +βœ‰οΈ Broadcasted transaction with: + - transaction ID: 'XXXXXX' + - fee ID: 'XXXXXX' + - fee transaction ID: 'XXXXXX' + (use this to check for rejected transactions) + +πŸ”„ Searching up to 12 blocks to confirm transaction (this may take several seconds)... +Explored XXXXXX blocks. +Transaction accepted. +βœ… Deployment confirmed! + +πŸ“’ Using the following consensus heights: 0,1,2,3,4,5,6,7,8,9,10,11 + To override, pass in `--consensus-heights` or override the environment variable `CONSENSUS_VERSION_HEIGHTS`. + +Attempting to determine the consensus version from the latest block height at http://localhost:3030... + +πŸš€ Execution Plan Summary +────────────────────────────────────────────── +πŸ”§ Configuration: + Private Key: APrivateKey1zkp2RWGDcde3... + Address: aleo1s3ws5tra87fjycnjrws... + Endpoint: http://localhost:3030 + Network: testnet + Consensus Version: 12 + +🎯 Execution Target: + Program: test_json_program.aleo + Function: main + Source: local + +πŸ’Έ Fee Info: + Priority Fee: 0 ΞΌcredits + Fee Record: no (public fee) + +βš™οΈ Actions: + - Transaction will NOT be printed to the console. + - Transaction will NOT be saved to a file. + - Transaction will be broadcast to http://localhost:3030 +────────────────────────────────────────────── + + +βž•Adding programs to the VM in the following order: + - test_json_program.aleo (local) + +βš™οΈ Executing test_json_program.aleo/main... + +πŸ“Š Execution Summary for test_json_program.aleo +────────────────────────────────────────────── +πŸ’° Cost Breakdown (credits) + Transaction Storage: 0.001328 + On-chain Execution: 0.000000 + Priority Fee: 0.000000 + Total Fee: 0.001328 +────────────────────────────────────────────── + +➑️ Output + + β€’ 3u32 + +πŸ“‘ Broadcasting execution for test_json_program.aleo... +πŸ’°Your current public balance is XXXXXX credits. + +βœ‰οΈ Broadcasted transaction with: + - transaction ID: 'XXXXXX' + - fee ID: 'XXXXXX' + - fee transaction ID: 'XXXXXX' + (use this to check for rejected transactions) + +πŸ”„ Searching up to 12 blocks to confirm transaction (this may take several seconds)... +Explored XXXXXX blocks. +Transaction accepted. +βœ… Execution confirmed! +program test_json_program.aleo; + +mapping expected_checksum: + key as boolean.public; + value as [u8; 32u32].public; + +function main: + input r0 as u32.public; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; + +constructor: + branch.eq edition 0u16 to end; + get expected_checksum[true] into r0; + assert.eq checksum r0; + position end; + + + +πŸ“’ Using the following consensus heights: 0,1,2,3,4,5,6,7,8,9,10,11 + To override, pass in `--consensus-heights` or override the environment variable `CONSENSUS_VERSION_HEIGHTS`. + +Attempting to determine the consensus version from the latest block height at http://localhost:3030... + +πŸ› οΈ Deployment Plan Summary +────────────────────────────────────────────── +πŸ”§ Configuration: + Private Key: APrivateKey1zkp2RWGDcde3... + Address: aleo1s3ws5tra87fjycnjrws... + Endpoint: http://localhost:3030 + Network: testnet + Consensus Version: 12 + +πŸ“¦ Deployment Tasks: + β€’ test_json_program.aleo β”‚ priority fee: 0 β”‚ fee record: no (public fee) + +βš™οΈ Actions: + β€’ Transaction(s) will NOT be printed to the console. + β€’ Transaction(s) will NOT be saved to a file. + β€’ Transaction(s) will be broadcast to http://localhost:3030 +────────────────────────────────────────────── + +Loaded the following programs into the VM: + - credits.aleo (default) + - test_json_program.aleo (edition 0) + +πŸ“¦ Creating deployment transaction for 'test_json_program.aleo'... + + +πŸ“Š Deployment Summary for test_json_program.aleo +────────────────────────────────────────────── + Program Size: 0.37 KB / 97.66 KB + Total Variables: 17,276 + Total Constraints: 12,927 + Max Variables: XXXXXX + Max Constraints: XXXXXX + +πŸ’° Cost Breakdown (credits) + Transaction Storage: 0.956000 + Program Synthesis: 0.030203 + Namespace: 1.000000 + Constructor: 0.010440 + Priority Fee: 0.000000 + Total Fee: 1.996643 +────────────────────────────────────────────── +πŸ“‘ Broadcasting upgrade for test_json_program.aleo... +πŸ’°Your current public balance is XXXXXX credits. + +βœ‰οΈ Broadcasted transaction with: + - transaction ID: 'XXXXXX' + - fee ID: 'XXXXXX' + - fee transaction ID: 'XXXXXX' + (use this to check for rejected transactions) + +πŸ”„ Searching up to 12 blocks to confirm transaction (this may take several seconds)... +Explored XXXXXX blocks. +Could not find the transaction. +❌ Failed to upgrade program test_json_program.aleo: could not find the transaction on the network. diff --git a/tests/expectations/cli/test_json/contents/build/imports/test_main.aleo b/tests/expectations/cli/test_json/contents/build/imports/test_main.aleo new file mode 100644 index 0000000000..1344822486 --- /dev/null +++ b/tests/expectations/cli/test_json/contents/build/imports/test_main.aleo @@ -0,0 +1,4 @@ +import test_json_program.aleo; +program test_main.aleo; + +function placeholder: diff --git a/tests/expectations/cli/test_json/contents/build/json-outputs/deploy.json b/tests/expectations/cli/test_json/contents/build/json-outputs/deploy.json new file mode 100644 index 0000000000..a222187c70 --- /dev/null +++ b/tests/expectations/cli/test_json/contents/build/json-outputs/deploy.json @@ -0,0 +1,36 @@ +{ + "config": { + "address": "XXXXXX", + "network": "testnet", + "endpoint": "http://localhost:3030", + "consensus_version": 12 + }, + "deployments": [ + { + "program_id": "test_json_program.aleo", + "transaction_id": "XXXXXX", + "stats": { + "program_size_bytes": 381, + "max_program_size_bytes": 100000, + "total_variables": 17276, + "total_constraints": 12927, + "max_variables": 2097152, + "max_constraints": 2097152, + "cost": { + "storage": 956000, + "namespace": 1000000, + "synthesis": 30203, + "constructor": 10440, + "execution": null, + "priority": 0, + "total": 1996643 + } + }, + "broadcast": { + "fee_id": "XXXXXX", + "fee_transaction_id": "XXXXXX", + "confirmed": true + } + } + ] +} \ No newline at end of file diff --git a/tests/expectations/cli/test_json/contents/build/json-outputs/execute.json b/tests/expectations/cli/test_json/contents/build/json-outputs/execute.json new file mode 100644 index 0000000000..cc5462e836 --- /dev/null +++ b/tests/expectations/cli/test_json/contents/build/json-outputs/execute.json @@ -0,0 +1,30 @@ +{ + "config": { + "address": "XXXXXX", + "network": "testnet", + "endpoint": "http://localhost:3030", + "consensus_version": 12 + }, + "program": "test_json_program.aleo", + "function": "main", + "outputs": [ + "3u32" + ], + "transaction_id": "XXXXXX", + "stats": { + "cost": { + "storage": 1328, + "namespace": null, + "synthesis": null, + "constructor": null, + "execution": 0, + "priority": 0, + "total": 1328 + } + }, + "broadcast": { + "fee_id": "XXXXXX", + "fee_transaction_id": "XXXXXX", + "confirmed": true + } +} \ No newline at end of file diff --git a/tests/expectations/cli/test_json/contents/build/json-outputs/query.json b/tests/expectations/cli/test_json/contents/build/json-outputs/query.json new file mode 100644 index 0000000000..bd78d634da --- /dev/null +++ b/tests/expectations/cli/test_json/contents/build/json-outputs/query.json @@ -0,0 +1 @@ +"program test_json_program.aleo;\n\nmapping expected_checksum:\n key as boolean.public;\n value as [u8; 32u32].public;\n\nfunction main:\n input r0 as u32.public;\n input r1 as u32.private;\n add r0 r1 into r2;\n output r2 as u32.private;\n\nconstructor:\n branch.eq edition 0u16 to end;\n get expected_checksum[true] into r0;\n assert.eq checksum r0;\n position end;\n" \ No newline at end of file diff --git a/tests/expectations/cli/test_json/contents/build/json-outputs/run.json b/tests/expectations/cli/test_json/contents/build/json-outputs/run.json new file mode 100644 index 0000000000..c2048abe0c --- /dev/null +++ b/tests/expectations/cli/test_json/contents/build/json-outputs/run.json @@ -0,0 +1,7 @@ +{ + "program": "test_json_program.aleo", + "function": "main", + "outputs": [ + "3u32" + ] +} \ No newline at end of file diff --git a/tests/expectations/cli/test_json/contents/build/json-outputs/synthesize.json b/tests/expectations/cli/test_json/contents/build/json-outputs/synthesize.json new file mode 100644 index 0000000000..4a023b3add --- /dev/null +++ b/tests/expectations/cli/test_json/contents/build/json-outputs/synthesize.json @@ -0,0 +1,21 @@ +{ + "program": "test_json_program.aleo", + "functions": [ + { + "name": "main", + "circuit_info": { + "num_public_inputs": 16, + "num_variables": 12940, + "num_constraints": 12927, + "num_non_zero_a": 32191, + "num_non_zero_b": 46449, + "num_non_zero_c": 16949, + "circuit_id": "XXXXXX" + }, + "prover_checksum": "XXXXXX", + "prover_size": 0, + "verifier_checksum": "XXXXXX", + "verifier_size": 0 + } + ] +} \ No newline at end of file diff --git a/tests/expectations/cli/test_json/contents/build/json-outputs/test.json b/tests/expectations/cli/test_json/contents/build/json-outputs/test.json new file mode 100644 index 0000000000..eaf5a7fefb --- /dev/null +++ b/tests/expectations/cli/test_json/contents/build/json-outputs/test.json @@ -0,0 +1,10 @@ +{ + "passed": 1, + "failed": 0, + "tests": [ + { + "name": "test_main.aleo/test_addition", + "passed": true + } + ] +} \ No newline at end of file diff --git a/tests/expectations/cli/test_json/contents/build/json-outputs/upgrade.json b/tests/expectations/cli/test_json/contents/build/json-outputs/upgrade.json new file mode 100644 index 0000000000..66961eab10 --- /dev/null +++ b/tests/expectations/cli/test_json/contents/build/json-outputs/upgrade.json @@ -0,0 +1,31 @@ +{ + "config": { + "address": "XXXXXX", + "network": "testnet", + "endpoint": "http://localhost:3030", + "consensus_version": 12 + }, + "deployments": [ + { + "program_id": "test_json_program.aleo", + "transaction_id": "XXXXXX", + "stats": { + "program_size_bytes": 381, + "max_program_size_bytes": 100000, + "total_variables": 17276, + "total_constraints": 12927, + "max_variables": 2097152, + "max_constraints": 2097152, + "cost": { + "storage": 956000, + "namespace": 1000000, + "synthesis": 30203, + "constructor": 10440, + "execution": null, + "priority": 0, + "total": 1996643 + } + } + } + ] +} \ No newline at end of file diff --git a/tests/expectations/cli/test_json/contents/build/main.aleo b/tests/expectations/cli/test_json/contents/build/main.aleo new file mode 100644 index 0000000000..38e7083cbe --- /dev/null +++ b/tests/expectations/cli/test_json/contents/build/main.aleo @@ -0,0 +1,17 @@ +program test_json_program.aleo; + +mapping expected_checksum: + key as boolean.public; + value as [u8; 32u32].public; + +function main: + input r0 as u32.public; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; + +constructor: + branch.eq edition 0u16 to end; + get expected_checksum[true] into r0; + assert.eq checksum r0; + position end; diff --git a/tests/expectations/cli/test_json/contents/build/program.json b/tests/expectations/cli/test_json/contents/build/program.json new file mode 100644 index 0000000000..7060cffa15 --- /dev/null +++ b/tests/expectations/cli/test_json/contents/build/program.json @@ -0,0 +1,9 @@ +{ + "program": "test_json_program.aleo", + "version": "0.1.0", + "description": "", + "license": "", + "leo": "3.4.0", + "dependencies": null, + "dev_dependencies": null +} diff --git a/tests/expectations/cli/test_json/contents/program.json b/tests/expectations/cli/test_json/contents/program.json new file mode 100644 index 0000000000..cadbd3c3b2 --- /dev/null +++ b/tests/expectations/cli/test_json/contents/program.json @@ -0,0 +1,9 @@ +{ + "program": "test_json_program.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "dependencies": null, + "dev_dependencies": null +} + diff --git a/tests/expectations/cli/test_json/contents/src/main.leo b/tests/expectations/cli/test_json/contents/src/main.leo new file mode 100644 index 0000000000..68037513c6 --- /dev/null +++ b/tests/expectations/cli/test_json/contents/src/main.leo @@ -0,0 +1,13 @@ +// The 'test_json_program' program. +program test_json_program.aleo { + mapping expected_checksum: bool => [u8; 32]; + + transition main(public a: u32, b: u32) -> u32 { + let c: u32 = a + b; + return c; + } + + @checksum(mapping = "test_json_program.aleo/expected_checksum", key = "true") + async constructor() {} +} + diff --git a/tests/expectations/cli/test_json/contents/tests/test_main.leo b/tests/expectations/cli/test_json/contents/tests/test_main.leo new file mode 100644 index 0000000000..fb43db45ef --- /dev/null +++ b/tests/expectations/cli/test_json/contents/tests/test_main.leo @@ -0,0 +1,11 @@ +import test_json_program.aleo; +program test_main.aleo { + // Required by snarkVM - every program needs at least one transition. + transition placeholder() {} + + @test + script test_addition() { + let result: u32 = test_json_program.aleo/main(1u32, 2u32); + assert_eq(result, 3u32); + } +} diff --git a/tests/tests/cli/test_command_shortcuts/contents/program.json b/tests/tests/cli/test_command_shortcuts/contents/program.json index c939191e70..8ee8dd8c29 100644 --- a/tests/tests/cli/test_command_shortcuts/contents/program.json +++ b/tests/tests/cli/test_command_shortcuts/contents/program.json @@ -4,8 +4,5 @@ "description": "", "license": "MIT", "dependencies": null, - "dev_dependencies": null, - "upgrade": { - "mode": "noupgrade" - } + "dev_dependencies": null } diff --git a/tests/tests/cli/test_json/COMMANDS b/tests/tests/cli/test_json/COMMANDS new file mode 100755 index 0000000000..551ce05682 --- /dev/null +++ b/tests/tests/cli/test_json/COMMANDS @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +LEO_BIN=${1} +PRIVATE_KEY="APrivateKey1zkp2RWGDcde3efb89rjhME1VYA8QMxcxep5DShNBR6n8Yjh" +COMMON_OPTS="--disable-update-check --devnet --network testnet --endpoint http://localhost:3030 --private-key $PRIVATE_KEY --consensus-heights 0,1,2,3,4,5,6,7,8,9,10,11" + +$LEO_BIN run --json-output main 1u32 2u32 + +$LEO_BIN test --json-output + +$LEO_BIN synthesize --json-output --local test_json_program.aleo $COMMON_OPTS + +$LEO_BIN deploy -y --json-output --broadcast $COMMON_OPTS + +$LEO_BIN execute -y --json-output --broadcast main 1u32 2u32 $COMMON_OPTS + +$LEO_BIN query --json-output program test_json_program.aleo $COMMON_OPTS + +$LEO_BIN upgrade -y --json-output --broadcast $COMMON_OPTS diff --git a/tests/tests/cli/test_json/contents/program.json b/tests/tests/cli/test_json/contents/program.json new file mode 100644 index 0000000000..cadbd3c3b2 --- /dev/null +++ b/tests/tests/cli/test_json/contents/program.json @@ -0,0 +1,9 @@ +{ + "program": "test_json_program.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "dependencies": null, + "dev_dependencies": null +} + diff --git a/tests/tests/cli/test_json/contents/src/main.leo b/tests/tests/cli/test_json/contents/src/main.leo new file mode 100644 index 0000000000..68037513c6 --- /dev/null +++ b/tests/tests/cli/test_json/contents/src/main.leo @@ -0,0 +1,13 @@ +// The 'test_json_program' program. +program test_json_program.aleo { + mapping expected_checksum: bool => [u8; 32]; + + transition main(public a: u32, b: u32) -> u32 { + let c: u32 = a + b; + return c; + } + + @checksum(mapping = "test_json_program.aleo/expected_checksum", key = "true") + async constructor() {} +} + diff --git a/tests/tests/cli/test_json/contents/tests/test_main.leo b/tests/tests/cli/test_json/contents/tests/test_main.leo new file mode 100644 index 0000000000..fb43db45ef --- /dev/null +++ b/tests/tests/cli/test_json/contents/tests/test_main.leo @@ -0,0 +1,11 @@ +import test_json_program.aleo; +program test_main.aleo { + // Required by snarkVM - every program needs at least one transition. + transition placeholder() {} + + @test + script test_addition() { + let result: u32 = test_json_program.aleo/main(1u32, 2u32); + assert_eq(result, 3u32); + } +}