1use crate::{MAX_PROGRAM_SIZE, *};
18
19use leo_errors::{PackageError, Result, UtilError};
20use leo_span::Symbol;
21
22use snarkvm::prelude::{Program as SvmProgram, TestnetV0};
23
24use indexmap::{IndexMap, IndexSet};
25use std::path::Path;
26
27#[derive(Clone, Debug)]
29pub struct Program {
30 pub name: Symbol,
32 pub data: ProgramData,
33 pub edition: Option<u16>,
34 pub dependencies: IndexSet<Dependency>,
35 pub is_local: bool,
36 pub is_test: bool,
37}
38
39impl Program {
40 pub fn from_aleo_path<P: AsRef<Path>>(name: Symbol, path: P, map: &IndexMap<Symbol, Dependency>) -> Result<Self> {
43 Self::from_aleo_path_impl(name, path.as_ref(), map)
44 }
45
46 fn from_aleo_path_impl(name: Symbol, path: &Path, map: &IndexMap<Symbol, Dependency>) -> Result<Self> {
47 let bytecode = std::fs::read_to_string(path).map_err(|e| {
48 UtilError::util_file_io_error(format_args!("Trying to read aleo file at {}", path.display()), e)
49 })?;
50
51 let dependencies = parse_dependencies_from_aleo(name, &bytecode, map)?;
52
53 Ok(Program {
54 name,
55 data: ProgramData::Bytecode(bytecode),
56 edition: None,
57 dependencies,
58 is_local: true,
59 is_test: false,
60 })
61 }
62
63 pub fn from_package_path<P: AsRef<Path>>(name: Symbol, path: P) -> Result<Self> {
66 Self::from_package_path_impl(name, path.as_ref())
67 }
68
69 fn from_package_path_impl(name: Symbol, path: &Path) -> Result<Self> {
70 let manifest = Manifest::read_from_file(path.join(MANIFEST_FILENAME))?;
71 let manifest_symbol = crate::symbol(&manifest.program)?;
72 if name != manifest_symbol {
73 return Err(PackageError::conflicting_manifest(
74 format_args!("{name}.aleo"),
75 format_args!("{manifest_symbol}.aleo"),
76 )
77 .into());
78 }
79 let source_directory = path.join(SOURCE_DIRECTORY);
80 source_directory.read_dir().map_err(|e| {
81 UtilError::util_file_io_error(format_args!("Failed to read directory {}", source_directory.display()), e)
82 })?;
83
84 let source_path = source_directory.join(MAIN_FILENAME);
85
86 Ok(Program {
87 name,
88 data: ProgramData::SourcePath { directory: path.to_path_buf(), source: source_path },
89 edition: None,
90 dependencies: manifest
91 .dependencies
92 .unwrap_or_default()
93 .into_iter()
94 .map(|dependency| canonicalize_dependency_path_relative_to(path, dependency))
95 .collect::<Result<IndexSet<_>, _>>()?,
96 is_local: true,
97 is_test: false,
98 })
99 }
100
101 pub fn from_test_path<P: AsRef<Path>>(source_path: P, main_program: Dependency) -> Result<Self> {
108 Self::from_path_test_impl(source_path.as_ref(), main_program)
109 }
110
111 fn from_path_test_impl(source_path: &Path, main_program: Dependency) -> Result<Self> {
112 let name = filename_no_leo_extension(source_path)
113 .ok_or_else(|| PackageError::failed_path(source_path.display(), ""))?;
114 let test_directory = source_path.parent().ok_or_else(|| {
115 UtilError::failed_to_open_file(format_args!("Failed to find directory for test {}", source_path.display()))
116 })?;
117 let package_directory = test_directory.parent().ok_or_else(|| {
118 UtilError::failed_to_open_file(format_args!("Failed to find package for test {}", source_path.display()))
119 })?;
120 let manifest = Manifest::read_from_file(package_directory.join(MANIFEST_FILENAME))?;
121 let mut dependencies = manifest
122 .dev_dependencies
123 .unwrap_or_default()
124 .into_iter()
125 .map(|dependency| canonicalize_dependency_path_relative_to(package_directory, dependency))
126 .collect::<Result<IndexSet<_>, _>>()?;
127 dependencies.insert(main_program);
128
129 Ok(Program {
130 name: Symbol::intern(name),
131 edition: None,
132 data: ProgramData::SourcePath {
133 directory: test_directory.to_path_buf(),
134 source: source_path.to_path_buf(),
135 },
136 dependencies,
137 is_local: true,
138 is_test: true,
139 })
140 }
141
142 pub fn fetch<P: AsRef<Path>>(
145 name: Symbol,
146 edition: Option<u16>,
147 home_path: P,
148 network: NetworkName,
149 endpoint: &str,
150 no_cache: bool,
151 ) -> Result<Self> {
152 Self::fetch_impl(name, edition, home_path.as_ref(), network, endpoint, no_cache)
153 }
154
155 fn fetch_impl(
156 name: Symbol,
157 edition: Option<u16>,
158 home_path: &Path,
159 network: NetworkName,
160 endpoint: &str,
161 no_cache: bool,
162 ) -> Result<Self> {
163 let cache_directory = home_path.join(format!("registry/{network}"));
165
166 let edition = match edition {
168 _ if name == Symbol::intern("credits") => Ok(0), Some(edition) => Ok(edition),
170 None => {
171 if name == Symbol::intern("credits") {
172 Ok(0)
174 } else {
175 let url = format!("{endpoint}/{network}/program/{name}.aleo/latest_edition");
176 fetch_from_network(&url).and_then(|contents| {
177 contents.parse::<u16>().map_err(|e| {
178 UtilError::failed_to_retrieve_from_endpoint(
179 url,
180 format!("Failed to parse edition as u16: {e}"),
181 )
182 })
183 })
184 }
185 }
186 };
187
188 let edition = edition.unwrap_or_else(|err| {
190 println!("Warning: Could not fetch edition for program `{name}`: {err}. Defaulting to edition 0.");
191 0
192 });
193
194 let cache_directory = cache_directory.join(format!("{name}/{edition}"));
196 let full_cache_path = cache_directory.join(format!("{name}.aleo"));
197 if !cache_directory.exists() {
198 std::fs::create_dir_all(&cache_directory).map_err(|err| {
200 UtilError::util_file_io_error(format!("Could not write path {}", cache_directory.display()), err)
201 })?;
202 }
203
204 let existing_bytecode = match full_cache_path.exists() {
206 false => None,
207 true => {
208 let existing_contents = std::fs::read_to_string(&full_cache_path).map_err(|e| {
209 UtilError::util_file_io_error(
210 format_args!("Trying to read cached file at {}", full_cache_path.display()),
211 e,
212 )
213 })?;
214 Some(existing_contents)
215 }
216 };
217
218 let bytecode = match (existing_bytecode, no_cache) {
219 (Some(bytecode), false) => bytecode,
221 (existing, _) => {
223 let primary_url = if name == Symbol::intern("credits") {
225 format!("{endpoint}/{network}/program/credits.aleo")
226 } else {
227 format!("{endpoint}/{network}/program/{name}.aleo/{edition}")
228 };
229 let secondary_url = format!("{endpoint}/{network}/program/{name}.aleo");
230 let contents = fetch_from_network(&primary_url)
231 .or_else(|_| fetch_from_network(&secondary_url))
232 .map_err(|err| {
233 UtilError::failed_to_retrieve_from_endpoint(
234 primary_url,
235 format_args!("Failed to fetch program `{name}` from network `{network}`: {err}"),
236 )
237 })?;
238
239 if let Some(existing_contents) = existing {
241 if existing_contents != contents {
242 println!(
243 "Warning: The cached file at `{}` is different from the one fetched from the network. The cached file will be overwritten.",
244 full_cache_path.display()
245 );
246 }
247 }
248
249 std::fs::write(&full_cache_path, &contents).map_err(|err| {
251 UtilError::util_file_io_error(
252 format_args!("Could not open file `{}`", full_cache_path.display()),
253 err,
254 )
255 })?;
256
257 contents
258 }
259 };
260
261 let dependencies = parse_dependencies_from_aleo(name, &bytecode, &IndexMap::new())?;
262
263 Ok(Program {
264 name,
265 data: ProgramData::Bytecode(bytecode),
266 edition: Some(edition),
267 dependencies,
268 is_local: false,
269 is_test: false,
270 })
271 }
272}
273
274fn canonicalize_dependency_path_relative_to(base: &Path, mut dependency: Dependency) -> Result<Dependency> {
279 if let Some(path) = &mut dependency.path {
280 if !path.is_absolute() {
281 let joined = base.join(&path);
282 *path = joined.canonicalize().map_err(|e| PackageError::failed_path(joined.display(), e))?;
283 }
284 }
285 Ok(dependency)
286}
287
288fn parse_dependencies_from_aleo(
290 name: Symbol,
291 bytecode: &str,
292 existing: &IndexMap<Symbol, Dependency>,
293) -> Result<IndexSet<Dependency>> {
294 let program_size = bytecode.len();
296
297 if program_size > MAX_PROGRAM_SIZE {
298 return Err(leo_errors::LeoError::UtilError(UtilError::program_size_limit_exceeded(
299 name,
300 program_size,
301 MAX_PROGRAM_SIZE,
302 )));
303 }
304
305 let svm_program: SvmProgram<TestnetV0> = bytecode.parse().map_err(|_| UtilError::snarkvm_parsing_error(name))?;
307 let dependencies = svm_program
308 .imports()
309 .keys()
310 .map(|program_id| {
311 if let Some(dependency) = existing.get(&Symbol::intern(&program_id.name().to_string())) {
314 dependency.clone()
315 } else {
316 let name = program_id.to_string();
317 Dependency { name, location: Location::Network, path: None, edition: None }
318 }
319 })
320 .collect();
321 Ok(dependencies)
322}