1use super::*;
18
19use leo_ast::NetworkName;
20use leo_package::{Package, ProgramData, TEST_PRIVATE_KEY};
21
22use aleo_std::StorageMode;
23
24use clap::Parser;
25
26#[cfg(not(feature = "only_testnet"))]
27use snarkvm::circuit::{AleoCanaryV0, AleoV0};
28use snarkvm::{
29 circuit::{Aleo, AleoTestnetV0},
30 prelude::{
31 Identifier,
32 ProgramID,
33 VM,
34 store::{ConsensusStore, helpers::memory::ConsensusMemory},
35 },
36};
37
38#[derive(Parser, Debug)]
40pub struct LeoRun {
41 #[clap(
42 name = "NAME",
43 help = "The name of the function to execute, e.g `helloworld.aleo/main` or `main`.",
44 default_value = "main"
45 )]
46 pub(crate) name: String,
47 #[clap(
48 name = "INPUTS",
49 help = "The program inputs e.g. `1u32`, `record1...` (record ciphertext), or `{ owner: ...}` "
50 )]
51 pub(crate) inputs: Vec<String>,
52 #[clap(flatten)]
53 pub(crate) env_override: EnvOptions,
54 #[clap(flatten)]
55 pub(crate) build_options: BuildOptions,
56}
57
58impl Command for LeoRun {
59 type Input = Option<Package>;
60 type Output = ();
61
62 fn log_span(&self) -> Span {
63 tracing::span!(tracing::Level::INFO, "Leo")
64 }
65
66 fn prelude(&self, context: Context) -> Result<Self::Input> {
67 let path = context.dir()?;
69 let home_path = context.home()?;
71 if Package::from_directory_no_graph(
73 path,
74 home_path,
75 self.env_override.network,
76 self.env_override.endpoint.as_deref(),
77 )
78 .is_ok()
79 {
80 let package = LeoBuild { env_override: self.env_override.clone(), options: self.build_options.clone() }
81 .execute(context)?;
82 Ok(Some(package))
84 } else {
85 Ok(None)
86 }
87 }
88
89 fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output> {
90 let network = match get_network(&self.env_override.network) {
92 Ok(network) => network,
93 Err(_) => {
94 println!("⚠️ No network specified, defaulting to 'testnet'.");
95 NetworkName::TestnetV0
96 }
97 };
98
99 match network {
101 NetworkName::TestnetV0 => handle_run::<AleoTestnetV0>(self, context, network, input),
102 NetworkName::MainnetV0 => {
103 #[cfg(feature = "only_testnet")]
104 panic!("Mainnet chosen with only_testnet feature");
105 #[cfg(not(feature = "only_testnet"))]
106 handle_run::<AleoV0>(self, context, network, input)
107 }
108 NetworkName::CanaryV0 => {
109 #[cfg(feature = "only_testnet")]
110 panic!("Canary chosen with only_testnet feature");
111 #[cfg(not(feature = "only_testnet"))]
112 handle_run::<AleoCanaryV0>(self, context, network, input)
113 }
114 }
115 }
116}
117
118fn handle_run<A: Aleo>(
120 command: LeoRun,
121 context: Context,
122 network: NetworkName,
123 package: Option<Package>,
124) -> Result<<LeoRun as Command>::Output> {
125 let private_key = match get_private_key::<A::Network>(&command.env_override.private_key) {
127 Ok(private_key) => private_key,
128 Err(_) => {
129 println!("⚠️ No valid private key specified, defaulting to '{TEST_PRIVATE_KEY}'.");
130 PrivateKey::<A::Network>::from_str(TEST_PRIVATE_KEY).expect("Failed to parse the test private key")
131 }
132 };
133
134 let (program_name, function_name) = match command.name.split_once('/') {
137 Some((program_name, function_name)) => (program_name.to_string(), function_name.to_string()),
138 None => match &package {
139 Some(package) => (
140 format!(
141 "{}.aleo",
142 package.programs.last().expect("There must be at least one program in a Leo package").name
143 ),
144 command.name,
145 ),
146 None => {
147 return Err(CliError::custom(format!(
148 "Running `leo execute {} ...`, without an explicit program name requires that your current working directory is a valid Leo project.",
149 command.name
150 )).into());
151 }
152 },
153 };
154
155 let program_id = ProgramID::<A::Network>::from_str(&program_name)
157 .map_err(|e| CliError::custom(format!("Failed to parse program name: {e}")))?;
158 let function_id = Identifier::<A::Network>::from_str(&function_name)
160 .map_err(|e| CliError::custom(format!("Failed to parse function name: {e}")))?;
161
162 let programs = if let Some(package) = &package {
165 let build_directory = package.build_directory();
167 let imports_directory = package.imports_directory();
168 let source_directory = package.source_directory();
169 package
171 .programs
172 .iter()
173 .clone()
174 .map(|program| {
175 let program_id = ProgramID::<A::Network>::from_str(&format!("{}.aleo", program.name))
176 .map_err(|e| CliError::custom(format!("Failed to parse program ID: {e}")))?;
177 match &program.data {
178 ProgramData::Bytecode(bytecode) => Ok((program_id, bytecode.to_string(), program.edition)),
179 ProgramData::SourcePath { source, .. } => {
180 let bytecode_path = if source.as_path() == source_directory.join("main.leo") {
182 build_directory.join("main.aleo")
183 } else {
184 imports_directory.join(format!("{}.aleo", program.name))
185 };
186 let bytecode = std::fs::read_to_string(&bytecode_path).map_err(|e| {
188 CliError::custom(format!("Failed to read bytecode at {}: {e}", bytecode_path.display()))
189 })?;
190 Ok((program_id, bytecode, program.edition))
192 }
193 }
194 })
195 .collect::<Result<Vec<_>>>()?
196 } else {
197 Vec::new()
198 };
199
200 let mut programs = programs
202 .into_iter()
203 .map(|(_, bytecode, edition)| {
204 let program = snarkvm::prelude::Program::<A::Network>::from_str(&bytecode)
206 .map_err(|e| CliError::custom(format!("Failed to parse program: {e}")))?;
207 Ok((program, edition))
209 })
210 .collect::<Result<Vec<_>>>()?;
211
212 let is_local = programs.iter().any(|(program, _)| program.id() == &program_id);
214
215 if is_local {
217 let program = &programs
218 .iter()
219 .find(|(program, _)| program.id() == &program_id)
220 .expect("Program should exist since it is local")
221 .0;
222 if !program.contains_function(&function_id) {
223 return Err(CliError::custom(format!(
224 "Function `{function_name}` does not exist in program `{program_name}`."
225 ))
226 .into());
227 }
228 }
229
230 let inputs =
231 command.inputs.into_iter().map(|string| parse_input(&string, &private_key)).collect::<Result<Vec<_>>>()?;
232
233 let rng = &mut rand::thread_rng();
235
236 let vm = VM::from(ConsensusStore::<A::Network, ConsensusMemory<A::Network>>::open(StorageMode::Production)?)?;
238
239 if !is_local {
242 let endpoint = get_endpoint(&command.env_override.endpoint)?;
244 println!("⬇️ Downloading {program_name} and its dependencies from {endpoint}...");
245 programs = load_latest_programs_from_network(&context, program_id, network, &endpoint)?;
247 };
248
249 println!("\n➕Adding programs to the VM in the following order:");
251 let programs_and_editions = programs
252 .into_iter()
253 .map(|(program, edition)| {
254 let edition = edition.unwrap_or(1);
256 let id = program.id().to_string();
258 match id == "credits.aleo" {
260 true => println!(" - {id} (already included)"),
261 false => println!(" - {id} (edition: {edition})"),
262 }
263 (program, edition)
264 })
265 .collect::<Vec<_>>();
266 vm.process().write().add_programs_with_editions(&programs_and_editions)?;
267
268 let authorization = vm.authorize(&private_key, program_id, function_id, inputs.iter(), rng)?;
270 let response = vm.process().read().evaluate::<A>(authorization)?;
271
272 match response.outputs().len() {
274 0 => (),
275 1 => println!("\n➡️ Output\n"),
276 _ => println!("\n➡️ Outputs\n"),
277 };
278 for output in response.outputs() {
279 println!(" • {output}");
280 }
281
282 Ok(())
283}