#![forbid(unsafe_code)]
use leo_ast::NetworkName;
use leo_errors::{PackageError, Result, UtilError};
use leo_span::Symbol;
use std::path::Path;
mod dependency;
pub use dependency::*;
mod location;
pub use location::*;
mod manifest;
pub use manifest::*;
mod package;
pub use package::*;
mod program;
pub use program::*;
pub const SOURCE_DIRECTORY: &str = "src";
pub const MAIN_FILENAME: &str = "main.leo";
pub const IMPORTS_DIRECTORY: &str = "build/imports";
pub const OUTPUTS_DIRECTORY: &str = "outputs";
pub const BUILD_DIRECTORY: &str = "build";
pub const ABI_FILENAME: &str = "abi.json";
pub const TESTS_DIRECTORY: &str = "tests";
pub const MAX_PROGRAM_SIZE: usize = <snarkvm::prelude::TestnetV0 as snarkvm::prelude::Network>::MAX_PROGRAM_SIZE;
pub type Edition = u16;
fn symbol(name: &str) -> Result<Symbol> {
name.strip_suffix(".aleo").map(Symbol::intern).ok_or_else(|| PackageError::invalid_network_name(name).into())
}
pub fn is_valid_aleo_name(name: &str) -> bool {
let Some(rest) = name.strip_suffix(".aleo") else {
return false;
};
if rest.is_empty() {
tracing::error!("Aleo names must be nonempty");
return false;
}
let first = rest.chars().next().unwrap();
if first == '_' {
tracing::error!("Aleo names cannot begin with an underscore");
return false;
}
if first.is_numeric() {
tracing::error!("Aleo names cannot begin with a number");
return false;
}
if rest.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_') {
tracing::error!("Aleo names must can only contain ASCII alphanumeric characters and underscores.");
return false;
}
if reserved_keywords().any(|kw| kw == rest) {
tracing::error!(
"Aleo names cannot be a SnarkVM reserved keyword. Reserved keywords are: {}.",
reserved_keywords().collect::<Vec<_>>().join(", ")
);
return false;
}
if rest.contains("aleo") {
tracing::error!("Aleo names cannot contain the keyword `aleo`.",);
return false;
}
true
}
pub fn reserved_keywords() -> impl Iterator<Item = &'static str> {
use snarkvm::prelude::{Program, TestnetV0};
let restricted = Program::<TestnetV0>::RESTRICTED_KEYWORDS.iter().flat_map(|(_, kws)| kws.iter().copied());
Program::<TestnetV0>::KEYWORDS.iter().copied().chain(restricted)
}
pub fn create_http_agent() -> ureq::Agent {
ureq::Agent::config_builder().max_redirects(0).http_status_as_error(false).build().new_agent()
}
pub fn fetch_from_network(url: &str) -> Result<String, UtilError> {
fetch_from_network_plain(url).map(|s| s.replace("\\n", "\n").replace('\"', ""))
}
pub fn fetch_from_network_plain(url: &str) -> Result<String, UtilError> {
let mut response = create_http_agent()
.get(url)
.header("X-Leo-Version", env!("CARGO_PKG_VERSION"))
.call()
.map_err(|e| UtilError::failed_to_retrieve_from_endpoint(url, e))?;
match response.status().as_u16() {
200..=299 => Ok(response.body_mut().read_to_string().unwrap()),
301 => Err(UtilError::endpoint_moved_error(url)),
_ => Err(UtilError::network_error(url, response.status())),
}
}
pub fn fetch_program_from_network(name: &str, endpoint: &str, network: NetworkName) -> Result<String, UtilError> {
let url = format!("{endpoint}/{network}/program/{name}");
let program = fetch_from_network(&url)?;
Ok(program)
}
pub fn fetch_latest_edition(name: &str, endpoint: &str, network: NetworkName) -> Result<Edition, UtilError> {
let name_without_suffix = name.strip_suffix(".aleo").unwrap_or(name);
let url = format!("{endpoint}/{network}/program/{name_without_suffix}.aleo/latest_edition");
let contents = fetch_from_network(&url)?;
contents
.parse::<u16>()
.map_err(|e| UtilError::failed_to_retrieve_from_endpoint(url, format!("Failed to parse edition as u16: {e}")))
}
pub fn verify_valid_program(name: &str, program: &str) -> Result<(), UtilError> {
use snarkvm::prelude::{Program, TestnetV0};
use std::str::FromStr as _;
let program_size = program.len();
if program_size > MAX_PROGRAM_SIZE {
return Err(UtilError::program_size_limit_exceeded(name, program_size, MAX_PROGRAM_SIZE));
}
match Program::<TestnetV0>::from_str(program) {
Ok(_) => Ok(()),
Err(_) => Err(UtilError::snarkvm_parsing_error(name)),
}
}
pub fn filename_no_leo_extension(path: &Path) -> Option<&str> {
filename_no_extension(path, ".leo")
}
pub fn filename_no_aleo_extension(path: &Path) -> Option<&str> {
filename_no_extension(path, ".aleo")
}
fn filename_no_extension<'a>(path: &'a Path, extension: &'static str) -> Option<&'a str> {
path.file_name().and_then(|os_str| os_str.to_str()).and_then(|s| s.strip_suffix(extension))
}