snarkvm_debug/package/
mod.rs

1// Copyright (C) 2019-2023 Aleo Systems Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7// http://www.apache.org/licenses/LICENSE-2.0
8
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15mod 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    /// The program ID.
51    program_id: ProgramID<N>,
52    /// The directory path.
53    directory: PathBuf,
54    /// The manifest file.
55    manifest_file: Manifest<N>,
56    /// The program file.
57    program_file: AleoFile<N>,
58}
59
60impl<N: Network> Package<N> {
61    /// Creates a new package, at the given directory with the given program name.
62    pub fn create(directory: &Path, program_id: &ProgramID<N>) -> Result<Self> {
63        // Ensure the directory path does not exist.
64        ensure!(!directory.exists(), "The program directory already exists: {}", directory.display());
65        // Ensure the program name is valid.
66        ensure!(!Program::is_reserved_keyword(program_id.name()), "Program name is invalid (reserved): {program_id}");
67
68        // Create the program directory.
69        if !directory.exists() {
70            std::fs::create_dir_all(directory)?;
71        }
72
73        // Create the manifest file.
74        let manifest_file = Manifest::create(directory, program_id)?;
75        // Create the program file.
76        let program_file = AleoFile::create(directory, program_id, true)?;
77        // Create the README file.
78        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    /// Opens the package at the given directory with the given program name.
84    pub fn open(directory: &Path) -> Result<Self> {
85        // Ensure the directory path exists.
86        ensure!(directory.exists(), "The program directory does not exist: {}", directory.display());
87        // Ensure the manifest file exists.
88        ensure!(
89            Manifest::<N>::exists_at(directory),
90            "Missing '{}' at '{}'",
91            Manifest::<N>::file_name(),
92            directory.display()
93        );
94        // Ensure the main program file exists.
95        ensure!(
96            AleoFile::<N>::main_exists_at(directory),
97            "Missing '{}' at '{}'",
98            AleoFile::<N>::main_file_name(),
99            directory.display()
100        );
101
102        // Open the manifest file.
103        let manifest_file = Manifest::open(directory)?;
104        // Retrieve the program ID.
105        let program_id = *manifest_file.program_id();
106        // Ensure the program name is valid.
107        ensure!(!Program::is_reserved_keyword(program_id.name()), "Program name is invalid (reserved): {program_id}");
108
109        // Open the program file.
110        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    /// Returns the program ID.
116    pub const fn program_id(&self) -> &ProgramID<N> {
117        &self.program_id
118    }
119
120    /// Returns the directory path.
121    pub const fn directory(&self) -> &PathBuf {
122        &self.directory
123    }
124
125    /// Returns the manifest file.
126    pub const fn manifest_file(&self) -> &Manifest<N> {
127        &self.manifest_file
128    }
129
130    /// Returns the program file.
131    pub const fn program_file(&self) -> &AleoFile<N> {
132        &self.program_file
133    }
134
135    /// Returns the program.
136    pub const fn program(&self) -> &Program<N> {
137        self.program_file.program()
138    }
139
140    /// Returns the build directory.
141    pub fn build_directory(&self) -> PathBuf {
142        self.directory.join("build")
143    }
144
145    /// Returns the imports directory.
146    pub fn imports_directory(&self) -> PathBuf {
147        self.directory.join("imports")
148    }
149
150    /// Returns a new process for the package.
151    pub fn get_process(&self) -> Result<Process<N>> {
152        // Create the process.
153        let mut process = Process::load()?;
154
155        // Prepare the imports directory.
156        let imports_directory = self.imports_directory();
157
158        // Initialize the 'credits.aleo' program ID.
159        let credits_program_id = ProgramID::<N>::from_str("credits.aleo")?;
160
161        // Add all import programs (in order) to the process.
162        self.program().imports().keys().try_for_each(|program_id| {
163            // Don't add `credits.aleo` as the process is already loaded with it.
164            if program_id != &credits_program_id {
165                // Open the Aleo program file.
166                let import_program_file = AleoFile::open(&imports_directory, program_id, false)?;
167                // Add the import program.
168                process.add_program(import_program_file.program())?;
169            }
170            Ok::<_, Error>(())
171        })?;
172
173        // Add the program to the process.
174        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    /// Samples a (temporary) package containing a `token.aleo` program.
194    pub(crate) fn sample_token_package() -> (PathBuf, Package<CurrentNetwork>) {
195        // Initialize the program.
196        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 the package using the program.
223        sample_package_with_program_and_imports(&program, &[])
224    }
225
226    /// Samples a (temporary) package containing a `wallet.aleo` program which imports `token.aleo`.
227    pub(crate) fn sample_wallet_package() -> (PathBuf, Package<CurrentNetwork>) {
228        // Initialize the imported program.
229        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        // Initialize the main program.
256        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 the package using the main program and imported program.
273        sample_package_with_program_and_imports(&main_program, &[imported_program])
274    }
275
276    /// Samples a (temporary) package containing a `grandparent.aleo` program which imports `parent.aleo` which imports `child.aleo`.
277    pub(crate) fn sample_nested_package() -> (PathBuf, Package<CurrentNetwork>) {
278        // Initialize the child program.
279        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        // Initialize the parent program.
296        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        // Initialize the grandparent program.
311        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 the package using the main program and imported program.
327        sample_package_with_program_and_imports(&grandparent_program, &[child_program, parent_program])
328    }
329
330    /// Samples a (temporary) package containing a `transfer.aleo` program which imports `credits.aleo`.
331    pub(crate) fn sample_transfer_package() -> (PathBuf, Package<CurrentNetwork>) {
332        // Initialize the imported program.
333        let imported_program = Program::credits().unwrap();
334
335        // Initialize the main program.
336        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 the package using the main program and imported program.
353        sample_package_with_program_and_imports(&main_program, &[imported_program])
354    }
355
356    /// Samples a (temporary) package using a main program and imported programs.
357    pub(crate) fn sample_package_with_program_and_imports(
358        main_program: &Program<CurrentNetwork>,
359        imported_programs: &[Program<CurrentNetwork>],
360    ) -> (PathBuf, Package<CurrentNetwork>) {
361        // Initialize a temporary directory.
362        let directory = temp_dir();
363
364        // If there are imports, create the imports directory.
365        if !imported_programs.is_empty() {
366            let imports_directory = directory.join("imports");
367            std::fs::create_dir_all(&imports_directory).unwrap();
368
369            // Add the imported programs.
370            for imported_program in imported_programs {
371                let imported_program_id = imported_program.id();
372
373                // Write the imported program string to an imports file in the temporary directory.
374                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        // Initialize the main program ID.
381        let main_program_id = main_program.id();
382
383        // Write the program string to a file in the temporary directory.
384        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        // Create the manifest file.
389        let _manifest_file = Manifest::create(&directory, main_program_id).unwrap();
390
391        // Open the package at the temporary directory.
392        let package = Package::<Testnet3>::open(&directory).unwrap();
393        assert_eq!(package.program_id(), main_program_id);
394
395        // Return the temporary directory and the package.
396        (directory, package)
397    }
398
399    /// Samples a candidate input to execute the sample package.
400    pub(crate) fn sample_package_run(
401        program_id: &ProgramID<CurrentNetwork>,
402    ) -> (PrivateKey<CurrentNetwork>, Identifier<CurrentNetwork>, Vec<Value<CurrentNetwork>>) {
403        // Initialize an RNG.
404        let rng = &mut TestRng::default();
405
406        match program_id.to_string().as_str() {
407            "token.aleo" => {
408                // Sample a random private key.
409                let private_key = crate::cli::helpers::dotenv_private_key().unwrap();
410                let caller = Address::try_from(&private_key).unwrap();
411
412                // Initialize the function name.
413                let function_name = Identifier::from_str("initialize").unwrap();
414
415                // Initialize the function inputs.
416                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                // Initialize caller 0.
423                let caller0_private_key = crate::cli::helpers::dotenv_private_key().unwrap();
424                let caller0 = Address::try_from(&caller0_private_key).unwrap();
425
426                // Initialize caller 1.
427                let caller1_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
428                let caller1 = Address::try_from(&caller1_private_key).unwrap();
429
430                // Declare the function name.
431                let function_name = Identifier::from_str("transfer").unwrap();
432
433                // Initialize the function inputs.
434                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                // Initialize caller 0.
445                let caller0_private_key = crate::cli::helpers::dotenv_private_key().unwrap();
446
447                // Initialize caller 1.
448                let caller1_private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
449                let caller1 = Address::try_from(&caller1_private_key).unwrap();
450
451                // Declare the function name.
452                let function_name = Identifier::from_str("double_wrapper_mint").unwrap();
453
454                // Initialize the function inputs.
455                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        // Samples a new package at a temporary directory.
477        let (directory, package) = crate::package::test_helpers::sample_token_package();
478
479        // Ensure the imports directory is correct.
480        assert_eq!(package.imports_directory(), directory.join("imports"));
481        // Ensure the imports directory does *not* exist, when the package does not contain imports.
482        assert!(!package.imports_directory().exists());
483
484        // Proactively remove the temporary directory (to conserve space).
485        std::fs::remove_dir_all(directory).unwrap();
486    }
487
488    #[test]
489    fn test_imports_directory_with_an_import() {
490        // Samples a new package with an import at a temporary directory.
491        let (directory, package) = crate::package::test_helpers::sample_wallet_package();
492
493        // Ensure the imports directory is correct.
494        assert_eq!(package.imports_directory(), directory.join("imports"));
495        // Ensure the imports directory exists, as the package contains an import.
496        assert!(package.imports_directory().exists());
497
498        // Proactively remove the temporary directory (to conserve space).
499        std::fs::remove_dir_all(directory).unwrap();
500    }
501
502    #[test]
503    fn test_build_directory() {
504        // Samples a new package at a temporary directory.
505        let (directory, package) = crate::package::test_helpers::sample_token_package();
506
507        // Ensure the build directory is correct.
508        assert_eq!(package.build_directory(), directory.join("build"));
509        // Ensure the build directory does *not* exist, when the package has not been built.
510        assert!(!package.build_directory().exists());
511
512        // Proactively remove the temporary directory (to conserve space).
513        std::fs::remove_dir_all(directory).unwrap();
514    }
515
516    #[test]
517    fn test_get_process() {
518        // Samples a new package at a temporary directory.
519        let (directory, package) = crate::package::test_helpers::sample_token_package();
520
521        // Get the program process and check all instructions.
522        assert!(package.get_process().is_ok());
523
524        // Proactively remove the temporary directory (to conserve space).
525        std::fs::remove_dir_all(directory).unwrap();
526    }
527
528    #[test]
529    fn test_package_run_and_execute_match() {
530        // Initialize the program.
531        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        // Samples a new package at a temporary directory.
542        let (directory, package) = crate::package::test_helpers::sample_package_with_program_and_imports(&program, &[]);
543
544        // Ensure the build directory does *not* exist.
545        assert!(!package.build_directory().exists());
546        // Build the package.
547        package.build::<CurrentAleo>(None).unwrap();
548        // Ensure the build directory exists.
549        assert!(package.build_directory().exists());
550
551        // Initialize an RNG.
552        let rng = &mut TestRng::default();
553        // Sample the function inputs.
554        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        // Construct the endpoint.
559        let endpoint = "https://api.explorer.aleo.org/v1".to_string();
560
561        // Run the program function.
562        let run_result = package.run::<CurrentAleo, _>(&private_key, function_name, &inputs, rng).ok();
563
564        // Execute the program function.
565        let execute_result =
566            package.execute::<CurrentAleo, _>(endpoint, &private_key, function_name, &inputs, rng).ok();
567
568        match (run_result, execute_result) {
569            // If both results are `None`, then they both failed.
570            (None, None) => {}
571            // If both results are `Some`, then check that the responses match.
572            (Some((run_response, _)), Some((execute_response, _, _))) => {
573                assert_eq!(run_response, execute_response);
574            }
575            // Otherwise, the results do not match.
576            _ => panic!("Run and execute results do not match"),
577        }
578
579        // Proactively remove the temporary directory (to conserve space).
580        std::fs::remove_dir_all(directory).unwrap();
581    }
582}