diff --git a/Cargo.lock b/Cargo.lock index 14e23735f3..9bca9db2f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1568,6 +1568,7 @@ dependencies = [ "snarkvm", "sys-info", "test_dir", + "text-tables", "toml 0.8.14", "tracing", "tracing-subscriber", @@ -1588,6 +1589,7 @@ dependencies = [ "serde", "serial_test", "snarkvm", + "text-tables", "toml 0.8.14", "tracing", ] @@ -3791,6 +3793,12 @@ dependencies = [ "rand", ] +[[package]] +name = "text-tables" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc41925991e82af3c3e21e25a9aad92e72930af57fbcc4b07867a18d1cd0459" + [[package]] name = "thiserror" version = "1.0.61" diff --git a/Cargo.toml b/Cargo.toml index 31154782e3..5e015daa67 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ ci_skip = [ "leo-compiler/ci_skip" ] noconfig = [ ] [dependencies] +text-tables = "0.3.1" ureq = "2.9.7" [dependencies.leo-ast] diff --git a/leo/cli/commands/deploy.rs b/leo/cli/commands/deploy.rs index 0562f68783..b5dc03a1da 100644 --- a/leo/cli/commands/deploy.rs +++ b/leo/cli/commands/deploy.rs @@ -33,6 +33,7 @@ use snarkvm::{ }, }; use std::{path::PathBuf, str::FromStr}; +use text_tables; /// Deploys an Aleo program. #[derive(Parser, Debug)] @@ -115,61 +116,70 @@ fn handle_deploy, N: Network>( // Fetch the package from the directory. let package = SnarkVMPackage::::open(path)?; - println!("📦 Creating deployment transaction for '{}'...\n", &name.bold()); + if !command.fee_options.estimate_fee { + println!("📦 Creating deployment transaction for '{}'...\n", &name.bold()); + } else { + println!("📦 Estimating deployment cost for '{}'...\n", &name.bold()); + } // Generate the deployment let deployment = package.deploy::(None)?; let deployment_id = deployment.to_deployment_id()?; - // Generate the deployment transaction. - let transaction = { - // Initialize an RNG. - let rng = &mut rand::thread_rng(); - let store = ConsensusStore::>::open(StorageMode::Production)?; + let store = ConsensusStore::>::open(StorageMode::Production)?; - // Initialize the VM. - let vm = VM::from(store)?; + // Initialize the VM. + let vm = VM::from(store)?; - // Compute the minimum deployment cost. - let (minimum_deployment_cost, _) = deployment_cost(&deployment)?; + // Compute the minimum deployment cost. + let (total_cost, (storage_cost, synthesis_cost, namespace_cost)) = deployment_cost(&deployment)?; + + if command.fee_options.estimate_fee { + // Use `credit` denomination instead of `microcredit`. + display_cost_breakdown(name, total_cost 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); + continue; + } - // Prepare the fees. - 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, - minimum_deployment_cost, - command.fee_options.priority_fee, - deployment_id, - rng, - )?; - vm.execute_fee_authorization(fee_authorization, Some(query.clone()), rng)? - } - None => { - let fee_authorization = vm.authorize_fee_public( - &private_key, - minimum_deployment_cost, - command.fee_options.priority_fee, - deployment_id, - rng, - )?; - vm.execute_fee_authorization(fee_authorization, Some(query.clone()), rng)? - } - }; - // Construct the owner. - let owner = ProgramOwner::new(&private_key, deployment_id, rng)?; - - // Create a new transaction. - Transaction::from_deployment(owner, deployment, fee)? + // Initialize an RNG. + let rng = &mut rand::thread_rng(); + + // Prepare the fees. + 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, + total_cost, + command.fee_options.priority_fee, + deployment_id, + rng, + )?; + vm.execute_fee_authorization(fee_authorization, Some(query.clone()), rng)? + } + None => { + let fee_authorization = vm.authorize_fee_public( + &private_key, + total_cost, + command.fee_options.priority_fee, + deployment_id, + rng, + )?; + vm.execute_fee_authorization(fee_authorization, Some(query.clone()), rng)? + } }; + // Construct the owner. + let owner = ProgramOwner::new(&private_key, deployment_id, rng)?; + + // Generate the deployment transaction. + let transaction = Transaction::from_deployment(owner, deployment, fee)?; + println!("✅ Created deployment transaction for '{}'", name.bold()); // Determine if the transaction should be broadcast, stored, or displayed to the user. handle_broadcast( - &format!("{}/{}/transaction/broadcast", command.options.endpoint, command.fee_options.network), + &format!("{}/{}/transaction/broadcast", command.options.endpoint, command.options.network), transaction, name, )?; @@ -181,3 +191,13 @@ fn handle_deploy, N: Network>( Ok(()) } + +// A helper function to display a cost breakdown of the deployment. +fn display_cost_breakdown(name: &String, total_cost: f64, storage_cost: f64, synthesis_cost: f64, namespace_cost: f64) { + println!("✅ Estimated deployment cost for '{}' is {} credits.", name.bold(), total_cost); + // Display the cost breakdown in a table. + let data = [[name, "Cost (credits)", "Cost reduction tips"], ["Storage", &format!("{:.6}", storage_cost), "Use less instructions"], ["Synthesis", &format!("{:.6}", synthesis_cost), "Remove expensive operations (Ex: SHA3), or unnecessary imports"], ["Namespace", &format!("{:.6}", namespace_cost), "Lengthen the program name (each additional character makes it 10x cheaper)"], ["Total", &format!("{:.6}", total_cost), ""]]; + let mut out = Vec::new(); + text_tables::render(&mut out, data).unwrap(); + println!("{}", ::std::str::from_utf8(&out).unwrap()); +} diff --git a/leo/cli/commands/mod.rs b/leo/cli/commands/mod.rs index 9e9efe03b3..fe629bb6bf 100644 --- a/leo/cli/commands/mod.rs +++ b/leo/cli/commands/mod.rs @@ -205,10 +205,10 @@ impl Default for BuildOptions { /// Used by Execute and Deploy commands. #[derive(Parser, Clone, Debug)] pub struct FeeOptions { + #[clap(long, help = "Estimate the deploy or execution fee without broadcasting to the network.", default_value = "false")] + pub(crate) estimate_fee: bool, #[clap(long, help = "Priority fee in microcredits. Defaults to 0.", default_value = "0")] pub(crate) priority_fee: u64, - #[clap(long, help = "Network to broadcast to. Defaults to mainnet.", default_value = "mainnet")] - pub(crate) network: String, #[clap(long, help = "Private key to authorize fee expenditure.")] pub(crate) private_key: Option, #[clap( diff --git a/leo/package/Cargo.toml b/leo/package/Cargo.toml index 1677c3ae60..fc76c4212e 100644 --- a/leo/package/Cargo.toml +++ b/leo/package/Cargo.toml @@ -47,6 +47,9 @@ features = [ "derive" ] [dependencies.serial_test] version = "3.1.1" +[dependencies.text-tables] +text-tables = "0.3.1" + [dependencies.toml] version = "0.8"