diff --git a/leo/cli/commands/common/options.rs b/leo/cli/commands/common/options.rs index 082f4b41b45..26ba8f70b13 100644 --- a/leo/cli/commands/common/options.rs +++ b/leo/cli/commands/common/options.rs @@ -109,8 +109,7 @@ pub struct EnvOptions { pub struct FeeOptions { #[clap( long, - help = "[UNUSED] Base fees in microcredits, delimited by `|`, and used in order. The fees must either be valid `u64` or `default`. Defaults to automatic calculation.", - hide = true, + help = "Base fees in microcredits, delimited by `|`, and used in order. The fees must either be valid `u64` or `default`. Defaults to automatic calculation.", value_delimiter = '|', value_parser = parse_amount )] diff --git a/leo/cli/commands/execute.rs b/leo/cli/commands/execute.rs index 7b635f70edc..9fcd39a0406 100644 --- a/leo/cli/commands/execute.rs +++ b/leo/cli/commands/execute.rs @@ -21,10 +21,11 @@ use leo_ast::NetworkName; use leo_package::{Package, ProgramData, fetch_program_from_network}; use aleo_std::StorageMode; -use snarkvm::prelude::{Execution, Itertools, Network, Program, execution_cost}; +use snarkvm::prelude::{Authorization, Execution, Itertools, Network, Program, execution_cost}; use clap::Parser; use colored::*; +use serde::{Serialize, ser::SerializeStruct}; use std::{convert::TryFrom, path::PathBuf}; #[cfg(not(feature = "only_testnet"))] @@ -33,10 +34,11 @@ use snarkvm::{ circuit::{Aleo, AleoTestnetV0}, prelude::{ ConsensusVersion, + Fee, Identifier, ProgramID, VM, - query::Query as SnarkVMQuery, + query::{Query as SnarkVMQuery, QueryTrait}, store::{ ConsensusStore, helpers::memory::{BlockMemory, ConsensusMemory}, @@ -58,6 +60,10 @@ pub struct LeoExecute { help = "The program inputs e.g. `1u32`, `record1...` (record ciphertext), or `{ owner: ...}` " )] inputs: Vec, + #[clap(long, help = "Generate the authorization only.", conflicts_with = "broadcast")] + pub(crate) authorization_only: bool, + #[clap(long, help = "Skips proving.")] + pub(crate) skip_proving: bool, #[clap(flatten)] pub(crate) fee_options: FeeOptions, #[clap(flatten)] @@ -179,7 +185,7 @@ fn handle_execute( "{}.aleo", package.programs.last().expect("There must be at least one program in a Leo package").name ), - command.name, + command.name.clone(), ), None => { return Err(CliError::custom(format!( @@ -265,11 +271,10 @@ fn handle_execute( } } - let inputs = - command.inputs.into_iter().map(|string| parse_input(&string, &private_key)).collect::>>()?; + let inputs = command.inputs.iter().map(|string| parse_input(string, &private_key)).collect::>>()?; // Get the first fee option. - let (_, priority_fee, record) = + let (base_fee, priority_fee, record) = parse_fee_options(&private_key, &command.fee_options, 1)?.into_iter().next().unwrap_or((None, None, None)); // Get the consensus version. @@ -287,9 +292,9 @@ fn handle_execute( is_local, priority_fee.unwrap_or(0), record.is_some(), - &command.action, consensus_version, &check_task_for_warnings(&endpoint, network, &programs, consensus_version), + &command, ); // Prompt the user to confirm the plan. @@ -337,32 +342,108 @@ fn handle_execute( vm.process().write().add_programs_with_editions(&programs_and_editions)?; // Execute the program and produce a transaction. - let (transaction, response) = vm.execute_with_response( - &private_key, - (&program_name, &function_name), - inputs.iter(), - record, - priority_fee.unwrap_or(0), - Some(&query), - rng, - )?; + let (output_name, output, response) = if command.authorization_only { + println!("\nšŸ›‚ Generating authorization for {program_name}/{function_name}..."); + + // Get the base fee. + let Some(base_fee) = base_fee else { + return Err(CliError::custom( + "When generating an authorization, a base fee must be provided with `--base-fee`.", + ) + .into()); + }; - // Print the execution stats. - print_execution_stats::( - &vm, - &program_name, - transaction.execution().expect("Expected execution"), - priority_fee, - consensus_version, - )?; + // Generate the authorizations. + let execution = + vm.process().read().authorize::(&private_key, &program_name, &function_name, inputs.iter(), rng)?; + + let id = execution.to_execution_id()?; + let fee = match record { + None => vm.authorize_fee_public(&private_key, base_fee, priority_fee.unwrap_or(0), id, rng)?, + Some(record) => { + vm.authorize_fee_private(&private_key, record, base_fee, priority_fee.unwrap_or(0), id, rng)? + } + }; + + // Evaluate the authorization to get the response. + let response = vm.process().read().evaluate::(execution.clone())?; + + ("authorization", ExecutionOutput::AuthorizationData { execution, fee }, response) + } else if command.skip_proving { + println!("\nāš™ļø Generating transaction WITHOUT a proof for {program_name}/{function_name}..."); + + // Generate the authorization. + let authorization = + vm.process().read().authorize::(&private_key, &program_name, &function_name, inputs.iter(), rng)?; + + // Get the state root. + let state_root = query.current_state_root()?; + + // Create an execution without the proof. + let execution = Execution::from(authorization.transitions().values().cloned(), state_root, None)?; + + // Calculate the cost. + let (cost, _) = execution_cost(&vm.process().read(), &execution, consensus_version)?; + + // Generate the fee authorization. + let id = authorization.to_execution_id()?; + let fee_authorization = match record { + None => { + vm.authorize_fee_public(&private_key, base_fee.unwrap_or(cost), priority_fee.unwrap_or(0), id, rng)? + } + Some(record) => vm.authorize_fee_private( + &private_key, + record, + base_fee.unwrap_or(cost), + priority_fee.unwrap_or(0), + id, + rng, + )?, + }; + + // Create a fee transition without a proof. + let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?; + + // Create the transaction. + let transaction = Transaction::from_execution(execution, Some(fee))?; + + // Evaluate the transaction to get the response. + let response = vm.process().read().evaluate::(authorization)?; + + ("transaction", ExecutionOutput::Transaction(Box::new(transaction)), response) + } else { + println!("\nāš™ļø Generating transaction for {program_name}/{function_name}..."); + + // Generate the transaction and get the response. + let (transaction, response) = vm.execute_with_response( + &private_key, + (&program_name, &function_name), + inputs.iter(), + record, + priority_fee.unwrap_or(0), + Some(&query), + rng, + )?; + + // Print the execution stats. + print_execution_stats::( + &vm, + &program_name, + transaction.execution().expect("Expected execution"), + priority_fee, + consensus_version, + )?; + + ("execution", ExecutionOutput::Transaction(Box::new(transaction)), response) + }; // Print the transaction. // If the `print` option is set, print the execution transaction to the console. // The transaction is printed in JSON format. if command.action.print { - let transaction_json = serde_json::to_string_pretty(&transaction) + let json = serde_json::to_string_pretty(&output) .map_err(|e| CliError::custom(format!("Failed to serialize transaction: {e}")))?; - println!("šŸ–Øļø Printing execution for {program_name}\n{transaction_json}"); + println!("šŸ–Øļø Printing {output_name} for {program_name}\n{json}"); } // If the `save` option is set, save the execution transaction to a file in the specified directory. @@ -373,11 +454,11 @@ fn handle_execute( std::fs::create_dir_all(path).map_err(|e| CliError::custom(format!("Failed to create directory: {e}")))?; // Save the transaction to a file. let file_path = PathBuf::from(path).join(format!("{program_name}.execution.json")); - println!("šŸ’¾ Saving execution for {program_name} at {}", file_path.display()); - let transaction_json = serde_json::to_string_pretty(&transaction) - .map_err(|e| CliError::custom(format!("Failed to serialize transaction: {e}")))?; + println!("šŸ’¾ Saving {output_name} for {program_name} at {}", file_path.display()); + let transaction_json = serde_json::to_string_pretty(&output) + .map_err(|e| CliError::custom(format!("Failed to serialize {output_name}: {e}")))?; std::fs::write(file_path, transaction_json) - .map_err(|e| CliError::custom(format!("Failed to write transaction to file: {e}")))?; + .map_err(|e| CliError::custom(format!("Failed to write {output_name} to file: {e}")))?; } match response.outputs().len() { @@ -390,9 +471,14 @@ fn handle_execute( } println!(); - // If the `broadcast` option is set, broadcast each deployment transaction to the network. + // If the `broadcast` option is set, broadcast each execution to the network. if command.action.broadcast { - println!("šŸ“” Broadcasting execution for {program_name}..."); + println!("šŸ“” Broadcasting {output_name} for {program_name}..."); + // Get the transaction. + let ExecutionOutput::Transaction(transaction) = output else { + println!("āŒ Cannot broadcast an authorization. Please run without `--authorization-only`."); + return Ok(()); + }; // Get and confirm the fee with the user. let mut fee_id = None; if let Some(fee) = transaction.fee_transition() { @@ -492,9 +578,9 @@ fn print_execution_plan( is_local: bool, priority_fee: u64, fee_record: bool, - action: &TransactionAction, consensus_version: ConsensusVersion, warnings: &[String], + command: &LeoExecute, ) { println!("\n{}", "šŸš€ Execution Plan Summary".bold().underline()); println!("{}", "──────────────────────────────────────────────".dimmed()); @@ -519,17 +605,23 @@ fn print_execution_plan( if !is_local { println!(" - Program and its dependencies will be downloaded from the network."); } - if action.print { + if command.authorization_only { + println!(" - Only the authorization will be generated."); + } + if command.skip_proving { + println!(" - A transaction will be generated, WITHOUT a proof."); + } + if command.action.print { println!(" - Transaction will be printed to the console."); } else { println!(" - Transaction will NOT be printed to the console."); } - if let Some(path) = &action.save { + if let Some(path) = &command.action.save { println!(" - Transaction will be saved to {}", path.bold()); } else { println!(" - Transaction will NOT be saved to a file."); } - if action.broadcast { + if command.action.broadcast { println!(" - Transaction will be broadcast to {}", endpoint.bold()); } else { println!(" - Transaction will NOT be broadcast to the network."); @@ -580,3 +672,29 @@ fn print_execution_stats( println!("{}", "──────────────────────────────────────────────".dimmed()); Ok(()) } + +// Possible outputs of an `execute` command. +enum ExecutionOutput { + // Produced if `--authorization-only` is set. + AuthorizationData { execution: Authorization, fee: Authorization }, + // Default output, a transaction. + // Note. The transaction may not have a proof if `--skip-proving` is set. + Transaction(Box>), +} + +impl Serialize for ExecutionOutput { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + ExecutionOutput::AuthorizationData { execution, fee } => { + let mut state = serializer.serialize_struct("AuthorizationData", 2)?; + state.serialize_field("execution", execution)?; + state.serialize_field("fee", fee)?; + state.end() + } + ExecutionOutput::Transaction(transaction) => transaction.serialize(serializer), + } + } +}