1mod abi;
18pub use abi::LeoAbi;
19
20mod add;
21pub use add::{DependencySource, GitRef, LeoAdd};
22
23mod account;
24pub use account::Account;
25
26mod build;
27pub use build::{LeoBuild, build_output};
28
29mod clean;
30pub use clean::LeoClean;
31
32mod common;
33pub use common::*;
34
35mod deploy;
36pub use deploy::LeoDeploy;
37use deploy::{
38 Task,
39 compute_deployment_stats,
40 max_program_size_for_consensus_version,
41 print_deployment_plan,
42 print_deployment_summary,
43 warn_if_transaction_oversized,
44};
45
46mod devnet;
47pub use devnet::LeoDevnet;
48
49mod devnode;
50pub use devnode::LeoDevnode;
51
52mod execute;
53pub use execute::LeoExecute;
54
55pub mod query;
56pub use query::LeoQuery;
57
58mod new;
59pub use new::LeoNew;
60
61mod remove;
62pub use remove::LeoRemove;
63
64mod run;
65pub use run::LeoRun;
66
67mod synthesize;
68pub use synthesize::LeoSynthesize;
69
70mod test;
71pub use test::LeoTest;
72
73mod update;
74pub use update::LeoUpdate;
75
76pub mod upgrade;
77pub use upgrade::LeoUpgrade;
78
79use super::*;
80use crate::cli::{helpers::context::*, query::QueryCommands};
81
82use leo_errors::{Handler, Result};
83use snarkvm::{
84 console::network::Network,
85 prelude::{Address, Ciphertext, Plaintext, PrivateKey, Record, Value, ViewKey, block::Transaction},
86};
87
88use clap::{Args, Parser};
89use colored::Colorize;
90use dialoguer::{Confirm, theme::ColorfulTheme};
91use std::{iter, str::FromStr};
92use tracing::span::Span;
93use ureq::http::Uri;
94
95pub trait Command {
97 type Input;
101
102 type Output;
106
107 fn log_span(&self) -> Span {
112 tracing::span!(tracing::Level::INFO, "Leo")
113 }
114
115 fn prelude(&self, context: Context) -> Result<Self::Input>
117 where
118 Self: std::marker::Sized;
119
120 fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output>
123 where
124 Self: std::marker::Sized;
125
126 fn execute(self, context: Context) -> Result<Self::Output>
129 where
130 Self: std::marker::Sized,
131 {
132 let input = self.prelude(context.clone())?;
133
134 let span = self.log_span();
136 let span = span.enter();
137
138 let out = self.apply(context, input);
140
141 drop(span);
142
143 out
144 }
145
146 fn try_execute(self, context: Context) -> Result<()>
150 where
151 Self: std::marker::Sized,
152 {
153 self.execute(context).map(|_| Ok(()))?
154 }
155}
156
157pub fn parse_input<N: Network>(input: &str, private_key: &PrivateKey<N>) -> Result<Value<N>> {
159 let input = input.trim();
161 if input.starts_with("record1") {
163 let view_key = ViewKey::<N>::try_from(private_key)
165 .map_err(|e| crate::errors::custom(format!("Failed to view key from the private key: {e}")))?;
166 Record::<N, Ciphertext<N>>::from_str(input)
168 .and_then(|ciphertext| ciphertext.decrypt(&view_key))
169 .map(Value::Record)
170 .map_err(|e| crate::errors::custom(format!("Failed to parse input as record: {e}")).into())
171 } else {
172 validate_cli_literal(input)?;
174 Value::from_str(input).map_err(|e| crate::errors::custom(format!("Failed to parse input: {e}")).into())
175 }
176}
177
178fn validate_cli_literal(input: &str) -> Result<()> {
181 const ALEO_BECH32_PREFIXES: &[&str] = &["aleo1", "sign1", "APrivateKey1", "AViewKey1"];
183 if ALEO_BECH32_PREFIXES.iter().any(|prefix| input.starts_with(prefix)) {
184 return Ok(());
185 }
186
187 const UNSIGNED_SUFFIXES: &[&str] = &["u128", "u64", "u32", "u16", "u8"];
189 const SIGNED_SUFFIXES: &[&str] = &["i128", "i64", "i32", "i16", "i8"];
190 const FIELD_LIKE_SUFFIXES: &[&str] = &["field", "scalar", "group"];
191
192 for suffix in UNSIGNED_SUFFIXES {
193 if let Some(prefix) = input.strip_suffix(suffix) {
194 return validate_numeric_prefix(prefix, suffix, false, false);
195 }
196 }
197 for suffix in SIGNED_SUFFIXES {
198 if let Some(prefix) = input.strip_suffix(suffix) {
199 return validate_numeric_prefix(prefix, suffix, true, false);
200 }
201 }
202 for suffix in FIELD_LIKE_SUFFIXES {
203 if let Some(prefix) = input.strip_suffix(suffix) {
204 if *suffix == "group" && prefix.starts_with('(') {
206 return Ok(());
207 }
208 return validate_numeric_prefix(prefix, suffix, true, true);
209 }
210 }
211
212 Ok(())
214}
215
216fn validate_numeric_prefix(prefix: &str, suffix: &str, allow_negative: bool, decimal_only: bool) -> Result<()> {
218 if prefix.is_empty() {
219 return Err(crate::errors::custom(format!(
220 "Invalid {suffix} literal: missing numeric value before '{suffix}'"
221 ))
222 .into());
223 }
224 let valid = if decimal_only {
225 is_valid_decimal(prefix, allow_negative)
226 } else {
227 is_valid_decimal(prefix, allow_negative) || is_valid_radix_prefixed(prefix, allow_negative)
228 };
229 if !valid {
230 return Err(crate::errors::custom(format!(
231 "Invalid {suffix} literal: '{prefix}' is not a valid numeric value"
232 ))
233 .into());
234 }
235 Ok(())
236}
237
238fn is_valid_decimal(s: &str, allow_negative: bool) -> bool {
240 let s = if allow_negative { s.strip_prefix('-').unwrap_or(s) } else { s };
241 if s.is_empty() {
242 return false;
243 }
244 let mut chars = s.chars();
245 if !chars.next().unwrap().is_ascii_digit() {
247 return false;
248 }
249 chars.all(|c| c.is_ascii_digit() || c == '_')
250}
251
252fn is_valid_radix_prefixed(s: &str, allow_negative: bool) -> bool {
254 let s = if allow_negative { s.strip_prefix('-').unwrap_or(s) } else { s };
255 if s.len() < 3 || !s.starts_with('0') {
256 return false;
257 }
258 let radix_char = s.as_bytes()[1];
259 let rest = &s[2..];
260 if rest.is_empty() || rest.starts_with('_') {
261 return false;
262 }
263 match radix_char {
264 b'x' | b'X' => rest.chars().all(|c| c.is_ascii_hexdigit() || c == '_'),
265 b'o' => rest.chars().all(|c| matches!(c, '0'..='7' | '_')),
266 b'b' => rest.chars().all(|c| matches!(c, '0' | '1' | '_')),
267 _ => false,
268 }
269}
270
271#[cfg(test)]
272mod tests {
273 use super::*;
274
275 #[test]
276 fn test_valid_literals() {
277 let valid = [
278 "42field",
279 "-7field",
280 "0field",
281 "1_000_000field",
282 "100u64",
283 "0x1Fu8",
284 "0b1010u32",
285 "0o77u16",
286 "-128i8",
287 "-0x80i16",
288 "0scalar",
289 "42group",
290 "(1, 2)group",
291 "true",
293 "false",
294 "aleo1qnr4dkkvkgfqph0vzc3y6z2eu975wnpz2925ntjccd5cfqxtyu8s7pyjh9",
295 "aleo1vnx8f43f7yjvs2ehlqsl78j2qh4409s8e4g7gx40u3v884gqgqxqscutu8",
298 "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu32",
299 "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu64",
300 "aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqscalar",
301 "sign195m229jvzr0wmnshj6f8gwplhkrkhjumgjmad553r997u7pjfgpfz4j2w0c9lp53mcqqdsmut2g3a2zuvgst85w38hv273mwjec3sqjsv9w6uglcy58gjh7x3l55z68zsf24kx7a73ctp8x8klhuw7l2p4s3aq8um5jp304js7qcnwdqj56q5r5088tyvxsgektun0rnmvtsuxpe6sj",
303 "sign1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu8",
304 "sign1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu32",
305 "sign1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu64",
306 "sign1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqscalar",
307 "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH",
310 "APrivateKey1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu8",
311 "AViewKey1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu32",
312 ];
313 for input in &valid {
314 assert!(validate_cli_literal(input).is_ok(), "expected '{input}' to be valid");
315 }
316 }
317
318 #[test]
319 fn test_invalid_literals() {
320 let invalid = [
321 "truefield",
322 "falsefield",
323 "field",
324 "scalar",
325 "u8",
326 "abcu64",
327 "-u8",
328 "hello_worldscalar",
329 "truegroup",
330 "xxxi128",
331 ];
332 for input in &invalid {
333 assert!(validate_cli_literal(input).is_err(), "expected '{input}' to be invalid");
334 }
335 }
336
337 #[test]
339 fn test_parse_input_value_types() {
340 use snarkvm::prelude::TestnetV0;
341 let private_key =
342 PrivateKey::<TestnetV0>::from_str("APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH").unwrap();
343 let inputs = [
344 "42u8",
346 "0u8",
347 "255u8",
348 "-7i32",
349 "-128i8",
350 "100u64",
351 "1_000_000u128",
352 "1_000_000field",
354 "-7field",
355 "0scalar",
356 "true",
358 "false",
359 "aleo1qnr4dkkvkgfqph0vzc3y6z2eu975wnpz2925ntjccd5cfqxtyu8s7pyjh9",
361 "aleo1vnx8f43f7yjvs2ehlqsl78j2qh4409s8e4g7gx40u3v884gqgqxqscutu8",
362 "sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq",
364 "[1u8, 2u8, 3u8]",
366 "{a: 1u8, b: 2u8}",
367 "{nested: {x: 1field, y: 2field}, count: 3u32}",
368 " 42u8 ",
370 ];
371 for input in &inputs {
372 assert!(parse_input::<TestnetV0>(input, &private_key).is_ok(), "expected '{input}' to parse");
373 }
374 }
375}