snarkvm_debug/file/
avm.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 crate::{
16    prelude::{FromBytes, Network, ProgramID, ToBytes},
17    synthesizer::Program,
18};
19
20use anyhow::{anyhow, ensure, Result};
21use std::{
22    fs::{self, File},
23    io::Write,
24    path::Path,
25};
26
27static AVM_FILE_EXTENSION: &str = "avm";
28
29pub struct AVMFile<N: Network> {
30    /// The file name (without the extension).
31    file_name: String,
32    /// The program.
33    program: Program<N>,
34}
35
36impl<N: Network> AVMFile<N> {
37    /// Creates a new AVM program file, given the directory path, program ID, and `is_main` indicator.
38    pub fn create(directory: &Path, program: Program<N>, is_main: bool) -> Result<Self> {
39        // Ensure the directory path exists.
40        ensure!(directory.exists(), "The program directory does not exist: '{}'", directory.display());
41        // Ensure the program name is valid.
42        ensure!(
43            !Program::is_reserved_keyword(program.id().name()),
44            "Program name is invalid (reserved): {}",
45            program.id()
46        );
47
48        // Create the file.
49        let file_name = if is_main { Self::main_file_name() } else { format!("{}.{AVM_FILE_EXTENSION}", program.id()) };
50        // Construct the file path.
51        let path = directory.join(file_name);
52        // Write the file (overwriting if it already exists).
53        File::create(&path)?.write_all(&program.to_bytes_le()?)?;
54
55        Self::from_filepath(&path)
56    }
57
58    /// Opens the AVM program file, given the directory path, program ID, and `is_main` indicator.
59    pub fn open(directory: &Path, program_id: &ProgramID<N>, is_main: bool) -> Result<Self> {
60        // Ensure the directory path exists.
61        ensure!(directory.exists(), "The build directory does not exist: '{}'", directory.display());
62
63        // Create the file.
64        let file_name = if is_main { Self::main_file_name() } else { format!("{program_id}.{AVM_FILE_EXTENSION}") };
65        // Construct the file path.
66        let path = directory.join(file_name);
67        // Ensure the file path exists.
68        ensure!(path.exists(), "The AVM file is missing: '{}'", path.display());
69
70        // Load the AVM file.
71        let avm_file = Self::from_filepath(&path)?;
72
73        Ok(avm_file)
74    }
75
76    /// Returns `true` if the file exists at the given path.
77    pub fn exists_at(&self, file_path: &Path) -> bool {
78        // Ensure the path is well-formed.
79        Self::check_path(file_path).is_ok() && file_path.exists()
80    }
81
82    /// Returns `true` if the main program file exists at the given directory path.
83    pub fn main_exists_at(directory: &Path) -> bool {
84        // Construct the file path.
85        let path = directory.join(Self::main_file_name());
86        // Return the result.
87        path.is_file() && path.exists()
88    }
89
90    /// Returns the main AVM program file name.
91    pub fn main_file_name() -> String {
92        format!("main.{AVM_FILE_EXTENSION}")
93    }
94
95    /// Returns the file name.
96    pub fn file_name(&self) -> &str {
97        &self.file_name
98    }
99
100    /// Returns the program.
101    pub const fn program(&self) -> &Program<N> {
102        &self.program
103    }
104
105    /// Removes the file at the given path, if it exists.
106    pub fn remove(&self, path: &Path) -> Result<()> {
107        // If the path does not exist, do nothing.
108        if !path.exists() {
109            Ok(())
110        } else {
111            // Ensure the path is well-formed.
112            Self::check_path(path)?;
113            // If the path exists, remove it.
114            if path.exists() {
115                // Remove the file.
116                fs::remove_file(path)?;
117            }
118            Ok(())
119        }
120    }
121}
122
123impl<N: Network> AVMFile<N> {
124    /// Checks that the given path has the correct file extension.
125    fn check_path(path: &Path) -> Result<()> {
126        // Ensure the given path is a file.
127        ensure!(path.is_file(), "The path is not a file.");
128
129        // Ensure the given path has the correct file extension.
130        let extension = path.extension().ok_or_else(|| anyhow!("File extension not found."))?;
131        ensure!(extension == AVM_FILE_EXTENSION, "File extension is incorrect.");
132
133        Ok(())
134    }
135
136    /// Reads the AVM program from the given file path, if it exists.
137    fn from_filepath(file: &Path) -> Result<Self> {
138        // Ensure the path is well-formed.
139        Self::check_path(file)?;
140
141        // Ensure the given path exists.
142        ensure!(file.exists(), "File does not exist: {}", file.display());
143
144        // Retrieve the file name.
145        let file_name = file
146            .file_stem()
147            .ok_or_else(|| anyhow!("File name not found."))?
148            .to_str()
149            .ok_or_else(|| anyhow!("File name not found."))?
150            .to_string();
151
152        // Read the program bytes.
153        let program_bytes = fs::read(file)?;
154        // Parse the program bytes.
155        let program = Program::from_bytes_le(&program_bytes)?;
156
157        Ok(Self { file_name, program })
158    }
159
160    /// Writes the AVM program to the file.
161    pub fn write_to(&self, path: &Path) -> Result<()> {
162        // Ensure the path is well-formed.
163        Self::check_path(path)?;
164
165        // Retrieve the file name.
166        let file_name = path
167            .file_stem()
168            .ok_or_else(|| anyhow!("File name not found."))?
169            .to_str()
170            .ok_or_else(|| anyhow!("File name not found."))?
171            .to_string();
172        // Ensure the file name matches the expected file name.
173        ensure!(file_name == self.file_name, "File name does not match.");
174
175        Ok(File::create(path)?.write_all(&self.program.to_bytes_le()?)?)
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182    use crate::prelude::Parser;
183
184    type CurrentNetwork = snarkvm_console::network::Testnet3;
185
186    fn temp_dir() -> std::path::PathBuf {
187        tempfile::tempdir().expect("Failed to open temporary directory").into_path()
188    }
189
190    #[test]
191    fn test_from_path() {
192        // Initialize a temporary directory.
193        let directory = temp_dir();
194
195        let program_string = r"
196program token.aleo;
197
198record token:
199    owner as address.private;
200    token_amount as u64.private;
201
202function compute:
203    input r0 as token.record;
204    add r0.token_amount r0.token_amount into r1;
205    output r1 as u64.private;";
206
207        // Initialize a new program.
208        let (string, program) = Program::<CurrentNetwork>::parse(program_string).unwrap();
209        assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
210
211        // Write the program bytes to a file in the temporary directory.
212        let path = directory.join("token.avm");
213        let mut file = File::create(&path).unwrap();
214        file.write_all(&program.to_bytes_le().unwrap()).unwrap();
215
216        // Read the program from the path.
217        let file = AVMFile::from_filepath(&path).unwrap();
218
219        assert_eq!("token", file.file_name());
220        assert_eq!(&program, file.program());
221    }
222}