1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright (C) 2019-2023 Aleo Systems Inc.
// This file is part of the snarkVM library.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:
// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

use super::*;

impl<N: Network> Package<N> {
    /// Returns `true` if the package is stale or has not been built.
    pub fn is_build_required<A: crate::circuit::Aleo<Network = N, BaseField = N::Field>>(&self) -> bool {
        // Prepare the build directory.
        let build_directory = self.build_directory();
        // If the build directory does not exist, then a build is required.
        if !build_directory.exists() {
            return true;
        }

        // If the main AVM file does not exists, then a build is required.
        if !AVMFile::<N>::main_exists_at(&build_directory) {
            return true;
        }

        // Open the main AVM file.
        let avm_file = match AVMFile::open(&build_directory, &self.program_id, true) {
            // Retrieve the main AVM file.
            Ok(file) => file,
            // If the main AVM file fails to open, then a build is required.
            Err(_) => return true,
        };

        // Check if the main program matches.
        let program = self.program();
        if avm_file.program() != program {
            return true;
        }

        // Next, check if the prover and verifier exist for each function.
        for function_name in program.functions().keys() {
            // Check if the prover file exists.
            if !ProverFile::exists_at(&build_directory, function_name) {
                // If not, we need to build the circuit.
                return true;
            }
            // Check if the verifier file exists.
            if !VerifierFile::exists_at(&build_directory, function_name) {
                // If not, we need to build the circuit.
                return true;
            }
        }

        // Skip building the package, as it has not changed.
        false
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use snarkvm_console::network::Testnet3;
    use std::{fs::File, io::Write};

    type CurrentNetwork = Testnet3;
    type Aleo = crate::circuit::AleoV0;

    fn temp_dir() -> PathBuf {
        tempfile::tempdir().expect("Failed to open temporary directory").into_path()
    }

    fn initialize_unbuilt_package(valid: bool) -> Result<Package<Testnet3>> {
        // Initialize a temporary directory.
        let directory = temp_dir();

        let program_id = ProgramID::<CurrentNetwork>::from_str("token.aleo").unwrap();

        let program_string = match valid {
            true => program_with_id("token.aleo"),
            false => program_with_id("invalid_id.aleo"),
        };

        // Write the program string to a file in the temporary directory.
        let path = directory.join("main.aleo");
        let mut file = File::create(path).unwrap();
        file.write_all(program_string.as_bytes()).unwrap();

        // Create the manifest file.
        let _manifest_file = Manifest::create(&directory, &program_id).unwrap();

        // Create the build directory.
        let build_directory = directory.join("build");
        std::fs::create_dir_all(build_directory).unwrap();

        // Open the package at the temporary directory.
        Package::<Testnet3>::open(&directory)
    }

    fn program_with_id(id: &str) -> String {
        format!(
            r"program {id};

record token:
    owner as address.private;
    token_amount as u64.private;

function compute:
    input r0 as token.record;
    add.w r0.token_amount r0.token_amount into r1;
    output r1 as u64.private;"
        )
    }

    #[test]
    fn test_for_new_package() {
        let package = initialize_unbuilt_package(true).unwrap();
        assert!(package.is_build_required::<Aleo>());
    }

    #[test]
    fn test_when_avm_file_does_not_exist() {
        let package = initialize_unbuilt_package(true).unwrap();
        assert!(package.build_directory().exists());
        assert!(!AVMFile::<CurrentNetwork>::main_exists_at(&package.build_directory()));
        assert!(package.is_build_required::<Aleo>());
    }

    #[test]
    fn test_fails_when_avm_and_package_program_ids_do_not_match() {
        let package = initialize_unbuilt_package(false);
        assert!(package.is_err());
    }

    #[test]
    fn test_when_prover_and_verifier_files_do_not_exist() {
        let package = initialize_unbuilt_package(true).unwrap();
        assert!(package.build_directory().exists());

        assert!(!AVMFile::<CurrentNetwork>::main_exists_at(&package.build_directory()));
        assert!(AVMFile::<CurrentNetwork>::create(&package.build_directory(), package.program().clone(), true).is_ok());
        assert!(AVMFile::<CurrentNetwork>::main_exists_at(&package.build_directory()));

        let avm_file = AVMFile::open(&package.build_directory(), &package.program_id, true).unwrap();
        assert_eq!(avm_file.program().id(), &package.program_id);
        assert_eq!(avm_file.program(), package.program());

        assert!(
            package
                .program()
                .functions()
                .keys()
                .filter(|k| {
                    ProverFile::exists_at(&package.build_directory(), k)
                        || VerifierFile::exists_at(&package.build_directory(), k)
                })
                .peekable()
                .peek()
                .is_none()
        );

        assert!(package.is_build_required::<Aleo>());
    }

    #[test]
    fn test_prebuilt_package_does_not_rebuild() {
        let package = initialize_unbuilt_package(true).unwrap();
        assert!(package.is_build_required::<Aleo>());

        package.build::<Aleo>(None).unwrap();
        assert!(!package.is_build_required::<Aleo>());
    }
}