use super::*;
use check_transaction::TransactionStatus;
use leo_ast::NetworkName;
use leo_package::{Package, ProgramData, fetch_program_from_network, retry_network_call};
use aleo_std::StorageMode;
use rand::CryptoRng;
#[cfg(not(feature = "only_testnet"))]
use snarkvm::prelude::{CanaryV0, MainnetV0};
use snarkvm::{
circuit::{Aleo, AleoTestnetV0},
ledger::{
query::{Query as SnarkVMQuery, QueryTrait},
store::helpers::memory::BlockMemory,
},
prelude::{
Certificate,
ConsensusVersion,
Deployment,
Fee,
Program,
ProgramID,
ProgramOwner,
Rng,
TestnetV0,
VM,
VerifyingKey,
deployment_cost,
execution_cost_for_authorization,
minimum_cost_in_microcredits_v1,
minimum_cost_in_microcredits_v2,
minimum_cost_in_microcredits_v3,
store::{ConsensusStore, helpers::memory::ConsensusMemory},
},
synthesizer::program::StackTrait,
};
use colored::*;
use itertools::Itertools;
use std::{collections::HashSet, fs, path::PathBuf};
#[derive(Parser, Debug)]
pub struct LeoDeploy {
#[clap(flatten)]
pub(crate) fee_options: FeeOptions,
#[clap(flatten)]
pub(crate) action: TransactionAction,
#[clap(flatten)]
pub(crate) env_override: EnvOptions,
#[clap(flatten)]
pub(crate) extra: ExtraOptions,
#[clap(long, help = "Skips deployment of any program that contains one of the given substrings.", value_delimiter = ',', num_args = 1..)]
pub(crate) skip: Vec<String>,
#[clap(flatten)]
pub(crate) build_options: BuildOptions,
#[clap(long, help = "Use placeholder certificate and verifying keys during deployment.", default_value = "false")]
pub(crate) skip_deploy_certificate: bool,
}
pub struct Task<N: Network> {
pub id: ProgramID<N>,
pub program: Program<N>,
pub edition: Option<u16>,
pub is_local: bool,
pub priority_fee: Option<u64>,
pub record: Option<Record<N, Plaintext<N>>>,
pub bytecode_size: usize,
}
impl Command for LeoDeploy {
type Input = Package;
type Output = DeployOutput;
fn log_span(&self) -> Span {
tracing::span!(tracing::Level::INFO, "Leo")
}
fn prelude(&self, context: Context) -> Result<Self::Input> {
LeoBuild {
env_override: self.env_override.clone(),
options: {
let mut options = self.build_options.clone();
options.no_cache = true;
options
},
}
.execute(context)
}
fn apply(self, context: Context, input: Self::Input) -> Result<Self::Output> {
if input.compilation_units.last().is_some_and(|p| p.kind.is_library()) {
return Err(CliError::custom("Cannot deploy a library package. Only programs can be deployed.").into());
}
let network = get_network(&self.env_override.network)?;
match network {
NetworkName::TestnetV0 => handle_deploy::<TestnetV0, AleoTestnetV0>(&self, context, network, input),
NetworkName::MainnetV0 => {
#[cfg(feature = "only_testnet")]
panic!("Mainnet chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
handle_deploy::<MainnetV0, snarkvm::circuit::AleoV0>(&self, context, network, input)
}
NetworkName::CanaryV0 => {
#[cfg(feature = "only_testnet")]
panic!("Canary chosen with only_testnet feature");
#[cfg(not(feature = "only_testnet"))]
handle_deploy::<CanaryV0, snarkvm::circuit::AleoCanaryV0>(&self, context, network, input)
}
}
}
}
fn handle_deploy<N: Network, A: Aleo<Network = N>>(
command: &LeoDeploy,
context: Context,
network: NetworkName,
package: Package,
) -> Result<<LeoDeploy as Command>::Output> {
let private_key = get_private_key(&command.env_override.private_key)?;
let address =
Address::try_from(&private_key).map_err(|e| CliError::custom(format!("Failed to parse address: {e}")))?;
let endpoint = get_endpoint(&command.env_override.endpoint)?;
let is_devnet = get_is_devnet(command.env_override.devnet);
let consensus_heights =
command.env_override.consensus_heights.clone().unwrap_or_else(|| get_consensus_heights(network, is_devnet));
validate_consensus_heights(&consensus_heights)
.map_err(|e| CliError::custom(format!("⚠️ Invalid consensus heights: {e}")))?;
let consensus_heights_string = consensus_heights.iter().format(",").to_string();
println!(
"\n📢 Using the following consensus heights: {consensus_heights_string}\n To override, pass in `--consensus-heights` or override the environment variable `CONSENSUS_VERSION_HEIGHTS`.\n"
);
#[allow(unsafe_code)]
unsafe {
std::env::set_var("CONSENSUS_VERSION_HEIGHTS", consensus_heights_string);
}
let programs = package.compilation_units.iter().filter(|unit| unit.kind.is_program()).cloned();
let programs_and_bytecode: Vec<(leo_package::CompilationUnit, String)> = programs
.into_iter()
.map(|program| {
let bytecode = match &program.data {
ProgramData::Bytecode(s) => s.clone(),
ProgramData::SourcePath { .. } => {
let aleo_name = format!("{}", program.name);
let aleo_path = if package.manifest.program == aleo_name {
package.build_directory().join("main.aleo")
} else {
package.imports_directory().join(aleo_name)
};
fs::read_to_string(aleo_path.clone())
.map_err(|e| CliError::custom(format!("Failed to read file {}: {e}", aleo_path.display())))?
}
};
Ok((program, bytecode))
})
.collect::<Result<_>>()?;
let fee_options = parse_fee_options(&private_key, &command.fee_options, programs_and_bytecode.len())?;
let tasks: Vec<Task<N>> = programs_and_bytecode
.into_iter()
.zip(fee_options)
.map(|((program, bytecode), (_base_fee, priority_fee, record))| {
let id_str = format!("{}", program.name);
let id =
id_str.parse().map_err(|e| CliError::custom(format!("Failed to parse program ID {id_str}: {e}")))?;
let bytecode_size = bytecode.len();
let parsed_program =
bytecode.parse().map_err(|e| CliError::custom(format!("Failed to parse program: {e}")))?;
Ok(Task {
id,
program: parsed_program,
edition: program.edition,
is_local: program.is_local,
priority_fee,
record,
bytecode_size,
})
})
.collect::<Result<_>>()?;
let (local, remote) = tasks.into_iter().partition::<Vec<_>, _>(|task| task.is_local);
let skipped: HashSet<ProgramID<N>> = local
.iter()
.filter_map(|task| {
let id_string = task.id.to_string();
command.skip.iter().any(|skip| id_string.contains(skip)).then_some(task.id)
})
.collect();
let consensus_version = get_consensus_version(
&command.extra.consensus_version,
&endpoint,
network,
&consensus_heights,
&context,
command.env_override.network_retries,
)?;
let config = Some(Config {
address: address.to_string(),
network: network.to_string(),
endpoint: Some(endpoint.clone()),
consensus_version: Some(consensus_version as u8),
});
print_deployment_plan(
&private_key,
&address,
&endpoint,
&network,
&local,
&skipped,
&remote,
&check_tasks_for_warnings(&endpoint, network, &local, consensus_version, command),
consensus_version,
command,
);
if !confirm("Do you want to proceed with deployment?", command.extra.yes)? {
println!("❌ Deployment aborted.");
return Ok(DeployOutput::default());
}
let rng = &mut rand::thread_rng();
let vm = VM::from(ConsensusStore::<N, ConsensusMemory<N>>::open(StorageMode::Production)?)?;
let programs_and_editions = remote
.into_iter()
.map(|task| {
let edition = match task.edition {
Some(e) => e,
None => leo_package::fetch_latest_edition(
&task.id.to_string(),
&endpoint,
network,
command.env_override.network_retries,
)?,
};
Ok((task.program, edition))
})
.collect::<Result<Vec<_>>>()?;
check_edition_constructor_requirements(&programs_and_editions, consensus_version, "deploy")?;
vm.process().write().add_programs_with_editions(&programs_and_editions)?;
let query = SnarkVMQuery::<N, BlockMemory<N>>::from(
endpoint
.parse::<Uri>()
.map_err(|e| CliError::custom(format!("Failed to parse endpoint URI '{endpoint}': {e}")))?,
);
let mut transactions = Vec::new();
let mut all_stats = Vec::new();
let mut all_broadcasts = Vec::new();
for Task { id, program, edition, priority_fee, record, bytecode_size, .. } in local {
if !skipped.contains(&id) {
if let Some(constructor) = program.constructor() {
println!(
r"
🔧 Your program '{}' has the following constructor.
──────────────────────────────────────────────
{constructor}
──────────────────────────────────────────────
Once it is deployed, it CANNOT be changed.
",
id.to_string().bold()
);
if !confirm("Would you like to proceed?", command.extra.yes)? {
println!("❌ Deployment aborted.");
return Ok(DeployOutput::default());
}
}
println!("📦 Creating deployment transaction for '{}'...\n", id.to_string().bold());
let (transaction, stats) = if command.skip_deploy_certificate {
println!("⚠️ Skipping deployment certificate and verifier key generation as per user request.\n");
deploy_with_placeholder_certificate::<N, A, _>(
&vm,
&private_key,
&program,
edition.unwrap_or(0),
record,
priority_fee,
bytecode_size,
consensus_version,
&query,
command.env_override.network_retries,
rng,
)?
} else {
let transaction = vm
.deploy(&private_key, &program, record, priority_fee.unwrap_or(0), Some(&query), rng)
.map_err(|e| CliError::custom(format!("Failed to generate deployment transaction: {e}")))?;
let deployment = transaction.deployment().expect("Expected a deployment in the transaction");
vm.process().write().add_program(&program)?;
let stats =
compute_deployment_stats(&vm, deployment, priority_fee, consensus_version, bytecode_size, rng)?;
(transaction, stats)
};
print_deployment_summary(&id.to_string(), &stats);
transactions.push((id, transaction));
all_stats.push(stats);
if !command.skip_deploy_certificate {
let (program_id, transaction) = transactions.last().expect("Transaction was just pushed");
let deployment = transaction.deployment().expect("Expected a deployment in the transaction");
validate_deployment_limits(deployment, program_id, &network)?;
}
}
if let Err(e) = vm.process().write().add_program(&program) {
tracing::debug!("Program {id} already in VM: {e}");
}
}
if command.action.print {
for (program_name, transaction) in transactions.iter() {
let transaction_json = serde_json::to_string_pretty(transaction)
.map_err(|e| CliError::custom(format!("Failed to serialize transaction: {e}")))?;
println!("🖨️ Printing deployment for {program_name}\n{transaction_json}")
}
}
if let Some(path) = &command.action.save {
std::fs::create_dir_all(path).map_err(|e| CliError::custom(format!("Failed to create directory: {e}")))?;
for (program_name, transaction) in transactions.iter() {
let file_path = PathBuf::from(path).join(format!("{program_name}.deployment.json"));
println!("💾 Saving deployment for {program_name} at {}", file_path.display());
let transaction_json = serde_json::to_string_pretty(transaction)
.map_err(|e| CliError::custom(format!("Failed to serialize transaction: {e}")))?;
std::fs::write(file_path, transaction_json)
.map_err(|e| CliError::custom(format!("Failed to write transaction to file: {e}")))?;
}
}
if command.action.broadcast {
for (i, (program_id, transaction)) in transactions.iter().enumerate() {
println!("\n📡 Broadcasting deployment for {}...", program_id.to_string().bold());
let fee = transaction.fee_transition().expect("Expected a fee in the transaction");
if !confirm_fee(&fee, &private_key, &address, &endpoint, network, &context, command.extra.yes)? {
println!("⏩ Deployment skipped.");
continue;
}
let fee_id = fee.id().to_string();
let fee_transaction_id = Transaction::from_fee(fee.clone())?.id().to_string();
let id = transaction.id().to_string();
let height_before =
check_transaction::current_height(&endpoint, network, command.env_override.network_retries)?;
let (message, status) = handle_broadcast(
&format!("{endpoint}/{network}/transaction/broadcast"),
transaction,
&program_id.to_string(),
)?;
let fail_and_prompt = |msg| {
println!("❌ Failed to deploy program {program_id}: {msg}.");
let count = transactions.len() - i - 1;
if count > 0 {
confirm("Do you want to continue with the next deployment?", command.extra.yes)
} else {
Ok(false)
}
};
match status {
200..=299 => {
let tx_status = check_transaction::check_transaction_with_message(
&id,
Some(&fee_id),
&endpoint,
network,
height_before + 1,
command.extra.max_wait,
command.extra.blocks_to_check,
command.env_override.network_retries,
)?;
let confirmed = tx_status == Some(TransactionStatus::Accepted);
if confirmed {
println!("✅ Deployment confirmed!");
} else if fail_and_prompt("could not find the transaction on the network")? {
continue;
} else {
return Ok(build_deploy_output(config.clone(), &transactions, &all_stats, &all_broadcasts));
}
all_broadcasts.push(BroadcastStats {
fee_id: fee_id.clone(),
fee_transaction_id: fee_transaction_id.clone(),
confirmed,
});
}
_ => {
if fail_and_prompt(&message)? {
continue;
} else {
return Ok(build_deploy_output(config.clone(), &transactions, &all_stats, &all_broadcasts));
}
}
}
}
}
Ok(build_deploy_output(config, &transactions, &all_stats, &all_broadcasts))
}
fn check_tasks_for_warnings<N: Network>(
endpoint: &str,
network: NetworkName,
tasks: &[Task<N>],
consensus_version: ConsensusVersion,
command: &LeoDeploy,
) -> Vec<String> {
let mut warnings = Vec::new();
for Task { id, is_local, program, bytecode_size, .. } in tasks {
if !is_local || !command.action.broadcast {
continue;
}
if fetch_program_from_network(&id.to_string(), endpoint, network, command.env_override.network_retries).is_ok()
{
warnings
.push(format!("The program '{id}' already exists on the network. Please use `leo upgrade` instead.",));
}
if consensus_version >= ConsensusVersion::V7
&& let Err(e) = program.check_program_naming_structure()
{
warnings.push(format!(
"The program '{id}' has an invalid naming scheme: {e}. The deployment will likely fail."
));
}
if let Err(e) = program.check_restricted_keywords_for_consensus_version(consensus_version) {
warnings.push(format!(
"The program '{id}' contains restricted keywords for consensus version {}: {e}. The deployment will likely fail.",
consensus_version as u8
));
}
if consensus_version < ConsensusVersion::V9 && program.contains_v9_syntax() {
warnings.push(format!("The program '{id}' uses V9 features but the consensus version is less than V9. The deployment will likely fail"));
}
if consensus_version >= ConsensusVersion::V9 && !program.contains_constructor() {
warnings
.push(format!("The program '{id}' does not contain a constructor. The deployment will likely fail",));
}
if let (_, _, Some(msg)) = format_program_size(*bytecode_size, N::LATEST_MAX_PROGRAM_SIZE()) {
warnings.push(format!("The program '{id}' is {msg}."));
}
}
if let Err(e) =
check_consensus_version_mismatch(consensus_version, endpoint, network, command.env_override.network_retries)
{
warnings.push(format!("{e}. In some cases, the deployment may fail"));
}
warnings
}
pub(crate) fn validate_deployment_limits<N: Network>(
deployment: &Deployment<N>,
program_id: &ProgramID<N>,
network: &NetworkName,
) -> Result<()> {
let combined_variables = deployment.num_combined_variables()?;
if combined_variables > N::MAX_DEPLOYMENT_VARIABLES {
return Err(CliError::variable_limit_exceeded(
program_id,
combined_variables,
N::MAX_DEPLOYMENT_VARIABLES,
network,
)
.into());
}
let constraints = deployment.num_combined_constraints()?;
if constraints > N::MAX_DEPLOYMENT_CONSTRAINTS {
return Err(CliError::constraint_limit_exceeded(
program_id,
constraints,
N::MAX_DEPLOYMENT_CONSTRAINTS,
network,
)
.into());
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn print_deployment_plan<N: Network>(
private_key: &PrivateKey<N>,
address: &Address<N>,
endpoint: &str,
network: &NetworkName,
local: &[Task<N>],
skipped: &HashSet<ProgramID<N>>,
remote: &[Task<N>],
warnings: &[String],
consensus_version: ConsensusVersion,
command: &LeoDeploy,
) {
use colored::*;
println!("\n{}", "🛠️ Deployment Plan Summary".bold());
println!("{}", "──────────────────────────────────────────────".dimmed());
println!("{}", "🔧 Configuration:".bold());
println!(" {:20}{}", "Private Key:".cyan(), format!("{}...", &private_key.to_string()[..24]).yellow());
println!(" {:20}{}", "Address:".cyan(), format!("{}...", &address.to_string()[..24]).yellow());
println!(" {:20}{}", "Endpoint:".cyan(), endpoint.yellow());
println!(" {:20}{}", "Network:".cyan(), network.to_string().yellow());
println!(" {:20}{}", "Consensus Version:".cyan(), (consensus_version as u8).to_string().yellow());
println!("\n{}", "📦 Deployment Tasks:".bold());
if local.is_empty() {
println!(" (none)");
} else {
for Task { id, priority_fee, record, .. } in local.iter().filter(|task| !skipped.contains(&task.id)) {
let priority_fee_str = priority_fee.map_or("0".into(), |v| v.to_string());
let record_str = if record.is_some() { "yes" } else { "no (public fee)" };
println!(
" • {} │ priority fee: {} │ fee record: {}",
id.to_string().cyan(),
priority_fee_str,
record_str
);
}
}
if !skipped.is_empty() {
println!("\n{}", "🚫 Skipped Programs:".bold().red());
for symbol in skipped {
println!(" • {}", symbol.to_string().dimmed());
}
}
if !remote.is_empty() {
println!("\n{}", "🌐 Remote Dependencies:".bold().red());
println!("{}", "(Leo will not generate transactions for these programs)".bold().red());
for Task { id, .. } in remote {
println!(" • {}", id.to_string().dimmed());
}
}
println!("\n{}", "⚙️ Actions:".bold());
if command.action.print {
println!(" • Transaction(s) will be printed to the console.");
} else {
println!(" • Transaction(s) will NOT be printed to the console.");
}
if let Some(path) = &command.action.save {
println!(" • Transaction(s) will be saved to {}", path.bold());
} else {
println!(" • Transaction(s) will NOT be saved to a file.");
}
if command.action.broadcast {
println!(" • Transaction(s) will be broadcast to {}", endpoint.bold());
} else {
println!(" • Transaction(s) will NOT be broadcast to the network.");
}
if !warnings.is_empty() {
println!("\n{}", "⚠️ Warnings:".bold().red());
for warning in warnings {
println!(" • {}", warning.dimmed());
}
}
println!("{}", "──────────────────────────────────────────────\n".dimmed());
}
pub(crate) fn compute_deployment_stats<N: Network, R: Rng + CryptoRng>(
vm: &VM<N, ConsensusMemory<N>>,
deployment: &Deployment<N>,
priority_fee: Option<u64>,
consensus_version: ConsensusVersion,
bytecode_size: usize,
rng: &mut R,
) -> Result<DeploymentStats> {
let variables = deployment.num_combined_variables()?;
let constraints = deployment.num_combined_constraints()?;
let (_, (storage_cost, synthesis_cost, constructor_cost, namespace_cost)) =
deployment_cost(&vm.process().read(), deployment, consensus_version)?;
let priority = priority_fee.unwrap_or(0);
let function_costs = calculate_function_costs(vm, deployment, consensus_version, rng)?;
Ok(DeploymentStats {
program_size_bytes: bytecode_size,
max_program_size_bytes: N::LATEST_MAX_PROGRAM_SIZE(),
total_variables: Some(variables),
total_constraints: Some(constraints),
max_variables: Some(N::MAX_DEPLOYMENT_VARIABLES),
max_constraints: Some(N::MAX_DEPLOYMENT_CONSTRAINTS),
storage_cost,
synthesis_cost,
namespace_cost,
constructor_cost,
priority_fee: priority,
total_cost: storage_cost + synthesis_cost + namespace_cost + constructor_cost + priority,
function_costs,
})
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn deploy_with_placeholder_certificate<N: Network, A: Aleo<Network = N>, R: Rng + CryptoRng>(
vm: &VM<N, ConsensusMemory<N>>,
private_key: &PrivateKey<N>,
program: &Program<N>,
edition: u16,
record: Option<Record<N, Plaintext<N>>>,
priority_fee: Option<u64>,
bytecode_size: usize,
consensus_version: ConsensusVersion,
query: &SnarkVMQuery<N, BlockMemory<N>>,
network_retries: u32,
rng: &mut R,
) -> Result<(Transaction<N>, DeploymentStats)> {
assert!(!program.functions().is_empty(), "Program `{}` has no functions", program.id());
let mut verifying_keys = Vec::with_capacity(program.functions().len() + program.records().len());
for function_name in program.functions().keys() {
let verifying_key = VerifyingKey::from_str(leo_compiler::run::PLACEHOLDER_VK)?;
let certificate = Certificate::from_str(leo_compiler::run::PLACEHOLDER_CERT)?;
verifying_keys.push((*function_name, (verifying_key, certificate)));
}
for record_name in program.records().keys() {
let verifying_key = VerifyingKey::from_str(leo_compiler::run::PLACEHOLDER_VK)?;
let certificate = Certificate::from_str(leo_compiler::run::PLACEHOLDER_CERT)?;
verifying_keys.push((*record_name, (verifying_key, certificate)));
}
let mut deployment = Deployment::new(edition, program.clone(), verifying_keys, None, None).unwrap();
deployment.set_program_owner_raw(Some(Address::try_from(private_key)?));
deployment.set_program_checksum_raw(Some(deployment.program().to_checksum()));
let deployment_id = deployment.to_deployment_id()?;
let owner = ProgramOwner::new(private_key, deployment_id, rng)?;
let (minimum_deployment_cost, (storage_cost, synthesis_cost, constructor_cost, namespace_cost)) =
deployment_cost(&vm.process().read(), &deployment, consensus_version)?;
let fee_authorization = match record {
Some(record) => vm
.process()
.read()
.authorize_fee_private::<A, _>(
private_key,
record,
minimum_deployment_cost,
priority_fee.unwrap_or(0),
deployment_id,
rng,
)
.map_err(|e| anyhow::anyhow!("{e}"))?,
None => vm
.process()
.read()
.authorize_fee_public::<A, _>(
private_key,
minimum_deployment_cost,
priority_fee.unwrap_or(0),
deployment_id,
rng,
)
.map_err(|e| anyhow::anyhow!("{e}"))?,
};
let state_root = retry_network_call(network_retries, || query.current_state_root())?;
let fee = Fee::from(fee_authorization.transitions().into_iter().next().unwrap().1, state_root, None)?;
vm.process().write().add_program(program)?;
let priority = priority_fee.unwrap_or(0);
let function_costs = calculate_function_costs(vm, &deployment, consensus_version, rng)?;
let stats = DeploymentStats {
program_size_bytes: bytecode_size,
max_program_size_bytes: N::LATEST_MAX_PROGRAM_SIZE(),
total_variables: None,
total_constraints: None,
max_variables: None,
max_constraints: None,
storage_cost,
synthesis_cost,
namespace_cost,
constructor_cost,
priority_fee: priority,
total_cost: storage_cost + synthesis_cost + namespace_cost + constructor_cost + priority,
function_costs,
};
let transaction = Transaction::from_deployment(owner, deployment, fee)?;
Ok((transaction, stats))
}
pub(crate) fn print_deployment_summary(program_id: &str, stats: &DeploymentStats) {
use colored::*;
println!("\n{} {}", "📊 Deployment Summary for".bold(), program_id.bold());
println!("{}", "──────────────────────────────────────────────".dimmed());
print!("{stats}");
println!("{}", "──────────────────────────────────────────────".dimmed());
}
pub(crate) fn calculate_function_costs<N: Network, R: Rng + CryptoRng>(
vm: &VM<N, ConsensusMemory<N>>,
deployment: &Deployment<N>,
consensus_version: ConsensusVersion,
rng: &mut R,
) -> Result<Vec<FunctionCostStats>> {
let stack = vm.process().read().get_stack(deployment.program().id())?;
let mut function_costs = Vec::new();
let sample_key = PrivateKey::new(rng)?;
let sample_address = Address::try_from(&sample_key)?;
for (function_name, _) in deployment.function_verifying_keys() {
let name = function_name.to_string();
let input_types = deployment.program().get_function(function_name)?.input_types();
let inputs = input_types
.into_iter()
.map(|ty| {
stack
.sample_value(&sample_address, &ty.into(), rng)
.map_err(|e| CliError::custom(format!("Failed to sample value: {e}")).into())
})
.collect::<Result<Vec<_>>>()?;
let (finalize_cost, storage_cost, execution_cost) =
match vm.authorize(&sample_key, deployment.program().id(), function_name, inputs.iter(), rng) {
Err(e) => {
tracing::debug!("Could not estimate execution cost for '{name}': {e}");
let static_finalize_cost = if consensus_version >= ConsensusVersion::V10 {
minimum_cost_in_microcredits_v3(&stack, function_name)?
} else if consensus_version >= ConsensusVersion::V2 {
minimum_cost_in_microcredits_v2(&stack, function_name)?
} else {
minimum_cost_in_microcredits_v1(&stack, function_name)?
};
(static_finalize_cost, None, None)
}
Ok(authorization) => {
let (total, (storage, finalize)) =
execution_cost_for_authorization(&vm.process().read(), &authorization, consensus_version)?;
(finalize, Some(storage), Some(total))
}
};
let has_dynamic_calls = stack.contains_dynamic_call(function_name).unwrap_or(false);
function_costs.push(FunctionCostStats { name, finalize_cost, storage_cost, execution_cost, has_dynamic_calls });
}
Ok(function_costs)
}