use serde::Serialize;
use indexmap::IndexMap;
use snarkvm::prelude::{Network, ProgramID, block::Transaction};
use std::fmt;
#[derive(Serialize, Default)]
pub struct BuildOutput {
pub program: String,
pub program_checksum: Vec<u8>,
pub function_checksums: IndexMap<String, Vec<u8>>,
}
pub(crate) fn microcredits_to_credits(microcredits: u64) -> f64 {
microcredits as f64 / 1_000_000.0
}
#[derive(Serialize, Clone, Default)]
pub struct FunctionCostStats {
pub name: String,
pub finalize_cost: u64,
#[serde(skip_serializing_if = "Option::is_none")]
pub storage_cost: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub execution_cost: Option<u64>,
#[serde(skip_serializing_if = "std::ops::Not::not")]
pub has_dynamic_calls: bool,
}
#[derive(Serialize, Clone, Default)]
pub struct DeploymentStats {
pub program_size_bytes: usize,
pub max_program_size_bytes: usize,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_variables: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub total_constraints: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_variables: Option<u64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub max_constraints: Option<u64>,
pub storage_cost: u64,
pub namespace_cost: u64,
pub synthesis_cost: u64,
pub constructor_cost: u64,
pub priority_fee: u64,
pub total_cost: u64,
#[serde(skip_serializing_if = "Vec::is_empty")]
pub function_costs: Vec<FunctionCostStats>,
}
impl fmt::Display for DeploymentStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use colored::*;
use num_format::{Locale, ToFormattedString};
writeln!(
f,
" {:22}{:.2} KB / {:.2} KB",
"Program Size:".cyan(),
self.program_size_bytes as f64 / 1024.0,
self.max_program_size_bytes as f64 / 1024.0
)?;
if let Some(total_variables) = self.total_variables {
writeln!(
f,
" {:22}{}",
"Total Variables:".cyan(),
total_variables.to_formatted_string(&Locale::en).yellow()
)?;
}
if let Some(total_constraints) = self.total_constraints {
writeln!(
f,
" {:22}{}",
"Total Constraints:".cyan(),
total_constraints.to_formatted_string(&Locale::en).yellow()
)?;
}
if let Some(max_variables) = self.max_variables {
writeln!(f, " {:22}{}", "Max Variables:".cyan(), max_variables.to_formatted_string(&Locale::en).green())?;
}
if let Some(max_constraints) = self.max_constraints {
writeln!(
f,
" {:22}{}",
"Max Constraints:".cyan(),
max_constraints.to_formatted_string(&Locale::en).green()
)?;
}
if self.total_variables.is_some() || self.total_constraints.is_some() {
writeln!(f)?;
}
writeln!(f, "{}", "💰 Cost Breakdown (credits)".bold())?;
writeln!(f, " {:22}{:.6}", "Transaction Storage:".cyan(), microcredits_to_credits(self.storage_cost))?;
writeln!(f, " {:22}{:.6}", "Program Synthesis:".cyan(), microcredits_to_credits(self.synthesis_cost))?;
writeln!(f, " {:22}{:.6}", "Namespace:".cyan(), microcredits_to_credits(self.namespace_cost))?;
writeln!(f, " {:22}{:.6}", "Constructor:".cyan(), microcredits_to_credits(self.constructor_cost))?;
writeln!(f, " {:22}{:.6}", "Priority Fee:".cyan(), microcredits_to_credits(self.priority_fee))?;
writeln!(f, " {:22}{:.6}", "Total Fee:".cyan(), microcredits_to_credits(self.total_cost))?;
for fc in &self.function_costs {
writeln!(f, "{}", format!(" Function '{}'", fc.name).bold())?;
let bound = if fc.has_dynamic_calls { " (lower bound)" } else { "" };
if let Some(execution_cost) = fc.execution_cost {
writeln!(
f,
" {:24}{:.6}{}",
"Total Execution Cost:".cyan(),
microcredits_to_credits(execution_cost),
bound.dimmed()
)?;
if let Some(storage_cost) = fc.storage_cost {
writeln!(
f,
" {:24}{:.6}{}",
"|- Storage Cost:".cyan(),
microcredits_to_credits(storage_cost),
bound.dimmed()
)?;
}
writeln!(
f,
" {:24}{:.6}{}",
"|- Finalize Cost:".cyan(),
microcredits_to_credits(fc.finalize_cost),
bound.dimmed()
)?;
} else {
writeln!(
f,
" {:24}{:.6} (lower bound)",
"Total Execution Cost:".cyan(),
microcredits_to_credits(fc.finalize_cost),
)?;
writeln!(f, " {:24}{}", "|- Storage Cost:".cyan(), "N/A (dynamic call)".dimmed())?;
writeln!(
f,
" {:24}{:.6}{}",
"|- Finalize Cost:".cyan(),
microcredits_to_credits(fc.finalize_cost),
bound.dimmed()
)?;
}
}
Ok(())
}
}
#[derive(Serialize, Clone, Default)]
pub struct ExecutionStats {
pub storage_cost: u64,
pub execution_cost: u64,
pub priority_fee: u64,
pub total_cost: u64,
}
impl fmt::Display for ExecutionStats {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
use colored::*;
writeln!(f, "{}", "💰 Cost Breakdown (credits)".bold())?;
writeln!(f, " {:22}{:.6}", "Transaction Storage:".cyan(), microcredits_to_credits(self.storage_cost))?;
writeln!(f, " {:22}{:.6}", "On-chain Execution:".cyan(), microcredits_to_credits(self.execution_cost))?;
writeln!(f, " {:22}{:.6}", "Priority Fee:".cyan(), microcredits_to_credits(self.priority_fee))?;
writeln!(f, " {:22}{:.6}", "Total Fee:".cyan(), microcredits_to_credits(self.total_cost))
}
}
#[derive(Serialize, Clone, Default)]
pub struct BroadcastStats {
pub fee_id: String,
pub fee_transaction_id: String,
pub confirmed: bool,
}
#[derive(Serialize, Clone, Default)]
pub struct Config {
pub address: String,
pub network: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub endpoint: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub consensus_version: Option<u8>,
}
#[derive(Serialize, Default)]
pub struct DeployOutput {
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<Config>,
pub deployments: Vec<DeployedProgram>,
}
#[derive(Serialize)]
pub struct DeployedProgram {
pub program_id: String,
pub transaction_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub stats: Option<DeploymentStats>,
#[serde(skip_serializing_if = "Option::is_none")]
pub broadcast: Option<BroadcastStats>,
}
#[derive(Serialize)]
pub struct RunOutput {
pub program: String,
pub function: String,
pub outputs: Vec<String>,
}
#[derive(Serialize, Default)]
pub struct ExecuteOutput {
#[serde(skip_serializing_if = "Option::is_none")]
pub config: Option<Config>,
pub program: String,
pub function: String,
pub outputs: Vec<String>,
pub transaction_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub stats: Option<ExecutionStats>,
#[serde(skip_serializing_if = "Option::is_none")]
pub broadcast: Option<BroadcastStats>,
}
#[derive(Serialize, Default)]
pub struct TestOutput {
pub passed: usize,
pub failed: usize,
pub tests: Vec<TestResult>,
}
#[derive(Serialize)]
pub struct TestResult {
pub name: String,
pub passed: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
#[derive(Serialize, Default)]
pub struct SynthesizeOutput {
pub program: String,
pub functions: Vec<SynthesizedFunction>,
}
#[derive(Serialize, Clone, Default)]
pub struct Metadata {
pub prover_checksum: String,
pub prover_size: usize,
pub verifier_checksum: String,
pub verifier_size: usize,
}
#[derive(Serialize, Clone, Default)]
pub struct CircuitInfo {
pub num_public_inputs: u64,
pub num_variables: u64,
pub num_constraints: u64,
pub num_non_zero_a: u64,
pub num_non_zero_b: u64,
pub num_non_zero_c: u64,
pub circuit_id: String,
}
#[derive(Serialize)]
pub struct SynthesizedFunction {
pub name: String,
pub circuit_info: CircuitInfo,
#[serde(flatten)]
pub metadata: Metadata,
}
pub fn build_deploy_output<N: Network>(
config: Option<Config>,
transactions: &[(ProgramID<N>, Transaction<N>)],
stats: &[DeploymentStats],
broadcasts: &[BroadcastStats],
) -> DeployOutput {
DeployOutput {
config,
deployments: transactions
.iter()
.zip(stats.iter().map(Some).chain(std::iter::repeat(None)))
.zip(broadcasts.iter().map(Some).chain(std::iter::repeat(None)))
.map(|(((program_id, transaction), stats), broadcast)| DeployedProgram {
program_id: program_id.to_string(),
transaction_id: transaction.id().to_string(),
stats: stats.cloned(),
broadcast: broadcast.cloned(),
})
.collect(),
}
}