use super::*;
use aleo_std::StorageMode;
use dialoguer::{Confirm, theme::ColorfulTheme};
use leo_retriever::NetworkName;
use num_format::{Locale, ToFormattedString};
use snarkvm::{
circuit::{Aleo, AleoTestnetV0},
ledger::query::Query as SnarkVMQuery,
package::Package as SnarkVMPackage,
prelude::{
ProgramOwner,
TestnetV0,
VM,
deployment_cost,
store::{ConsensusStore, helpers::memory::ConsensusMemory},
},
};
#[cfg(not(feature = "only_testnet"))]
use snarkvm::{
circuit::{AleoCanaryV0, AleoV0},
prelude::{CanaryV0, MainnetV0},
};
use std::path::PathBuf;
use text_tables;
#[derive(Parser, Debug)]
pub struct Deploy {
#[clap(flatten)]
pub(crate) fee_options: FeeOptions,
#[clap(long, help = "Disables building of the project before deployment.", default_value = "false")]
pub(crate) no_build: bool,
#[clap(long, help = "Enables recursive deployment of dependencies.", default_value = "false")]
pub(crate) recursive: bool,
#[clap(
long,
help = "Time in seconds to wait between consecutive deployments. This is to help prevent a program from trying to be included in an earlier block than its dependency program.",
default_value = "12"
)]
pub(crate) wait: u64,
#[clap(flatten)]
pub(crate) options: BuildOptions,
}
impl Command for Deploy {
type Input = ();
type Output = ();
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Leo")
}
fn prelude(&self, context: Context) -> Result<Self::Input> {
if !self.no_build {
(LeoBuild { options: self.options.clone() }).execute(context)?;
}
Ok(())
}
fn apply(self, context: Context, _: Self::Input) -> Result<Self::Output> {
let network = NetworkName::try_from(context.get_network(&self.options.network)?)?;
let endpoint = context.get_endpoint(&self.options.endpoint)?;
match network {
NetworkName::TestnetV0 => handle_deploy::<AleoTestnetV0, TestnetV0>(&self, context, network, &endpoint),
NetworkName::MainnetV0 => {
#[cfg(feature = "only_testnet")]
panic!("Mainnet chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
return handle_deploy::<AleoV0, MainnetV0>(&self, context, network, &endpoint);
}
NetworkName::CanaryV0 => {
#[cfg(feature = "only_testnet")]
panic!("Canary chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
return handle_deploy::<AleoCanaryV0, CanaryV0>(&self, context, network, &endpoint);
}
}
}
}
fn handle_deploy<A: Aleo<Network = N, BaseField = N::Field>, N: Network>(
command: &Deploy,
context: Context,
network: NetworkName,
endpoint: &str,
) -> Result<<Deploy as Command>::Output> {
let project_name = context.open_manifest::<N>()?.program_id().to_string();
let private_key = context.get_private_key(&command.fee_options.private_key)?;
let address = Address::try_from(&private_key)?;
let query = SnarkVMQuery::from(endpoint);
let mut all_paths: Vec<(String, PathBuf)> = Vec::new();
if command.recursive {
if command.fee_options.record.is_some() {
return Err(CliError::recursive_deploy_with_record().into());
}
all_paths = context.local_dependency_paths()?;
}
all_paths.push((project_name, context.dir()?.join("build")));
for (index, (name, path)) in all_paths.iter().enumerate() {
let package = SnarkVMPackage::<N>::open(path)?;
println!("📦 Creating deployment transaction for '{}'...\n", &name.bold());
let deployment = package.deploy::<A>(None)?;
let variables = deployment.num_combined_variables()?;
let constraints = deployment.num_combined_constraints()?;
if variables > N::MAX_DEPLOYMENT_VARIABLES {
return Err(CliError::variable_limit_exceeded(name, N::MAX_DEPLOYMENT_VARIABLES, network).into());
}
if constraints > N::MAX_DEPLOYMENT_CONSTRAINTS {
return Err(CliError::constraint_limit_exceeded(name, N::MAX_DEPLOYMENT_CONSTRAINTS, network).into());
}
println!(
"📊 Deployment Summary:\n Total Variables: {:>10}\n Total Constraints: {:>10}",
variables.to_formatted_string(&Locale::en),
constraints.to_formatted_string(&Locale::en)
);
let deployment_id = deployment.to_deployment_id()?;
let store = ConsensusStore::<N, ConsensusMemory<N>>::open(StorageMode::Production)?;
let vm = VM::from(store)?;
let base_fee = match command.fee_options.base_fee {
Some(base_fee) => base_fee,
None => {
let (base_fee, (storage_cost, synthesis_cost, namespace_cost)) = deployment_cost(&deployment)?;
deploy_cost_breakdown(
name,
base_fee as f64 / 1_000_000.0,
storage_cost as f64 / 1_000_000.0,
synthesis_cost as f64 / 1_000_000.0,
namespace_cost as f64 / 1_000_000.0,
command.fee_options.priority_fee as f64 / 1_000_000.0,
)?;
base_fee
}
};
let rng = &mut rand::thread_rng();
let fee = match &command.fee_options.record {
Some(record) => {
let fee_record = parse_record(&private_key, record)?;
let fee_authorization = vm.authorize_fee_private(
&private_key,
fee_record,
base_fee,
command.fee_options.priority_fee,
deployment_id,
rng,
)?;
vm.execute_fee_authorization(fee_authorization, Some(query.clone()), rng)?
}
None => {
check_balance(
&private_key,
endpoint,
&network.to_string(),
&context,
base_fee + command.fee_options.priority_fee,
)?;
let fee_authorization = vm.authorize_fee_public(
&private_key,
base_fee,
command.fee_options.priority_fee,
deployment_id,
rng,
)?;
vm.execute_fee_authorization(fee_authorization, Some(query.clone()), rng)?
}
};
let owner = ProgramOwner::new(&private_key, deployment_id, rng)?;
let transaction = Transaction::from_deployment(owner, deployment, fee)?;
if !command.fee_options.dry_run {
if !command.fee_options.yes {
let prompt = format!(
"Do you want to submit deployment of program `{name}` to network {} via endpoint {} using address {}?",
network, endpoint, address
);
let confirmation =
Confirm::with_theme(&ColorfulTheme::default()).with_prompt(prompt).default(false).interact();
if let Ok(confirmation) = confirmation {
if !confirmation {
println!("✅ Successfully aborted the execution transaction for '{}'\n", name.bold());
return Ok(());
}
} else {
return Err(CliError::confirmation_failed().into());
}
}
println!("✅ Created deployment transaction for '{}'\n", name.bold());
handle_broadcast(&format!("{}/{}/transaction/broadcast", endpoint, network), transaction, name)?;
if index < all_paths.len() - 1 {
std::thread::sleep(std::time::Duration::from_secs(command.wait));
}
} else {
println!("✅ Successful dry run deployment for '{}'\n", name.bold());
}
}
Ok(())
}
fn deploy_cost_breakdown(
name: &String,
base_fee: f64,
storage_cost: f64,
synthesis_cost: f64,
namespace_cost: f64,
priority_fee: f64,
) -> Result<()> {
println!("\nBase deployment cost for '{}' is {} credits.\n", name.bold(), base_fee);
let data = [
[name, "Cost (credits)"],
["Transaction Storage", &format!("{:.6}", storage_cost)],
["Program Synthesis", &format!("{:.6}", synthesis_cost)],
["Namespace", &format!("{:.6}", namespace_cost)],
["Priority Fee", &format!("{:.6}", priority_fee)],
["Total", &format!("{:.6}", base_fee + priority_fee)],
];
let mut out = Vec::new();
text_tables::render(&mut out, data).map_err(CliError::table_render_failed)?;
println!("{}", ::std::str::from_utf8(&out).map_err(CliError::table_render_failed)?);
Ok(())
}