snarkvm/file/
avm.rs

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