mod build;
mod clean;
mod deploy;
mod execute;
mod is_build_required;
mod run;
pub use deploy::{DeployRequest, DeployResponse};
use crate::{
algorithms::snark::varuna::VarunaVersion,
console::{
account::PrivateKey,
network::{ConsensusVersion, Network},
program::{Identifier, Locator, ProgramID, Response, Value},
},
file::{AVMFile, AleoFile, Manifest, ProverFile, README, VerifierFile},
ledger::{
block::Execution,
query::{Query, QueryTrait},
store::helpers::memory::BlockMemory,
},
prelude::{Deserialize, Deserializer, Serialize, SerializeStruct, Serializer},
synthesizer::{
process::{Assignments, CallMetrics, CallStack, Process},
program::{CallOperator, Instruction, Program, StackTrait},
},
utilities::dev_println,
};
use anyhow::{Result, bail, ensure};
use core::str::FromStr;
use rand::{CryptoRng, Rng};
use std::path::{Path, PathBuf};
pub struct Package<N: Network> {
program_id: ProgramID<N>,
directory: PathBuf,
manifest_file: Manifest<N>,
program_file: AleoFile<N>,
}
impl<N: Network> Package<N> {
pub fn create(directory: &Path, program_id: &ProgramID<N>) -> Result<Self> {
ensure!(!directory.exists(), "The program directory already exists: {}", directory.display());
ensure!(!Program::is_reserved_keyword(program_id.name()), "Program name is invalid (reserved): {program_id}");
if !directory.exists() {
std::fs::create_dir_all(directory)?;
}
let manifest_file = Manifest::create(directory, program_id)?;
let program_file = AleoFile::create(directory, program_id, true)?;
let _readme_file = README::create::<N>(directory, program_id)?;
Ok(Self { program_id: *program_id, directory: directory.to_path_buf(), manifest_file, program_file })
}
pub fn open(directory: &Path) -> Result<Self> {
ensure!(directory.exists(), "The program directory does not exist: {}", directory.display());
ensure!(
Manifest::<N>::exists_at(directory),
"Missing '{}' at '{}'",
Manifest::<N>::file_name(),
directory.display()
);
ensure!(
AleoFile::<N>::main_exists_at(directory),
"Missing '{}' at '{}'",
AleoFile::<N>::main_file_name(),
directory.display()
);
let manifest_file = Manifest::open(directory)?;
let program_id = *manifest_file.program_id();
ensure!(!Program::is_reserved_keyword(program_id.name()), "Program name is invalid (reserved): {program_id}");
let program_file = AleoFile::open(directory, &program_id, true)?;
Ok(Self { program_id, directory: directory.to_path_buf(), manifest_file, program_file })
}
pub const fn program_id(&self) -> &ProgramID<N> {
&self.program_id
}
pub const fn directory(&self) -> &PathBuf {
&self.directory
}
pub const fn manifest_file(&self) -> &Manifest<N> {
&self.manifest_file
}
pub const fn program_file(&self) -> &AleoFile<N> {
&self.program_file
}
pub const fn program(&self) -> &Program<N> {
self.program_file.program()
}
pub fn build_directory(&self) -> PathBuf {
self.directory.join("build")
}
pub fn imports_directory(&self) -> PathBuf {
self.directory.join("imports")
}
pub fn get_process(&self) -> Result<Process<N>> {
let mut process = Process::load()?;
let imports_directory = self.imports_directory();
let mut programs = self
.program()
.imports()
.keys()
.map(|program_id| {
let is_main = false;
let import_program_file = AleoFile::open(&imports_directory, program_id, is_main)?;
Ok(import_program_file.program().clone())
})
.collect::<Result<Vec<_>>>()?;
programs.push(self.program().clone());
let programs_and_editions = programs
.into_iter()
.map(|program| {
let program_id = program.id();
if let Some(edition) = self.manifest_file.editions().get(program_id) {
(program, *edition)
} else {
dev_println!(
" Could not find an edition for '{}' in the manifest, using edition 0...",
program_id.to_string()
);
(program, 0)
}
})
.collect::<Vec<_>>();
process.add_programs_with_editions(&programs_and_editions)?;
Ok(process)
}
}
#[cfg(test)]
pub(crate) mod test_helpers {
use super::*;
use snarkvm_console::{account::Address, network::MainnetV0, prelude::TestRng};
use std::{fs::File, io::Write};
use anyhow::anyhow;
type CurrentNetwork = MainnetV0;
fn temp_dir() -> PathBuf {
tempfile::tempdir().expect("Failed to open temporary directory").keep()
}
fn env_template() -> String {
r#"
NETWORK=mainnet
PRIVATE_KEY={{PASTE_YOUR_PRIVATE_KEY_HERE}}
"#
.to_string()
}
fn dotenv_load() -> Result<()> {
dotenvy::dotenv().map_err(|_| {
anyhow!(
"Missing a '.env' file. Create the '.env' file in your package's root directory with the following:\n\n{}\n",
env_template()
)
})?;
Ok(())
}
fn dotenv_private_key() -> Result<PrivateKey<CurrentNetwork>> {
if cfg!(test) {
let rng = &mut crate::utilities::TestRng::fixed(123456789);
PrivateKey::<CurrentNetwork>::new(rng)
} else {
use std::str::FromStr;
dotenv_load()?;
let private_key = dotenvy::var("PRIVATE_KEY").map_err(|e| anyhow!("Missing PRIVATE_KEY - {e}"))?;
PrivateKey::<CurrentNetwork>::from_str(&private_key)
}
}
pub(crate) fn sample_token_package() -> (PathBuf, Package<CurrentNetwork>) {
let program = Program::<CurrentNetwork>::from_str(
"
program token.aleo;
record token:
owner as address.private;
amount as u64.private;
function initialize:
input r0 as address.private;
input r1 as u64.private;
cast r0 r1 into r2 as token.record;
output r2 as token.record;
function transfer:
input r0 as token.record;
input r1 as address.private;
input r2 as u64.private;
sub r0.amount r2 into r3;
cast r1 r2 into r4 as token.record;
cast r0.owner r3 into r5 as token.record;
output r4 as token.record;
output r5 as token.record;",
)
.unwrap();
sample_package_with_program_and_imports(&program, &[])
}
pub(crate) fn sample_wallet_package() -> (PathBuf, Package<CurrentNetwork>) {
let imported_program = Program::<CurrentNetwork>::from_str(
"
program token.aleo;
record token:
owner as address.private;
amount as u64.private;
function initialize:
input r0 as address.private;
input r1 as u64.private;
cast r0 r1 into r2 as token.record;
output r2 as token.record;
function transfer:
input r0 as token.record;
input r1 as address.private;
input r2 as u64.private;
sub r0.amount r2 into r3;
cast r1 r2 into r4 as token.record;
cast r0.owner r3 into r5 as token.record;
output r4 as token.record;
output r5 as token.record;",
)
.unwrap();
let main_program = Program::<CurrentNetwork>::from_str(
"
import token.aleo;
program wallet.aleo;
function transfer:
input r0 as token.aleo/token.record;
input r1 as address.private;
input r2 as u64.private;
call token.aleo/transfer r0 r1 r2 into r3 r4;
output r3 as token.aleo/token.record;
output r4 as token.aleo/token.record;",
)
.unwrap();
sample_package_with_program_and_imports(&main_program, &[imported_program])
}
pub(crate) fn sample_nested_package() -> (PathBuf, Package<CurrentNetwork>) {
let child_program = Program::<CurrentNetwork>::from_str(
"
program child.aleo;
record A:
owner as address.private;
val as u32.private;
function mint:
input r0 as address.private;
input r1 as u32.private;
cast r0 r1 into r2 as A.record;
output r2 as A.record;",
)
.unwrap();
let parent_program = Program::<CurrentNetwork>::from_str(
"
import child.aleo;
program parent.aleo;
function wrapper_mint:
input r0 as address.private;
input r1 as u32.private;
call child.aleo/mint r0 r1 into r2;
output r2 as child.aleo/A.record;",
)
.unwrap();
let grandparent_program = Program::<CurrentNetwork>::from_str(
"
import child.aleo;
import parent.aleo;
program grandparent.aleo;
function double_wrapper_mint:
input r0 as address.private;
input r1 as u32.private;
call parent.aleo/wrapper_mint r0 r1 into r2;
output r2 as child.aleo/A.record;",
)
.unwrap();
sample_package_with_program_and_imports(&grandparent_program, &[child_program, parent_program])
}
pub(crate) fn sample_transfer_package() -> (PathBuf, Package<CurrentNetwork>) {
let imported_program = Program::credits().unwrap();
let main_program = Program::<CurrentNetwork>::from_str(
"
import credits.aleo;
program transfer.aleo;
function main:
input r0 as credits.aleo/credits.record;
input r1 as address.private;
input r2 as u64.private;
call credits.aleo/transfer_private r0 r1 r2 into r3 r4;
output r3 as credits.aleo/credits.record;
output r4 as credits.aleo/credits.record;",
)
.unwrap();
sample_package_with_program_and_imports(&main_program, &[imported_program])
}
pub(crate) fn sample_package_with_program_and_imports(
main_program: &Program<CurrentNetwork>,
imported_programs: &[Program<CurrentNetwork>],
) -> (PathBuf, Package<CurrentNetwork>) {
let directory = temp_dir();
if !imported_programs.is_empty() {
let imports_directory = directory.join("imports");
std::fs::create_dir_all(&imports_directory).unwrap();
for imported_program in imported_programs {
let imported_program_id = imported_program.id();
let import_filepath = imports_directory.join(imported_program_id.to_string());
let mut file = File::create(import_filepath).unwrap();
file.write_all(imported_program.to_string().as_bytes()).unwrap();
}
}
let main_program_id = main_program.id();
let main_filepath = directory.join("main.aleo");
let mut file = File::create(main_filepath).unwrap();
file.write_all(main_program.to_string().as_bytes()).unwrap();
let _manifest_file = Manifest::create(&directory, main_program_id).unwrap();
let package = Package::<MainnetV0>::open(&directory).unwrap();
assert_eq!(package.program_id(), main_program_id);
(directory, package)
}
pub(crate) fn sample_package_run(
program_id: &ProgramID<CurrentNetwork>,
) -> (PrivateKey<CurrentNetwork>, Identifier<CurrentNetwork>, Vec<Value<CurrentNetwork>>) {
let rng = &mut TestRng::default();
match program_id.to_string().as_str() {
"token.aleo" => {
let private_key = dotenv_private_key().unwrap();
let caller = Address::try_from(&private_key).unwrap();
let function_name = Identifier::from_str("initialize").unwrap();
let r0 = Value::from_str(&caller.to_string()).unwrap();
let r1 = Value::from_str("100u64").unwrap();
(private_key, function_name, vec![r0, r1])
}
"wallet.aleo" => {
let caller0_private_key = dotenv_private_key().unwrap();
let caller0 = Address::try_from(&caller0_private_key).unwrap();
let caller1_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
let caller1 = Address::try_from(&caller1_private_key).unwrap();
let function_name = Identifier::from_str("transfer").unwrap();
let r0 = Value::<CurrentNetwork>::from_str(&format!(
"{{ owner: {caller0}.private, amount: 100u64.private, _nonce: 0group.public }}"
))
.unwrap();
let r1 = Value::<CurrentNetwork>::from_str(&caller1.to_string()).unwrap();
let r2 = Value::<CurrentNetwork>::from_str("99u64").unwrap();
(caller0_private_key, function_name, vec![r0, r1, r2])
}
"grandparent.aleo" => {
let caller0_private_key = dotenv_private_key().unwrap();
let caller1_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
let caller1 = Address::try_from(&caller1_private_key).unwrap();
let function_name = Identifier::from_str("double_wrapper_mint").unwrap();
let r0 = Value::<CurrentNetwork>::from_str(&caller1.to_string()).unwrap();
let r1 = Value::<CurrentNetwork>::from_str("1u32").unwrap();
(caller0_private_key, function_name, vec![r0, r1])
}
_ => panic!("Invalid program ID for sample package (while testing)"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::MainnetV0;
use snarkvm_utilities::TestRng;
type CurrentAleo = snarkvm_circuit::network::AleoV0;
type CurrentNetwork = MainnetV0;
#[test]
fn test_imports_directory() {
let (directory, package) = crate::package::test_helpers::sample_token_package();
assert_eq!(package.imports_directory(), directory.join("imports"));
assert!(!package.imports_directory().exists());
std::fs::remove_dir_all(directory).unwrap();
}
#[test]
fn test_imports_directory_with_an_import() {
let (directory, package) = crate::package::test_helpers::sample_wallet_package();
assert_eq!(package.imports_directory(), directory.join("imports"));
assert!(package.imports_directory().exists());
std::fs::remove_dir_all(directory).unwrap();
}
#[test]
fn test_build_directory() {
let (directory, package) = crate::package::test_helpers::sample_token_package();
assert_eq!(package.build_directory(), directory.join("build"));
assert!(!package.build_directory().exists());
std::fs::remove_dir_all(directory).unwrap();
}
#[test]
fn test_get_process() {
let (directory, package) = crate::package::test_helpers::sample_token_package();
assert!(package.get_process().is_ok());
std::fs::remove_dir_all(directory).unwrap();
}
#[test]
fn test_package_run_and_execute_match() {
let program = Program::<CurrentNetwork>::from_str(
"
program foo.aleo;
function bar:
input r0 as boolean.private;
assert.eq r0 false;",
)
.unwrap();
let (directory, package) = crate::package::test_helpers::sample_package_with_program_and_imports(&program, &[]);
assert!(!package.build_directory().exists());
package.build::<CurrentAleo>().unwrap();
assert!(package.build_directory().exists());
let rng = &mut TestRng::default();
let private_key = PrivateKey::new(rng).unwrap();
let function_name = Identifier::from_str("bar").unwrap();
let inputs = vec![Value::from_str("true").unwrap()];
let endpoint = "https://api.explorer.aleo.org/v1".to_string();
let run_result = package.run::<CurrentAleo, _>(&private_key, function_name, &inputs, rng).ok();
let execute_result =
package.execute::<CurrentAleo, _>(endpoint, &private_key, function_name, &inputs, rng).ok();
match (run_result, execute_result) {
(None, None) => {}
(Some((run_response, _)), Some((execute_response, _, _))) => {
assert_eq!(run_response, execute_response);
}
_ => panic!("Run and execute results do not match"),
}
std::fs::remove_dir_all(directory).unwrap();
}
}