1mod build;
16mod clean;
17mod deploy;
18mod execute;
19mod is_build_required;
20mod run;
21
22pub use build::{BuildRequest, BuildResponse};
23pub use deploy::{DeployRequest, DeployResponse};
24
25use crate::{
26 console::{
27 account::PrivateKey,
28 network::Network,
29 program::{Identifier, Locator, ProgramID, Response, Value},
30 },
31 file::{AVMFile, AleoFile, Manifest, ProverFile, VerifierFile, README},
32 ledger::{block::Execution, query::Query, store::helpers::memory::BlockMemory},
33 prelude::{Deserialize, Deserializer, Serialize, SerializeStruct, Serializer},
34 synthesizer::{
35 process::{Assignments, CallMetrics, CallStack, Process, StackExecute},
36 program::{CallOperator, Instruction, Program},
37 snark::{ProvingKey, VerifyingKey},
38 },
39};
40
41use anyhow::{bail, ensure, Error, Result};
42use core::str::FromStr;
43use rand::{CryptoRng, Rng};
44use std::path::{Path, PathBuf};
45
46#[cfg(feature = "aleo-cli")]
47use colored::Colorize;
48
49pub struct Package<N: Network> {
50 program_id: ProgramID<N>,
52 directory: PathBuf,
54 manifest_file: Manifest<N>,
56 program_file: AleoFile<N>,
58}
59
60impl<N: Network> Package<N> {
61 pub fn create(directory: &Path, program_id: &ProgramID<N>) -> Result<Self> {
63 ensure!(!directory.exists(), "The program directory already exists: {}", directory.display());
65 ensure!(!Program::is_reserved_keyword(program_id.name()), "Program name is invalid (reserved): {program_id}");
67
68 if !directory.exists() {
70 std::fs::create_dir_all(directory)?;
71 }
72
73 let manifest_file = Manifest::create(directory, program_id)?;
75 let program_file = AleoFile::create(directory, program_id, true)?;
77 let _readme_file = README::create::<N>(directory, program_id)?;
79
80 Ok(Self { program_id: *program_id, directory: directory.to_path_buf(), manifest_file, program_file })
81 }
82
83 pub fn open(directory: &Path) -> Result<Self> {
85 ensure!(directory.exists(), "The program directory does not exist: {}", directory.display());
87 ensure!(
89 Manifest::<N>::exists_at(directory),
90 "Missing '{}' at '{}'",
91 Manifest::<N>::file_name(),
92 directory.display()
93 );
94 ensure!(
96 AleoFile::<N>::main_exists_at(directory),
97 "Missing '{}' at '{}'",
98 AleoFile::<N>::main_file_name(),
99 directory.display()
100 );
101
102 let manifest_file = Manifest::open(directory)?;
104 let program_id = *manifest_file.program_id();
106 ensure!(!Program::is_reserved_keyword(program_id.name()), "Program name is invalid (reserved): {program_id}");
108
109 let program_file = AleoFile::open(directory, &program_id, true)?;
111
112 Ok(Self { program_id, directory: directory.to_path_buf(), manifest_file, program_file })
113 }
114
115 pub const fn program_id(&self) -> &ProgramID<N> {
117 &self.program_id
118 }
119
120 pub const fn directory(&self) -> &PathBuf {
122 &self.directory
123 }
124
125 pub const fn manifest_file(&self) -> &Manifest<N> {
127 &self.manifest_file
128 }
129
130 pub const fn program_file(&self) -> &AleoFile<N> {
132 &self.program_file
133 }
134
135 pub const fn program(&self) -> &Program<N> {
137 self.program_file.program()
138 }
139
140 pub fn build_directory(&self) -> PathBuf {
142 self.directory.join("build")
143 }
144
145 pub fn imports_directory(&self) -> PathBuf {
147 self.directory.join("imports")
148 }
149
150 pub fn get_process(&self) -> Result<Process<N>> {
152 let mut process = Process::load()?;
154
155 let imports_directory = self.imports_directory();
157
158 let credits_program_id = ProgramID::<N>::from_str("credits.aleo")?;
160
161 self.program().imports().keys().try_for_each(|program_id| {
163 if program_id != &credits_program_id {
165 let import_program_file = AleoFile::open(&imports_directory, program_id, false)?;
167 process.add_program(import_program_file.program())?;
169 }
170 Ok::<_, Error>(())
171 })?;
172
173 process.add_program(self.program())?;
175
176 Ok(process)
177 }
178}
179
180#[cfg(test)]
181pub(crate) mod test_helpers {
182 use super::*;
183 use snarkvm_console::{account::Address, network::Testnet3, prelude::TestRng};
184
185 use std::{fs::File, io::Write};
186
187 type CurrentNetwork = Testnet3;
188
189 fn temp_dir() -> PathBuf {
190 tempfile::tempdir().expect("Failed to open temporary directory").into_path()
191 }
192
193 pub(crate) fn sample_token_package() -> (PathBuf, Package<CurrentNetwork>) {
195 let program = Program::<CurrentNetwork>::from_str(
197 "
198program token.aleo;
199
200record token:
201 owner as address.private;
202 amount as u64.private;
203
204function initialize:
205 input r0 as address.private;
206 input r1 as u64.private;
207 cast r0 r1 into r2 as token.record;
208 output r2 as token.record;
209
210function transfer:
211 input r0 as token.record;
212 input r1 as address.private;
213 input r2 as u64.private;
214 sub r0.amount r2 into r3;
215 cast r1 r2 into r4 as token.record;
216 cast r0.owner r3 into r5 as token.record;
217 output r4 as token.record;
218 output r5 as token.record;",
219 )
220 .unwrap();
221
222 sample_package_with_program_and_imports(&program, &[])
224 }
225
226 pub(crate) fn sample_wallet_package() -> (PathBuf, Package<CurrentNetwork>) {
228 let imported_program = Program::<CurrentNetwork>::from_str(
230 "
231program token.aleo;
232
233record token:
234 owner as address.private;
235 amount as u64.private;
236
237function initialize:
238 input r0 as address.private;
239 input r1 as u64.private;
240 cast r0 r1 into r2 as token.record;
241 output r2 as token.record;
242
243function transfer:
244 input r0 as token.record;
245 input r1 as address.private;
246 input r2 as u64.private;
247 sub r0.amount r2 into r3;
248 cast r1 r2 into r4 as token.record;
249 cast r0.owner r3 into r5 as token.record;
250 output r4 as token.record;
251 output r5 as token.record;",
252 )
253 .unwrap();
254
255 let main_program = Program::<CurrentNetwork>::from_str(
257 "
258import token.aleo;
259
260program wallet.aleo;
261
262function transfer:
263 input r0 as token.aleo/token.record;
264 input r1 as address.private;
265 input r2 as u64.private;
266 call token.aleo/transfer r0 r1 r2 into r3 r4;
267 output r3 as token.aleo/token.record;
268 output r4 as token.aleo/token.record;",
269 )
270 .unwrap();
271
272 sample_package_with_program_and_imports(&main_program, &[imported_program])
274 }
275
276 pub(crate) fn sample_nested_package() -> (PathBuf, Package<CurrentNetwork>) {
278 let child_program = Program::<CurrentNetwork>::from_str(
280 "
281program child.aleo;
282
283record A:
284 owner as address.private;
285 val as u32.private;
286
287function mint:
288 input r0 as address.private;
289 input r1 as u32.private;
290 cast r0 r1 into r2 as A.record;
291 output r2 as A.record;",
292 )
293 .unwrap();
294
295 let parent_program = Program::<CurrentNetwork>::from_str(
297 "
298import child.aleo;
299
300program parent.aleo;
301
302function wrapper_mint:
303 input r0 as address.private;
304 input r1 as u32.private;
305 call child.aleo/mint r0 r1 into r2;
306 output r2 as child.aleo/A.record;",
307 )
308 .unwrap();
309
310 let grandparent_program = Program::<CurrentNetwork>::from_str(
312 "
313import child.aleo;
314import parent.aleo;
315
316program grandparent.aleo;
317
318function double_wrapper_mint:
319 input r0 as address.private;
320 input r1 as u32.private;
321 call parent.aleo/wrapper_mint r0 r1 into r2;
322 output r2 as child.aleo/A.record;",
323 )
324 .unwrap();
325
326 sample_package_with_program_and_imports(&grandparent_program, &[child_program, parent_program])
328 }
329
330 pub(crate) fn sample_transfer_package() -> (PathBuf, Package<CurrentNetwork>) {
332 let imported_program = Program::credits().unwrap();
334
335 let main_program = Program::<CurrentNetwork>::from_str(
337 "
338import credits.aleo;
339
340program transfer.aleo;
341
342function main:
343 input r0 as credits.aleo/credits.record;
344 input r1 as address.private;
345 input r2 as u64.private;
346 call credits.aleo/transfer_private r0 r1 r2 into r3 r4;
347 output r3 as credits.aleo/credits.record;
348 output r4 as credits.aleo/credits.record;",
349 )
350 .unwrap();
351
352 sample_package_with_program_and_imports(&main_program, &[imported_program])
354 }
355
356 pub(crate) fn sample_package_with_program_and_imports(
358 main_program: &Program<CurrentNetwork>,
359 imported_programs: &[Program<CurrentNetwork>],
360 ) -> (PathBuf, Package<CurrentNetwork>) {
361 let directory = temp_dir();
363
364 if !imported_programs.is_empty() {
366 let imports_directory = directory.join("imports");
367 std::fs::create_dir_all(&imports_directory).unwrap();
368
369 for imported_program in imported_programs {
371 let imported_program_id = imported_program.id();
372
373 let import_filepath = imports_directory.join(imported_program_id.to_string());
375 let mut file = File::create(import_filepath).unwrap();
376 file.write_all(imported_program.to_string().as_bytes()).unwrap();
377 }
378 }
379
380 let main_program_id = main_program.id();
382
383 let main_filepath = directory.join("main.aleo");
385 let mut file = File::create(main_filepath).unwrap();
386 file.write_all(main_program.to_string().as_bytes()).unwrap();
387
388 let _manifest_file = Manifest::create(&directory, main_program_id).unwrap();
390
391 let package = Package::<Testnet3>::open(&directory).unwrap();
393 assert_eq!(package.program_id(), main_program_id);
394
395 (directory, package)
397 }
398
399 pub(crate) fn sample_package_run(
401 program_id: &ProgramID<CurrentNetwork>,
402 ) -> (PrivateKey<CurrentNetwork>, Identifier<CurrentNetwork>, Vec<Value<CurrentNetwork>>) {
403 let rng = &mut TestRng::default();
405
406 match program_id.to_string().as_str() {
407 "token.aleo" => {
408 let private_key = crate::cli::helpers::dotenv_private_key().unwrap();
410 let caller = Address::try_from(&private_key).unwrap();
411
412 let function_name = Identifier::from_str("initialize").unwrap();
414
415 let r0 = Value::from_str(&caller.to_string()).unwrap();
417 let r1 = Value::from_str("100u64").unwrap();
418
419 (private_key, function_name, vec![r0, r1])
420 }
421 "wallet.aleo" => {
422 let caller0_private_key = crate::cli::helpers::dotenv_private_key().unwrap();
424 let caller0 = Address::try_from(&caller0_private_key).unwrap();
425
426 let caller1_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
428 let caller1 = Address::try_from(&caller1_private_key).unwrap();
429
430 let function_name = Identifier::from_str("transfer").unwrap();
432
433 let r0 = Value::<CurrentNetwork>::from_str(&format!(
435 "{{ owner: {caller0}.private, amount: 100u64.private, _nonce: 0group.public }}"
436 ))
437 .unwrap();
438 let r1 = Value::<CurrentNetwork>::from_str(&caller1.to_string()).unwrap();
439 let r2 = Value::<CurrentNetwork>::from_str("99u64").unwrap();
440
441 (caller0_private_key, function_name, vec![r0, r1, r2])
442 }
443 "grandparent.aleo" => {
444 let caller0_private_key = crate::cli::helpers::dotenv_private_key().unwrap();
446
447 let caller1_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
449 let caller1 = Address::try_from(&caller1_private_key).unwrap();
450
451 let function_name = Identifier::from_str("double_wrapper_mint").unwrap();
453
454 let r0 = Value::<CurrentNetwork>::from_str(&caller1.to_string()).unwrap();
456 let r1 = Value::<CurrentNetwork>::from_str("1u32").unwrap();
457
458 (caller0_private_key, function_name, vec![r0, r1])
459 }
460 _ => panic!("Invalid program ID for sample package (while testing)"),
461 }
462 }
463}
464
465#[cfg(test)]
466mod tests {
467 use super::*;
468 use crate::prelude::Testnet3;
469 use snarkvm_utilities::TestRng;
470
471 type CurrentAleo = snarkvm_circuit::network::AleoV0;
472 type CurrentNetwork = Testnet3;
473
474 #[test]
475 fn test_imports_directory() {
476 let (directory, package) = crate::package::test_helpers::sample_token_package();
478
479 assert_eq!(package.imports_directory(), directory.join("imports"));
481 assert!(!package.imports_directory().exists());
483
484 std::fs::remove_dir_all(directory).unwrap();
486 }
487
488 #[test]
489 fn test_imports_directory_with_an_import() {
490 let (directory, package) = crate::package::test_helpers::sample_wallet_package();
492
493 assert_eq!(package.imports_directory(), directory.join("imports"));
495 assert!(package.imports_directory().exists());
497
498 std::fs::remove_dir_all(directory).unwrap();
500 }
501
502 #[test]
503 fn test_build_directory() {
504 let (directory, package) = crate::package::test_helpers::sample_token_package();
506
507 assert_eq!(package.build_directory(), directory.join("build"));
509 assert!(!package.build_directory().exists());
511
512 std::fs::remove_dir_all(directory).unwrap();
514 }
515
516 #[test]
517 fn test_get_process() {
518 let (directory, package) = crate::package::test_helpers::sample_token_package();
520
521 assert!(package.get_process().is_ok());
523
524 std::fs::remove_dir_all(directory).unwrap();
526 }
527
528 #[test]
529 fn test_package_run_and_execute_match() {
530 let program = Program::<CurrentNetwork>::from_str(
532 "
533program foo.aleo;
534
535function bar:
536 input r0 as boolean.private;
537 assert.eq r0 false;",
538 )
539 .unwrap();
540
541 let (directory, package) = crate::package::test_helpers::sample_package_with_program_and_imports(&program, &[]);
543
544 assert!(!package.build_directory().exists());
546 package.build::<CurrentAleo>(None).unwrap();
548 assert!(package.build_directory().exists());
550
551 let rng = &mut TestRng::default();
553 let private_key = PrivateKey::new(rng).unwrap();
555 let function_name = Identifier::from_str("bar").unwrap();
556 let inputs = vec![Value::from_str("true").unwrap()];
557
558 let endpoint = "https://api.explorer.aleo.org/v1".to_string();
560
561 let run_result = package.run::<CurrentAleo, _>(&private_key, function_name, &inputs, rng).ok();
563
564 let execute_result =
566 package.execute::<CurrentAleo, _>(endpoint, &private_key, function_name, &inputs, rng).ok();
567
568 match (run_result, execute_result) {
569 (None, None) => {}
571 (Some((run_response, _)), Some((execute_response, _, _))) => {
573 assert_eq!(run_response, execute_response);
574 }
575 _ => panic!("Run and execute results do not match"),
577 }
578
579 std::fs::remove_dir_all(directory).unwrap();
581 }
582}