1use 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 file_name: String,
33 program: Program<N>,
35}
36
37impl<N: Network> AVMFile<N> {
38 pub fn create(directory: &Path, program: Program<N>, is_main: bool) -> Result<Self> {
40 ensure!(directory.exists(), "The program directory does not exist: '{}'", directory.display());
42 ensure!(
44 !Program::is_reserved_keyword(program.id().name()),
45 "Program name is invalid (reserved): {}",
46 program.id()
47 );
48
49 let file_name = if is_main { Self::main_file_name() } else { format!("{}.{AVM_FILE_EXTENSION}", program.id()) };
51 let path = directory.join(file_name);
53 File::create(&path)?.write_all(&program.to_bytes_le()?)?;
55
56 Self::from_filepath(&path)
57 }
58
59 pub fn open(directory: &Path, program_id: &ProgramID<N>, is_main: bool) -> Result<Self> {
61 ensure!(directory.exists(), "The build directory does not exist: '{}'", directory.display());
63
64 let file_name = if is_main { Self::main_file_name() } else { format!("{program_id}.{AVM_FILE_EXTENSION}") };
66 let path = directory.join(file_name);
68 ensure!(path.exists(), "The AVM file is missing: '{}'", path.display());
70
71 let avm_file = Self::from_filepath(&path)?;
73
74 Ok(avm_file)
75 }
76
77 pub fn exists_at(&self, file_path: &Path) -> bool {
79 Self::check_path(file_path).is_ok() && file_path.exists()
81 }
82
83 pub fn main_exists_at(directory: &Path) -> bool {
85 let path = directory.join(Self::main_file_name());
87 path.is_file() && path.exists()
89 }
90
91 pub fn main_file_name() -> String {
93 format!("main.{AVM_FILE_EXTENSION}")
94 }
95
96 pub fn file_name(&self) -> &str {
98 &self.file_name
99 }
100
101 pub const fn program(&self) -> &Program<N> {
103 &self.program
104 }
105
106 pub fn remove(&self, path: &Path) -> Result<()> {
108 if !path.exists() {
110 Ok(())
111 } else {
112 Self::check_path(path)?;
114 if path.exists() {
116 fs::remove_file(path)?;
118 }
119 Ok(())
120 }
121 }
122}
123
124impl<N: Network> AVMFile<N> {
125 fn check_path(path: &Path) -> Result<()> {
127 ensure!(path.is_file(), "The path is not a file.");
129
130 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 fn from_filepath(file: &Path) -> Result<Self> {
139 Self::check_path(file)?;
141
142 ensure!(file.exists(), "File does not exist: {}", file.display());
144
145 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 let program_bytes = fs::read(file)?;
155 let program = Program::from_bytes_le(&program_bytes)?;
157
158 Ok(Self { file_name, program })
159 }
160
161 pub fn write_to(&self, path: &Path) -> Result<()> {
163 Self::check_path(path)?;
165
166 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!(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 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 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 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 let file = AVMFile::from_filepath(&path).unwrap();
219
220 assert_eq!("token", file.file_name());
221 assert_eq!(&program, file.program());
222 }
223}