mod build;
mod clean;
mod deploy;
mod execute;
mod is_build_required;
mod run;
pub use build::{BuildRequest, BuildResponse};
pub use deploy::{DeployRequest, DeployResponse};
use crate::{
console::{
account::PrivateKey,
network::Network,
program::{Identifier, Locator, ProgramID, Response, Value},
},
file::{AVMFile, AleoFile, Manifest, ProverFile, VerifierFile, README},
ledger::{block::Execution, query::Query, store::helpers::memory::BlockMemory},
prelude::{Deserialize, Deserializer, Serialize, SerializeStruct, Serializer},
synthesizer::{
process::{Assignments, CallMetrics, CallStack, Process, StackExecute},
program::{CallOperator, Instruction, Program},
snark::{ProvingKey, VerifyingKey},
},
};
use anyhow::{bail, ensure, Error, Result};
use core::str::FromStr;
use rand::{CryptoRng, Rng};
use std::path::{Path, PathBuf};
#[cfg(feature = "aleo-cli")]
use colored::Colorize;
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 credits_program_id = ProgramID::<N>::from_str("credits.aleo")?;
self.program().imports().keys().try_for_each(|program_id| {
if program_id != &credits_program_id {
let import_program_file = AleoFile::open(&imports_directory, program_id, false)?;
process.add_program(import_program_file.program())?;
}
Ok::<_, Error>(())
})?;
process.add_program(self.program())?;
Ok(process)
}
}
#[cfg(test)]
pub(crate) mod test_helpers {
use super::*;
use snarkvm_console::{account::Address, network::Testnet3, prelude::TestRng};
use std::{fs::File, io::Write};
type CurrentNetwork = Testnet3;
fn temp_dir() -> PathBuf {
tempfile::tempdir().expect("Failed to open temporary directory").into_path()
}
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::<Testnet3>::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 = crate::cli::helpers::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 = crate::cli::helpers::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 = crate::cli::helpers::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::Testnet3;
use snarkvm_utilities::TestRng;
type CurrentAleo = snarkvm_circuit::network::AleoV0;
type CurrentNetwork = Testnet3;
#[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>(None).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();
}
}