snarkvm_debug/package/
is_build_required.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
15use super::*;
16
17impl<N: Network> Package<N> {
18    /// Returns `true` if the package is stale or has not been built.
19    pub fn is_build_required<A: crate::circuit::Aleo<Network = N, BaseField = N::Field>>(&self) -> bool {
20        // Prepare the build directory.
21        let build_directory = self.build_directory();
22        // If the build directory does not exist, then a build is required.
23        if !build_directory.exists() {
24            return true;
25        }
26
27        // If the main AVM file does not exists, then a build is required.
28        if !AVMFile::<N>::main_exists_at(&build_directory) {
29            return true;
30        }
31
32        // Open the main AVM file.
33        let avm_file = match AVMFile::open(&build_directory, &self.program_id, true) {
34            // Retrieve the main AVM file.
35            Ok(file) => file,
36            // If the main AVM file fails to open, then a build is required.
37            Err(_) => return true,
38        };
39
40        // Check if the main program matches.
41        let program = self.program();
42        if avm_file.program() != program {
43            return true;
44        }
45
46        // Next, check if the prover and verifier exist for each function.
47        for function_name in program.functions().keys() {
48            // Check if the prover file exists.
49            if !ProverFile::exists_at(&build_directory, function_name) {
50                // If not, we need to build the circuit.
51                return true;
52            }
53            // Check if the verifier file exists.
54            if !VerifierFile::exists_at(&build_directory, function_name) {
55                // If not, we need to build the circuit.
56                return true;
57            }
58        }
59
60        // Skip building the package, as it has not changed.
61        false
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use snarkvm_console::network::Testnet3;
69    use std::{fs::File, io::Write};
70
71    type CurrentNetwork = Testnet3;
72    type Aleo = crate::circuit::AleoV0;
73
74    fn temp_dir() -> PathBuf {
75        tempfile::tempdir().expect("Failed to open temporary directory").into_path()
76    }
77
78    fn initialize_unbuilt_package(valid: bool) -> Result<Package<Testnet3>> {
79        // Initialize a temporary directory.
80        let directory = temp_dir();
81
82        let program_id = ProgramID::<CurrentNetwork>::from_str("token.aleo").unwrap();
83
84        let program_string = match valid {
85            true => program_with_id("token.aleo"),
86            false => program_with_id("invalid_id.aleo"),
87        };
88
89        // Write the program string to a file in the temporary directory.
90        let path = directory.join("main.aleo");
91        let mut file = File::create(path).unwrap();
92        file.write_all(program_string.as_bytes()).unwrap();
93
94        // Create the manifest file.
95        let _manifest_file = Manifest::create(&directory, &program_id).unwrap();
96
97        // Create the build directory.
98        let build_directory = directory.join("build");
99        std::fs::create_dir_all(build_directory).unwrap();
100
101        // Open the package at the temporary directory.
102        Package::<Testnet3>::open(&directory)
103    }
104
105    fn program_with_id(id: &str) -> String {
106        format!(
107            r"program {id};
108
109record token:
110    owner as address.private;
111    token_amount as u64.private;
112
113function compute:
114    input r0 as token.record;
115    add.w r0.token_amount r0.token_amount into r1;
116    output r1 as u64.private;"
117        )
118    }
119
120    #[test]
121    fn test_for_new_package() {
122        let package = initialize_unbuilt_package(true).unwrap();
123        assert!(package.is_build_required::<Aleo>());
124    }
125
126    #[test]
127    fn test_when_avm_file_does_not_exist() {
128        let package = initialize_unbuilt_package(true).unwrap();
129        assert!(package.build_directory().exists());
130        assert!(!AVMFile::<CurrentNetwork>::main_exists_at(&package.build_directory()));
131        assert!(package.is_build_required::<Aleo>());
132    }
133
134    #[test]
135    fn test_fails_when_avm_and_package_program_ids_do_not_match() {
136        let package = initialize_unbuilt_package(false);
137        assert!(package.is_err());
138    }
139
140    #[test]
141    fn test_when_prover_and_verifier_files_do_not_exist() {
142        let package = initialize_unbuilt_package(true).unwrap();
143        assert!(package.build_directory().exists());
144
145        assert!(!AVMFile::<CurrentNetwork>::main_exists_at(&package.build_directory()));
146        assert!(AVMFile::<CurrentNetwork>::create(&package.build_directory(), package.program().clone(), true).is_ok());
147        assert!(AVMFile::<CurrentNetwork>::main_exists_at(&package.build_directory()));
148
149        let avm_file = AVMFile::open(&package.build_directory(), &package.program_id, true).unwrap();
150        assert_eq!(avm_file.program().id(), &package.program_id);
151        assert_eq!(avm_file.program(), package.program());
152
153        assert!(
154            package
155                .program()
156                .functions()
157                .keys()
158                .filter(|k| {
159                    ProverFile::exists_at(&package.build_directory(), k)
160                        || VerifierFile::exists_at(&package.build_directory(), k)
161                })
162                .peekable()
163                .peek()
164                .is_none()
165        );
166
167        assert!(package.is_build_required::<Aleo>());
168    }
169
170    #[test]
171    fn test_prebuilt_package_does_not_rebuild() {
172        let package = initialize_unbuilt_package(true).unwrap();
173        assert!(package.is_build_required::<Aleo>());
174
175        package.build::<Aleo>(None).unwrap();
176        assert!(!package.is_build_required::<Aleo>());
177    }
178}