1use super::*;
18
19use super::common::load_extra_programs_into_vm;
20use check_transaction::TransactionStatus;
21use leo_ast::NetworkName;
22use leo_package::{Package, ProgramData, fetch_program_from_network, retry_network_call};
23
24use aleo_std::StorageMode;
25use rand::{CryptoRng, Rng};
26use snarkvm::prelude::{
27 Authorization,
28 Execution,
29 Fee,
30 Field,
31 Itertools,
32 Network,
33 Program,
34 execution_cost,
35 execution_cost_for_authorization,
36};
37
38use clap::Parser;
39use colored::*;
40use std::{convert::TryFrom, path::PathBuf};
41
42#[cfg(not(feature = "only_testnet"))]
43use snarkvm::circuit::{AleoCanaryV0, AleoV0};
44use snarkvm::{
45 circuit::{Aleo, AleoTestnetV0},
46 prelude::{
47 ConsensusVersion,
48 Identifier,
49 ProgramID,
50 VM,
51 query::{Query as SnarkVMQuery, QueryTrait},
52 store::{
53 ConsensusStore,
54 helpers::memory::{BlockMemory, ConsensusMemory},
55 },
56 },
57};
58
59#[derive(Parser, Debug)]
61pub struct LeoExecute {
62 #[clap(
63 name = "NAME",
64 help = "The name of the function to execute, e.g `helloworld.aleo::main` or `main`.",
65 default_value = "main"
66 )]
67 name: String,
68 #[clap(
69 name = "INPUTS",
70 help = "The program inputs e.g. `1u32`, `record1...` (record ciphertext), or `{ owner: ...}` "
71 )]
72 inputs: Vec<String>,
73 #[clap(long, help = "Skips proving.")]
74 pub(crate) skip_execute_proof: bool,
75 #[clap(flatten)]
76 pub(crate) fee_options: FeeOptions,
77 #[clap(flatten)]
78 pub(crate) action: TransactionAction,
79 #[clap(flatten)]
80 pub(crate) env_override: EnvOptions,
81 #[clap(flatten)]
82 pub(crate) key_override: PrivateKeyOptions,
83 #[clap(flatten)]
84 pub(crate) consensus_override: ConsensusOptions,
85 #[clap(flatten)]
86 pub(crate) extra: ExtraOptions,
87 #[clap(flatten)]
88 build_options: BuildOptions,
89 #[clap(
90 long,
91 value_name = "DIR",
92 help = "Directory containing local .aleo imports when --path points to an Aleo bytecode file; other imports are fetched from the network"
93 )]
94 pub(crate) imports_dir: Option<PathBuf>,
95 #[clap(
96 long = "with",
97 help = "Additional programs to load into the VM (comma-separated). \
98 If a path exists locally, it is read as an .aleo bytecode file; \
99 otherwise it is fetched from the network endpoint.",
100 value_delimiter = ','
101 )]
102 pub(crate) with: Vec<String>,
103}
104
105impl Command for LeoExecute {
106 type Input = Option<Package>;
107 type Output = ExecuteOutput;
108
109 fn log_span(&self) -> Span {
110 tracing::span!(tracing::Level::INFO, "Leo")
111 }
112
113 fn prelude(&self, context: Context) -> Result<Self::Input> {
114 let path = context.dir()?;
116 let home_path = context.home()?;
118 let network = get_network(&self.env_override.network)?;
120 let endpoint = get_endpoint(&self.env_override.endpoint)?;
122 if path.extension().and_then(|extension| extension.to_str()) == Some("aleo") {
123 if context.package_filter.is_some() {
124 return Err(crate::errors::custom("`--package` cannot be used with an Aleo bytecode file.").into());
125 }
126 return Package::from_aleo_file(
127 path,
128 home_path,
129 self.imports_dir.as_deref(),
130 true,
131 self.build_options.no_local,
132 Some(network),
133 Some(&endpoint),
134 self.env_override.network_retries,
135 )
136 .map(Some);
137 }
138 if self.imports_dir.is_some() {
139 return Err(
140 crate::errors::custom("`--imports-dir` requires `--path` to point to an Aleo bytecode file.").into()
141 );
142 }
143 if Package::from_directory_no_graph(
145 path,
146 home_path,
147 Some(network),
148 Some(&endpoint),
149 self.env_override.network_retries,
150 )
151 .is_ok()
152 {
153 let package = LeoBuild {
154 env_override: self.env_override.clone(),
155 options: {
156 let mut options = self.build_options.clone();
157 options.no_cache = true;
158 options
159 },
160 rename: None,
161 }
162 .execute(context)?;
163 Ok(Some(package))
165 } else {
166 Ok(None)
167 }
168 }
169
170 fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output> {
171 if let Some(package) = &input
173 && package.compilation_units.last().is_some_and(|p| p.kind.is_library())
174 {
175 return Err(
176 crate::errors::custom("Cannot execute a library package. Only programs can be executed.").into()
177 );
178 }
179
180 let network = get_network(&self.env_override.network)?;
182 match network {
184 NetworkName::TestnetV0 => handle_execute::<AleoTestnetV0>(self, context, network, input),
185 NetworkName::MainnetV0 => {
186 #[cfg(feature = "only_testnet")]
187 panic!("Mainnet chosen with only_testnet feature");
188 #[cfg(not(feature = "only_testnet"))]
189 handle_execute::<AleoV0>(self, context, network, input)
190 }
191 NetworkName::CanaryV0 => {
192 #[cfg(feature = "only_testnet")]
193 panic!("Canary chosen with only_testnet feature");
194 #[cfg(not(feature = "only_testnet"))]
195 handle_execute::<AleoCanaryV0>(self, context, network, input)
196 }
197 }
198 }
199}
200
201fn handle_execute<A: Aleo>(
203 command: LeoExecute,
204 context: Context,
205 network: NetworkName,
206 package: Option<Package>,
207) -> Result<<LeoExecute as Command>::Output> {
208 let private_key = get_private_key(&command.key_override.private_key)?;
210 let address = Address::<A::Network>::try_from(&private_key)
211 .map_err(|e| crate::errors::custom(format!("Failed to parse address: {e}")))?;
212
213 let endpoint = get_endpoint(&command.env_override.endpoint)?;
215
216 let is_devnet = get_is_devnet(command.consensus_override.devnet);
218
219 let consensus_heights = command
221 .consensus_override
222 .consensus_heights
223 .clone()
224 .unwrap_or_else(|| get_consensus_heights(network, is_devnet));
225 validate_consensus_heights(&consensus_heights)
227 .map_err(|e| crate::errors::custom(format!("Invalid consensus heights: {e}")))?;
228 let consensus_heights_string = consensus_heights.iter().format(",").to_string();
230 println!(
231 "\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"
232 );
233
234 #[allow(unsafe_code)]
236 unsafe {
237 std::env::set_var("CONSENSUS_VERSION_HEIGHTS", consensus_heights_string);
244 }
245
246 let (program_name, function_name) = match command.name.split_once('/').or_else(|| command.name.split_once("::")) {
249 Some((program_name, function_name)) => (program_name.to_string(), function_name.to_string()),
250 None => match &package {
251 Some(package) => (
252 format!(
253 "{}",
254 package.compilation_units.last().expect("There must be at least one program in a Leo package").name
255 ),
256 command.name,
257 ),
258 None => {
259 return Err(crate::errors::custom(format!(
260 "Running `leo execute {} ...`, without an explicit program name requires that your current working directory is a valid Leo project.",
261 command.name
262 )).into());
263 }
264 },
265 };
266
267 let program_id = ProgramID::<A::Network>::from_str(&program_name)
269 .map_err(|e| crate::errors::custom(format!("Failed to parse program name: {e}")))?;
270 let function_id = Identifier::<A::Network>::from_str(&function_name)
272 .map_err(|e| crate::errors::custom(format!("Failed to parse function name: {e}")))?;
273
274 let programs = if let Some(package) = &package {
277 package
279 .compilation_units
280 .iter()
281 .clone()
282 .filter(|unit| !unit.kind.is_library())
283 .map(|unit| {
284 let program_id = ProgramID::<A::Network>::from_str(&format!("{}", unit.name))
285 .map_err(|e| crate::errors::custom(format!("Failed to parse program ID: {e}")))?;
286 match &unit.data {
287 ProgramData::Bytecode(bytecode) => Ok((program_id, bytecode.to_string(), unit.edition)),
288 ProgramData::SourcePath { .. } => {
289 let bytecode_path = package.unit_bytecode_path(&unit.name.to_string());
291 let bytecode = std::fs::read_to_string(&bytecode_path).map_err(|e| {
293 crate::errors::custom(format!(
294 "Failed to read bytecode at {}: {e}",
295 bytecode_path.display()
296 ))
297 })?;
298 Ok((program_id, bytecode, unit.edition))
300 }
301 }
302 })
303 .collect::<Result<Vec<_>>>()?
304 } else {
305 Vec::new()
306 };
307
308 let mut programs = programs
310 .into_iter()
311 .map(|(_, bytecode, edition)| {
312 let program = snarkvm::prelude::Program::<A::Network>::from_str(&bytecode)
314 .map_err(|e| crate::errors::custom(format!("Failed to parse program: {e}")))?;
315 Ok((program, edition))
317 })
318 .collect::<Result<Vec<_>>>()?;
319
320 let is_local = programs.iter().any(|(program, _)| program.id() == &program_id);
322
323 if is_local {
325 let program = &programs
326 .iter()
327 .find(|(program, _)| program.id() == &program_id)
328 .expect("Program should exist since it is local")
329 .0;
330 if program.contains_view(&function_id) {
335 return Err(crate::errors::custom(format!(
336 "`{function_name}` is a `view fn`; views are read-only and do not produce a transaction, \
337 so they cannot be executed."
338 ))
339 .into());
340 }
341 if !program.contains_function(&function_id) {
342 return Err(crate::errors::custom(format!(
343 "Function `{function_name}` does not exist in program `{program_name}`."
344 ))
345 .into());
346 }
347 }
348
349 let inputs =
350 command.inputs.into_iter().map(|string| parse_input(&string, &private_key)).collect::<Result<Vec<_>>>()?;
351
352 let (priority_fee, record) =
354 parse_fee_options(&private_key, &command.fee_options, 1)?.into_iter().next().unwrap_or((None, None));
355
356 let consensus_version = get_consensus_version(
358 &command.extra.consensus_version,
359 &endpoint,
360 network,
361 &consensus_heights,
362 &context,
363 command.env_override.network_retries,
364 )?;
365
366 let config = Some(Config {
368 address: address.to_string(),
369 network: network.to_string(),
370 endpoint: Some(endpoint.clone()),
371 consensus_version: Some(consensus_version as u8),
372 });
373
374 let rng = &mut rand::rng();
376
377 let vm = VM::from(ConsensusStore::<A::Network, ConsensusMemory<A::Network>>::open(StorageMode::Production)?)?;
379
380 let query = SnarkVMQuery::<A::Network, BlockMemory<A::Network>>::from(
382 endpoint
383 .parse::<Uri>()
384 .map_err(|e| crate::errors::custom(format!("Failed to parse endpoint URI '{endpoint}': {e}")))?,
385 );
386
387 if !is_local {
390 println!("β¬οΈ Downloading {program_name} and its dependencies from {endpoint}...");
391 programs = load_latest_programs_from_network(
392 &context,
393 program_id,
394 network,
395 &endpoint,
396 command.env_override.network_retries,
397 )?;
398 };
399
400 let warnings =
401 check_task_for_warnings(&endpoint, network, &programs, consensus_version, command.env_override.network_retries);
402
403 println!("\nβAdding programs to the VM in the following order:");
405 let programs_and_editions = programs
406 .into_iter()
407 .map(|(program, edition)| {
408 print_program_source(&program.id().to_string(), edition);
409 let edition = edition.unwrap_or(LOCAL_PROGRAM_DEFAULT_EDITION);
410 (program, edition)
411 })
412 .collect::<Vec<_>>();
413 vm.process().lock().add_programs_with_editions(&programs_and_editions)?;
414
415 if !command.with.is_empty() {
417 load_extra_programs_into_vm::<A::Network>(
418 &command.with,
419 &vm,
420 &context,
421 network,
422 Some(&endpoint),
423 command.env_override.network_retries,
424 )?;
425 }
426
427 println!("\nβοΈ Preparing calls for review: {program_name}/{function_name}...");
428 let authorization = if command.skip_execute_proof {
430 vm.process()
431 .authorize::<A, _>(&private_key, &program_name, &function_name, inputs.iter(), rng)
432 .map_err(|e| anyhow::anyhow!("{e}"))?
433 } else {
434 vm.authorize(&private_key, &program_name, &function_name, inputs.iter(), rng)
435 .map_err(|e| anyhow::anyhow!("{e}"))?
436 };
437
438 print_execution_plan::<A::Network>(
439 &address,
440 &endpoint,
441 &network,
442 &program_name,
443 &function_name,
444 is_local,
445 priority_fee.unwrap_or(0),
446 record.is_some(),
447 &command.action,
448 consensus_version,
449 &warnings,
450 command.skip_execute_proof,
451 &authorization,
452 )?;
453
454 let (estimated_cost, (est_storage, est_exec)) =
456 execution_cost_for_authorization(vm.process(), &authorization, consensus_version)?;
457 let stats = print_execution_cost_summary(&program_name, est_storage, est_exec, priority_fee);
458
459 if !confirm("Approve all calls and fees shown above?", command.extra.yes)? {
461 println!("β Execution aborted.");
462 return Ok(ExecuteOutput::default());
463 }
464
465 let (output_name, output, response) = if command.skip_execute_proof {
467 let state_root = retry_network_call(command.env_override.network_retries, || query.current_state_root())?;
469
470 let execution = Execution::from(authorization.transitions().values().cloned(), state_root, None)?;
472
473 let (cost, _) = execution_cost(vm.process(), &execution, consensus_version)?;
475
476 let id = authorization.to_execution_id()?;
478 let fee_authorization =
479 authorize_fee::<A, _>(&vm, &private_key, record, cost, priority_fee.unwrap_or(0), id, rng)?;
480
481 let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?;
483
484 let transaction = Transaction::from_execution(execution, Some(fee))?;
486
487 let response = vm.process().evaluate::<A>(authorization).map_err(|e| anyhow::anyhow!("{e}"))?;
489
490 ("transaction", Box::new(transaction), response)
491 } else {
492 let is_fee_required = !(authorization.is_split() || authorization.is_upgrade());
494 let is_priority_fee_declared = priority_fee.unwrap_or(0) > 0;
495
496 let fee_authorization = if is_fee_required || is_priority_fee_declared {
498 let execution_id = authorization.to_execution_id()?;
499 Some(authorize_fee::<A, _>(
500 &vm,
501 &private_key,
502 record,
503 estimated_cost,
504 priority_fee.unwrap_or(0),
505 execution_id,
506 rng,
507 )?)
508 } else {
509 None
510 };
511
512 let (transaction, response) =
514 vm.execute_authorization_with_response(authorization, fee_authorization, Some(&query), rng)?;
515 ("transaction", Box::new(transaction), response)
516 };
517
518 let transaction = output.clone();
519
520 if command.action.print {
524 let transaction_json = serde_json::to_string_pretty(&output)
525 .map_err(|e| crate::errors::custom(format!("Failed to serialize transaction: {e}")))?;
526 println!("π¨οΈ Printing execution for {output_name}\n{transaction_json}");
527 }
528
529 if let Some(path) = &command.action.save {
533 std::fs::create_dir_all(path).map_err(|e| crate::errors::custom(format!("Failed to create directory: {e}")))?;
535 let file_path = PathBuf::from(path).join(format!("{output_name}.execution.json"));
537 println!("πΎ Saving execution for {output_name} at {}", file_path.display());
538 let transaction_json = serde_json::to_string_pretty(&output)
539 .map_err(|e| crate::errors::custom(format!("Failed to serialize transaction: {e}")))?;
540 std::fs::write(file_path, transaction_json)
541 .map_err(|e| crate::errors::custom(format!("Failed to write transaction to file: {e}")))?;
542 }
543
544 let mut broadcast_stats = None;
545
546 let outputs: Vec<String> = response.outputs().iter().map(|o| o.to_string()).collect();
548
549 match outputs.len() {
550 0 => (),
551 1 => println!("\nβ‘οΈ Output\n"),
552 _ => println!("\nβ‘οΈ Outputs\n"),
553 };
554 for o in &outputs {
555 println!(" β’ {o}");
556 }
557 println!();
558
559 if command.action.broadcast {
561 println!("π‘ Broadcasting execution for {program_name}...");
562 let mut fee_id = None;
564 let mut fee_transaction_id = None;
565 if let Some(fee) = transaction.fee_transition() {
566 if !confirm_fee(&fee, &private_key, &address, &endpoint, network, &context, command.extra.yes)? {
568 println!("β Execution aborted.");
569 return Ok(ExecuteOutput {
570 config: config.clone(),
571 program: program_name.clone(),
572 function: function_name.clone(),
573 outputs,
574 transaction_id: transaction.id().to_string(),
575 stats: Some(stats),
576 broadcast: None,
577 });
578 }
579 fee_id = Some(fee.id().to_string());
580 fee_transaction_id = Some(Transaction::from_fee(fee.clone())?.id().to_string());
581 }
582 let id = transaction.id().to_string();
583 let height_before =
584 check_transaction::current_height(&endpoint, network, command.env_override.network_retries)?;
585 let (message, status) =
587 handle_broadcast(&format!("{endpoint}/{network}/transaction/broadcast"), &transaction, &program_name)?;
588
589 match status {
590 200..=299 => {
591 let tx_status = check_transaction::check_transaction_with_message(
592 &id,
593 fee_id.as_deref(),
594 &endpoint,
595 network,
596 height_before + 1,
597 command.extra.max_wait,
598 command.extra.blocks_to_check,
599 command.env_override.network_retries,
600 )?;
601 let confirmed = tx_status == Some(TransactionStatus::Accepted);
602 if confirmed {
603 println!("β
Execution confirmed!");
604 }
605 broadcast_stats = Some(BroadcastStats {
606 fee_id: fee_id.unwrap_or_default(),
607 fee_transaction_id: fee_transaction_id.unwrap_or_default(),
608 confirmed,
609 });
610 }
611 _ => {
612 println!("β Failed to broadcast execution: {message}.");
613 }
614 }
615 }
616
617 Ok(ExecuteOutput {
618 config,
619 program: program_name.clone(),
620 function: function_name.clone(),
621 outputs,
622 transaction_id: transaction.id().to_string(),
623 stats: Some(stats),
624 broadcast: broadcast_stats,
625 })
626}
627
628fn authorize_fee<A: Aleo, R: Rng + CryptoRng>(
630 vm: &VM<A::Network, ConsensusMemory<A::Network>>,
631 private_key: &PrivateKey<A::Network>,
632 record: Option<Record<A::Network, Plaintext<A::Network>>>,
633 base_fee: u64,
634 priority_fee: u64,
635 execution_id: Field<A::Network>,
636 rng: &mut R,
637) -> Result<Authorization<A::Network>> {
638 match record {
639 None => vm.authorize_fee_public(private_key, base_fee, priority_fee, execution_id, rng),
640 Some(record) => vm.authorize_fee_private(private_key, record, base_fee, priority_fee, execution_id, rng),
641 }
642 .map_err(Into::into)
643}
644
645fn check_task_for_warnings<N: Network>(
649 endpoint: &str,
650 network: NetworkName,
651 programs: &[(Program<N>, Option<u16>)],
652 consensus_version: ConsensusVersion,
653 network_retries: u32,
654) -> Vec<String> {
655 let mut warnings = Vec::new();
656 for (program, _) in programs {
657 if let Ok(remote_program) =
659 fetch_program_from_network(&program.id().to_string(), endpoint, network, network_retries)
660 {
661 let remote_program = match Program::<N>::from_str(&remote_program) {
663 Ok(program) => program,
664 Err(e) => {
665 warnings.push(format!("Could not parse '{}' from the network. Error: {e}", program.id()));
666 continue;
667 }
668 };
669 if remote_program != *program {
671 warnings.push(format!(
672 "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.",
673 program.id()
674 ));
675 }
676 } else {
677 warnings.push(format!(
678 "The program '{}' does not exist on the network. You may use `leo deploy --broadcast` to deploy it.",
679 program.id()
680 ));
681 }
682 }
683 if let Err(e) = check_consensus_version_mismatch(consensus_version, endpoint, network, network_retries) {
685 warnings.push(format!("{e}. In some cases, the execution may fail"));
686 }
687 warnings
688}
689
690#[allow(clippy::too_many_arguments)]
692fn print_execution_plan<N: Network>(
693 address: &Address<N>,
694 endpoint: &str,
695 network: &NetworkName,
696 program_name: &str,
697 function_name: &str,
698 is_local: bool,
699 priority_fee: u64,
700 fee_record: bool,
701 action: &TransactionAction,
702 consensus_version: ConsensusVersion,
703 warnings: &[String],
704 skip_execute_proof: bool,
705 authorization: &Authorization<N>,
706) -> Result<()> {
707 println!("\n{}", "π Execution Plan Summary".bold().underline());
708 println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ".dimmed());
709
710 println!("{}", "π§ Configuration:".bold());
711 println!(" {:20}{}", "Address:".cyan(), address.to_string().yellow());
712 println!(" {:20}{}", "Endpoint:", endpoint.escape_debug().to_string().yellow());
713 println!(" {:20}{}", "Network:", network.to_string().yellow());
714 println!(" {:20}{}", "Consensus Version:", (consensus_version as u8).to_string().yellow());
715
716 println!("\n{}", "π― Execution Target:".bold());
717 println!(" {:16}{}", "Program:", program_name.cyan());
718 println!(" {:16}{}", "Function:", function_name.cyan());
719 println!(" {:16}{}", "Source:", if is_local { "local" } else { "remote" });
720
721 println!("\n{}", "Calls to approve:".bold());
722 let transitions = authorization.transitions();
723 for (index, request) in authorization.to_vec_deque().iter().enumerate() {
724 println!(" Call {}: {}/{}", index + 1, request.program_id(), request.function_name());
725 println!(" Signer: {}", request.signer());
726 for (index, (input_id, value)) in request.input_ids().iter().zip(request.inputs()).enumerate() {
727 match value {
728 snarkvm::prelude::Value::Record(_) | snarkvm::prelude::Value::DynamicRecord(_) => {
729 println!(" Input {}: <private record>", index + 1)
730 }
731 _ if matches!(input_id, snarkvm::prelude::InputID::Private(_)) => {
732 println!(" Input {}: <private input>", index + 1)
733 }
734 _ => println!(" Input {}: {}", index + 1, value.to_string().escape_debug()),
735 }
736 }
737 let transition = transitions
739 .values()
740 .find(|transition| transition.tcm() == request.tcm())
741 .ok_or_else(|| crate::errors::custom("Cannot review an execution with a missing transition."))?;
742 for future in transition.outputs().iter().filter_map(|output| output.future()) {
743 if future.program_id().to_string() == "credits.aleo"
744 && matches!(
745 future.function_name().to_string().as_str(),
746 "transfer_public" | "transfer_public_as_signer"
747 )
748 && let [
749 snarkvm::prelude::Argument::Plaintext(debit),
750 snarkvm::prelude::Argument::Plaintext(recipient),
751 snarkvm::prelude::Argument::Plaintext(amount),
752 ] = future.arguments()
753 {
754 println!(" Debit: {}", debit.to_string().escape_debug());
755 println!(" Recipient: {}", recipient.to_string().escape_debug());
756 println!(" Amount: {} microcredits", amount.to_string().escape_debug());
757 }
758 println!(" Finalize: {}", future.to_string().escape_debug());
759 }
760 }
761 println!(" Final balances depend on on-chain state.");
762
763 println!("\n{}", "πΈ Fee Info:".bold());
764 println!(" {:16}{}", "Priority Fee:", format!("{priority_fee} ΞΌcredits").green());
765 println!(" {:16}{}", "Fee Record:", if fee_record { "yes" } else { "no (public fee)" });
766
767 println!("\n{}", "βοΈ Actions:".bold());
768 if !is_local {
769 println!(" - Program and its dependencies were loaded from network bytecode.");
770 }
771 if skip_execute_proof {
772 println!(" - A transaction will be generated, WITHOUT a proof.");
773 }
774 if action.print {
775 println!(" - Transaction will be printed to the console.");
776 } else {
777 println!(" - Transaction will NOT be printed to the console.");
778 }
779 if let Some(path) = &action.save {
780 println!(" - Transaction will be saved to {}", path.escape_debug().to_string().bold());
781 } else {
782 println!(" - Transaction will NOT be saved to a file.");
783 }
784 if action.broadcast {
785 println!(" - Transaction will be broadcast to {}", endpoint.escape_debug().to_string().bold());
786 } else {
787 println!(" - Transaction will NOT be broadcast to the network.");
788 }
789
790 if !warnings.is_empty() {
792 println!("\n{}", "β οΈ Warnings:".bold().red());
793 for warning in warnings {
794 println!(" β’ {}", warning.escape_debug().to_string().dimmed());
795 }
796 }
797
798 println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ\n".dimmed());
799 Ok(())
800}
801
802fn print_execution_cost_summary(
804 program_name: &str,
805 storage_cost: u64,
806 execution_cost: u64,
807 priority_fee: Option<u64>,
808) -> ExecutionStats {
809 use colored::*;
810
811 let priority = priority_fee.unwrap_or(0);
812 let total = storage_cost + execution_cost + priority;
813 let stats = ExecutionStats { storage_cost, execution_cost, priority_fee: priority, total_cost: total };
814
815 println!("\n{} {}", "π Execution Cost Summary for".bold(), program_name.bold());
816 println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ".dimmed());
817 print!("{stats}");
818 println!("{}", "ββββββββββββββββββββββββββββββββββββββββββββββ".dimmed());
819
820 stats
821}