diff --git a/Cargo.lock b/Cargo.lock index f861411b1d..0c218a01a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1398,6 +1398,7 @@ name = "leo-compiler" version = "1.10.0" dependencies = [ "dotenvy", + "indexmap 1.9.3", "leo-ast", "leo-errors", "leo-package", @@ -1424,6 +1425,7 @@ dependencies = [ "colored", "derivative", "leo-span", + "reqwest", "serde", "thiserror", ] @@ -1452,7 +1454,7 @@ dependencies = [ "rand", "rand_chacha", "rand_core", - "reqwest", + "retriever", "rusty-hook", "self_update 0.39.0", "serde", @@ -1475,6 +1477,7 @@ dependencies = [ "lazy_static", "leo-errors", "rand", + "retriever", "serde", "snarkvm", "toml 0.8.8", @@ -1533,6 +1536,7 @@ dependencies = [ "backtrace", "clap", "criterion", + "indexmap 1.9.3", "leo-compiler", "leo-errors", "leo-span", @@ -1621,16 +1625,6 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2172,7 +2166,6 @@ dependencies = [ "js-sys", "log", "mime", - "mime_guess", "native-tls", "once_cell", "percent-encoding", @@ -2191,6 +2184,24 @@ dependencies = [ "winreg", ] +[[package]] +name = "retriever" +version = "1.10.0" +dependencies = [ + "disassembler", + "indexmap 1.9.3", + "leo-ast", + "leo-errors", + "leo-passes", + "leo-span", + "serde", + "serde_json", + "sha2", + "tempfile", + "toml 0.8.8", + "ureq", +] + [[package]] name = "ring" version = "0.17.5" @@ -3788,15 +3799,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "unicase" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.13" diff --git a/Cargo.toml b/Cargo.toml index 92bdfce6e1..5397a89afb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,8 @@ members = [ "errors", "leo/package", "tests/test-framework", - "utils/disassembler" + "utils/disassembler", + "utils/retriever" ] [workspace.dependencies.snarkvm] @@ -84,6 +85,10 @@ version = "=1.10.0" path = "./compiler/span" version = "=1.10.0" +[dependencies.retriever] +path = "./utils/retriever" +version = "1.10.0" + [dependencies.backtrace] version = "0.3.68" @@ -123,10 +128,6 @@ default-features = false [dependencies.rand_core] version = "0.6.4" -[dependencies.reqwest] -version = "0.11.22" -features = [ "blocking", "json", "multipart" ] - [dependencies.self_update] version = "0.39.0" features = [ "archive-zip" ] diff --git a/compiler/ast/src/stub/mod.rs b/compiler/ast/src/stub/mod.rs index 414f11226b..0b37ac5777 100644 --- a/compiler/ast/src/stub/mod.rs +++ b/compiler/ast/src/stub/mod.rs @@ -21,7 +21,7 @@ pub use finalize_stub::*; pub mod function_stub; pub use function_stub::*; -use crate::{ConstDeclaration, Mapping, ProgramId, Struct}; +use crate::{ConstDeclaration, Identifier, Mapping, NodeID, ProgramId, Struct}; use leo_span::{Span, Symbol}; use serde::{Deserialize, Serialize}; use std::fmt; @@ -45,6 +45,24 @@ pub struct Stub { pub span: Span, } +impl Default for Stub { + /// Constructs an empty program stub + fn default() -> Self { + Self { + imports: Vec::new(), + stub_id: ProgramId { + name: Identifier::new(Symbol::intern(""), NodeID::default()), + network: Identifier::new(Symbol::intern(""), NodeID::default()), + }, + consts: Vec::new(), + structs: Vec::new(), + mappings: Vec::new(), + functions: Vec::new(), + span: Span::default(), + } + } +} + impl fmt::Display for Stub { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "stub {} {{", self.stub_id)?; diff --git a/compiler/compiler/Cargo.toml b/compiler/compiler/Cargo.toml index 24214a1d80..0fad5624c9 100644 --- a/compiler/compiler/Cargo.toml +++ b/compiler/compiler/Cargo.toml @@ -41,6 +41,10 @@ version = "=1.10.0" [dependencies.sha2] version = "0.10" +[dependencies.indexmap] +version = "1.9" +features = [] + [dev-dependencies.leo-test-framework] path = "../../tests/test-framework" @@ -72,4 +76,4 @@ version = "3.8" [features] default = [ ] -ci_skip = [ "leo-ast/ci_skip" ] +ci_skip = [ "leo-ast/ci_skip" ] \ No newline at end of file diff --git a/compiler/compiler/src/compiler.rs b/compiler/compiler/src/compiler.rs index 02e9d287a0..20f18b37e8 100644 --- a/compiler/compiler/src/compiler.rs +++ b/compiler/compiler/src/compiler.rs @@ -18,16 +18,17 @@ //! //! The [`Compiler`] type compiles Leo programs into R1CS circuits. pub use leo_ast::{Ast, InputAst}; -use leo_ast::{NodeBuilder, Program}; +use leo_ast::{NodeBuilder, Program, Stub}; use leo_errors::{emitter::Handler, CompilerError, Result}; pub use leo_passes::SymbolTable; use leo_passes::*; -use leo_span::{source_map::FileName, symbol::with_session_globals}; +use leo_span::{source_map::FileName, symbol::with_session_globals, Symbol}; use sha2::{Digest, Sha256}; use std::{fs, path::PathBuf}; use crate::CompilerOptions; +use indexmap::{IndexMap, IndexSet}; /// The primary entry point of the Leo compiler. #[derive(Clone)] @@ -54,6 +55,8 @@ pub struct Compiler<'a> { assigner: Assigner, /// The type table. type_table: TypeTable, + /// The stubs for imported programs. Produced by `Retriever` module. + import_stubs: IndexMap, } impl<'a> Compiler<'a> { @@ -65,6 +68,7 @@ impl<'a> Compiler<'a> { main_file_path: PathBuf, output_directory: PathBuf, compiler_options: Option, + import_stubs: IndexMap, ) -> Self { let node_builder = NodeBuilder::default(); let assigner = Assigner::default(); @@ -80,6 +84,7 @@ impl<'a> Compiler<'a> { compiler_options: compiler_options.unwrap_or_default(), node_builder, assigner, + import_stubs, type_table, } } @@ -324,6 +329,8 @@ impl<'a> Compiler<'a> { pub fn compile(&mut self) -> Result<(SymbolTable, String)> { // Parse the program. self.parse_program()?; + // Copy the dependencies specified in `program.json` into the AST. + self.add_import_stubs()?; // Run the intermediate compiler stages. let (symbol_table, struct_graph, call_graph) = self.compiler_stages()?; // Run code generation. @@ -361,4 +368,41 @@ impl<'a> Compiler<'a> { } Ok(()) } + + /// Merges the dependencies defined in `program.json` with the dependencies imported in `.leo` file + fn add_import_stubs(&mut self) -> Result<()> { + // Create a list of both the explicit dependencies specified in the `.leo` file, as well as the implicit ones derived from those dependencies. + let (mut unexplored, mut explored): (IndexSet, IndexSet) = + (self.ast.ast.imports.keys().cloned().collect(), IndexSet::new()); + while !unexplored.is_empty() { + let mut current_dependencies: IndexSet = IndexSet::new(); + for program_name in unexplored.iter() { + if let Some(stub) = self.import_stubs.get(program_name) { + // Add the program to the explored set + explored.insert(*program_name); + for dependency in stub.imports.iter() { + // If dependency is already explored then don't need to re-explore it + if explored.insert(dependency.name.name) { + current_dependencies.insert(dependency.name.name); + } + } + } else { + return Err(CompilerError::imported_program_not_found( + self.program_name.clone(), + *program_name, + self.ast.ast.imports[program_name].1, + ) + .into()); + } + } + + // Create next batch to explore + unexplored = current_dependencies; + } + + // Combine the dependencies from `program.json` and `.leo` file while preserving the post-order + self.ast.ast.stubs = + self.import_stubs.clone().into_iter().filter(|(program_name, _)| explored.contains(program_name)).collect(); + Ok(()) + } } diff --git a/compiler/compiler/tests/utilities/mod.rs b/compiler/compiler/tests/utilities/mod.rs index 1d017768a8..c7896d5152 100644 --- a/compiler/compiler/tests/utilities/mod.rs +++ b/compiler/compiler/tests/utilities/mod.rs @@ -29,6 +29,7 @@ use leo_test_framework::{test::TestConfig, Test}; use snarkvm::prelude::*; +use indexmap::IndexMap; use leo_ast::ProgramVisitor; use snarkvm::{file::Manifest, package::Package}; use std::{ @@ -142,7 +143,15 @@ pub fn new_compiler( let output_dir = PathBuf::from("/tmp/output/"); fs::create_dir_all(output_dir.clone()).unwrap(); - Compiler::new(String::from("test"), String::from("aleo"), handler, main_file_path, output_dir, compiler_options) + Compiler::new( + String::from("test"), + String::from("aleo"), + handler, + main_file_path, + output_dir, + compiler_options, + IndexMap::new(), + ) } pub fn parse_program<'a>( diff --git a/compiler/parser/src/parser/file.rs b/compiler/parser/src/parser/file.rs index 45fb6ee63f..f8a0dce778 100644 --- a/compiler/parser/src/parser/file.rs +++ b/compiler/parser/src/parser/file.rs @@ -412,7 +412,7 @@ impl ParserContext<'_> { }; // Parse the function body. Allow empty blocks. `fn foo(a:u8);` - let (has_empty_block, block) = match &self.token.token { + let (_has_empty_block, block) = match &self.token.token { Token::LeftCurly => (false, self.parse_block()?), Token::Semicolon => { let semicolon = self.expect(&Token::Semicolon)?; @@ -425,11 +425,6 @@ impl ParserContext<'_> { let finalize = match self.eat(&Token::Finalize) { false => None, true => { - // Make sure has function body. Don't want `fn foo(); finalize foo { ... }` to be valid parsing. - if has_empty_block { - return Err(ParserError::empty_function_cannot_have_finalize(self.token.span).into()); - } - // Get starting span. let start = self.prev_token.span; diff --git a/compiler/passes/src/code_generation/visit_program.rs b/compiler/passes/src/code_generation/visit_program.rs index 925621a1e1..c3aced3ed2 100644 --- a/compiler/passes/src/code_generation/visit_program.rs +++ b/compiler/passes/src/code_generation/visit_program.rs @@ -16,7 +16,7 @@ use crate::CodeGenerator; -use leo_ast::{functions, Function, Mapping, Mode, Program, ProgramScope, Struct, Stub, Type, Variant}; +use leo_ast::{functions, Function, Mapping, Mode, Program, ProgramScope, Struct, Type, Variant}; use indexmap::IndexMap; use itertools::Itertools; @@ -28,22 +28,10 @@ impl<'a> CodeGenerator<'a> { // Accumulate instructions into a program string. let mut program_string = String::new(); - if !input.imports.is_empty() { - // Visit each import statement and produce a Aleo import instruction. - program_string.push_str( - &input - .imports - .iter() - .map(|(identifier, (imported_program, _))| self.visit_import(identifier, imported_program)) - .join("\n"), - ); - - // Newline separator. - program_string.push('\n'); - } - - // Import stub programs - program_string.push_str(&input.stubs.values().map(|stub| self.visit_stub(stub)).join("\n")); + // Print out the dependencies of the program. Already arranged in post order by Retriever module. + input.stubs.iter().for_each(|(program_name, _)| { + program_string.push_str(&format!("import {}.aleo;\n", program_name)); + }); // Retrieve the program scope. // Note that type checking guarantees that there is exactly one program scope. @@ -112,15 +100,6 @@ impl<'a> CodeGenerator<'a> { program_string } - fn visit_stub(&mut self, input: &'a Stub) -> String { - format!("import {}.aleo;", input.stub_id.name) - } - - fn visit_import(&mut self, import_name: &'a Symbol, _import_program: &'a Program) -> String { - // Generate string for import statement. - format!("import {import_name}.aleo;") - } - fn visit_struct_or_record(&mut self, struct_: &'a Struct) -> String { if struct_.is_record { self.visit_record(struct_) } else { self.visit_struct(struct_) } } diff --git a/compiler/passes/src/type_checking/check_program.rs b/compiler/passes/src/type_checking/check_program.rs index 78fdb8d643..b83f2be2f0 100644 --- a/compiler/passes/src/type_checking/check_program.rs +++ b/compiler/passes/src/type_checking/check_program.rs @@ -28,25 +28,7 @@ use std::collections::HashSet; impl<'a> ProgramVisitor<'a> for TypeChecker<'a> { fn visit_program(&mut self, input: &'a Program) { - match self.is_imported { - // If the program is imported, then it is not allowed to import any other programs. - true => { - input.imports.values().for_each(|(_, span)| { - self.emit_err(TypeCheckerError::imported_program_cannot_import_program(*span)) - }); - } - // Otherwise, typecheck the imported programs. - false => { - // Set `self.is_imported`. - let previous_is_imported = core::mem::replace(&mut self.is_imported, true); - - // Typecheck the imported programs. - input.imports.values().for_each(|import| self.visit_import(&import.0)); - - // Set `self.is_imported` to its previous state. - self.is_imported = previous_is_imported; - } - } + // Calculate the intersection of the imports specified in the `.leo` file and the dependencies derived from the `program.json` file. // Typecheck the program's stubs. input.stubs.values().for_each(|stub| self.visit_stub(stub)); @@ -102,11 +84,6 @@ impl<'a> ProgramVisitor<'a> for TypeChecker<'a> { } fn visit_struct_stub(&mut self, input: &'a Struct) { - // Allow records only. - if !input.is_record { - self.emit_err(TypeCheckerError::stubs_cannot_have_non_record_structs(input.span)); - } - self.visit_struct(input); } diff --git a/compiler/passes/src/type_checking/checker.rs b/compiler/passes/src/type_checking/checker.rs index 0b480d70a6..98bf547dea 100644 --- a/compiler/passes/src/type_checking/checker.rs +++ b/compiler/passes/src/type_checking/checker.rs @@ -60,8 +60,6 @@ pub struct TypeChecker<'a> { /// Whether or not we are currently traversing a finalize block. pub(crate) is_finalize: bool, - /// Whether or not we are currently traversing an imported program. - pub(crate) is_imported: bool, /// Whether or not we are currently traversing a return statement. pub(crate) is_return: bool, } @@ -129,7 +127,6 @@ impl<'a> TypeChecker<'a> { has_return: false, has_finalize: false, is_finalize: false, - is_imported: false, is_return: false, } } diff --git a/errors/Cargo.toml b/errors/Cargo.toml index 103627f233..a8614f9423 100644 --- a/errors/Cargo.toml +++ b/errors/Cargo.toml @@ -43,3 +43,5 @@ features = [ "derive", "rc" ] [dependencies.thiserror] version = "1.0.49" +[dependencies] +reqwest = "0.11.22" \ No newline at end of file diff --git a/errors/README.md b/errors/README.md index a3bfcfaf63..392b6a83f1 100644 --- a/errors/README.md +++ b/errors/README.md @@ -73,3 +73,7 @@ When SnarkVM implements better error codes and messages, we can bubble them up. ### State The errors for the `leo-state` crate. Its error codes will range from 1_000-1_999 and be prefixed with the characters `STA`. + +### Utils + +The errors related to dependency retrieval in the `utils` crate. Its error codes will range from 10_000-10_999 and be prefixed with the characters `DEP`. diff --git a/errors/src/errors/compiler/compiler_errors.rs b/errors/src/errors/compiler/compiler_errors.rs index 7ff0d4a958..dfb2300ee7 100644 --- a/errors/src/errors/compiler/compiler_errors.rs +++ b/errors/src/errors/compiler/compiler_errors.rs @@ -70,4 +70,11 @@ create_messages!( msg: format!("The program scope name `{program_scope_name}` must match `{file_name}`."), help: None, } + + @formatted + imported_program_not_found { + args: (main_program_name: impl Display, dependency_name: impl Display), + msg: format!("`{main_program_name}` imports `{dependency_name}.aleo`, but `{dependency_name}.aleo` is not found in `program.json`."), + help: Some(format!("Run `leo add {dependency_name}.aleo` to add `{dependency_name}.aleo` to `program.json`. Consult `leo add --help` for more information.")), + } ); diff --git a/errors/src/errors/parser/parser_errors.rs b/errors/src/errors/parser/parser_errors.rs index f98fd70c3d..f080fd288b 100644 --- a/errors/src/errors/parser/parser_errors.rs +++ b/errors/src/errors/parser/parser_errors.rs @@ -215,10 +215,10 @@ create_messages!( } @formatted - leo_imports_only { + leo_and_aleo_imports_only { args: (), - msg: "Invalid import call to non-leo file.", - help: Some("Only imports of Leo `.leo` files are currently supported.".to_string()), + msg: "Invalid import call to non-leo non-aleo file.", + help: Some("Only imports of Leo `.leo` and Aleo `.aleo` files are currently supported.".to_string()), } @formatted @@ -292,14 +292,6 @@ create_messages!( help: None, } - /// Enforce that empty functions cannot have finalize functions attached to them - @formatted - empty_function_cannot_have_finalize { - args: (), - msg: format!("Empty functions cannot have finalize functions attached to them."), - help: None, - } - @formatted array_must_have_at_least_one_element { args: (kind: impl Display), diff --git a/errors/src/errors/utils/util_errors.rs b/errors/src/errors/utils/util_errors.rs index 2de440a53b..2f0c226755 100644 --- a/errors/src/errors/utils/util_errors.rs +++ b/errors/src/errors/utils/util_errors.rs @@ -74,4 +74,39 @@ create_messages!( msg: format!("Duplicate dependency found: {dependency}"), help: None, } + + @backtraced + reqwest_error { + args: (error: impl Display), + msg: format!("{}", error), + help: None, + } + + @backtraced + failed_to_open_file { + args: (error: impl Display), + msg: format!("Failed to open file {error}"), + help: None, + } + + @backtraced + failed_to_read_file { + args: (error: impl Display), + msg: format!("Failed to read file {error}"), + help: None, + } + + @backtraced + failed_to_deserialize_file { + args: (error: impl Display), + msg: format!("Failed to deserialize file {error}"), + help: None, + } + + @formatted + failed_to_retrieve_dependencies { + args: (error: impl Display), + msg: format!("Failed to retrieve dependencies. {error}"), + help: None, + } ); diff --git a/leo/cli/cli.rs b/leo/cli/cli.rs index c87c2fe1ed..30104068be 100644 --- a/leo/cli/cli.rs +++ b/leo/cli/cli.rs @@ -133,3 +133,30 @@ pub fn run_with_args(cli: CLI) -> Result<()> { Commands::Update { command } => command.try_execute(context), } } + +#[test] +pub fn build_nested_test() -> Result<()> { + use leo_span::symbol::create_session_if_not_set_then; + use std::env; + const BUILD_DIRECTORY: &str = "utils/tmp/nested"; + + let cli = CLI { + debug: false, + quiet: false, + command: Commands::Build { command: Build { options: Default::default() } }, + path: Some(PathBuf::from(BUILD_DIRECTORY)), + }; + + // Set $HOME to tmp directory so that tests do not modify users real home directory + let original_home = env::var("HOME").unwrap(); + env::set_var("HOME", "utils/tmp"); + + create_session_if_not_set_then(|_| { + run_with_args(cli).expect("Failed to run build command"); + }); + + // Reset $HOME + env::set_var("HOME", original_home); + + Ok(()) +} diff --git a/leo/cli/commands/build.rs b/leo/cli/commands/build.rs index 726d89c3ad..61249b728a 100644 --- a/leo/cli/commands/build.rs +++ b/leo/cli/commands/build.rs @@ -16,7 +16,7 @@ use super::*; -use leo_ast::{NodeBuilder, Struct}; +use leo_ast::{NodeBuilder, Struct, Stub}; use leo_compiler::{Compiler, CompilerOptions, InputAst, OutputOptions}; use leo_package::{build::BuildDirectory, inputs::InputFile, outputs::OutputsDirectory, source::SourceDirectory}; use leo_span::{symbol::with_session_globals, Symbol}; @@ -27,11 +27,14 @@ use snarkvm::{ }; use indexmap::IndexMap; +use leo_errors::UtilError; use std::{ io::Write, path::{Path, PathBuf}, }; +use retriever::Retriever; + impl From for CompilerOptions { fn from(options: BuildOptions) -> Self { let mut out_options = Self { @@ -125,7 +128,6 @@ impl Command for Build { &build_directory, &handler, self.options.clone(), - false, )?); } @@ -175,13 +177,12 @@ impl Command for Build { #[allow(clippy::too_many_arguments)] fn compile_leo_file( file_path: PathBuf, - _package_path: &Path, + package_path: &Path, program_id: &ProgramID, outputs: &Path, build: &Path, handler: &Handler, options: BuildOptions, - is_import: bool, ) -> Result> { // Construct the Leo file name with extension `foo.leo`. let file_name = @@ -189,17 +190,19 @@ fn compile_leo_file( // If the program is an import, construct program name from file_path // Otherwise, use the program_id found in `package.json`. - let program_name = match is_import { - false => program_id.name().to_string(), - true => file_name.strip_suffix(".leo").ok_or_else(PackageError::failed_to_get_file_name)?.to_string(), - }; + let program_name = program_id.name().to_string(); // Create the path to the Aleo file. let mut aleo_file_path = build.to_path_buf(); - aleo_file_path.push(match is_import { - true => format!("{program_name}.{}", program_id.network()), - false => format!("main.{}", program_id.network()), - }); + aleo_file_path.push(format!("main.{}", program_id.network())); + + // Retrieve dependencies from `program.json` + let mut retriever = Retriever::new(package_path) + .map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?; + + // Only retrieve dependencies for main leo program + let stubs: IndexMap = + retriever.retrieve().map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?; // Create a new instance of the Leo compiler. let mut compiler = Compiler::new( @@ -209,6 +212,7 @@ fn compile_leo_file( file_path.clone(), outputs.to_path_buf(), Some(options.into()), + stubs, ); // Compile the Leo program into Aleo instructions. diff --git a/leo/package/Cargo.toml b/leo/package/Cargo.toml index 59c97f519d..538728b063 100644 --- a/leo/package/Cargo.toml +++ b/leo/package/Cargo.toml @@ -42,6 +42,10 @@ version = "0.8" [dependencies.tracing] version = "0.1" +[dependencies.retriever] +path = "../../utils/retriever" +version = "1.10.0" + [dev-dependencies.lazy_static] version = "1.3.0" diff --git a/tests/test-framework/Cargo.toml b/tests/test-framework/Cargo.toml index 66ef2441cf..51a67cd713 100644 --- a/tests/test-framework/Cargo.toml +++ b/tests/test-framework/Cargo.toml @@ -29,6 +29,9 @@ version = "=1.10.0" [dependencies.backtrace] version = "0.3.68" +[dependencies.indexmap] +version = "1.9" + [dependencies.clap] version = "4.4" features = [ "derive" ] diff --git a/tests/test-framework/benches/leo_compiler.rs b/tests/test-framework/benches/leo_compiler.rs index 4ee7ed60a6..995075f507 100644 --- a/tests/test-framework/benches/leo_compiler.rs +++ b/tests/test-framework/benches/leo_compiler.rs @@ -16,6 +16,7 @@ //! This file contains tools for benchmarking the Leo compiler and its stages. +use indexmap::IndexMap; use leo_compiler::{BuildOptions, Compiler, CompilerOptions, OutputOptions}; use leo_errors::emitter::{Emitter, Handler}; use leo_span::{source_map::FileName, symbol::SESSION_GLOBALS}; @@ -105,6 +106,7 @@ fn new_compiler(handler: &Handler) -> Compiler<'_> { dce_ast: false, }, }), + IndexMap::new(), ) } diff --git a/tests/tests/compiler/function/test.leo b/tests/tests/compiler/function/test.leo new file mode 100644 index 0000000000..a4fdeb0a2d --- /dev/null +++ b/tests/tests/compiler/function/test.leo @@ -0,0 +1,34 @@ +/* +namespace: Compile +expectation: Pass +*/ + +program test.aleo { + struct message { + sender: u8, + object: u8, + } + mapping balances: u8 => u8; + + transition a(arg1: message) -> message { + return message { + sender: 1u8, + object: 1u8, + }; + } + + function b(arg1: message) -> message { + return message { + sender: 1u8, + object: 1u8, + }; + } + + function c(arg1: balances) -> balances { + return arg1; + } + + transition d(arg1: balances) -> balances { + return arg1; + } +} diff --git a/utils/disassembler/src/tests/credits.aleo b/utils/disassembler/src/tests/credits.aleo new file mode 100644 index 0000000000..43596f55e1 --- /dev/null +++ b/utils/disassembler/src/tests/credits.aleo @@ -0,0 +1,845 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the snarkVM library. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at: +// http://www.apache.org/licenses/LICENSE-2.0 + +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/**********************************************************************************************************************/ + +program credits.aleo; + +/**********************************************************************************************************************/ + +/// The `committee` mapping contains the active validator set and their corresponding stake. +mapping committee: + // The key represents the address of the validator. + key as address.public; + // The value represents the committee state of the validator. + value as committee_state.public; + +// The `committee_state` struct tracks the total stake of the validator, and whether they are open to stakers. +struct committee_state: + // The amount of microcredits bonded to the validator, by the validator and its delegators. + microcredits as u64; + // The boolean flag indicating if the validator is open to stakers. + is_open as boolean; + +/**********************************************************************************************************************/ + +// The `bonded` mapping represents the amount of microcredits that are currently bonded. +mapping bonded: + // The key represents the address of the staker, which includes the validators and their delegators. + key as address.public; + // The value represents the bond state. + value as bond_state.public; + +// The `bond_state` struct tracks the amount of microcredits that are currently bonded to the specified validator. +struct bond_state: + // The address of the validator. + validator as address; + // The amount of microcredits that are currently bonded to the specified validator. + microcredits as u64; + +/**********************************************************************************************************************/ + +// The `unbonding` mapping contains a set of stakers with their unbonding microcredits and unlock height. +mapping unbonding: + // The key represents the address of the staker, which includes the validators and their delegators. + key as address.public; + // The value represents the unbond state. + value as unbond_state.public; + +// The `unbond_state` struct tracks the microcredits that are currently unbonding, along with the unlock height. +struct unbond_state: + // The amount of microcredits that are currently unbonding. + microcredits as u64; + // The block height at which the unbonding will be complete, and can be claimed. + height as u32; + +/**********************************************************************************************************************/ + +// The `account` mapping is used to store credits publicly. +mapping account: + // The key represents the address of the owner. + key as address.public; + // The value represents the amount of public microcredits that belong to the specified owner. + value as u64.public; + +/**********************************************************************************************************************/ + +// The `credits` record is used to store credits privately. +record credits: + // The address of the owner. + owner as address.private; + // The amount of private microcredits that belong to the specified owner. + microcredits as u64.private; + +/**********************************************************************************************************************/ + +// This function allows any staker to bond their microcredits to a validator. +// The corresponding functions for 'bond_public' are 'unbond_public' and 'claim_unbond_public'. +function bond_public: + // Input the validator's address. + input r0 as address.public; + // Input the amount of microcredits to bond. + input r1 as u64.public; + + // Determine if the amount is at least one credit. + gte r1 1_000_000u64 into r2; + // Enforce the amount is at least one credit. + assert.eq r2 true; + + // Bond the specified amount of microcredits to the specified validator. + async bond_public self.caller r0 r1 into r3; + // Output the finalize future. + output r3 as credits.aleo/bond_public.future; + +finalize bond_public: + // Input the staker's address. + input r0 as address.public; + // Input the validator's address. + input r1 as address.public; + // Input the amount of microcredits to bond. + input r2 as u64.public; + + // Determine whether the caller is a validator. + is.eq r0 r1 into r3; + // If the caller is a validator, jump to the `bond_validator` logic. + branch.eq r3 true to bond_validator; + // If the caller is not a validator, jump to the `bond_delegator` logic. + branch.eq r3 false to bond_delegator; + + /******* Bond Validator *******/ + + // Starts the `bond_validator` logic. + position bond_validator; + + /* Committee */ + + // Construct the initial committee state. + // Note: We set the initial 'is_open' state to 'true'. + cast 0u64 true into r4 as committee_state; + // Retrieve the committee state of the specified validator. + get.or_use committee[r0] r4 into r5; + // Ensure that the validator is open to stakers. + assert.eq r5.is_open true; + + // Increment the stake for the specified validator. + add r5.microcredits r2 into r6; + // Construct the updated committee state. + cast r6 r5.is_open into r7 as committee_state; + + /* Bonded */ + + // Construct the initial bond state. + cast r1 0u64 into r8 as bond_state; + // Get the bond state for the caller, or default to the initial bond state. + get.or_use bonded[r0] r8 into r9; + // Enforce the validator matches in the bond state. + assert.eq r9.validator r1; + + // Increment the microcredits in the bond state. + add r9.microcredits r2 into r10; + // Determine if the amount is at least one million credits. + gte r10 1_000_000_000_000u64 into r11; + // Enforce the amount is at least one million credits. + assert.eq r11 true; + + // Construct the updated bond state. + cast r1 r10 into r12 as bond_state; + + /* Account */ + + // Get the balance of the caller. + // If the account does not exist, this finalize scope will fail. + get account[r0] into r13; + // Decrement the balance of the caller. + sub r13 r2 into r14; + + /* Writes */ + + // Update the committee state of the specified validator. + set r7 into committee[r0]; + // Update the bond state for the caller. + set r12 into bonded[r0]; + // Update the balance of the caller. + set r14 into account[r0]; + + // Ends the `bond_validator` logic. + branch.eq true true to end; + + /******* Bond Delegator *******/ + + // Starts the `bond_delegator` logic. + position bond_delegator; + + /* Committee */ + + // Check if the caller is a validator. + contains committee[r0] into r15; + // Enforce the caller is *not* a validator. + assert.eq r15 false; + + // Get the stake for the specified validator. + // If the validator does not exist, this finalize scope will fail. + get committee[r1] into r16; + // Ensure that the validator is open to stakers. + assert.eq r16.is_open true; + + // Increment the stake for the specified validator. + add r16.microcredits r2 into r17; + // Construct the updated committee state. + cast r17 r16.is_open into r18 as committee_state; + + /* Bonded */ + + // Construct the initial bond state. + cast r1 0u64 into r19 as bond_state; + // Get the bond state for the caller, or default to the initial bond state. + get.or_use bonded[r0] r19 into r20; + // Enforce the validator matches in the bond state. + assert.eq r20.validator r1; + + // Increment the microcredits in the bond state. + add r20.microcredits r2 into r21; + // Determine if the amount is at least 10 credits. + gte r21 10_000_000u64 into r22; + // Enforce the amount is at least 10 credits. + assert.eq r22 true; + + // Construct the updated bond state. + cast r1 r21 into r23 as bond_state; + + /* Account */ + + // Get the balance of the caller. + // If the account does not exist, this finalize scope will fail. + get account[r0] into r24; + // Decrement the balance of the caller. + sub r24 r2 into r25; + + /* Writes */ + + // Update the committee state for the specified validator. + set r18 into committee[r1]; + // Update the bond state for the caller. + set r23 into bonded[r0]; + // Update the balance of the caller. + set r25 into account[r0]; + + // The terminus. + position end; + +/**********************************************************************************************************************/ + +// This function allows any staker to unbond their microcredits from a validator. +// The corresponding functions for 'unbond_public' is 'claim_unbond_public'. +function unbond_public: + // Input the amount of microcredits to unbond. + input r0 as u64.public; + + // Unbond the specified amount of microcredits to the caller. + async unbond_public self.caller r0 into r1; + // Output the finalize future. + output r1 as credits.aleo/unbond_public.future; + +finalize unbond_public: + // Input the staker's address. + input r0 as address.public; + // Input the amount of microcredits to unbond. + input r1 as u64.public; + + // Construct the initial unbond state. + cast 0u64 0u32 into r2 as unbond_state; + // Get the unbond state for the caller, or default to the initial unbond state. + get.or_use unbonding[r0] r2 into r3; + + // Compute the height at which the unbonding will be complete, starting from the current block. + // Note: Calling unbond across multiple blocks before the unbonding is complete will reset the height each time. + add block.height 360u32 into r4; + + // Determine if the caller is a validator or delegator. + contains committee[r0] into r5; + + // If the caller is a validator, jump to the `unbond_validator` logic. + branch.eq r5 true to unbond_validator; + // If the caller is not a validator, jump to the `unbond_delegator` logic. + branch.eq r5 false to unbond_delegator; + + /******* Unbond Validator *******/ + + // Starts the `unbond_validator` logic. + position unbond_validator; + + /* Committee */ + + // Get the committee state for the specified validator. + get committee[r0] into r6; + // Decrement the stake for the specified validator. + sub r6.microcredits r1 into r7; + + /* Bonded */ + + // Get the bond state for the validator, or fail if it does not exist. + get bonded[r0] into r8; + // Ensure that the validator matches in the bond state. + assert.eq r8.validator r0; + // Decrement the microcredits in the bond state. + sub r8.microcredits r1 into r9; + + // Determine if the remaining bond is at least one million credits. + gte r9 1_000_000_000_000u64 into r10; + + // If the remaining balance is at least 1 million credits, jump to the `decrement_validator` logic. + branch.eq r10 true to decrement_validator; + // If the remaining balance is less than 1 million credits, jump to the `remove_validator` logic. + branch.eq r10 false to remove_validator; + + /*** Decrement Validator ***/ + + // Starts the `decrement_validator` logic. + position decrement_validator; + + /* Committee */ + + // Construct the updated committee state. + cast r7 r6.is_open into r11 as committee_state; + // Update the committee state for the validator. + set r11 into committee[r0]; + + /* Bonded */ + + // Construct the updated bond state. + cast r0 r9 into r12 as bond_state; + // Update the bond state for the validator. + set r12 into bonded[r0]; + + /* Unbonding */ + + // Increment the microcredits in the unbond state. + add r3.microcredits r1 into r13; + + // Construct the updated unbond state. + cast r13 r4 into r14 as unbond_state; + // Update the unbond state for the caller. + set r14 into unbonding[r0]; + + // Ends the `decrement_validator` logic. + branch.eq true true to end; + + /*** Remove Validator ***/ + + // Starts the `remove_validator` logic. + position remove_validator; + + // Ensure that the validator has no delegators. + assert.eq r6.microcredits r8.microcredits; + + /* Committee */ + + // Remove the validator from the committee. + remove committee[r0]; + + /* Bonded */ + + // Remove the bond state for the validator. + remove bonded[r0]; + + /* Unbonding */ + + // Increment the microcredits in the unbond state. + add r3.microcredits r8.microcredits into r15; + + // Construct the updated unbond state. + cast r15 r4 into r16 as unbond_state; + // Update the unbond state for the caller. + set r16 into unbonding[r0]; + + // Ends the `remove_validator` logic. + branch.eq true true to end; + + /******* Unbond Delegator *******/ + + // Starts the `unbond_delegator` logic. + position unbond_delegator; + + // Get the bond state for the caller, or fail if it does not exist. + get bonded[r0] into r17; + // Decrement the microcredits in the bond state. + sub r17.microcredits r1 into r18; + + // Determine if the remaining bond is at least 10 credits. + gte r18 10_000_000u64 into r19; + + // If the remaining balance is at least 10 credits, jump to the `decrement_delegator` logic. + branch.eq r19 true to decrement_delegator; + // If the remaining balance is less than 10 credits, jump to the `remove_delegator` logic. + branch.eq r19 false to remove_delegator; + + /*** Decrement Delegator ***/ + + // Starts the `decrement_delegator` logic. + position decrement_delegator; + + /* Committee */ + + // Get the stake for the specified validator. + // If the validator does not exist, this finalize scope will fail. + get committee[r17.validator] into r20; + // Decrement the stake for the specified validator. + sub r20.microcredits r1 into r21; + // Construct the updated committee state. + cast r21 r20.is_open into r22 as committee_state; + // Update the stake for the specified validator. + set r22 into committee[r17.validator]; + + /* Bonded */ + + // Construct the updated bond state. + cast r17.validator r18 into r23 as bond_state; + // Update the bond state for the caller. + set r23 into bonded[r0]; + + /* Unbonding */ + + // Increment the microcredits in the unbond state. + add r3.microcredits r1 into r24; + + // Construct the updated unbond state. + cast r24 r4 into r25 as unbond_state; + // Update the unbond state for the caller. + set r25 into unbonding[r0]; + + // Ends the `decrement_delegator` logic. + branch.eq true true to end; + + /*** Remove Delegator ***/ + + // Starts the `remove_delegator` logic. + position remove_delegator; + + /* Committee */ + + // Get the stake for the specified validator. + // If the validator does not exist, this finalize scope will fail. + get committee[r17.validator] into r26; + // Decrement the stake for the specified validator. + sub r26.microcredits r17.microcredits into r27; + // Construct the updated committee state. + cast r27 r26.is_open into r28 as committee_state; + // Update the stake for the specified validator. + set r28 into committee[r17.validator]; + + /* Bonded */ + + // Remove the caller from the bonded mapping. + remove bonded[r0]; + + /* Unbonding */ + + // Increment the microcredits in the unbond state. + add r3.microcredits r17.microcredits into r29; + + // Construct the updated unbond state. + cast r29 r4 into r30 as unbond_state; + // Update the unbond state for the caller. + set r30 into unbonding[r0]; + + // The terminus. + position end; + +/**********************************************************************************************************************/ + +// This function allows a validator to unbond any delegator that is bonded to them. +function unbond_delegator_as_validator: + // Input the delegator's address. + input r0 as address.public; + + // Unbond the delegator as the validator. + async unbond_delegator_as_validator self.caller r0 into r1; + // Output the finalize future. + output r1 as credits.aleo/unbond_delegator_as_validator.future; + +finalize unbond_delegator_as_validator: + // Input the validator's address. + input r0 as address.public; + // Input the delegator's address. + input r1 as address.public; + + /* Start Committee */ + + // Get the committee state for the specified validator. + // If the validator does not exist, this finalize scope will fail. + get committee[r0] into r2; + // Enforce that the validator is closed to stakers. + assert.eq r2.is_open false; + + // Check if the delegator is a validator. + contains committee[r1] into r3; + // Enforce the delegator is *not* a validator. + assert.eq r3 false; + + /* End Committee */ + + /* Start Bonded */ + + // Get the bond state for the delegator, or fail if it does not exist. + get bonded[r1] into r4; + // Enforce that the delegator is bonded to the validator. + assert.eq r4.validator r0; + + /* End Bonded */ + + /* Start Committee */ + + // Decrement the stake for the specified validator. + sub r2.microcredits r4.microcredits into r5; + // Construct the updated committee state. + cast r5 r2.is_open into r6 as committee_state; + + /* End Committee */ + + /* Start Unbond */ + + // Construct the initial unbond state. + cast 0u64 0u32 into r7 as unbond_state; + // Get the unbond state for the delegator, or default to the initial unbond state. + get.or_use unbonding[r1] r7 into r8; + + // Increment the microcredits in the unbond state. + add r8.microcredits r4.microcredits into r9; + // Compute the height at which the unbonding will be complete, starting from the current block. + // Note: Calling unbond across multiple blocks before the unbonding is complete will reset the height each time. + add block.height 360u32 into r10; + + // Construct the updated unbond state. + cast r9 r10 into r11 as unbond_state; + + /* End Unbond */ + + /* Start Writes */ + + // Update the committee state for the specified validator. + set r6 into committee[r0]; + // Remove the bond state for the delegator. + remove bonded[r1]; + // Update the unbond state for the delegator. + set r11 into unbonding[r1]; + + /* End Writes */ + +/**********************************************************************************************************************/ + +// This function allows any staker to claim their microcredits after the unbonding period. +function claim_unbond_public: + // Claim the unbonded microcredits. + async claim_unbond_public self.caller into r0; + // Output the finalize future. + output r0 as credits.aleo/claim_unbond_public.future; + +finalize claim_unbond_public: + // Input the staker's address. + input r0 as address.public; + + // Get the unbond state for the caller, or fail if it does not exist. + get unbonding[r0] into r1; + // Determine if unbonding is complete. + gte block.height r1.height into r2; + // Enforce the unbonding is complete. + assert.eq r2 true; + + // Add the unbonded amount to the stakers's public balance. + // Increments `account[r0]` by `r1`. + // If `account[r0]` does not exist, 0u64 is used. + // If `account[r0] + r2` overflows, `claim_unbond_public` is reverted. + get.or_use account[r0] 0u64 into r3; + add r1.microcredits r3 into r4; + set r4 into account[r0]; + + // Remove the unbond state for the caller. + remove unbonding[r0]; + +/**********************************************************************************************************************/ + +// This function allows a validator to set their state to be either opened or closed to stakers. +// When the validator is open to stakers, any staker (including the validator) can bond or unbond from the validator. +// When the validator is closed to stakers, all stakers can only unbond from the validator. +// +// This function serves two primary purposes: +// 1. Allow a validator to leave the committee, by closing themselves to stakers and then unbonding all of their stakers. +// 2. Allow a validator to maintain their % of stake, by closing themselves to allowing more stakers to bond to them. +function set_validator_state: + // Input the 'is_open' state. + input r0 as boolean.public; + // Set the validator to be either open or closed to stakers. + async set_validator_state self.caller r0 into r1; + // Output the finalize future. + output r1 as credits.aleo/set_validator_state.future; + +finalize set_validator_state: + // Input the validator's address. + input r0 as address.public; + // Input the 'is_open' state. + input r1 as boolean.public; + + // Get the committee state for the specified validator. + // If the validator does not exist, this finalize scope will fail. + get committee[r0] into r2; + + // Construct the updated committee state. + cast r2.microcredits r1 into r3 as committee_state; + // Update the committee state for the specified validator. + set r3 into committee[r0]; + +/**********************************************************************************************************************/ + +// The `transfer_public` function sends the specified amount +// from the sender's `account` to the receiver's `account`. +function transfer_public: + // Input the receiver. + input r0 as address.public; + // Input the amount. + input r1 as u64.public; + // Transfer the credits publicly. + async transfer_public self.caller r0 r1 into r2; + // Output the finalize future. + output r2 as credits.aleo/transfer_public.future; + +finalize transfer_public: + // Input the sender. + input r0 as address.public; + // Input the receiver. + input r1 as address.public; + // Input the amount. + input r2 as u64.public; + // Decrements `account[r0]` by `r2`. + // If `account[r0]` does not exist, 0u64 is used. + // If `account[r0] - r2` underflows, `transfer_public` is reverted. + get.or_use account[r0] 0u64 into r3; + sub r3 r2 into r4; + set r4 into account[r0]; + // Increments `account[r1]` by `r2`. + // If `account[r1]` does not exist, 0u64 is used. + // If `account[r1] + r2` overflows, `transfer_public` is reverted. + get.or_use account[r1] 0u64 into r5; + add r5 r2 into r6; + set r6 into account[r1]; + +/**********************************************************************************************************************/ + +// The `transfer_private` function sends the specified amount +// from the sender's record to the receiver in a record. +function transfer_private: + // Input the sender's record. + input r0 as credits.record; + // Input the receiver. + input r1 as address.private; + // Input the amount. + input r2 as u64.private; + // Checks the given record has a sufficient amount. + // This `sub` operation is safe, and the proof will fail + // if an underflow occurs. The destination register `r3` holds + // the change amount to be returned to the sender. + sub r0.microcredits r2 into r3; + // Construct a record for the specified receiver. + cast r1 r2 into r4 as credits.record; + // Construct a record with the change amount for the sender. + cast r0.owner r3 into r5 as credits.record; + // Output the receiver's record. + output r4 as credits.record; + // Output the sender's change record. + output r5 as credits.record; + +/**********************************************************************************************************************/ + +// The `transfer_private_to_public` function turns a specified amount +// from a record into public credits for the specified receiver. +// +// This function preserves privacy for the sender's record, however +// it publicly reveals the receiver and the amount. +function transfer_private_to_public: + // Input the sender's record. + input r0 as credits.record; + // Input the receiver. + input r1 as address.public; + // Input the amount. + input r2 as u64.public; + // Checks the given record has a sufficient amount. + // This `sub` operation is safe, and the proof will fail + // if an underflow occurs. The destination register `r3` holds + // the change amount for the sender. + sub r0.microcredits r2 into r3; + // Construct a record with the change amount for the sender. + cast r0.owner r3 into r4 as credits.record; + // Increment the amount publicly for the receiver. + async transfer_private_to_public r1 r2 into r5; + // Output the sender's change record. + output r4 as credits.record; + // Output the finalize future. + output r5 as credits.aleo/transfer_private_to_public.future; + +finalize transfer_private_to_public: + // Input the receiver. + input r0 as address.public; + // Input the amount. + input r1 as u64.public; + // Retrieve the balance of the sender. + // If `account[r0]` does not exist, 0u64 is used. + get.or_use account[r0] 0u64 into r2; + // Increments `account[r0]` by `r1`. + // If `r1 + r2` overflows, `transfer_private_to_public` is reverted. + add r1 r2 into r3; + // Updates the balance of the sender. + set r3 into account[r0]; + +/**********************************************************************************************************************/ + +// The `transfer_public_to_private` function turns a specified amount +// from the mapping `account` into a record for the specified receiver. +// +// This function publicly reveals the sender, the receiver, and the specified amount. +// However, subsequent methods using the receiver's record can preserve the receiver's privacy. +function transfer_public_to_private: + // Input the receiver. + input r0 as address.private; + // Input the amount. + input r1 as u64.public; + // Construct a record for the receiver. + cast r0 r1 into r2 as credits.record; + // Decrement the balance of the sender publicly. + async transfer_public_to_private self.caller r1 into r3; + // Output the record of the receiver. + output r2 as credits.record; + // Output the finalize future. + output r3 as credits.aleo/transfer_public_to_private.future; + +finalize transfer_public_to_private: + // Input the sender. + input r0 as address.public; + // Input the amount. + input r1 as u64.public; + // Retrieve the balance of the sender. + // If `account[r0]` does not exist, 0u64 is used. + get.or_use account[r0] 0u64 into r2; + // Decrements `account[r0]` by `r1`. + // If `r2 - r1` underflows, `transfer_public_to_private` is reverted. + sub r2 r1 into r3; + // Updates the balance of the sender. + set r3 into account[r0]; + +/**********************************************************************************************************************/ + +// The `join` function combines two records into one. +function join: + // Input the first record. + input r0 as credits.record; + // Input the second record. + input r1 as credits.record; + // Combines the amount of the first record and the second record. + // This `add` operation is safe, and the proof will fail + // if an overflow occurs. + add r0.microcredits r1.microcredits into r2; + // Construct a record with the combined amount. + cast r0.owner r2 into r3 as credits.record; + // Output the record. + output r3 as credits.record; + +/**********************************************************************************************************************/ + +// The `split` function splits a record into two records. The given input amount will be stored in the first record, +// and the remaining amount will be stored in the second record, with the fee deducted from the remaining amount. +// If the caller executes a transaction that contains only a call to this function, then the transaction does not +// require a fee, unless the caller wishes to provide an additional fee. Transactions that contain multiple transitions +// (that include one or more calls to this function) will require a fee as per standard consensus rules. +function split: + // Input the record. + input r0 as credits.record; + // Input the amount to split. + input r1 as u64.private; + // Checks the given record has a sufficient amount to split. + // This `sub` operation is safe, and the proof will fail + // if an underflow occurs. + sub r0.microcredits r1 into r2; + // Checks the given record has a sufficient fee to remove. + // This `sub` operation is safe, and the proof will fail + // if an underflow occurs. + sub r2 10_000u64 into r3; + // Construct the first record. + cast r0.owner r1 into r4 as credits.record; + // Construct the second record. + cast r0.owner r3 into r5 as credits.record; + // Output the first record. + output r4 as credits.record; + // Output the second record. + output r5 as credits.record; + +/**********************************************************************************************************************/ + +// The `fee_private` function charges the specified amount from the sender's record. +function fee_private: + // Input the sender's record. + input r0 as credits.record; + // Input the amount. + input r1 as u64.public; + // Input the deployment or execution root. + input r2 as field.public; + // Ensure the amount is nonzero. + assert.neq r1 0u64; + // Ensure the deployment or execution root is nonzero. + assert.neq r2 0field; + // Checks the given record has a sufficient amount. + // This `sub` operation is safe, and the proof will fail + // if an underflow occurs. The destination register `r3` holds + // the change amount for the sender. + sub r0.microcredits r1 into r3; + // Construct a record with the change amount for the sender. + cast r0.owner r3 into r4 as credits.record; + // Output the sender's change record. + output r4 as credits.record; + +/**********************************************************************************************************************/ + +// The `fee_public` function charges the specified amount from the sender's account. +function fee_public: + // Input the amount. + input r0 as u64.public; + // Input the deployment or execution root. + input r1 as field.public; + // Ensure the amount is nonzero. + assert.neq r0 0u64; + // Ensure the deployment or execution root is nonzero. + assert.neq r1 0field; + // Decrement the balance of the sender publicly. + async fee_public self.caller r0 into r2; + // Output the finalize future. + output r2 as credits.aleo/fee_public.future; + +finalize fee_public: + // Input the sender's address. + input r0 as address.public; + // Input the amount. + input r1 as u64.public; + // Retrieve the balance of the sender. + // If `account[r0]` does not exist, `fee_public` is reverted. + get account[r0] into r2; + // Decrements `account[r0]` by `r1`. + // If `r2 - r1` underflows, `fee_public` is reverted. + sub r2 r1 into r3; + // Updates the balance of the sender. + set r3 into account[r0]; + +/**********************************************************************************************************************/ + +// Open Questions: +// fn bond +// - if the bond is now 33% or more, close the validator. (determine how hard to impl this) + +/**********************************************************************************************************************/ diff --git a/utils/disassembler/src/tests/large_functions.aleo b/utils/disassembler/src/tests/large_functions.aleo new file mode 100644 index 0000000000..7d80e01b5f --- /dev/null +++ b/utils/disassembler/src/tests/large_functions.aleo @@ -0,0 +1,242 @@ +import credits.aleo; + +program large_functions.aleo; + +function join_3: + input r0 as credits.aleo/credits.record; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + assert.eq r0.owner r1.owner; + assert.eq r1.owner r2.owner; + call credits.aleo/join r0 r1 into r3; + call credits.aleo/join r3 r2 into r4; + output r4 as credits.aleo/credits.record; + +function join_5: + input r0 as credits.aleo/credits.record; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + input r3 as credits.aleo/credits.record; + input r4 as credits.aleo/credits.record; + assert.eq r0.owner r1.owner; + assert.eq r1.owner r2.owner; + assert.eq r2.owner r3.owner; + assert.eq r3.owner r4.owner; + call credits.aleo/join r0 r1 into r5; + call credits.aleo/join r2 r3 into r6; + call credits.aleo/join r5 r6 into r7; + call credits.aleo/join r7 r4 into r8; + output r8 as credits.aleo/credits.record; + +function join6: + input r0 as credits.aleo/credits.record; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + input r3 as credits.aleo/credits.record; + input r4 as credits.aleo/credits.record; + input r5 as credits.aleo/credits.record; + assert.eq r0.owner r1.owner; + assert.eq r1.owner r2.owner; + assert.eq r2.owner r3.owner; + assert.eq r3.owner r4.owner; + assert.eq r4.owner r5.owner; + call credits.aleo/join r0 r1 into r6; + call credits.aleo/join r2 r3 into r7; + call credits.aleo/join r4 r5 into r8; + call credits.aleo/join r6 r7 into r9; + call credits.aleo/join r9 r8 into r10; + output r10 as credits.aleo/credits.record; + +function join7: + input r0 as credits.aleo/credits.record; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + input r3 as credits.aleo/credits.record; + input r4 as credits.aleo/credits.record; + input r5 as credits.aleo/credits.record; + input r6 as credits.aleo/credits.record; + assert.eq r0.owner r1.owner; + assert.eq r1.owner r2.owner; + assert.eq r2.owner r3.owner; + assert.eq r3.owner r4.owner; + assert.eq r4.owner r5.owner; + assert.eq r5.owner r6.owner; + call credits.aleo/join r0 r1 into r7; + call credits.aleo/join r2 r3 into r8; + call credits.aleo/join r4 r5 into r9; + call credits.aleo/join r7 r8 into r10; + call credits.aleo/join r10 r9 into r11; + call credits.aleo/join r11 r6 into r12; + output r12 as credits.aleo/credits.record; + +function join8: + input r0 as credits.aleo/credits.record; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + input r3 as credits.aleo/credits.record; + input r4 as credits.aleo/credits.record; + input r5 as credits.aleo/credits.record; + input r6 as credits.aleo/credits.record; + input r7 as credits.aleo/credits.record; + assert.eq r0.owner r1.owner; + assert.eq r1.owner r2.owner; + assert.eq r2.owner r3.owner; + assert.eq r3.owner r4.owner; + assert.eq r4.owner r5.owner; + assert.eq r5.owner r6.owner; + assert.eq r6.owner r7.owner; + call credits.aleo/join r0 r1 into r8; + call credits.aleo/join r2 r3 into r9; + call credits.aleo/join r4 r5 into r10; + call credits.aleo/join r6 r7 into r11; + call credits.aleo/join r8 r9 into r12; + call credits.aleo/join r11 r10 into r13; + call credits.aleo/join r12 r13 into r14; + output r14 as credits.aleo/credits.record; + +function join9: + input r0 as credits.aleo/credits.record; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + input r3 as credits.aleo/credits.record; + input r4 as credits.aleo/credits.record; + input r5 as credits.aleo/credits.record; + input r6 as credits.aleo/credits.record; + input r7 as credits.aleo/credits.record; + input r8 as credits.aleo/credits.record; + assert.eq r0.owner r1.owner; + assert.eq r1.owner r2.owner; + assert.eq r2.owner r3.owner; + assert.eq r3.owner r4.owner; + assert.eq r4.owner r5.owner; + assert.eq r5.owner r6.owner; + assert.eq r6.owner r7.owner; + assert.eq r7.owner r8.owner; + call credits.aleo/join r0 r1 into r9; + call credits.aleo/join r2 r3 into r10; + call credits.aleo/join r4 r5 into r11; + call credits.aleo/join r6 r7 into r12; + call credits.aleo/join r9 r10 into r13; + call credits.aleo/join r12 r11 into r14; + call credits.aleo/join r13 r14 into r15; + call credits.aleo/join r15 r8 into r16; + output r16 as credits.aleo/credits.record; + +function join10: + input r0 as credits.aleo/credits.record; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + input r3 as credits.aleo/credits.record; + input r4 as credits.aleo/credits.record; + input r5 as credits.aleo/credits.record; + input r6 as credits.aleo/credits.record; + input r7 as credits.aleo/credits.record; + input r8 as credits.aleo/credits.record; + input r9 as credits.aleo/credits.record; + assert.eq r0.owner r1.owner; + assert.eq r1.owner r2.owner; + assert.eq r2.owner r3.owner; + assert.eq r3.owner r4.owner; + assert.eq r4.owner r5.owner; + assert.eq r5.owner r6.owner; + assert.eq r6.owner r7.owner; + assert.eq r7.owner r8.owner; + assert.eq r8.owner r9.owner; + call credits.aleo/join r0 r1 into r10; + call credits.aleo/join r2 r3 into r11; + call credits.aleo/join r4 r5 into r12; + call credits.aleo/join r6 r7 into r13; + call credits.aleo/join r8 r9 into r14; + call credits.aleo/join r10 r11 into r15; + call credits.aleo/join r13 r12 into r16; + call credits.aleo/join r15 r16 into r17; + call credits.aleo/join r17 r14 into r18; + output r18 as credits.aleo/credits.record; + +function transfer_3: + input r0 as address.private; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + input r3 as credits.aleo/credits.record; + assert.eq r1.owner r2.owner; + assert.eq r2.owner r3.owner; + call credits.aleo/transfer_private r1 r0 r1.microcredits into r4 r5; + call credits.aleo/transfer_private r2 r0 r2.microcredits into r6 r7; + call credits.aleo/transfer_private r3 r0 r3.microcredits into r8 r9; + output r4 as credits.aleo/credits.record; + output r5 as credits.aleo/credits.record; + output r6 as credits.aleo/credits.record; + output r7 as credits.aleo/credits.record; + output r8 as credits.aleo/credits.record; + output r9 as credits.aleo/credits.record; + +function transfer_5: + input r0 as address.private; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + input r3 as credits.aleo/credits.record; + input r4 as credits.aleo/credits.record; + input r5 as credits.aleo/credits.record; + assert.eq r1.owner r2.owner; + assert.eq r2.owner r3.owner; + assert.eq r3.owner r4.owner; + assert.eq r4.owner r5.owner; + call credits.aleo/transfer_private r1 r0 r1.microcredits into r6 r7; + call credits.aleo/transfer_private r2 r0 r2.microcredits into r8 r9; + call credits.aleo/transfer_private r3 r0 r3.microcredits into r10 r11; + call credits.aleo/transfer_private r4 r0 r4.microcredits into r12 r13; + call credits.aleo/transfer_private r5 r0 r5.microcredits into r14 r15; + output r6 as credits.aleo/credits.record; + output r7 as credits.aleo/credits.record; + output r8 as credits.aleo/credits.record; + output r9 as credits.aleo/credits.record; + output r10 as credits.aleo/credits.record; + output r11 as credits.aleo/credits.record; + output r12 as credits.aleo/credits.record; + output r13 as credits.aleo/credits.record; + output r14 as credits.aleo/credits.record; + output r15 as credits.aleo/credits.record; + +function split_3: + input r0 as u64.private; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + input r3 as credits.aleo/credits.record; + assert.eq r1.owner r2.owner; + assert.eq r2.owner r3.owner; + call credits.aleo/split r1 r0 into r4 r5; + call credits.aleo/split r2 r0 into r6 r7; + call credits.aleo/split r3 r0 into r8 r9; + output r4 as credits.aleo/credits.record; + output r5 as credits.aleo/credits.record; + output r6 as credits.aleo/credits.record; + output r7 as credits.aleo/credits.record; + output r8 as credits.aleo/credits.record; + output r9 as credits.aleo/credits.record; + +function split_5: + input r0 as u64.private; + input r1 as credits.aleo/credits.record; + input r2 as credits.aleo/credits.record; + input r3 as credits.aleo/credits.record; + input r4 as credits.aleo/credits.record; + input r5 as credits.aleo/credits.record; + assert.eq r1.owner r2.owner; + assert.eq r2.owner r3.owner; + assert.eq r3.owner r4.owner; + assert.eq r4.owner r5.owner; + call credits.aleo/split r1 r0 into r6 r7; + call credits.aleo/split r2 r0 into r8 r9; + call credits.aleo/split r3 r0 into r10 r11; + call credits.aleo/split r4 r0 into r12 r13; + call credits.aleo/split r5 r0 into r14 r15; + output r6 as credits.aleo/credits.record; + output r7 as credits.aleo/credits.record; + output r8 as credits.aleo/credits.record; + output r9 as credits.aleo/credits.record; + output r10 as credits.aleo/credits.record; + output r11 as credits.aleo/credits.record; + output r12 as credits.aleo/credits.record; + output r13 as credits.aleo/credits.record; + output r14 as credits.aleo/credits.record; + output r15 as credits.aleo/credits.record; diff --git a/utils/retriever/Cargo.toml b/utils/retriever/Cargo.toml new file mode 100644 index 0000000000..2c611294dd --- /dev/null +++ b/utils/retriever/Cargo.toml @@ -0,0 +1,48 @@ +[package] +name = "retriever" +version = "1.10.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies.serde] +version = "1.0" +features = [ "derive"] + +[dependencies.serde_json] +version = "1.0" + +[dependencies.leo-errors] +path = "../../errors" +version = "=1.10.0" + +[dependencies.indexmap] +version = "1.9" +features = [ "serde-1" ] + +[dependencies.leo-ast] +version = "1.9.4" +path = "../../compiler/ast" + +[dependencies.toml] +version = "0.8.6" + +[dependencies.disassembler] +version = "1.10.0" +path = "../disassembler" + +[dependencies.sha2] +version = "0.10.8" + +[dependencies.leo-span] +version = "1.9.4" +path = "../../compiler/span" + +[dependencies.ureq] +version = "2.7" + +[dependencies.leo-passes] +version = "1.9.4" +path = "../../compiler/passes" +[dependencies] +tempfile = "3.8.1" \ No newline at end of file diff --git a/utils/retriever/src/lib.rs b/utils/retriever/src/lib.rs new file mode 100644 index 0000000000..f84548a112 --- /dev/null +++ b/utils/retriever/src/lib.rs @@ -0,0 +1,374 @@ +// Copyright (C) 2019-2023 Aleo Systems Inc. +// This file is part of the Leo library. + +// The Leo library is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// The Leo library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with the Leo library. If not, see . + +use disassembler::disassemble_from_str; +use indexmap::{IndexMap, IndexSet}; +use leo_ast::Stub; +use leo_errors::UtilError; +use leo_passes::{common::DiGraph, DiGraphError}; +use leo_span::Symbol; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use std::{ + env, + fmt, + fs, + fs::File, + io::Read, + path::{Path, PathBuf}, +}; + +const ALEO_EXPLORER_URL: &str = "https://api.explorer.aleo.org/v1"; + +// Struct representation of program's `program.json` specification +#[derive(Debug, Clone, Serialize, Deserialize)] +struct ProgramSpecification { + program: String, + version: String, + description: String, + license: String, + dependencies: Vec, +} + +// Retrievable locations for an external program +#[derive(Debug, Clone, std::cmp::Eq, PartialEq, Hash, Serialize, Deserialize)] +enum Location { + #[serde(rename = "network")] + Network, + #[serde(rename = "local")] + Local, + #[serde(rename = "git")] + Git, +} + +// Retrievable networks for an external program +#[derive(Debug, Clone, std::cmp::Eq, PartialEq, Hash, Serialize, Deserialize)] +pub enum Network { + #[serde(rename = "testnet3")] + Testnet3, + #[serde(rename = "mainnet")] + Mainnet, +} + +impl fmt::Display for Network { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Network::Testnet3 => write!(f, "testnet3"), + Network::Mainnet => write!(f, "mainnet"), + } + } +} + +// Information required to retrieve external program +#[derive(Debug, Clone, std::cmp::Eq, PartialEq, Hash, Serialize, Deserialize)] +struct Program { + name: String, + location: Location, + network: Network, +} + +// Contents of a leo.lock entry for a program +#[derive(Debug, Clone)] +struct LockContents { + dependencies: Vec, + checksum: String, +} + +// Retriever is responsible for retrieving external programs +pub struct Retriever { + programs: Vec, + path: PathBuf, + lock_file: IndexMap, + stubs: IndexMap, + explored: IndexSet, + dependency_graph: DiGraph, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct LockFileEntry { + name: String, + network: Network, + location: Location, + checksum: String, + dependencies: Vec, +} + +impl Retriever { + // Initialize a new Retriever + pub fn new(path: &Path) -> Result { + let lock_path = path.to_path_buf().join("leo.lock"); + if !lock_path.exists() { + std::fs::create_dir_all(path).map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; // TODO: How to get rid of requirement for span? + File::create(lock_path.clone()).map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + } + + let mut file = File::open(lock_path).map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + + // Read `leo.lock` into a string, and deserialize from TOML to a `LockFile` struct. + let mut lock_file_contents = String::new(); + file.read_to_string(&mut lock_file_contents) + .map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + let parsed_lock_file: IndexMap> = toml::from_str(&lock_file_contents) + .map_err(|err| UtilError::toml_serizalization_error(err, Default::default()))?; + + // Construct a mapping of all programs in the `leo.lock` file to their specification. + let mut lock_file_map = IndexMap::new(); + match parsed_lock_file.get("package") { + None => (), + Some(packages) => { + for package in packages { + let program = Program { + name: package.name.clone(), + location: package.location.clone(), + network: package.network.clone(), + }; + let lock_content = LockContents { + // Assign the dependency location and network to match the program's + dependencies: package + .dependencies + .clone() + .into_iter() + .map(|name| Program { + name, + location: package.location.clone(), + network: package.network.clone(), + }) + .collect(), + checksum: package.checksum.clone(), + }; + lock_file_map.insert(program, lock_content); + } + } + } + + // Open `program.json` which is located at `package_path/program.json`. + let mut file = File::open(path.join("program.json")) + .map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + + // Read the file content + let mut content = String::new(); + file.read_to_string(&mut content).map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + + // Deserialize the content into Program + let program_data: ProgramSpecification = serde_json::from_str(&content) + .map_err(|err| UtilError::json_serialization_error(err, Default::default()))?; + + Ok(Self { + programs: program_data.dependencies, + path: path.to_path_buf(), + stubs: IndexMap::new(), + lock_file: lock_file_map, + explored: IndexSet::new(), + dependency_graph: DiGraph::new(IndexSet::new()), + }) + } + + // Retrieve all dependencies for a program + pub fn retrieve(&mut self) -> Result, UtilError> { + let mut programs_to_retrieve = self.programs.clone(); + + while !programs_to_retrieve.is_empty() { + let (mut results, mut dependencies) = (Vec::new(), Vec::new()); + // Visit all programs + for program in programs_to_retrieve.iter() { + match program.location { + Location::Network => results.push(retrieve_from_network( + self.path.clone(), + program.name.clone(), + program.network.clone(), + )?), + Location::Git => panic!("Location::Git is not supported yet"), + Location::Local => panic!("Location::Local is not supported yet"), + } + + // Mark as visited + if !self.explored.insert(program.clone()) { + Err(UtilError::circular_dependency_error(Default::default()))?; + } + } + + for (stub, program, entry) in results { + // Add dependencies to list of dependencies + entry.dependencies.clone().iter().for_each(|dep| { + if !self.explored.contains(dep) { + dependencies.push(dep.clone()); + // Trim off `.aleo` from end of the program names to be consistent with formatting in AST + self.dependency_graph.add_edge( + Symbol::intern(&program.name.clone()[..program.name.len() - 5]), + Symbol::intern(&dep.name.clone()[..dep.name.len() - 5]), + ); + } + }); + + // Add stub to list of stubs + if let Some(existing) = self.stubs.insert(stub.stub_id.name.name, stub.clone()) { + Err(UtilError::duplicate_dependency_name_error(existing.stub_id.name.name, Default::default()))?; + } + + // Update lock file + self.lock_file.insert(program, entry); + } + + programs_to_retrieve = dependencies; + } + + // Write the finalized dependency information to `leo.lock` + self.write_lock_file()?; + + // Check for dependency cycles + match self.dependency_graph.post_order() { + Ok(order) => { + // Return stubs in post order + Ok(order + .iter() + .map(|id| match self.stubs.get(id) { + Some(s) => (*id, s.clone()), + None => panic!("Stub {id} not found"), + }) + .collect()) + } + Err(DiGraphError::CycleDetected(_)) => Err(UtilError::circular_dependency_error(Default::default()))?, + } + } + + // Write lock file + fn write_lock_file(&self) -> Result<(), UtilError> { + // Create struct representation of lock file + let mut lock_file: IndexMap> = IndexMap::new(); + let packages: Vec = self + .lock_file + .iter() + .map(|(program, entry)| LockFileEntry { + name: program.name.clone(), + network: program.network.clone(), + location: program.location.clone(), + checksum: entry.checksum.clone(), + dependencies: entry.dependencies.iter().map(|dep| dep.name.clone()).collect(), + }) + .collect(); + lock_file.insert("package".to_string(), packages); + + // Serialize the data to a TOML string + let toml_str = + toml::to_string(&lock_file).map_err(|err| UtilError::toml_serizalization_error(err, Default::default()))?; + + // Write the TOML string to a file + std::fs::write(self.path.join("leo.lock"), toml_str) + .map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + Ok(()) + } +} + +// Retrieve from network +fn retrieve_from_network( + project_path: PathBuf, + name: String, + network: Network, +) -> Result<(Stub, Program, LockContents), UtilError> { + // Check if the file is already cached in `~/.aleo/registry/{network}/{program}` + let registry_directory = &format!("{}/.aleo/registry/{}", env::var("HOME").unwrap(), network); + let path_str = &format!("{}/{}", registry_directory, name); + let path = Path::new(&path_str); + let mut file_str: String; + if !path.exists() { + // Create directories along the way if they don't exist + std::fs::create_dir_all(Path::new(®istry_directory)) + .map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + + // TODO: Refactor this so that we do the match statement here (instead of in `Retriever::retrieve()`) + // Fetch from network + println!("Retrieving {} from {:?}.", name.clone(), network.clone()); + file_str = fetch_from_network(name.clone(), network.clone())?; + file_str = file_str.replace("\\n", "\n").replace('\"', ""); + println!("Successfully retrieved {} from {:?}!", name, network); + + // Write file to cache + std::fs::write(path, file_str.clone().replace("\\n", "\n")) + .map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + } else { + // Read file from cache + file_str = fs::read_to_string(path).map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + } + + // Copy the file into build directory. We can assume build directory exists because of its initialization in `leo/cli/commands/build.rs`. + let import_dir = project_path.join("build/imports"); + let import_dir_path = import_dir.as_path(); + std::fs::create_dir_all(import_dir_path).map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + let build_location = PathBuf::from(import_dir_path).join(name.clone()); + std::fs::write(build_location, file_str.clone()) + .map_err(|err| UtilError::util_file_io_error(err, Default::default()))?; + + // Hash the file contents + let mut hasher = Sha256::new(); + hasher.update(file_str.as_bytes()); + let hash = hasher.finalize(); + + // Disassemble into Stub + let stub: Stub = disassemble_from_str(file_str)?; + + // Create entry for leo.lock + Ok(( + stub.clone(), + Program { name: name.clone(), location: Location::Network, network: network.clone() }, + LockContents { + dependencies: stub + .imports + .clone() + .iter() + .map(|id| Program { + name: id.name.name.to_string() + "." + id.network.name.to_string().as_str(), + location: Location::Network, + network: network.clone(), + }) + .collect(), + checksum: format!("{hash:x}"), + }, + )) +} + +fn fetch_from_network(program: String, network: Network) -> Result { + let url = format!("{}/{}/program/{}", ALEO_EXPLORER_URL, network.clone(), program); + let response = ureq::get(&url.clone()).call().unwrap(); + if response.status() == 200 { + Ok(response.into_string().unwrap()) + } else { + Err(UtilError::network_error(url, response.status(), Default::default())) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use leo_span::symbol::create_session_if_not_set_then; + + #[test] + fn temp_dir_test() { + // Set $HOME to tmp directory so that tests do not modify users real home directory + let original_home = env::var("HOME").unwrap(); + env::set_var("HOME", "../tmp"); + + // Test pulling nested dependencies from network + const BUILD_DIRECTORY: &str = "../tmp/nested"; + create_session_if_not_set_then(|_| { + let build_dir = PathBuf::from(BUILD_DIRECTORY); + let mut retriever = Retriever::new(&build_dir).expect("Failed to build retriever"); + retriever.retrieve().expect("failed to retrieve"); + }); + + // Reset $HOME + env::set_var("HOME", original_home); + } +} diff --git a/utils/tmp/.aleo/registry/testnet3/nested_example_layer_0.aleo b/utils/tmp/.aleo/registry/testnet3/nested_example_layer_0.aleo new file mode 100644 index 0000000000..c1532ce30a --- /dev/null +++ b/utils/tmp/.aleo/registry/testnet3/nested_example_layer_0.aleo @@ -0,0 +1,10 @@ +import nested_example_layer_2.aleo; +import nested_example_layer_1.aleo; + +program nested_example_layer_0.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + call nested_example_layer_1.aleo/external_function r0 r1 into r2; + output r2 as u32.private; diff --git a/utils/tmp/.aleo/registry/testnet3/nested_example_layer_1.aleo b/utils/tmp/.aleo/registry/testnet3/nested_example_layer_1.aleo new file mode 100644 index 0000000000..4efaf63929 --- /dev/null +++ b/utils/tmp/.aleo/registry/testnet3/nested_example_layer_1.aleo @@ -0,0 +1,9 @@ +import nested_example_layer_2.aleo; + +program nested_example_layer_1.aleo; + +function external_function: + input r0 as u32.public; + input r1 as u32.private; + call nested_example_layer_2.aleo/external_nested_function r0 r1 into r2; + output r2 as u32.private; diff --git a/utils/tmp/.aleo/registry/testnet3/nested_example_layer_2.aleo b/utils/tmp/.aleo/registry/testnet3/nested_example_layer_2.aleo new file mode 100644 index 0000000000..459b896b67 --- /dev/null +++ b/utils/tmp/.aleo/registry/testnet3/nested_example_layer_2.aleo @@ -0,0 +1,7 @@ +program nested_example_layer_2.aleo; + +function external_nested_function: + input r0 as u32.public; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; diff --git a/utils/tmp/nested/.gitignore b/utils/tmp/nested/.gitignore new file mode 100644 index 0000000000..f721f7f6f4 --- /dev/null +++ b/utils/tmp/nested/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/utils/tmp/nested/README.md b/utils/tmp/nested/README.md new file mode 100644 index 0000000000..8bf41e3b4e --- /dev/null +++ b/utils/tmp/nested/README.md @@ -0,0 +1,13 @@ +# nested.aleo + +## Build Guide + +To compile this Aleo program, run: +```bash +snarkvm build +``` + +To execute this Aleo program, run: +```bash +snarkvm run hello +``` diff --git a/utils/tmp/nested/build/imports/nested_example_layer_0.aleo b/utils/tmp/nested/build/imports/nested_example_layer_0.aleo new file mode 100644 index 0000000000..c1532ce30a --- /dev/null +++ b/utils/tmp/nested/build/imports/nested_example_layer_0.aleo @@ -0,0 +1,10 @@ +import nested_example_layer_2.aleo; +import nested_example_layer_1.aleo; + +program nested_example_layer_0.aleo; + +function main: + input r0 as u32.public; + input r1 as u32.private; + call nested_example_layer_1.aleo/external_function r0 r1 into r2; + output r2 as u32.private; diff --git a/utils/tmp/nested/build/imports/nested_example_layer_1.aleo b/utils/tmp/nested/build/imports/nested_example_layer_1.aleo new file mode 100644 index 0000000000..4efaf63929 --- /dev/null +++ b/utils/tmp/nested/build/imports/nested_example_layer_1.aleo @@ -0,0 +1,9 @@ +import nested_example_layer_2.aleo; + +program nested_example_layer_1.aleo; + +function external_function: + input r0 as u32.public; + input r1 as u32.private; + call nested_example_layer_2.aleo/external_nested_function r0 r1 into r2; + output r2 as u32.private; diff --git a/utils/tmp/nested/build/imports/nested_example_layer_2.aleo b/utils/tmp/nested/build/imports/nested_example_layer_2.aleo new file mode 100644 index 0000000000..459b896b67 --- /dev/null +++ b/utils/tmp/nested/build/imports/nested_example_layer_2.aleo @@ -0,0 +1,7 @@ +program nested_example_layer_2.aleo; + +function external_nested_function: + input r0 as u32.public; + input r1 as u32.private; + add r0 r1 into r2; + output r2 as u32.private; diff --git a/utils/tmp/nested/build/main.aleo b/utils/tmp/nested/build/main.aleo new file mode 100644 index 0000000000..e2e063b232 --- /dev/null +++ b/utils/tmp/nested/build/main.aleo @@ -0,0 +1,12 @@ +import nested_example_layer_2.aleo; +import nested_example_layer_1.aleo; +import nested_example_layer_0.aleo; +program nested.aleo; + + + +function example: + input r0 as u32.public; + input r1 as u32.private; + call nested_example_layer_0.aleo/main r0 r1 into r2; + output r2 as u32.private; diff --git a/utils/tmp/nested/build/program.json b/utils/tmp/nested/build/program.json new file mode 100644 index 0000000000..adb2055992 --- /dev/null +++ b/utils/tmp/nested/build/program.json @@ -0,0 +1,13 @@ +{ + "program": "nested.aleo", + "version": "0.0.0", + "description": "", + "license": "MIT", + "dependencies": [ + { + "name": "nested_example_layer_0.aleo", + "location": "network", + "network": "testnet3" + } + ] +} diff --git a/utils/tmp/nested/inputs/nested.in b/utils/tmp/nested/inputs/nested.in new file mode 100644 index 0000000000..4585af6c29 --- /dev/null +++ b/utils/tmp/nested/inputs/nested.in @@ -0,0 +1,4 @@ +// The program input for nested/src/main.leo +[main] +public a: u32 = 1u32; +b: u32 = 2u32; diff --git a/utils/tmp/nested/leo.lock b/utils/tmp/nested/leo.lock new file mode 100644 index 0000000000..61ae06f3ef --- /dev/null +++ b/utils/tmp/nested/leo.lock @@ -0,0 +1,20 @@ +[[package]] +name = "nested_example_layer_0.aleo" +network = "testnet3" +location = "network" +checksum = "23414151de5687d5daa447533109ee810b7e763c4d0659e2f53562123e639b2c" +dependencies = ["nested_example_layer_2.aleo", "nested_example_layer_1.aleo"] + +[[package]] +name = "nested_example_layer_2.aleo" +network = "testnet3" +location = "network" +checksum = "b987b8490b214ad9120a47b218a1c1387c7c2763aaf2aa9c81002a4c7020b3e4" +dependencies = [] + +[[package]] +name = "nested_example_layer_1.aleo" +network = "testnet3" +location = "network" +checksum = "17701f7369fe6bad74cdfb956bd9d85c6753ffedbf43be9fe5a4bfdc1751617b" +dependencies = ["nested_example_layer_2.aleo"] diff --git a/utils/tmp/nested/program.json b/utils/tmp/nested/program.json new file mode 100644 index 0000000000..adb2055992 --- /dev/null +++ b/utils/tmp/nested/program.json @@ -0,0 +1,13 @@ +{ + "program": "nested.aleo", + "version": "0.0.0", + "description": "", + "license": "MIT", + "dependencies": [ + { + "name": "nested_example_layer_0.aleo", + "location": "network", + "network": "testnet3" + } + ] +} diff --git a/utils/tmp/nested/src/main.leo b/utils/tmp/nested/src/main.leo new file mode 100644 index 0000000000..319496dcd5 --- /dev/null +++ b/utils/tmp/nested/src/main.leo @@ -0,0 +1,8 @@ +// The 'nested' program. +import nested_example_layer_0.aleo; +program nested.aleo { + transition example(public a: u32, b: u32) -> u32 { + let c: u32 = nested_example_layer_0.aleo/main(a, b); + return c; + } +}