mod abi;
pub use abi::LeoAbi;
mod add;
pub use add::{DependencySource, GitRef, LeoAdd};
mod account;
pub use account::Account;
mod build;
pub use build::{LeoBuild, build_output};
mod clean;
pub use clean::LeoClean;
mod common;
pub use common::*;
mod deploy;
pub use deploy::LeoDeploy;
use deploy::{
Task,
compute_deployment_stats,
max_program_size_for_consensus_version,
print_deployment_plan,
print_deployment_summary,
warn_if_transaction_oversized,
};
mod devnet;
pub use devnet::LeoDevnet;
mod devnode;
pub use devnode::LeoDevnode;
mod execute;
pub use execute::LeoExecute;
pub mod query;
pub use query::LeoQuery;
mod new;
pub use new::LeoNew;
mod remove;
pub use remove::LeoRemove;
mod run;
pub use run::LeoRun;
mod synthesize;
pub use synthesize::LeoSynthesize;
mod test;
pub use test::LeoTest;
mod update;
pub use update::LeoUpdate;
pub mod upgrade;
pub use upgrade::LeoUpgrade;
use super::*;
use crate::cli::{helpers::context::*, query::QueryCommands};
use leo_errors::{Handler, Result};
use snarkvm::{
console::network::Network,
prelude::{Address, Ciphertext, Plaintext, PrivateKey, Record, Value, ViewKey, block::Transaction},
};
use clap::{Args, Parser};
use colored::Colorize;
use dialoguer::{Confirm, theme::ColorfulTheme};
use std::{iter, str::FromStr};
use tracing::span::Span;
use ureq::http::Uri;
pub trait Command {
type Input;
type Output;
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Leo")
}
fn prelude(&self, context: Context) -> Result<Self::Input>
where
Self: std::marker::Sized;
fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output>
where
Self: std::marker::Sized;
fn execute(self, context: Context) -> Result<Self::Output>
where
Self: std::marker::Sized,
{
let input = self.prelude(context.clone())?;
let span = self.log_span();
let span = span.enter();
let out = self.apply(context, input);
drop(span);
out
}
fn try_execute(self, context: Context) -> Result<()>
where
Self: std::marker::Sized,
{
self.execute(context).map(|_| Ok(()))?
}
}
pub fn parse_input<N: Network>(input: &str, private_key: &PrivateKey<N>) -> Result<Value<N>> {
let input = input.trim();
if input.starts_with("record1") {
let view_key = ViewKey::<N>::try_from(private_key)
.map_err(|e| crate::errors::custom(format!("Failed to view key from the private key: {e}")))?;
Record::<N, Ciphertext<N>>::from_str(input)
.and_then(|ciphertext| ciphertext.decrypt(&view_key))
.map(Value::Record)
.map_err(|e| crate::errors::custom(format!("Failed to parse input as record: {e}")).into())
} else {
validate_cli_literal(input)?;
Value::from_str(input).map_err(|e| crate::errors::custom(format!("Failed to parse input: {e}")).into())
}
}
fn validate_cli_literal(input: &str) -> Result<()> {
const ALEO_BECH32_PREFIXES: &[&str] = &["aleo1", "sign1", "APrivateKey1", "AViewKey1"];
if ALEO_BECH32_PREFIXES.iter().any(|prefix| input.starts_with(prefix)) {
return Ok(());
}
const UNSIGNED_SUFFIXES: &[&str] = &["u128", "u64", "u32", "u16", "u8"];
const SIGNED_SUFFIXES: &[&str] = &["i128", "i64", "i32", "i16", "i8"];
const FIELD_LIKE_SUFFIXES: &[&str] = &["field", "scalar", "group"];
for suffix in UNSIGNED_SUFFIXES {
if let Some(prefix) = input.strip_suffix(suffix) {
return validate_numeric_prefix(prefix, suffix, false, false);
}
}
for suffix in SIGNED_SUFFIXES {
if let Some(prefix) = input.strip_suffix(suffix) {
return validate_numeric_prefix(prefix, suffix, true, false);
}
}
for suffix in FIELD_LIKE_SUFFIXES {
if let Some(prefix) = input.strip_suffix(suffix) {
if *suffix == "group" && prefix.starts_with('(') {
return Ok(());
}
return validate_numeric_prefix(prefix, suffix, true, true);
}
}
Ok(())
}
fn validate_numeric_prefix(prefix: &str, suffix: &str, allow_negative: bool, decimal_only: bool) -> Result<()> {
if prefix.is_empty() {
return Err(crate::errors::custom(format!(
"Invalid {suffix} literal: missing numeric value before '{suffix}'"
))
.into());
}
let valid = if decimal_only {
is_valid_decimal(prefix, allow_negative)
} else {
is_valid_decimal(prefix, allow_negative) || is_valid_radix_prefixed(prefix, allow_negative)
};
if !valid {
return Err(crate::errors::custom(format!(
"Invalid {suffix} literal: '{prefix}' is not a valid numeric value"
))
.into());
}
Ok(())
}
fn is_valid_decimal(s: &str, allow_negative: bool) -> bool {
let s = if allow_negative { s.strip_prefix('-').unwrap_or(s) } else { s };
if s.is_empty() {
return false;
}
let mut chars = s.chars();
if !chars.next().unwrap().is_ascii_digit() {
return false;
}
chars.all(|c| c.is_ascii_digit() || c == '_')
}
fn is_valid_radix_prefixed(s: &str, allow_negative: bool) -> bool {
let s = if allow_negative { s.strip_prefix('-').unwrap_or(s) } else { s };
if s.len() < 3 || !s.starts_with('0') {
return false;
}
let radix_char = s.as_bytes()[1];
let rest = &s[2..];
if rest.is_empty() || rest.starts_with('_') {
return false;
}
match radix_char {
b'x' | b'X' => rest.chars().all(|c| c.is_ascii_hexdigit() || c == '_'),
b'o' => rest.chars().all(|c| matches!(c, '0'..='7' | '_')),
b'b' => rest.chars().all(|c| matches!(c, '0' | '1' | '_')),
_ => false,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_valid_literals() {
let valid = [
"42field",
"-7field",
"0field",
"1_000_000field",
"100u64",
"0x1Fu8",
"0b1010u32",
"0o77u16",
"-128i8",
"-0x80i16",
"0scalar",
"42group",
"(1, 2)group",
"true",
"false",
"aleo1qnr4dkkvkgfqph0vzc3y6z2eu975wnpz2925ntjccd5cfqxtyu8s7pyjh9",
"aleo1vnx8f43f7yjvs2ehlqsl78j2qh4409s8e4g7gx40u3v884gqgqxqscutu8",
"aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu32",
"aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu64",
"aleo1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqscalar",
"sign195m229jvzr0wmnshj6f8gwplhkrkhjumgjmad553r997u7pjfgpfz4j2w0c9lp53mcqqdsmut2g3a2zuvgst85w38hv273mwjec3sqjsv9w6uglcy58gjh7x3l55z68zsf24kx7a73ctp8x8klhuw7l2p4s3aq8um5jp304js7qcnwdqj56q5r5088tyvxsgektun0rnmvtsuxpe6sj",
"sign1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu8",
"sign1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu32",
"sign1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu64",
"sign1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqscalar",
"APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH",
"APrivateKey1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu8",
"AViewKey1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqu32",
];
for input in &valid {
assert!(validate_cli_literal(input).is_ok(), "expected '{input}' to be valid");
}
}
#[test]
fn test_invalid_literals() {
let invalid = [
"truefield",
"falsefield",
"field",
"scalar",
"u8",
"abcu64",
"-u8",
"hello_worldscalar",
"truegroup",
"xxxi128",
];
for input in &invalid {
assert!(validate_cli_literal(input).is_err(), "expected '{input}' to be invalid");
}
}
#[test]
fn test_parse_input_value_types() {
use snarkvm::prelude::TestnetV0;
let private_key =
PrivateKey::<TestnetV0>::from_str("APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH").unwrap();
let inputs = [
"42u8",
"0u8",
"255u8",
"-7i32",
"-128i8",
"100u64",
"1_000_000u128",
"1_000_000field",
"-7field",
"0scalar",
"true",
"false",
"aleo1qnr4dkkvkgfqph0vzc3y6z2eu975wnpz2925ntjccd5cfqxtyu8s7pyjh9",
"aleo1vnx8f43f7yjvs2ehlqsl78j2qh4409s8e4g7gx40u3v884gqgqxqscutu8",
"sign1nnvrjlksrkxdpwsrw8kztjukzhmuhe5zf3srk38h7g32u4kqtqpxn3j5a6k8zrqcfx580a96956nsjvluzt64cqf54pdka9mgksfqp8esm5elrqqunzqzmac7kzutl6zk7mqht3c0m9kg4hklv7h2js0qmxavwnpuwyl4lzldl6prs4qeqy9wxyp8y44nnydg3h8sg6ue99qkwsnaqq",
"[1u8, 2u8, 3u8]",
"{a: 1u8, b: 2u8}",
"{nested: {x: 1field, y: 2field}, count: 3u32}",
" 42u8 ",
];
for input in &inputs {
assert!(parse_input::<TestnetV0>(input, &private_key).is_ok(), "expected '{input}' to parse");
}
}
}