1use super::*;
18
19use super::common::load_extra_programs_into_vm;
20use leo_ast::{NetworkName, TEST_PRIVATE_KEY};
21use leo_package::{Package, ProgramData};
22
23use aleo_std::StorageMode;
24
25use clap::Parser;
26use std::path::PathBuf;
27
28#[cfg(not(feature = "only_testnet"))]
29use snarkvm::circuit::{AleoCanaryV0, AleoV0};
30use snarkvm::{
31 circuit::{Aleo, AleoTestnetV0},
32 prelude::{
33 Identifier,
34 ProgramID,
35 VM,
36 store::{ConsensusStore, helpers::memory::ConsensusMemory},
37 },
38};
39
40#[derive(Parser, Debug)]
42pub struct LeoRun {
43 #[clap(
44 name = "NAME",
45 help = "The name of the function to execute, e.g `helloworld.aleo::main` or `main`.",
46 default_value = "main"
47 )]
48 pub(crate) name: String,
49 #[clap(
50 name = "INPUTS",
51 help = "The program inputs e.g. `1u32`, `record1...` (record ciphertext), or `{ owner: ...}` "
52 )]
53 pub(crate) inputs: Vec<String>,
54 #[clap(flatten)]
55 pub(crate) env_override: EnvOptions,
56 #[clap(flatten)]
57 pub(crate) key_override: PrivateKeyOptions,
58 #[clap(flatten)]
59 pub(crate) build_options: BuildOptions,
60 #[clap(
61 long,
62 value_name = "DIR",
63 help = "Directory containing local .aleo imports when --path points to an Aleo bytecode file; other imports are fetched from the network"
64 )]
65 pub(crate) imports_dir: Option<PathBuf>,
66 #[clap(
67 long = "with",
68 help = "Additional programs to load into the VM (comma-separated). \
69 If a path exists locally, it is read as an .aleo bytecode file; \
70 otherwise it is fetched from the network endpoint.",
71 value_delimiter = ','
72 )]
73 pub(crate) with: Vec<String>,
74}
75
76impl Command for LeoRun {
77 type Input = Option<Package>;
78 type Output = RunOutput;
79
80 fn log_span(&self) -> Span {
81 tracing::span!(tracing::Level::INFO, "Leo")
82 }
83
84 fn prelude(&self, context: Context) -> Result<Self::Input> {
85 let path = context.dir()?;
87 let home_path = context.home()?;
89 if path.extension().and_then(|extension| extension.to_str()) == Some("aleo") {
90 if context.package_filter.is_some() {
91 return Err(crate::errors::custom("`--package` cannot be used with an Aleo bytecode file.").into());
92 }
93 let network = match get_network(&self.env_override.network) {
94 Ok(network) => network,
95 Err(_) => {
96 println!("⚠️ No network specified, defaulting to 'testnet'.");
97 NetworkName::TestnetV0
98 }
99 };
100 let endpoint = match get_endpoint(&self.env_override.endpoint) {
101 Ok(endpoint) => endpoint,
102 Err(_) => {
103 println!("⚠️ No endpoint specified, defaulting to '{DEFAULT_ENDPOINT}'.");
104 DEFAULT_ENDPOINT.to_string()
105 }
106 };
107 return Package::from_aleo_file(
108 path,
109 home_path,
110 self.imports_dir.as_deref(),
111 self.build_options.no_cache,
112 self.build_options.no_local,
113 Some(network),
114 Some(&endpoint),
115 self.env_override.network_retries,
116 )
117 .map(Some);
118 }
119 if self.imports_dir.is_some() {
120 return Err(
121 crate::errors::custom("`--imports-dir` requires `--path` to point to an Aleo bytecode file.").into()
122 );
123 }
124 if Package::from_directory_no_graph(
126 path,
127 home_path,
128 self.env_override.network,
129 self.env_override.endpoint.as_deref(),
130 self.env_override.network_retries,
131 )
132 .is_ok()
133 {
134 let package =
135 LeoBuild { env_override: self.env_override.clone(), options: self.build_options.clone(), rename: None }
136 .execute(context)?;
137 Ok(Some(package))
139 } else {
140 Ok(None)
141 }
142 }
143
144 fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output> {
145 if let Some(package) = &input
147 && package.compilation_units.last().is_some_and(|p| p.kind.is_library())
148 {
149 return Err(crate::errors::custom("Cannot run a library package. Only programs can be run.").into());
150 }
151
152 let network = match get_network(&self.env_override.network) {
154 Ok(network) => network,
155 Err(_) => {
156 println!("⚠️ No network specified, defaulting to 'testnet'.");
157 NetworkName::TestnetV0
158 }
159 };
160
161 match network {
163 NetworkName::TestnetV0 => handle_run::<AleoTestnetV0>(self, context, network, input),
164 NetworkName::MainnetV0 => {
165 #[cfg(feature = "only_testnet")]
166 panic!("Mainnet chosen with only_testnet feature");
167 #[cfg(not(feature = "only_testnet"))]
168 handle_run::<AleoV0>(self, context, network, input)
169 }
170 NetworkName::CanaryV0 => {
171 #[cfg(feature = "only_testnet")]
172 panic!("Canary chosen with only_testnet feature");
173 #[cfg(not(feature = "only_testnet"))]
174 handle_run::<AleoCanaryV0>(self, context, network, input)
175 }
176 }
177 }
178}
179
180fn handle_run<A: Aleo>(
182 command: LeoRun,
183 context: Context,
184 network: NetworkName,
185 package: Option<Package>,
186) -> Result<<LeoRun as Command>::Output> {
187 let private_key = match get_private_key::<A::Network>(&command.key_override.private_key) {
189 Ok(private_key) => private_key,
190 Err(_) => {
191 println!("⚠️ No valid private key specified, defaulting to '{TEST_PRIVATE_KEY}'.");
192 PrivateKey::<A::Network>::from_str(TEST_PRIVATE_KEY).expect("Failed to parse the test private key")
193 }
194 };
195
196 let (program_name, function_name) = match command.name.split_once('/').or_else(|| command.name.split_once("::")) {
199 Some((program_name, function_name)) => (program_name.to_string(), function_name.to_string()),
200 None => match &package {
201 Some(package) => (
202 package
203 .compilation_units
204 .last()
205 .expect("There must be at least one program in a Leo package")
206 .name
207 .to_string(),
208 command.name,
209 ),
210 None => {
211 return Err(crate::errors::custom(format!(
212 "Running `leo execute {} ...`, without an explicit program name requires that your current working directory is a valid Leo project.",
213 command.name
214 )).into());
215 }
216 },
217 };
218
219 let program_id = ProgramID::<A::Network>::from_str(&program_name)
221 .map_err(|e| crate::errors::custom(format!("Failed to parse program name: {e}")))?;
222 let function_id = Identifier::<A::Network>::from_str(&function_name)
224 .map_err(|e| crate::errors::custom(format!("Failed to parse function name: {e}")))?;
225
226 let programs = if let Some(package) = &package {
229 package
231 .compilation_units
232 .iter()
233 .clone()
234 .filter(|unit| !unit.kind.is_library())
235 .map(|unit| {
236 let program_id = ProgramID::<A::Network>::from_str(&format!("{}", unit.name))
237 .map_err(|e| crate::errors::custom(format!("Failed to parse program ID: {e}")))?;
238 match &unit.data {
239 ProgramData::Bytecode(bytecode) => Ok((program_id, bytecode.to_string(), unit.edition)),
240 ProgramData::SourcePath { .. } => {
241 let bytecode_path = package.unit_bytecode_path(&unit.name.to_string());
243 let bytecode = std::fs::read_to_string(&bytecode_path).map_err(|e| {
245 crate::errors::custom(format!(
246 "Failed to read bytecode at {}: {e}",
247 bytecode_path.display()
248 ))
249 })?;
250 Ok((program_id, bytecode, unit.edition))
252 }
253 }
254 })
255 .collect::<Result<Vec<_>>>()?
256 } else {
257 Vec::new()
258 };
259
260 let mut programs = programs
262 .into_iter()
263 .map(|(_, bytecode, edition)| {
264 let program = snarkvm::prelude::Program::<A::Network>::from_str(&bytecode)
266 .map_err(|e| crate::errors::custom(format!("Failed to parse program: {e}")))?;
267 Ok((program, edition))
269 })
270 .collect::<Result<Vec<_>>>()?;
271
272 let is_local = programs.iter().any(|(program, _)| program.id() == &program_id);
274
275 if is_local {
277 let program = &programs
278 .iter()
279 .find(|(program, _)| program.id() == &program_id)
280 .expect("Program should exist since it is local")
281 .0;
282 if program.contains_view(&function_id) {
287 return Err(crate::errors::custom(format!(
288 "`{function_name}` is a `view fn`; views are read-only and cannot be simulated by `leo run` \
289 (which evaluates against an empty in-memory finalize store)."
290 ))
291 .into());
292 }
293 if !program.contains_function(&function_id) {
294 return Err(crate::errors::custom(format!(
295 "Function `{function_name}` does not exist in program `{program_name}`."
296 ))
297 .into());
298 }
299 }
300
301 let inputs =
302 command.inputs.into_iter().map(|string| parse_input(&string, &private_key)).collect::<Result<Vec<_>>>()?;
303
304 let rng = &mut rand::rng();
306
307 let vm = VM::from(ConsensusStore::<A::Network, ConsensusMemory<A::Network>>::open(StorageMode::Production)?)?;
309
310 if !is_local {
313 let endpoint = get_endpoint(&command.env_override.endpoint)?;
315 println!("⬇️ Downloading {program_name} and its dependencies from {endpoint}...");
316 programs = load_latest_programs_from_network(
318 &context,
319 program_id,
320 network,
321 &endpoint,
322 command.env_override.network_retries,
323 )?;
324 };
325
326 println!("\n➕Adding programs to the VM in the following order:");
328 let programs_and_editions = programs
329 .into_iter()
330 .map(|(program, edition)| {
331 print_program_source(&program.id().to_string(), edition);
332 let edition = edition.unwrap_or(LOCAL_PROGRAM_DEFAULT_EDITION);
333 (program, edition)
334 })
335 .collect::<Vec<_>>();
336 vm.process().lock().add_programs_with_editions(&programs_and_editions)?;
337
338 if !command.with.is_empty() {
340 let endpoint = get_endpoint(&command.env_override.endpoint).ok();
341 load_extra_programs_into_vm::<A::Network>(
342 &command.with,
343 &vm,
344 &context,
345 network,
346 endpoint.as_deref(),
347 command.env_override.network_retries,
348 )?;
349 }
350
351 let authorization = vm
353 .authorize(&private_key, program_id, function_id, inputs.iter(), rng)
354 .map_err(|e| crate::errors::custom(format!("Failed to authorize execution: {e}")))?;
355 let response = vm
356 .process()
357 .evaluate::<A>(authorization)
358 .map_err(|e| crate::errors::custom(format!("Failed to evaluate program: {e}")))?;
359
360 let outputs: Vec<String> = response.outputs().iter().map(|o| o.to_string()).collect();
362
363 match outputs.len() {
365 0 => (),
366 1 => println!("\n➡️ Output\n"),
367 _ => println!("\n➡️ Outputs\n"),
368 };
369 for output in &outputs {
370 println!(" • {output}");
371 }
372
373 Ok(RunOutput { program: program_id.to_string(), function: function_id.to_string(), outputs })
374}