use super::*;
use super::common::load_extra_programs_into_vm;
use leo_ast::{NetworkName, TEST_PRIVATE_KEY};
use leo_package::{Package, ProgramData};
use aleo_std::StorageMode;
use clap::Parser;
#[cfg(not(feature = "only_testnet"))]
use snarkvm::circuit::{AleoCanaryV0, AleoV0};
use snarkvm::{
circuit::{Aleo, AleoTestnetV0},
prelude::{
Identifier,
ProgramID,
VM,
store::{ConsensusStore, helpers::memory::ConsensusMemory},
},
};
#[derive(Parser, Debug)]
pub struct LeoRun {
#[clap(
name = "NAME",
help = "The name of the function to execute, e.g `helloworld.aleo::main` or `main`.",
default_value = "main"
)]
pub(crate) name: String,
#[clap(
name = "INPUTS",
help = "The program inputs e.g. `1u32`, `record1...` (record ciphertext), or `{ owner: ...}` "
)]
pub(crate) inputs: Vec<String>,
#[clap(flatten)]
pub(crate) env_override: EnvOptions,
#[clap(flatten)]
pub(crate) key_override: PrivateKeyOptions,
#[clap(flatten)]
pub(crate) 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 LeoRun {
type Input = Option<Package>;
type Output = RunOutput;
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()?;
if Package::from_directory_no_graph(
path,
home_path,
self.env_override.network,
self.env_override.endpoint.as_deref(),
self.env_override.network_retries,
)
.is_ok()
{
let package =
LeoBuild { env_override: self.env_override.clone(), options: self.build_options.clone(), 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 run a library package. Only programs can be run.").into());
}
let network = match get_network(&self.env_override.network) {
Ok(network) => network,
Err(_) => {
println!("⚠️ No network specified, defaulting to 'testnet'.");
NetworkName::TestnetV0
}
};
match network {
NetworkName::TestnetV0 => handle_run::<AleoTestnetV0>(self, context, network, input),
NetworkName::MainnetV0 => {
#[cfg(feature = "only_testnet")]
panic!("Mainnet chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
handle_run::<AleoV0>(self, context, network, input)
}
NetworkName::CanaryV0 => {
#[cfg(feature = "only_testnet")]
panic!("Canary chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
handle_run::<AleoCanaryV0>(self, context, network, input)
}
}
}
}
fn handle_run<A: Aleo>(
command: LeoRun,
context: Context,
network: NetworkName,
package: Option<Package>,
) -> Result<<LeoRun as Command>::Output> {
let private_key = match get_private_key::<A::Network>(&command.key_override.private_key) {
Ok(private_key) => private_key,
Err(_) => {
println!("⚠️ No valid private key specified, defaulting to '{TEST_PRIVATE_KEY}'.");
PrivateKey::<A::Network>::from_str(TEST_PRIVATE_KEY).expect("Failed to parse the test private key")
}
};
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) => (
package
.compilation_units
.last()
.expect("There must be at least one program in a Leo package")
.name
.to_string(),
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 cannot be simulated by `leo run` \
(which evaluates against an empty in-memory finalize store)."
))
.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 rng = &mut rand::rng();
let vm = VM::from(ConsensusStore::<A::Network, ConsensusMemory<A::Network>>::open(StorageMode::Production)?)?;
if !is_local {
let endpoint = get_endpoint(&command.env_override.endpoint)?;
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() {
let endpoint = get_endpoint(&command.env_override.endpoint).ok();
load_extra_programs_into_vm::<A::Network>(
&command.with,
&vm,
&context,
network,
endpoint.as_deref(),
command.env_override.network_retries,
)?;
}
let authorization = vm
.authorize(&private_key, program_id, function_id, inputs.iter(), rng)
.map_err(|e| crate::errors::custom(format!("Failed to authorize execution: {e}")))?;
let response = vm
.process()
.evaluate::<A>(authorization)
.map_err(|e| crate::errors::custom(format!("Failed to evaluate program: {e}")))?;
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 output in &outputs {
println!(" • {output}");
}
Ok(RunOutput { program: program_id.to_string(), function: function_id.to_string(), outputs })
}