pub mod add;
pub use add::LeoAdd;
pub mod account;
pub use account::Account;
pub mod build;
pub use build::LeoBuild;
pub mod clean;
pub use clean::LeoClean;
pub mod debug;
pub use debug::LeoDebug;
pub mod deploy;
pub use deploy::Deploy;
pub mod example;
pub use example::LeoExample;
pub mod execute;
pub use execute::LeoExecute;
pub mod query;
pub use query::LeoQuery;
pub mod new;
pub use new::LeoNew;
pub mod remove;
pub use remove::LeoRemove;
pub mod run;
pub use run::LeoRun;
pub mod update;
pub use update::LeoUpdate;
use super::*;
use crate::cli::helpers::context::*;
use leo_errors::{CliError, PackageError, Result, emitter::Handler};
use leo_package::{build::*, outputs::OutputsDirectory, package::*};
use snarkvm::prelude::{Address, Ciphertext, Plaintext, PrivateKey, Record, ViewKey, block::Transaction};
use clap::Parser;
use colored::Colorize;
use std::str::FromStr;
use tracing::span::Span;
use crate::cli::query::QueryCommands;
use snarkvm::console::network::Network;
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(()))?
}
}
#[derive(Parser, Clone, Debug)]
pub struct BuildOptions {
#[clap(long, help = "Endpoint to retrieve network state from. Overrides setting in `.env`.")]
pub endpoint: Option<String>,
#[clap(long, help = "Network to broadcast to. Overrides setting in `.env`.")]
pub(crate) network: Option<String>,
#[clap(long, help = "Does not recursively compile dependencies.")]
pub non_recursive: bool,
#[clap(long, help = "Enables offline mode.")]
pub offline: bool,
#[clap(long, help = "Enable spans in AST snapshots.")]
pub enable_ast_spans: bool,
#[clap(long, help = "Enables dead code elimination in the compiler.")]
pub enable_dce: bool,
#[clap(long, help = "Writes all AST snapshots for the different compiler phases.")]
pub enable_all_ast_snapshots: bool,
#[clap(long, help = "Writes AST snapshot of the initial parse.")]
pub enable_initial_ast_snapshot: bool,
#[clap(long, help = "Writes AST snapshot of the unrolled AST.")]
pub enable_unrolled_ast_snapshot: bool,
#[clap(long, help = "Writes AST snapshot of the SSA AST.")]
pub enable_ssa_ast_snapshot: bool,
#[clap(long, help = "Writes AST snapshot of the flattened AST.")]
pub enable_flattened_ast_snapshot: bool,
#[clap(long, help = "Writes AST snapshot of the destructured AST.")]
pub enable_destructured_ast_snapshot: bool,
#[clap(long, help = "Writes AST snapshot of the inlined AST.")]
pub enable_inlined_ast_snapshot: bool,
#[clap(long, help = "Writes AST snapshot of the dead code eliminated (DCE) AST.")]
pub enable_dce_ast_snapshot: bool,
#[clap(long, help = "Max depth to type check nested conditionals.", default_value = "10")]
pub conditional_block_max_depth: usize,
#[clap(long, help = "Disable type checking of nested conditional branches in finalize scope.")]
pub disable_conditional_branch_type_checking: bool,
}
impl Default for BuildOptions {
fn default() -> Self {
Self {
endpoint: None,
network: None,
non_recursive: false,
offline: false,
enable_dce: false,
enable_all_ast_snapshots: false,
enable_initial_ast_snapshot: false,
enable_unrolled_ast_snapshot: false,
enable_ssa_ast_snapshot: false,
enable_flattened_ast_snapshot: false,
enable_destructured_ast_snapshot: false,
enable_inlined_ast_snapshot: false,
enable_ast_spans: false,
enable_dce_ast_snapshot: false,
conditional_block_max_depth: 10,
disable_conditional_branch_type_checking: false,
}
}
}
#[derive(Parser, Clone, Debug, Default)]
pub struct FeeOptions {
#[clap(short, long, help = "Don't ask for confirmation.", default_value = "false")]
pub(crate) yes: bool,
#[clap(long, help = "Performs a dry-run of transaction generation")]
pub(crate) dry_run: bool,
#[clap(long, help = "Base fee in microcredits. Automatically calculated if not provided.")]
pub(crate) base_fee: Option<u64>,
#[clap(long, help = "Priority fee in microcredits. Defaults to 0.", default_value = "0")]
pub(crate) priority_fee: u64,
#[clap(long, help = "Private key to authorize fee expenditure.")]
pub(crate) private_key: Option<String>,
#[clap(
short,
help = "Record to pay for fee privately. If one is not specified, a public fee will be taken.",
long
)]
record: Option<String>,
#[clap(long, help = "Consensus version to use for the transaction.")]
pub(crate) consensus_version: Option<u8>,
}
pub fn parse_record<N: Network>(private_key: &PrivateKey<N>, record: &str) -> Result<Record<N, Plaintext<N>>> {
match record.starts_with("record1") {
true => {
let ciphertext = Record::<N, Ciphertext<N>>::from_str(record)?;
let view_key = ViewKey::try_from(private_key)?;
Ok(ciphertext.decrypt(&view_key)?)
}
false => Ok(Record::<N, Plaintext<N>>::from_str(record)?),
}
}
fn check_balance<N: Network>(
private_key: &PrivateKey<N>,
endpoint: &str,
network: &str,
context: &Context,
total_cost: u64,
) -> Result<()> {
let address = Address::<N>::try_from(ViewKey::try_from(private_key)?)?;
let mut public_balance = LeoQuery {
endpoint: Some(endpoint.to_string()),
network: Some(network.to_string()),
command: QueryCommands::Program {
command: query::LeoProgram {
name: "credits".to_string(),
mappings: false,
mapping_value: Some(vec!["account".to_string(), address.to_string()]),
},
},
}
.execute(Context::new(context.path.clone(), context.home.clone(), true)?)?;
public_balance.truncate(public_balance.len() - 3);
let balance = if let Ok(credits) = public_balance.parse::<u64>() {
credits
} else {
return Err(CliError::invalid_balance(address).into());
};
if balance < total_cost {
Err(PackageError::insufficient_balance(address, public_balance, total_cost).into())
} else {
println!("Your current public balance is {} credits.\n", balance as f64 / 1_000_000.0);
Ok(())
}
}
fn get_latest_block_height(endpoint: &str, network: &str, context: &Context) -> Result<u32> {
let height = LeoQuery {
endpoint: Some(endpoint.to_string()),
network: Some(network.to_string()),
command: QueryCommands::Block {
command: query::LeoBlock {
id: None,
latest: false,
latest_hash: false,
latest_height: true,
range: None,
transactions: false,
to_height: false,
},
},
}
.execute(Context::new(context.path.clone(), context.home.clone(), true)?)?;
let height = height.parse::<u32>().map_err(CliError::string_parse_error)?;
Ok(height)
}
fn handle_broadcast<N: Network>(endpoint: &String, transaction: Transaction<N>, operation: &String) -> Result<()> {
println!("Broadcasting transaction to {}...\n", endpoint.clone());
let transaction_id = transaction.id();
let response = ureq::AgentBuilder::new()
.redirects(0)
.build()
.post(endpoint)
.set("X-Leo-Version", env!("CARGO_PKG_VERSION"))
.send_json(&transaction)
.map_err(|err| CliError::broadcast_error(err.to_string()))?;
match response.status() {
200 => {
let _response_string =
response.into_string().map_err(CliError::string_parse_error)?.trim_matches('\"').to_string();
match transaction {
Transaction::Deploy(..) => {
println!(
"⌛ Deployment {transaction_id} ('{}') has been broadcast to {}.\n",
operation.bold(),
endpoint
);
}
Transaction::Execute(..) => {
println!(
"⌛ Execution {transaction_id} ('{}') has been broadcast to {}.\n",
operation.bold(),
endpoint
);
}
Transaction::Fee(..) => {
println!("❌ Failed to broadcast fee '{}' to the {}.\n", operation.bold(), endpoint);
}
}
Ok(())
}
301 => {
let msg = format!(
"⚠️ The endpoint `{endpoint}` has been permanently moved. Try using `https://api.explorer.provable.com/v1` in your `.env` file or via the `--endpoint` flag."
);
Err(CliError::broadcast_error(msg).into())
}
_ => {
let code = response.status();
let error_message = match response.into_string() {
Ok(response) => format!("(status code {code}: {:?})", response),
Err(err) => format!("({err})"),
};
let msg = match transaction {
Transaction::Deploy(..) => {
format!("❌ Failed to deploy '{}' to {}: {}", operation.bold(), &endpoint, error_message)
}
Transaction::Execute(..) => {
format!(
"❌ Failed to broadcast execution '{}' to {}: {}",
operation.bold(),
&endpoint,
error_message
)
}
Transaction::Fee(..) => {
format!("❌ Failed to broadcast fee '{}' to {}: {}", operation.bold(), &endpoint, error_message)
}
};
Err(CliError::broadcast_error(msg).into())
}
}
}