1use super::*;
18
19use check_transaction::TransactionStatus;
20use leo_package::{NetworkName, Package, ProgramData};
21
22use aleo_std::StorageMode;
23use clap::Parser;
24use colored::*;
25use snarkvm::prelude::{Execution, Network};
26use std::{convert::TryFrom, path::PathBuf};
27
28#[cfg(not(feature = "only_testnet"))]
29use snarkvm::circuit::{AleoCanaryV0, AleoV0};
30use snarkvm::{
31 circuit::{Aleo, AleoTestnetV0},
32 prelude::{
33 ConsensusVersion,
34 Identifier,
35 ProgramID,
36 VM,
37 Value,
38 execution_cost_v1,
39 execution_cost_v2,
40 query::Query as SnarkVMQuery,
41 store::{ConsensusStore, helpers::memory::ConsensusMemory},
42 },
43};
44
45#[derive(Parser, Debug)]
47pub struct LeoExecute {
48 #[clap(
49 name = "NAME",
50 help = "The name of the function to execute, e.g `helloworld.aleo/main` or `main`.",
51 default_value = "main"
52 )]
53 name: String,
54 #[clap(name = "INPUTS", help = "The inputs to the program.")]
55 inputs: Vec<String>,
56 #[clap(flatten)]
57 pub(crate) fee_options: FeeOptions,
58 #[clap(flatten)]
59 pub(crate) action: TransactionAction,
60 #[clap(flatten)]
61 pub(crate) env_override: EnvOptions,
62 #[clap(flatten)]
63 pub(crate) extra: ExtraOptions,
64 #[clap(flatten)]
65 build_options: BuildOptions,
66}
67
68impl Command for LeoExecute {
69 type Input = Option<Package>;
70 type Output = ();
71
72 fn log_span(&self) -> Span {
73 tracing::span!(tracing::Level::INFO, "Leo")
74 }
75
76 fn prelude(&self, context: Context) -> Result<Self::Input> {
77 let path = context.dir()?;
79 let home_path = context.home()?;
81 if Package::from_directory_no_graph(path, home_path).is_ok() {
83 let package = LeoBuild {
84 env_override: self.env_override.clone(),
85 options: {
86 let mut options = self.build_options.clone();
87 options.no_cache = true;
88 options
89 },
90 }
91 .execute(context)?;
92 Ok(Some(package))
94 } else {
95 Ok(None)
96 }
97 }
98
99 fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output> {
100 let network = context.get_network(&self.env_override.network)?.parse()?;
102 match network {
104 NetworkName::TestnetV0 => handle_execute::<AleoTestnetV0>(self, context, network, input),
105 NetworkName::MainnetV0 => {
106 #[cfg(feature = "only_testnet")]
107 panic!("Mainnet chosen with only_testnet feature");
108 #[cfg(not(feature = "only_testnet"))]
109 return handle_execute::<AleoV0>(self, context, network, input);
110 }
111 NetworkName::CanaryV0 => {
112 #[cfg(feature = "only_testnet")]
113 panic!("Canary chosen with only_testnet feature");
114 #[cfg(not(feature = "only_testnet"))]
115 return handle_execute::<AleoCanaryV0>(self, context, network, input);
116 }
117 }
118 }
119}
120
121fn handle_execute<A: Aleo>(
123 command: LeoExecute,
124 context: Context,
125 network: NetworkName,
126 package: Option<Package>,
127) -> Result<<LeoExecute as Command>::Output> {
128 let private_key = context.get_private_key(&command.env_override.private_key)?;
130 let address = Address::<A::Network>::try_from(&private_key)
131 .map_err(|e| CliError::custom(format!("Failed to parse address: {e}")))?;
132
133 let endpoint = context.get_endpoint(&command.env_override.endpoint)?;
135
136 let (program_name, function_name) = match command.name.split_once('/') {
139 Some((program_name, function_name)) => (program_name.to_string(), function_name.to_string()),
140 None => match &package {
141 Some(package) => (
142 package.programs.last().expect("There must be at least one program in a Leo package").name.to_string(),
143 command.name,
144 ),
145 None => {
146 return Err(CliError::custom(format!(
147 "Running `leo execute {} ...`, without an explicit program name requires that your current working directory is a valid Leo project.",
148 command.name
149 )).into());
150 }
151 },
152 };
153
154 let program_id = ProgramID::<A::Network>::from_str(&program_name)
156 .map_err(|e| CliError::custom(format!("Failed to parse program name: {e}")))?;
157 let function_id = Identifier::<A::Network>::from_str(&function_name)
159 .map_err(|e| CliError::custom(format!("Failed to parse function name: {e}")))?;
160
161 let programs = if let Some(package) = &package {
164 let build_directory = package.build_directory();
166 let imports_directory = package.imports_directory();
167 let source_directory = package.source_directory();
168 package
170 .programs
171 .iter()
172 .clone()
173 .map(|program| {
174 let program_id = ProgramID::<A::Network>::from_str(&format!("{}.aleo", program.name))
175 .map_err(|e| CliError::custom(format!("Failed to parse program ID: {e}")))?;
176 match &program.data {
177 ProgramData::Bytecode(bytecode) => Ok((program_id, bytecode.to_string())),
178 ProgramData::SourcePath(path) => {
179 let bytecode_path = if path.as_path() == source_directory.join("main.leo") {
181 build_directory.join("main.aleo")
182 } else {
183 imports_directory.join(format!("{}.aleo", program.name))
184 };
185 let bytecode = std::fs::read_to_string(&bytecode_path).map_err(|e| {
187 CliError::custom(format!("Failed to read bytecode at {}: {e}", bytecode_path.display()))
188 })?;
189 Ok((program_id, bytecode))
191 }
192 }
193 })
194 .collect::<Result<Vec<_>>>()?
195 } else {
196 Vec::new()
197 };
198
199 let mut programs = programs
201 .into_iter()
202 .map(|(name, bytecode)| {
203 let program = snarkvm::prelude::Program::<A::Network>::from_str(&bytecode)
205 .map_err(|e| CliError::custom(format!("Failed to parse program: {e}")))?;
206 Ok((name, program))
208 })
209 .collect::<Result<Vec<_>>>()?;
210
211 let is_local = programs.iter().any(|(name, _)| name == &program_id);
213
214 if is_local {
216 let program =
217 &programs.iter().find(|(name, _)| name == &program_id).expect("Program should exist since it is local").1;
218 if !program.contains_function(&function_id) {
219 return Err(CliError::custom(format!(
220 "Function `{function_name}` does not exist in program `{program_name}`."
221 ))
222 .into());
223 }
224 }
225
226 let inputs = command
227 .inputs
228 .into_iter()
229 .map(|input| {
230 Value::from_str(&input).map_err(|e| CliError::custom(format!("Failed to parse input: {e}")).into())
231 })
232 .collect::<Result<Vec<_>>>()?;
233
234 let (_, priority_fee, record) =
236 parse_fee_options(&private_key, &command.fee_options, 1)?.into_iter().next().unwrap_or((None, None, None));
237
238 let consensus_version =
240 get_consensus_version::<A::Network>(&command.extra.consensus_version, &endpoint, network, &context)?;
241
242 print_execution_plan::<A::Network>(
244 &private_key,
245 &address,
246 &endpoint,
247 &network,
248 &program_name,
249 &function_name,
250 is_local,
251 priority_fee.unwrap_or(0),
252 record.is_some(),
253 &command.action,
254 consensus_version,
255 );
256
257 if !confirm("Do you want to proceed with execution?", command.extra.yes)? {
259 println!("β Execution aborted.");
260 return Ok(());
261 }
262
263 let rng = &mut rand::thread_rng();
265
266 let vm = VM::from(ConsensusStore::<A::Network, ConsensusMemory<A::Network>>::open(StorageMode::Production)?)?;
268
269 let query = SnarkVMQuery::from(&endpoint);
271
272 if !is_local {
275 println!("β¬οΈ Downloading {program_name} and its dependencies from {endpoint}...");
276 programs = load_programs_from_network(&context, program_id, network, &endpoint)?;
277 };
278
279 println!("Adding programs to the VM ...");
281 for (_, program) in programs {
282 vm.process().write().add_program(&program)?;
283 }
284
285 let transaction = vm.execute(
287 &private_key,
288 (&program_name, &function_name),
289 inputs.iter(),
290 record,
291 priority_fee.unwrap_or(0),
292 Some(query),
293 rng,
294 )?;
295
296 print_execution_stats::<A::Network>(
298 &vm,
299 &program_name,
300 transaction.execution().expect("Expected execution"),
301 priority_fee,
302 consensus_version,
303 )?;
304
305 if command.action.print {
309 let transaction_json = serde_json::to_string_pretty(&transaction)
310 .map_err(|e| CliError::custom(format!("Failed to serialize transaction: {e}")))?;
311 println!("π¨οΈ Printing execution for {program_name}\n{transaction_json}");
312 }
313
314 if let Some(path) = &command.action.save {
318 std::fs::create_dir_all(path).map_err(|e| CliError::custom(format!("Failed to create directory: {e}")))?;
320 let file_path = PathBuf::from(path).join(format!("{program_name}.execution.json"));
322 println!("πΎ Saving execution for {program_name} at {}", file_path.display());
323 let transaction_json = serde_json::to_string_pretty(&transaction)
324 .map_err(|e| CliError::custom(format!("Failed to serialize transaction: {e}")))?;
325 std::fs::write(file_path, transaction_json)
326 .map_err(|e| CliError::custom(format!("Failed to write transaction to file: {e}")))?;
327 }
328
329 if command.action.broadcast {
331 println!("π‘ Broadcasting execution for {program_name}...");
332 let fee = transaction.fee_transition().expect("Expected a fee in the transaction");
334 if !confirm_fee(&fee, &private_key, &address, &endpoint, network, &context, command.extra.yes)? {
335 println!("β Execution aborted.");
336 return Ok(());
337 }
338 let fee_id = fee.id().to_string();
339 let id = transaction.id().to_string();
340 let height_before = check_transaction::current_height(&endpoint, network)?;
341 let response =
343 handle_broadcast(&format!("{}/{}/transaction/broadcast", endpoint, network), &transaction, &program_name)?;
344
345 let fail = |msg| {
346 println!("β Failed to broadcast execution: {}.", msg);
347 Ok(())
348 };
349
350 match response.status() {
351 200..=299 => {
352 let status = check_transaction::check_transaction_with_message(
353 &id,
354 Some(&fee_id),
355 &endpoint,
356 network,
357 height_before + 1,
358 command.extra.max_wait,
359 command.extra.blocks_to_check,
360 )?;
361 if status == Some(TransactionStatus::Accepted) {
362 println!("β
Execution confirmed!");
363 }
364 }
365 _ => {
366 let error_message =
367 response.into_string().map_err(|e| CliError::custom(format!("Failed to read response: {e}")))?;
368 return fail(&error_message);
369 }
370 }
371 }
372
373 Ok(())
374}
375
376#[allow(clippy::too_many_arguments)]
378fn print_execution_plan<N: Network>(
379 private_key: &PrivateKey<N>,
380 address: &Address<N>,
381 endpoint: &str,
382 network: &NetworkName,
383 program_name: &str,
384 function_name: &str,
385 is_local: bool,
386 priority_fee: u64,
387 fee_record: bool,
388 action: &TransactionAction,
389 consensus_version: ConsensusVersion,
390) {
391 println!("\n{}", "π Execution Plan Summary".bold().underline());
392 println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ".dimmed());
393
394 println!("{}", "π§ Configuration:".bold());
395 println!(" {:20}{}", "Private Key:".cyan(), format!("{}...", &private_key.to_string()[..24]).yellow());
396 println!(" {:20}{}", "Address:".cyan(), format!("{}...", &address.to_string()[..24]).yellow());
397 println!(" {:20}{}", "Endpoint:", endpoint.yellow());
398 println!(" {:20}{}", "Network:", network.to_string().yellow());
399 println!(" {:20}{}", "Consensus Version:", (consensus_version as u8).to_string().yellow());
400
401 println!("\n{}", "π― Execution Target:".bold());
402 println!(" {:16}{}", "Program:", program_name.cyan());
403 println!(" {:16}{}", "Function:", function_name.cyan());
404 println!(" {:16}{}", "Source:", if is_local { "local" } else { "remote" });
405
406 println!("\n{}", "πΈ Fee Info:".bold());
407 println!(" {:16}{}", "Priority Fee:", format!("{} ΞΌcredits", priority_fee).green());
408 println!(" {:16}{}", "Fee Record:", if fee_record { "yes" } else { "no (public fee)" });
409
410 println!("\n{}", "βοΈ Actions:".bold());
411 if !is_local {
412 println!(" - Program and its dependencies will be downloaded from the network.");
413 }
414 if action.print {
415 println!(" - Transaction will be printed to the console.");
416 } else {
417 println!(" - Transaction will NOT be printed to the console.");
418 }
419 if let Some(path) = &action.save {
420 println!(" - Transaction will be saved to {}", path.bold());
421 } else {
422 println!(" - Transaction will NOT be saved to a file.");
423 }
424 if action.broadcast {
425 println!(" - Transaction will be broadcast to {}", endpoint.bold());
426 } else {
427 println!(" - Transaction will NOT be broadcast to the network.");
428 }
429 println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ\n".dimmed());
430}
431
432fn print_execution_stats<N: Network>(
435 vm: &VM<N, ConsensusMemory<N>>,
436 program_name: &str,
437 execution: &Execution<N>,
438 priority_fee: Option<u64>,
439 consensus_version: ConsensusVersion,
440) -> Result<()> {
441 use colored::*;
442
443 let (base_fee, (storage_cost, execution_cost)) = if consensus_version == ConsensusVersion::V1 {
445 execution_cost_v1(&vm.process().read(), execution)?
446 } else {
447 execution_cost_v2(&vm.process().read(), execution)?
448 };
449
450 let base_cr = base_fee as f64 / 1_000_000.0;
451 let prio_cr = priority_fee.unwrap_or(0) as f64 / 1_000_000.0;
452 let total_cr = base_cr + prio_cr;
453
454 println!("\n{} {}", "π Execution Summary for".bold(), program_name.bold());
456 println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ".dimmed());
457
458 println!("{}", "π° Cost Breakdown (credits)".bold());
460 println!(" {:22}{}{:.6}", "Transaction Storage:".cyan(), "".yellow(), storage_cost as f64 / 1_000_000.0);
461 println!(" {:22}{}{:.6}", "Onβchain Execution:".cyan(), "".yellow(), execution_cost as f64 / 1_000_000.0);
462 println!(" {:22}{}{:.6}", "Priority Fee:".cyan(), "".yellow(), prio_cr);
463 println!(" {:22}{}{:.6}", "Total Fee:".cyan(), "".yellow(), total_cr);
464
465 println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ".dimmed());
467 Ok(())
468}