snarkvm_debug/file/
aleo.rs1use crate::{
16 file::Manifest,
17 prelude::{Network, ProgramID},
18 synthesizer::Program,
19};
20
21use anyhow::{anyhow, bail, ensure, Result};
22use core::str::FromStr;
23use std::{
24 fs::{self, File},
25 io::Write,
26 path::Path,
27};
28
29static ALEO_FILE_EXTENSION: &str = "aleo";
30
31pub struct AleoFile<N: Network> {
32 file_name: String,
34 program_string: String,
36 program: Program<N>,
38}
39
40impl<N: Network> FromStr for AleoFile<N> {
41 type Err = anyhow::Error;
42
43 #[inline]
45 fn from_str(s: &str) -> Result<Self> {
46 let program = Program::from_str(s)?;
47 let program_string = s.to_string();
48
49 let file_name = match program.id().is_aleo() {
51 true => program.id().name().to_string(),
52 false => program.id().to_string(),
53 };
54
55 Ok(Self { file_name, program_string, program })
56 }
57}
58
59impl<N: Network> AleoFile<N> {
60 pub fn create(directory: &Path, program_id: &ProgramID<N>, is_main: bool) -> Result<Self> {
62 ensure!(directory.exists(), "The program directory does not exist: '{}'", directory.display());
64 ensure!(!Program::is_reserved_keyword(program_id.name()), "Program name is invalid (reserved): '{program_id}'");
66
67 let program_string = format!(
69 r#"// The '{program_id}' program.
70program {program_id};
71
72function hello:
73 input r0 as u32.public;
74 input r1 as u32.private;
75 add r0 r1 into r2;
76 output r2 as u32.private;
77"#
78 );
79
80 let file_name = if is_main {
82 Self::main_file_name()
83 } else {
84 match program_id.is_aleo() {
85 true => program_id.to_string(),
86 false => format!("{program_id}.{ALEO_FILE_EXTENSION}"),
87 }
88 };
89 let path = directory.join(file_name);
91 ensure!(!path.exists(), "The Aleo file already exists: {}", path.display());
93
94 File::create(&path)?.write_all(program_string.as_bytes())?;
96
97 Self::from_filepath(&path)
99 }
100
101 pub fn open(directory: &Path, program_id: &ProgramID<N>, is_main: bool) -> Result<Self> {
103 ensure!(directory.exists(), "The program directory does not exist: '{}'", directory.display());
105
106 let file_name = if is_main {
108 Self::main_file_name()
109 } else {
110 match program_id.is_aleo() {
111 true => program_id.to_string(),
112 false => format!("{program_id}.{ALEO_FILE_EXTENSION}"),
113 }
114 };
115 let path = directory.join(file_name);
117 ensure!(path.exists(), "The Aleo file is missing: '{}'", path.display());
119
120 let aleo_file = Self::from_filepath(&path)?;
122
123 if is_main && aleo_file.program.id() != program_id {
125 bail!("The program ID from `{}` does not match in '{}'", Manifest::<N>::file_name(), path.display())
126 }
127
128 Ok(aleo_file)
129 }
130
131 pub fn exists_at(&self, file_path: &Path) -> bool {
133 Self::check_path(file_path).is_ok() && file_path.exists()
135 }
136
137 pub fn main_exists_at(directory: &Path) -> bool {
139 let path = directory.join(Self::main_file_name());
141 path.is_file() && path.exists()
143 }
144
145 pub fn main_file_name() -> String {
147 format!("main.{ALEO_FILE_EXTENSION}")
148 }
149
150 pub fn file_name(&self) -> &str {
152 &self.file_name
153 }
154
155 pub fn program_string(&self) -> &str {
157 &self.program_string
158 }
159
160 pub const fn program(&self) -> &Program<N> {
162 &self.program
163 }
164
165 pub fn write_to(&self, path: &Path) -> Result<()> {
167 Self::check_path(path)?;
169
170 let file_name = path
172 .file_stem()
173 .ok_or_else(|| anyhow!("File name not found."))?
174 .to_str()
175 .ok_or_else(|| anyhow!("File name not found."))?
176 .to_string();
177 ensure!(file_name == self.file_name, "File name does not match.");
179
180 Ok(File::create(path)?.write_all(self.program_string.as_bytes())?)
181 }
182
183 pub fn remove(&self, path: &Path) -> Result<()> {
185 if !path.exists() {
187 Ok(())
188 } else {
189 Self::check_path(path)?;
191 if path.exists() {
193 fs::remove_file(path)?;
195 }
196 Ok(())
197 }
198 }
199}
200
201impl<N: Network> AleoFile<N> {
202 fn check_path(path: &Path) -> Result<()> {
204 ensure!(path.is_file(), "The path is not a file.");
206
207 let extension = path.extension().ok_or_else(|| anyhow!("File extension not found."))?;
209 ensure!(extension == ALEO_FILE_EXTENSION, "File extension is incorrect.");
210
211 Ok(())
212 }
213
214 fn from_filepath(file: &Path) -> Result<Self> {
216 Self::check_path(file)?;
218
219 ensure!(file.exists(), "File does not exist: {}", file.display());
221
222 let file_name = file
224 .file_stem()
225 .ok_or_else(|| anyhow!("File name not found."))?
226 .to_str()
227 .ok_or_else(|| anyhow!("File name not found."))?
228 .to_string();
229
230 let program_string = fs::read_to_string(file)?;
232 let program = Program::from_str(&program_string)?;
234
235 Ok(Self { file_name, program_string, program })
236 }
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242 use crate::prelude::Parser;
243
244 type CurrentNetwork = snarkvm_console::network::Testnet3;
245
246 fn temp_dir() -> std::path::PathBuf {
247 tempfile::tempdir().expect("Failed to open temporary directory").into_path()
248 }
249
250 #[test]
251 fn test_from_str() {
252 let program_string = r"
253program token.aleo;
254
255record token:
256 owner as address.private;
257 token_amount as u64.private;
258
259function compute:
260 input r0 as token.record;
261 add r0.token_amount r0.token_amount into r1;
262 output r1 as u64.private;";
263
264 let (string, program) = Program::<CurrentNetwork>::parse(program_string).unwrap();
266 assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
267
268 let file = AleoFile::from_str(program_string).unwrap();
270 assert_eq!("token", file.file_name());
271 assert_eq!(program_string, file.program_string());
272 assert_eq!(&program, file.program());
273 }
274
275 #[test]
276 fn test_from_path() {
277 let directory = temp_dir();
279
280 let program_string = r"
281program token.aleo;
282
283record token:
284 owner as address.private;
285 token_amount as u64.private;
286
287function compute:
288 input r0 as token.record;
289 add r0.token_amount r0.token_amount into r1;
290 output r1 as u64.private;";
291
292 let path = directory.join("token.aleo");
294 let mut file = File::create(&path).unwrap();
295 file.write_all(program_string.as_bytes()).unwrap();
296
297 let file = AleoFile::from_filepath(&path).unwrap();
299
300 let (string, program) = Program::<CurrentNetwork>::parse(program_string).unwrap();
302 assert!(string.is_empty(), "Parser did not consume all of the string: '{string}'");
303
304 assert_eq!("token", file.file_name());
305 assert_eq!(program_string, file.program_string());
306 assert_eq!(&program, file.program());
307 }
308}