use super::*;
use super::common::load_extra_programs_into_vm;
use check_transaction::TransactionStatus;
use leo_ast::NetworkName;
use leo_package::{Package, ProgramData, fetch_program_from_network, retry_network_call};
use aleo_std::StorageMode;
use rand::{CryptoRng, Rng};
use snarkvm::prelude::{
Authorization,
Execution,
Fee,
Field,
Itertools,
Network,
Program,
execution_cost,
execution_cost_for_authorization,
};
use clap::Parser;
use colored::*;
use std::{convert::TryFrom, path::PathBuf};
#[cfg(not(feature = "only_testnet"))]
use snarkvm::circuit::{AleoCanaryV0, AleoV0};
use snarkvm::{
circuit::{Aleo, AleoTestnetV0},
prelude::{
ConsensusVersion,
Identifier,
ProgramID,
VM,
query::{Query as SnarkVMQuery, QueryTrait},
store::{
ConsensusStore,
helpers::memory::{BlockMemory, ConsensusMemory},
},
},
};
#[derive(Parser, Debug)]
pub struct LeoExecute {
#[clap(
name = "NAME",
help = "The name of the function to execute, e.g `helloworld.aleo::main` or `main`.",
default_value = "main"
)]
name: String,
#[clap(
name = "INPUTS",
help = "The program inputs e.g. `1u32`, `record1...` (record ciphertext), or `{ owner: ...}` "
)]
inputs: Vec<String>,
#[clap(long, help = "Skips proving.")]
pub(crate) skip_execute_proof: bool,
#[clap(flatten)]
pub(crate) fee_options: FeeOptions,
#[clap(flatten)]
pub(crate) action: TransactionAction,
#[clap(flatten)]
pub(crate) env_override: EnvOptions,
#[clap(flatten)]
pub(crate) key_override: PrivateKeyOptions,
#[clap(flatten)]
pub(crate) consensus_override: ConsensusOptions,
#[clap(flatten)]
pub(crate) extra: ExtraOptions,
#[clap(flatten)]
build_options: BuildOptions,
#[clap(
long = "with",
help = "Additional programs to load into the VM (comma-separated). \
If a path exists locally, it is read as an .aleo bytecode file; \
otherwise it is fetched from the network endpoint.",
value_delimiter = ','
)]
pub(crate) with: Vec<String>,
}
impl Command for LeoExecute {
type Input = Option<Package>;
type Output = ExecuteOutput;
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Leo")
}
fn prelude(&self, context: Context) -> Result<Self::Input> {
let path = context.dir()?;
let home_path = context.home()?;
let network = get_network(&self.env_override.network)?;
let endpoint = get_endpoint(&self.env_override.endpoint)?;
if Package::from_directory_no_graph(
path,
home_path,
Some(network),
Some(&endpoint),
self.env_override.network_retries,
)
.is_ok()
{
let package = LeoBuild {
env_override: self.env_override.clone(),
options: {
let mut options = self.build_options.clone();
options.no_cache = true;
options
},
rename: None,
}
.execute(context)?;
Ok(Some(package))
} else {
Ok(None)
}
}
fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output> {
if let Some(package) = &input
&& package.compilation_units.last().is_some_and(|p| p.kind.is_library())
{
return Err(
crate::errors::custom("Cannot execute a library package. Only programs can be executed.").into()
);
}
let network = get_network(&self.env_override.network)?;
match network {
NetworkName::TestnetV0 => handle_execute::<AleoTestnetV0>(self, context, network, input),
NetworkName::MainnetV0 => {
#[cfg(feature = "only_testnet")]
panic!("Mainnet chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
handle_execute::<AleoV0>(self, context, network, input)
}
NetworkName::CanaryV0 => {
#[cfg(feature = "only_testnet")]
panic!("Canary chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
handle_execute::<AleoCanaryV0>(self, context, network, input)
}
}
}
}
fn handle_execute<A: Aleo>(
command: LeoExecute,
context: Context,
network: NetworkName,
package: Option<Package>,
) -> Result<<LeoExecute as Command>::Output> {
let private_key = get_private_key(&command.key_override.private_key)?;
let address = Address::<A::Network>::try_from(&private_key)
.map_err(|e| crate::errors::custom(format!("Failed to parse address: {e}")))?;
let endpoint = get_endpoint(&command.env_override.endpoint)?;
let is_devnet = get_is_devnet(command.consensus_override.devnet);
let consensus_heights = command
.consensus_override
.consensus_heights
.clone()
.unwrap_or_else(|| get_consensus_heights(network, is_devnet));
validate_consensus_heights(&consensus_heights)
.map_err(|e| crate::errors::custom(format!("Invalid consensus heights: {e}")))?;
let consensus_heights_string = consensus_heights.iter().format(",").to_string();
println!(
"\nπ’ Using the following consensus heights: {consensus_heights_string}\n To override, pass in `--consensus-heights` or override the environment variable `CONSENSUS_VERSION_HEIGHTS`.\n"
);
#[allow(unsafe_code)]
unsafe {
std::env::set_var("CONSENSUS_VERSION_HEIGHTS", consensus_heights_string);
}
let (program_name, function_name) = match command.name.split_once('/').or_else(|| command.name.split_once("::")) {
Some((program_name, function_name)) => (program_name.to_string(), function_name.to_string()),
None => match &package {
Some(package) => (
format!(
"{}",
package.compilation_units.last().expect("There must be at least one program in a Leo package").name
),
command.name,
),
None => {
return Err(crate::errors::custom(format!(
"Running `leo execute {} ...`, without an explicit program name requires that your current working directory is a valid Leo project.",
command.name
)).into());
}
},
};
let program_id = ProgramID::<A::Network>::from_str(&program_name)
.map_err(|e| crate::errors::custom(format!("Failed to parse program name: {e}")))?;
let function_id = Identifier::<A::Network>::from_str(&function_name)
.map_err(|e| crate::errors::custom(format!("Failed to parse function name: {e}")))?;
let programs = if let Some(package) = &package {
package
.compilation_units
.iter()
.clone()
.filter(|unit| !unit.kind.is_library())
.map(|unit| {
let program_id = ProgramID::<A::Network>::from_str(&format!("{}", unit.name))
.map_err(|e| crate::errors::custom(format!("Failed to parse program ID: {e}")))?;
match &unit.data {
ProgramData::Bytecode(bytecode) => Ok((program_id, bytecode.to_string(), unit.edition)),
ProgramData::SourcePath { .. } => {
let bytecode_path = package.unit_bytecode_path(&unit.name.to_string());
let bytecode = std::fs::read_to_string(&bytecode_path).map_err(|e| {
crate::errors::custom(format!(
"Failed to read bytecode at {}: {e}",
bytecode_path.display()
))
})?;
Ok((program_id, bytecode, unit.edition))
}
}
})
.collect::<Result<Vec<_>>>()?
} else {
Vec::new()
};
let mut programs = programs
.into_iter()
.map(|(_, bytecode, edition)| {
let program = snarkvm::prelude::Program::<A::Network>::from_str(&bytecode)
.map_err(|e| crate::errors::custom(format!("Failed to parse program: {e}")))?;
Ok((program, edition))
})
.collect::<Result<Vec<_>>>()?;
let is_local = programs.iter().any(|(program, _)| program.id() == &program_id);
if is_local {
let program = &programs
.iter()
.find(|(program, _)| program.id() == &program_id)
.expect("Program should exist since it is local")
.0;
if program.contains_view(&function_id) {
return Err(crate::errors::custom(format!(
"`{function_name}` is a `view fn`; views are read-only and do not produce a transaction, \
so they cannot be executed."
))
.into());
}
if !program.contains_function(&function_id) {
return Err(crate::errors::custom(format!(
"Function `{function_name}` does not exist in program `{program_name}`."
))
.into());
}
}
let inputs =
command.inputs.into_iter().map(|string| parse_input(&string, &private_key)).collect::<Result<Vec<_>>>()?;
let (priority_fee, record) =
parse_fee_options(&private_key, &command.fee_options, 1)?.into_iter().next().unwrap_or((None, None));
let consensus_version = get_consensus_version(
&command.extra.consensus_version,
&endpoint,
network,
&consensus_heights,
&context,
command.env_override.network_retries,
)?;
let config = Some(Config {
address: address.to_string(),
network: network.to_string(),
endpoint: Some(endpoint.clone()),
consensus_version: Some(consensus_version as u8),
});
print_execution_plan::<A::Network>(
&private_key,
&address,
&endpoint,
&network,
&program_name,
&function_name,
is_local,
priority_fee.unwrap_or(0),
record.is_some(),
&command.action,
consensus_version,
&check_task_for_warnings(
&endpoint,
network,
&programs,
consensus_version,
command.env_override.network_retries,
),
command.skip_execute_proof,
);
if !confirm("Do you want to proceed with execution?", command.extra.yes)? {
println!("β Execution aborted.");
return Ok(ExecuteOutput::default());
}
let rng = &mut rand::rng();
let vm = VM::from(ConsensusStore::<A::Network, ConsensusMemory<A::Network>>::open(StorageMode::Production)?)?;
let query = SnarkVMQuery::<A::Network, BlockMemory<A::Network>>::from(
endpoint
.parse::<Uri>()
.map_err(|e| crate::errors::custom(format!("Failed to parse endpoint URI '{endpoint}': {e}")))?,
);
if !is_local {
println!("β¬οΈ Downloading {program_name} and its dependencies from {endpoint}...");
programs = load_latest_programs_from_network(
&context,
program_id,
network,
&endpoint,
command.env_override.network_retries,
)?;
};
println!("\nβAdding programs to the VM in the following order:");
let programs_and_editions = programs
.into_iter()
.map(|(program, edition)| {
print_program_source(&program.id().to_string(), edition);
let edition = edition.unwrap_or(LOCAL_PROGRAM_DEFAULT_EDITION);
(program, edition)
})
.collect::<Vec<_>>();
vm.process().lock().add_programs_with_editions(&programs_and_editions)?;
if !command.with.is_empty() {
load_extra_programs_into_vm::<A::Network>(
&command.with,
&vm,
&context,
network,
Some(&endpoint),
command.env_override.network_retries,
)?;
}
let authorization = if command.skip_execute_proof {
println!("\nβοΈ Generating transaction WITHOUT a proof for {program_name}/{function_name}...");
vm.process()
.authorize::<A, _>(&private_key, &program_name, &function_name, inputs.iter(), rng)
.map_err(|e| anyhow::anyhow!("{e}"))?
} else {
println!("\nβοΈ Executing {program_name}/{function_name}...");
vm.authorize(&private_key, &program_name, &function_name, inputs.iter(), rng)
.map_err(|e| anyhow::anyhow!("{e}"))?
};
let (estimated_cost, (est_storage, est_exec)) =
execution_cost_for_authorization(vm.process(), &authorization, consensus_version)?;
let stats = print_execution_cost_summary(&program_name, est_storage, est_exec, priority_fee);
let (output_name, output, response) = if command.skip_execute_proof {
let state_root = retry_network_call(command.env_override.network_retries, || query.current_state_root())?;
let execution = Execution::from(authorization.transitions().values().cloned(), state_root, None)?;
let (cost, _) = execution_cost(vm.process(), &execution, consensus_version)?;
let id = authorization.to_execution_id()?;
let fee_authorization =
authorize_fee::<A, _>(&vm, &private_key, record, cost, priority_fee.unwrap_or(0), id, rng)?;
let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?;
let transaction = Transaction::from_execution(execution, Some(fee))?;
let response = vm.process().evaluate::<A>(authorization).map_err(|e| anyhow::anyhow!("{e}"))?;
("transaction", Box::new(transaction), response)
} else {
let is_fee_required = !(authorization.is_split() || authorization.is_upgrade());
let is_priority_fee_declared = priority_fee.unwrap_or(0) > 0;
let fee_authorization = if is_fee_required || is_priority_fee_declared {
let execution_id = authorization.to_execution_id()?;
Some(authorize_fee::<A, _>(
&vm,
&private_key,
record,
estimated_cost,
priority_fee.unwrap_or(0),
execution_id,
rng,
)?)
} else {
None
};
let (transaction, response) =
vm.execute_authorization_with_response(authorization, fee_authorization, Some(&query), rng)?;
("transaction", Box::new(transaction), response)
};
let transaction = output.clone();
if command.action.print {
let transaction_json = serde_json::to_string_pretty(&output)
.map_err(|e| crate::errors::custom(format!("Failed to serialize transaction: {e}")))?;
println!("π¨οΈ Printing execution for {output_name}\n{transaction_json}");
}
if let Some(path) = &command.action.save {
std::fs::create_dir_all(path).map_err(|e| crate::errors::custom(format!("Failed to create directory: {e}")))?;
let file_path = PathBuf::from(path).join(format!("{output_name}.execution.json"));
println!("πΎ Saving execution for {output_name} at {}", file_path.display());
let transaction_json = serde_json::to_string_pretty(&output)
.map_err(|e| crate::errors::custom(format!("Failed to serialize transaction: {e}")))?;
std::fs::write(file_path, transaction_json)
.map_err(|e| crate::errors::custom(format!("Failed to write transaction to file: {e}")))?;
}
let mut broadcast_stats = None;
let outputs: Vec<String> = response.outputs().iter().map(|o| o.to_string()).collect();
match outputs.len() {
0 => (),
1 => println!("\nβ‘οΈ Output\n"),
_ => println!("\nβ‘οΈ Outputs\n"),
};
for o in &outputs {
println!(" β’ {o}");
}
println!();
if command.action.broadcast {
println!("π‘ Broadcasting execution for {program_name}...");
let mut fee_id = None;
let mut fee_transaction_id = None;
if let Some(fee) = transaction.fee_transition() {
if !confirm_fee(&fee, &private_key, &address, &endpoint, network, &context, command.extra.yes)? {
println!("β Execution aborted.");
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, command.env_override.network_retries)?;
let (message, status) =
handle_broadcast(&format!("{endpoint}/{network}/transaction/broadcast"), &transaction, &program_name)?;
match status {
200..=299 => {
let tx_status = check_transaction::check_transaction_with_message(
&id,
fee_id.as_deref(),
&endpoint,
network,
height_before + 1,
command.extra.max_wait,
command.extra.blocks_to_check,
command.env_override.network_retries,
)?;
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,
});
}
_ => {
println!("β Failed to broadcast execution: {message}.");
}
}
}
Ok(ExecuteOutput {
config,
program: program_name.clone(),
function: function_name.clone(),
outputs,
transaction_id: transaction.id().to_string(),
stats: Some(stats),
broadcast: broadcast_stats,
})
}
fn authorize_fee<A: Aleo, R: Rng + CryptoRng>(
vm: &VM<A::Network, ConsensusMemory<A::Network>>,
private_key: &PrivateKey<A::Network>,
record: Option<Record<A::Network, Plaintext<A::Network>>>,
base_fee: u64,
priority_fee: u64,
execution_id: Field<A::Network>,
rng: &mut R,
) -> Result<Authorization<A::Network>> {
match record {
None => vm.authorize_fee_public(private_key, base_fee, priority_fee, execution_id, rng),
Some(record) => vm.authorize_fee_private(private_key, record, base_fee, priority_fee, execution_id, rng),
}
.map_err(Into::into)
}
fn check_task_for_warnings<N: Network>(
endpoint: &str,
network: NetworkName,
programs: &[(Program<N>, Option<u16>)],
consensus_version: ConsensusVersion,
network_retries: u32,
) -> Vec<String> {
let mut warnings = Vec::new();
for (program, _) in programs {
if let Ok(remote_program) =
fetch_program_from_network(&program.id().to_string(), endpoint, network, network_retries)
{
let remote_program = match Program::<N>::from_str(&remote_program) {
Ok(program) => program,
Err(e) => {
warnings.push(format!("Could not parse '{}' from the network. Error: {e}", program.id()));
continue;
}
};
if remote_program != *program {
warnings.push(format!(
"The program '{}' on the network does not match the local copy. If you have a local dependency, you may use the `--no-local` flag to use the network version instead.",
program.id()
));
}
} else {
warnings.push(format!(
"The program '{}' does not exist on the network. You may use `leo deploy --broadcast` to deploy it.",
program.id()
));
}
}
if let Err(e) = check_consensus_version_mismatch(consensus_version, endpoint, network, network_retries) {
warnings.push(format!("{e}. In some cases, the execution may fail"));
}
warnings
}
#[allow(clippy::too_many_arguments)]
fn print_execution_plan<N: Network>(
private_key: &PrivateKey<N>,
address: &Address<N>,
endpoint: &str,
network: &NetworkName,
program_name: &str,
function_name: &str,
is_local: bool,
priority_fee: u64,
fee_record: bool,
action: &TransactionAction,
consensus_version: ConsensusVersion,
warnings: &[String],
skip_execute_proof: bool,
) {
println!("\n{}", "π Execution Plan Summary".bold().underline());
println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ".dimmed());
println!("{}", "π§ Configuration:".bold());
println!(" {:20}{}", "Private Key:".cyan(), format!("{}...", &private_key.to_string()[..24]).yellow());
println!(" {:20}{}", "Address:".cyan(), format!("{}...", &address.to_string()[..24]).yellow());
println!(" {:20}{}", "Endpoint:", endpoint.yellow());
println!(" {:20}{}", "Network:", network.to_string().yellow());
println!(" {:20}{}", "Consensus Version:", (consensus_version as u8).to_string().yellow());
println!("\n{}", "π― Execution Target:".bold());
println!(" {:16}{}", "Program:", program_name.cyan());
println!(" {:16}{}", "Function:", function_name.cyan());
println!(" {:16}{}", "Source:", if is_local { "local" } else { "remote" });
println!("\n{}", "πΈ Fee Info:".bold());
println!(" {:16}{}", "Priority Fee:", format!("{priority_fee} ΞΌcredits").green());
println!(" {:16}{}", "Fee Record:", if fee_record { "yes" } else { "no (public fee)" });
println!("\n{}", "βοΈ Actions:".bold());
if !is_local {
println!(" - Program and its dependencies will be downloaded from the network.");
}
if skip_execute_proof {
println!(" - A transaction will be generated, WITHOUT a proof.");
}
if 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 {
println!(" - Transaction will be saved to {}", path.bold());
} else {
println!(" - Transaction will NOT be saved to a file.");
}
if action.broadcast {
println!(" - Transaction will be broadcast to {}", endpoint.bold());
} else {
println!(" - Transaction will NOT be broadcast to the network.");
}
if !warnings.is_empty() {
println!("\n{}", "β οΈ Warnings:".bold().red());
for warning in warnings {
println!(" β’ {}", warning.dimmed());
}
}
println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ\n".dimmed());
}
fn print_execution_cost_summary(
program_name: &str,
storage_cost: u64,
execution_cost: u64,
priority_fee: Option<u64>,
) -> ExecutionStats {
use colored::*;
let priority = priority_fee.unwrap_or(0);
let total = storage_cost + execution_cost + priority;
let stats = ExecutionStats { storage_cost, execution_cost, priority_fee: priority, total_cost: total };
println!("\n{} {}", "π Execution Cost Summary for".bold(), program_name.bold());
println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ".dimmed());
print!("{stats}");
println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ".dimmed());
stats
}