use snarkvm::prelude::{ConsensusVersion, Network, Program};
const PROGRAM_SIZE_WARNING_THRESHOLD: usize = 90;
pub fn format_program_size(size: usize, max_size: usize) -> (f64, f64, Option<String>) {
let size_kb = size as f64 / 1024.0;
let max_kb = max_size as f64 / 1024.0;
let percentage = (size as f64 / max_size as f64) * 100.0;
let warning = if size > max_size * PROGRAM_SIZE_WARNING_THRESHOLD / 100 {
Some(format!("approaching the size limit ({percentage:.1}% of {max_kb:.2} KB)"))
} else {
None
};
(size_kb, max_kb, warning)
}
pub const LOCAL_PROGRAM_DEFAULT_EDITION: leo_package::Edition = 1;
pub fn print_program_source(id: &str, edition: Option<leo_package::Edition>) {
match (id, edition) {
("credits.aleo", _) => println!(" - {id} (already included)"),
(_, Some(e)) => println!(" - {id} (edition: {e})"),
(_, None) => println!(" - {id} (local)"),
}
}
pub fn check_edition_constructor_requirements<N: Network>(
programs: &[(Program<N>, leo_package::Edition)],
consensus_version: ConsensusVersion,
action: &str,
) -> Result<(), leo_errors::Backtraced> {
if consensus_version < ConsensusVersion::V8 {
return Ok(());
}
for (program, edition) in programs {
if *edition == 0 && !program.contains_constructor() {
let id = program.id();
if id.to_string() != "credits.aleo" {
return Err(crate::errors::custom(format!(
"Cannot {action} with dependency '{id}' (edition 0)\n\n\
Programs at edition 0 without a constructor cannot be executed under \
consensus version V8 or later (current: V{}).\n\n\
The program '{id}' must be upgraded on-chain before it can be used.",
consensus_version as u8
)));
}
}
}
Ok(())
}
pub fn load_extra_programs_into_vm<N: Network>(
entries: &[String],
vm: &snarkvm::prelude::VM<N, snarkvm::prelude::store::helpers::memory::ConsensusMemory<N>>,
context: &crate::cli::context::Context,
network: leo_ast::NetworkName,
endpoint: Option<&str>,
network_retries: u32,
) -> leo_errors::Result<()> {
use snarkvm::prelude::ProgramID;
use std::{path::Path, str::FromStr};
let mut extras: Vec<(Program<N>, leo_package::Edition)> = Vec::new();
for entry in entries {
let path = Path::new(entry);
if path.is_file() {
println!("📂 Loading local program from {entry}...");
let bytecode = std::fs::read_to_string(path)
.map_err(|e| crate::errors::custom(format!("Failed to read program file '{entry}': {e}")))?;
let program = Program::<N>::from_str(&bytecode)
.map_err(|e| crate::errors::custom(format!("Failed to parse program from '{entry}': {e}")))?;
extras.push((program, LOCAL_PROGRAM_DEFAULT_EDITION));
} else if path.exists() {
return Err(crate::errors::custom(format!("'{entry}' exists but is not a file.")).into());
} else {
let endpoint = endpoint.ok_or_else(|| {
crate::errors::custom(format!(
"'{entry}' is not a local file; fetching from the network requires --endpoint to be set."
))
})?;
let name = if entry.ends_with(".aleo") { entry.clone() } else { format!("{entry}.aleo") };
println!("⬇️ Fetching remote program {name} and its dependencies from {endpoint}...");
let program_id = ProgramID::<N>::from_str(&name)
.map_err(|e| crate::errors::custom(format!("Failed to parse program ID '{name}': {e}")))?;
let fetched = super::query::load_latest_programs_from_network(
context,
program_id,
network,
endpoint,
network_retries,
)?;
extras.extend(fetched.into_iter().map(|(p, ed)| (p, ed.unwrap_or(LOCAL_PROGRAM_DEFAULT_EDITION))));
}
}
vm.process().lock().add_programs_with_editions(&extras)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use snarkvm::prelude::TestnetV0;
use std::str::FromStr;
#[test]
fn test_edition_constructor_error_message() {
let program = Program::<TestnetV0>::from_str(
"program old_program.aleo;\nfunction main:\n input r0 as u32.public;\n output r0 as u32.public;\n",
)
.unwrap();
let result = check_edition_constructor_requirements(&[(program, 0)], ConsensusVersion::V9, "deploy");
assert!(result.is_err());
}
}