leo_package/
lib.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17//! This crate deals with Leo packages on the file system and network.
18//!
19//! The main type is `Package`, which deals with Leo packages on the local filesystem.
20//! A Leo package directory is intended to have a structure like this:
21//! .
22//! ├── .env
23//! ├── program.json
24//! ├── build
25//! │   ├── imports
26//! │   │   └── credits.aleo
27//! │   └── main.aleo
28//! ├── outputs
29//! │   ├── program.TypeChecking.ast
30//! │   └── program.TypeChecking.json
31//! ├── src
32//! │   └── main.leo
33//! └── tests
34//!     └── test_something.leo
35//!
36//! The file `program.json` is a manifest containing the program name, version, description,
37//! and license, together with information about its dependencies.
38//!
39//! The file `.env` contains definitions for the environment variables NETWORK, PRIVATE_KEY,
40//! and ENDPOINT that may be used when deploying or executing the program.
41//!
42//! Such a directory structure, together with a `.gitignore` file, may be created
43//! on the file system using `Package::initialize`.
44//! ```no_run
45//! # use leo_package::{NetworkName, Package};
46//! let path = Package::initialize("my_package", "path/to/parent", NetworkName::TestnetV0, "http://localhost:3030").unwrap();
47//! ```
48//!
49//! `tests` is where unit test files may be placed.
50//!
51//! Given an existing directory with such a structure, a `Package` may be created from it with
52//! `Package::from_directory`:
53//! ```no_run
54//! # use leo_package::Package;
55//! let package = Package::from_directory("path/to/package", "/home/me/.aleo").unwrap();
56//! ```
57//! This will read the manifest and env file and keep their data in `package.manifest` and `package.env`.
58//! It will also process dependencies and store them in topological order in `package.programs`. This processing
59//! will involve fetching bytecode from the network for network dependencies.
60//!
61//! If you want to simply read the manifest and env file without processing dependencies, use
62//! `Package::from_directory_no_graph`.
63//!
64//! `Program` generally doesn't need to be created directly, as `Package` will create `Program`s
65//! for the main program and all dependencies. However, if you'd like to fetch bytecode for
66//! a program, you can use `Program::fetch`.
67
68#![forbid(unsafe_code)]
69
70use leo_errors::{PackageError, Result, UtilError};
71use leo_span::Symbol;
72
73use std::path::Path;
74
75mod dependency;
76pub use dependency::*;
77
78mod env;
79pub use env::*;
80
81mod location;
82pub use location::*;
83
84mod manifest;
85pub use manifest::*;
86
87mod network_name;
88pub use network_name::*;
89
90mod package;
91pub use package::*;
92
93mod program;
94pub use program::*;
95
96pub const SOURCE_DIRECTORY: &str = "src";
97
98pub const MAIN_FILENAME: &str = "main.leo";
99
100pub const IMPORTS_DIRECTORY: &str = "build/imports";
101
102pub const OUTPUTS_DIRECTORY: &str = "outputs";
103
104pub const BUILD_DIRECTORY: &str = "build";
105
106pub const TESTS_DIRECTORY: &str = "tests";
107
108pub const TEST_PRIVATE_KEY: &str = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH";
109
110fn symbol(name: &str) -> Result<Symbol> {
111    name.strip_suffix(".aleo").map(Symbol::intern).ok_or_else(|| PackageError::invalid_network_name(name).into())
112}
113
114/// Is this a valid name for an Aleo program?
115///
116/// Namely, it must be of the format "xxx.aleo" where `xxx` is nonempty,
117/// consist solely of ASCII alphanumeric characters and underscore, and
118/// begin with a letter.
119pub fn is_valid_aleo_name(name: &str) -> bool {
120    let Some(rest) = name.strip_suffix(".aleo") else {
121        return false;
122    };
123
124    // Check that the name is nonempty.
125    if rest.is_empty() {
126        tracing::error!("Aleo names must be nonempty");
127        return false;
128    }
129
130    let first = rest.chars().next().unwrap();
131
132    // Check that the first character is not an underscore.
133    if first == '_' {
134        tracing::error!("Aleo names cannot begin with an underscore");
135        return false;
136    }
137
138    // Check that the first character is not a number.
139    if first.is_numeric() {
140        tracing::error!("Aleo names cannot begin with a number");
141        return false;
142    }
143
144    // Iterate and check that the name is valid.
145    if rest.chars().any(|c| !c.is_ascii_alphanumeric() && c != '_') {
146        tracing::error!("Aleo names must can only contain ASCII alphanumeric characters and underscores.");
147        return false;
148    }
149
150    true
151}
152
153// Fetch the given endpoint url and return the sanitized response.
154pub fn fetch_from_network(url: &str) -> Result<String, UtilError> {
155    let response = ureq::AgentBuilder::new()
156        .redirects(0)
157        .build()
158        .get(url)
159        .set("X-Leo-Version", env!("CARGO_PKG_VERSION"))
160        .call()
161        .map_err(|err| UtilError::failed_to_retrieve_from_endpoint(err, Default::default()))?;
162    match response.status() {
163        200 => Ok(response.into_string().unwrap().replace("\\n", "\n").replace('\"', "")),
164        301 => Err(UtilError::endpoint_moved_error(url)),
165        _ => Err(UtilError::network_error(url, response.status(), Default::default())),
166    }
167}
168
169// Verify that a fetched program is valid aleo instructions.
170pub fn verify_valid_program(name: &str, program: &str) -> Result<(), UtilError> {
171    use snarkvm::prelude::{Program, TestnetV0};
172    use std::str::FromStr as _;
173    match Program::<TestnetV0>::from_str(program) {
174        Ok(_) => Ok(()),
175        Err(_) => Err(UtilError::snarkvm_parsing_error(name)),
176    }
177}
178
179pub fn filename_no_leo_extension(path: &Path) -> Option<&str> {
180    filename_no_extension(path, ".leo")
181}
182
183pub fn filename_no_aleo_extension(path: &Path) -> Option<&str> {
184    filename_no_extension(path, ".aleo")
185}
186
187fn filename_no_extension<'a>(path: &'a Path, extension: &'static str) -> Option<&'a str> {
188    path.file_name().and_then(|os_str| os_str.to_str()).and_then(|s| s.strip_suffix(extension))
189}