diff --git a/Cargo.lock b/Cargo.lock index bab7d222f4..247dd86f57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,12 +362,27 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" +[[package]] +name = "cassowary" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] + [[package]] name = "cc" version = "1.0.98" @@ -482,6 +497,20 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "compact_str" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6050c3a16ddab2e412160b31f2c871015704239bca62f72f6e5f0be631d3f644" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "console" version = "0.15.8" @@ -491,7 +520,7 @@ dependencies = [ "encode_unicode", "lazy_static", "libc", - "unicode-width", + "unicode-width 0.1.12", "windows-sys 0.52.0", ] @@ -700,6 +729,41 @@ dependencies = [ "syn 2.0.82", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote 1.0.36", + "strsim", + "syn 2.0.82", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote 1.0.36", + "syn 2.0.82", +] + [[package]] name = "der" version = "0.7.9" @@ -754,6 +818,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.10.7" @@ -1079,7 +1149,7 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" dependencies = [ - "unicode-width", + "unicode-width 0.1.12", ] [[package]] @@ -1365,6 +1435,12 @@ dependencies = [ "tracing", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1407,7 +1483,27 @@ dependencies = [ "instant", "number_prefix", "portable-atomic", - "unicode-width", + "unicode-width 0.1.12", +] + +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + +[[package]] +name = "instability" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b829f37dead9dc39df40c2d3376c179fdfd2ac771f53f55d3c30dc096a3c0c6e" +dependencies = [ + "darling", + "indoc", + "pretty_assertions", + "proc-macro2", + "quote 1.0.36", + "syn 2.0.82", ] [[package]] @@ -1561,6 +1657,33 @@ dependencies = [ "thiserror", ] +[[package]] +name = "leo-interpreter" +version = "2.4.0" +dependencies = [ + "colored", + "crossterm", + "dialoguer", + "indexmap 2.6.0", + "leo-ast", + "leo-errors", + "leo-package", + "leo-parser", + "leo-passes", + "leo-span", + "leo-test-framework", + "rand", + "rand_chacha", + "ratatui", + "serial_test", + "snarkvm", + "snarkvm-circuit", + "snarkvm-synthesizer-program", + "tempfile", + "toml 0.8.19", + "tui-input", +] + [[package]] name = "leo-lang" version = "2.4.0" @@ -1577,6 +1700,7 @@ dependencies = [ "leo-ast", "leo-compiler", "leo-errors", + "leo-interpreter", "leo-package", "leo-retriever", "leo-span", @@ -2130,11 +2254,21 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_assertions" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae130e2f271fbc2ac3a40fb1d07180839cdbbe443c7a27e1e3c13c5cac0116d" +dependencies = [ + "diff", + "yansi", +] + [[package]] name = "proc-macro2" -version = "1.0.84" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2202,6 +2336,27 @@ dependencies = [ "rand_core", ] +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags 2.5.0", + "cassowary", + "compact_str", + "crossterm", + "indoc", + "instability", + "itertools 0.13.0", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + [[package]] name = "rayon" version = "1.10.0" @@ -2470,6 +2625,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "rusty-hook" version = "0.11.2" @@ -3716,12 +3877,40 @@ dependencies = [ "der", ] +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote 1.0.36", + "rustversion", + "syn 2.0.82", +] + [[package]] name = "subtle" version = "2.5.0" @@ -4140,6 +4329,16 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +[[package]] +name = "tui-input" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1733c47f1a217b7deff18730ff7ca4ecafc5771368f715ab072d679a36114" +dependencies = [ + "ratatui", + "unicode-width 0.2.0", +] + [[package]] name = "typenum" version = "1.17.0" @@ -4167,12 +4366,35 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-truncate" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" +dependencies = [ + "itertools 0.13.0", + "unicode-segmentation", + "unicode-width 0.1.12", +] + [[package]] name = "unicode-width" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +[[package]] +name = "unicode-width" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" + [[package]] name = "unicode-xid" version = "0.0.4" @@ -4582,6 +4804,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zerocopy" version = "0.7.34" diff --git a/Cargo.toml b/Cargo.toml index 17c25b2e4a..2bebb2e881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,7 @@ members = [ "compiler/span", "docs/grammar", "errors", + "interpreter", "leo/package", "tests/test-framework", "utils/disassembler", @@ -60,6 +61,10 @@ version = "2.4.0" path = "./errors" version = "2.4.0" +[workspace.dependencies.leo-interpreter] +path = "./interpreter" +version = "2.4.0" + [workspace.dependencies.leo-package] path = "./leo/package" version = "2.4.0" @@ -84,6 +89,9 @@ version = "2.4.0" version = "0.1.24" default-features = false +[workspace.dependencies.colored] +version = "2.0" + [workspace.dependencies.indexmap] version = "2.6" features = [ "serde" ] @@ -95,6 +103,10 @@ version = "0.13.0" version = "0.8" default-features = false +[workspace.dependencies.rand_chacha] +version = "0.3.0" +default-features = false + [workspace.dependencies.regex] version = "1.11.1" @@ -109,6 +121,9 @@ features = [ "derive", "rc" ] version = "1.0" features = [ "preserve_order" ] +[workspace.dependencies.tempfile] +version = "3.13" + [workspace.dependencies.toml] version = "0.8" features = [ "preserve_order" ] @@ -135,85 +150,87 @@ num-format = "0.4.4" text-tables = "0.3.1" ureq = "2.10.1" - [dependencies.leo-ast] - workspace = true +[dependencies.leo-ast] +workspace = true - [dependencies.leo-compiler] - workspace = true +[dependencies.leo-compiler] +workspace = true - [dependencies.leo-errors] - workspace = true +[dependencies.leo-errors] +workspace = true - [dependencies.leo-package] - workspace = true +[dependencies.leo-interpreter] +workspace = true - [dependencies.leo-span] - workspace = true +[dependencies.leo-package] +workspace = true - [dependencies.leo-retriever] - workspace = true +[dependencies.leo-span] +workspace = true - [dependencies.aleo-std] - workspace = true +[dependencies.leo-retriever] +workspace = true - [dependencies.backtrace] - version = "0.3.74" +[dependencies.aleo-std] +workspace = true - [dependencies.clap] - version = "4.5" - features = [ "derive", "env", "color", "unstable-styles" ] +[dependencies.backtrace] +version = "0.3.74" - [dependencies.colored] - version = "2.0" +[dependencies.clap] +version = "4.5" +features = [ "derive", "env", "color", "unstable-styles" ] - [dependencies.dotenvy] - version = "0.15.7" +[dependencies.colored] +workspace = true - [dependencies.indexmap] - workspace = true +[dependencies.dotenvy] +version = "0.15.7" - [dependencies.rand] - workspace = true +[dependencies.indexmap] +workspace = true - [dependencies.rand_chacha] - version = "0.3.0" - default-features = false +[dependencies.rand] +workspace = true - [dependencies.self_update] - version = "0.41.0" - features = [ "archive-zip", "compression-zip-deflate" ] +[dependencies.rand_chacha] +workspace = true - [dependencies.serde] - workspace = true +[dependencies.self_update] +version = "0.41.0" +features = [ "archive-zip", "compression-zip-deflate" ] - [dependencies.serde_json] - workspace = true +[dependencies.serde] +workspace = true - [dependencies.serial_test] - version = "3.1.1" +[dependencies.serde_json] +workspace = true - [dependencies.snarkvm] - workspace = true - features = [ "circuit", "console" ] +[dependencies.serial_test] +version = "3.1.1" - [dependencies.sys-info] - version = "0.9.1" +[dependencies.snarkvm] +workspace = true +features = [ "circuit", "console" ] - [dependencies.toml] - workspace = true +[dependencies.sys-info] +version = "0.9.1" - [dependencies.tracing] - version = "0.1" +[dependencies.toml] +workspace = true + +[dependencies.tracing] +version = "0.1" - [dependencies.tracing-subscriber] - version = "0.3.18" - features = [ "fmt" ] +[dependencies.tracing-subscriber] +version = "0.3.18" +features = [ "fmt" ] - [dependencies.crossterm] - version = "0.28.1" +[dependencies.crossterm] +version = "0.28.1" - [dependencies.rpassword] - version = "7.3.1" +[dependencies.rpassword] +version = "7.3.1" [target."cfg(windows)".dependencies.ansi_term] version = "0.12.1" diff --git a/compiler/ast/src/functions/core_function.rs b/compiler/ast/src/functions/core_function.rs index 3732666df4..6af63f333c 100644 --- a/compiler/ast/src/functions/core_function.rs +++ b/compiler/ast/src/functions/core_function.rs @@ -289,6 +289,9 @@ pub enum CoreFunction { SignatureVerify, FutureAwait, + + CheatCodePrintMapping, + CheatCodeSetBlockHeight, } impl CoreFunction { @@ -566,6 +569,9 @@ impl CoreFunction { (sym::signature, sym::verify) => Self::SignatureVerify, (sym::Future, sym::Await) => Self::FutureAwait, + + (sym::CheatCode, sym::print_mapping) => Self::CheatCodePrintMapping, + (sym::CheatCode, sym::set_block_height) => Self::CheatCodeSetBlockHeight, _ => return None, }) } @@ -844,6 +850,9 @@ impl CoreFunction { Self::SignatureVerify => 3, Self::FutureAwait => 1, + + Self::CheatCodePrintMapping => 1, + Self::CheatCodeSetBlockHeight => 1, } } @@ -1101,7 +1110,9 @@ impl CoreFunction { | CoreFunction::SHA3_512HashToScalar | CoreFunction::GroupToXCoordinate | CoreFunction::GroupToYCoordinate - | CoreFunction::SignatureVerify => false, + | CoreFunction::SignatureVerify + | CoreFunction::CheatCodePrintMapping + | CoreFunction::CheatCodeSetBlockHeight => false, } } } diff --git a/compiler/compiler/Cargo.toml b/compiler/compiler/Cargo.toml index 21c0d439f7..bfbec08316 100644 --- a/compiler/compiler/Cargo.toml +++ b/compiler/compiler/Cargo.toml @@ -74,7 +74,7 @@ workspace = true version = "3.1.1" [dev-dependencies.tempfile] -version = "3.13" +workspace = true [features] default = [ ] diff --git a/compiler/parser/src/parser/mod.rs b/compiler/parser/src/parser/mod.rs index f39da3a305..d91d30e2eb 100644 --- a/compiler/parser/src/parser/mod.rs +++ b/compiler/parser/src/parser/mod.rs @@ -22,7 +22,7 @@ use crate::{Token, tokenizer::*}; use leo_ast::*; -use leo_errors::{Result, emitter::Handler}; +use leo_errors::{ParserError, Result, emitter::Handler}; use leo_span::{Span, span::BytePos}; use snarkvm::prelude::Network; @@ -49,3 +49,35 @@ pub fn parse( tokens.parse_program() } + +pub fn parse_expression( + handler: &Handler, + node_builder: &NodeBuilder, + source: &str, + start_pos: BytePos, +) -> Result { + let mut context = ParserContext::::new(handler, node_builder, crate::tokenize(source, start_pos)?); + + let expression = context.parse_expression()?; + if context.token.token == Token::Eof { + Ok(expression) + } else { + Err(ParserError::unexpected(context.token.token, Token::Eof, context.token.span).into()) + } +} + +pub fn parse_statement( + handler: &Handler, + node_builder: &NodeBuilder, + source: &str, + start_pos: BytePos, +) -> Result { + let mut context = ParserContext::::new(handler, node_builder, crate::tokenize(source, start_pos)?); + + let statement = context.parse_statement()?; + if context.token.token == Token::Eof { + Ok(statement) + } else { + Err(ParserError::unexpected(context.token.token, Token::Eof, context.token.span).into()) + } +} diff --git a/compiler/passes/src/code_generation/visit_expressions.rs b/compiler/passes/src/code_generation/visit_expressions.rs index 3ab9a3bc4b..eb4c7cbcde 100644 --- a/compiler/passes/src/code_generation/visit_expressions.rs +++ b/compiler/passes/src/code_generation/visit_expressions.rs @@ -485,6 +485,10 @@ impl<'a> CodeGenerator<'a> { writeln!(instruction, " {};", arguments[0]).expect("failed to write to string"); (String::new(), instruction) } + sym::CheatCode => { + (String::new(), String::new()) + // Do nothing. Cheat codes do not generate instructions. + } _ => { unreachable!("All core functions should be known at this phase of compilation") } diff --git a/compiler/passes/src/dead_code_elimination/eliminate_expression.rs b/compiler/passes/src/dead_code_elimination/eliminate_expression.rs index d6fe402e01..0e4115dea0 100644 --- a/compiler/passes/src/dead_code_elimination/eliminate_expression.rs +++ b/compiler/passes/src/dead_code_elimination/eliminate_expression.rs @@ -32,9 +32,12 @@ impl ExpressionReconstructor for DeadCodeEliminator<'_> { /// Reconstructs the associated function access expression. fn reconstruct_associated_function(&mut self, input: AssociatedFunction) -> (Expression, Self::AdditionalOutput) { - // If the associated function manipulates a mapping, mark the statement as necessary. + // If the associated function manipulates a mapping, or a cheat code, mark the statement as necessary. match (&input.variant.name, input.name.name) { - (&sym::Mapping, sym::remove) | (&sym::Mapping, sym::set) | (&sym::Future, sym::Await) => { + (&sym::Mapping, sym::remove) + | (&sym::Mapping, sym::set) + | (&sym::Future, sym::Await) + | (&sym::CheatCode, _) => { self.is_necessary = true; } _ => {} diff --git a/compiler/passes/src/type_checking/checker.rs b/compiler/passes/src/type_checking/checker.rs index 3b85386cb7..764f2fa227 100644 --- a/compiler/passes/src/type_checking/checker.rs +++ b/compiler/passes/src/type_checking/checker.rs @@ -1020,6 +1020,16 @@ impl<'a, N: Network> TypeChecker<'a, N> { Some(Type::Boolean) } CoreFunction::FutureAwait => Some(Type::Unit), + CoreFunction::CheatCodePrintMapping => { + // Check that the argument is a mapping. + let _ = self.assert_mapping_type(&arguments[0].0, arguments[0].1); + Some(Type::Unit) + } + CoreFunction::CheatCodeSetBlockHeight => { + // Assert that the argument is a u32. + self.assert_type(&arguments[0].0, &Type::Integer(IntegerType::U32), arguments[0].1); + Some(Type::Unit) + } } } diff --git a/compiler/span/src/source_map.rs b/compiler/span/src/source_map.rs index 955992b3b5..36240e288b 100644 --- a/compiler/span/src/source_map.rs +++ b/compiler/span/src/source_map.rs @@ -83,7 +83,7 @@ impl SourceMap { } /// Find the source file containing `pos`. - fn find_source_file(&self, pos: BytePos) -> Option> { + pub fn find_source_file(&self, pos: BytePos) -> Option> { Some(self.inner.borrow().source_files[self.find_source_file_index(pos)?].clone()) } diff --git a/compiler/span/src/symbol.rs b/compiler/span/src/symbol.rs index 48ae35d370..bd22d4704b 100644 --- a/compiler/span/src/symbol.rs +++ b/compiler/span/src/symbol.rs @@ -204,6 +204,11 @@ symbols! { verify, Await: "await", + // CheatCodes + CheatCode, + print_mapping, + set_block_height, + // types address, bool, diff --git a/errors/src/errors/interpreter_halt.rs b/errors/src/errors/interpreter_halt.rs new file mode 100644 index 0000000000..a8fe561dcf --- /dev/null +++ b/errors/src/errors/interpreter_halt.rs @@ -0,0 +1,50 @@ +// Copyright (C) 2019-2024 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 std::fmt; + +use leo_span::Span; + +/// Represents the interpreter halting, which should not be considered an +/// actual runtime error. +#[derive(Clone, Debug, Error)] +pub struct InterpreterHalt { + /// Optional Span where the halt occurred. + span: Option, + + /// User visible message. + message: String, +} + +impl InterpreterHalt { + pub fn new(message: String) -> Self { + InterpreterHalt { span: None, message } + } + + pub fn new_spanned(message: String, span: Span) -> Self { + InterpreterHalt { span: Some(span), message } + } + + pub fn span(&self) -> Option { + self.span + } +} + +impl fmt::Display for InterpreterHalt { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.message) + } +} diff --git a/errors/src/errors/mod.rs b/errors/src/errors/mod.rs index f4a834bae8..ac602c91db 100644 --- a/errors/src/errors/mod.rs +++ b/errors/src/errors/mod.rs @@ -37,6 +37,9 @@ pub use self::flattener::*; pub mod loop_unroller; pub use self::loop_unroller::*; +pub mod interpreter_halt; +pub use self::interpreter_halt::*; + /// Contains the Package error definitions. pub mod package; pub use self::package::*; @@ -70,6 +73,8 @@ pub enum LeoError { /// Represents a Compiler Error in a Leo Error. #[error(transparent)] CompilerError(#[from] CompilerError), + #[error(transparent)] + InterpreterHalt(#[from] InterpreterHalt), /// Represents a Package Error in a Leo Error. #[error(transparent)] PackageError(#[from] PackageError), @@ -118,6 +123,7 @@ impl LeoError { UtilError(error) => error.error_code(), LastErrorCode(_) => unreachable!(), Anyhow(_) => "SnarkVM Error".to_string(), // todo: implement error codes for snarkvm errors. + InterpreterHalt(_) => "Interpreter Halt".to_string(), } } @@ -138,6 +144,7 @@ impl LeoError { UtilError(error) => error.exit_code(), LastErrorCode(code) => *code, Anyhow(_) => 11000, // todo: implement exit codes for snarkvm errors. + InterpreterHalt(_) => 1, } } } diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml new file mode 100644 index 0000000000..fd58c12e7d --- /dev/null +++ b/interpreter/Cargo.toml @@ -0,0 +1,83 @@ +[package] +name = "leo-interpreter" +version = "2.4.0" +authors = [ "The Leo Team " ] +description = "Interpreter for the Leo programming language" +homepage = "https://leo-lang.org" +repository = "https://github.com/ProvableHQ/leo" +keywords = [ + "aleo", + "cryptography", + "leo", + "programming-language", + "zero-knowledge" +] +categories = [ "compilers", "cryptography", "web-programming" ] +include = [ "Cargo.toml", "src", "README.md", "LICENSE.md" ] +license = "GPL-3.0" +edition = "2021" +rust-version = "1.82.0" + +[dependencies.snarkvm] +workspace = true + +[dependencies.snarkvm-circuit] +version = "1.0.0" + +[dependencies.snarkvm-synthesizer-program] +version = "1.0.0" + +[dependencies.leo-ast] +workspace = true + +[dependencies.leo-passes] +workspace = true + +[dependencies.leo-errors] +workspace = true + +[dependencies.leo-package] +workspace = true + +[dependencies.leo-parser] +workspace = true + +[dependencies.leo-span] +workspace = true + +[dependencies.colored] +workspace = true + +[dependencies.crossterm] +version = "0.28.1" + +[dependencies.indexmap] +workspace = true + +[dependencies.dialoguer] +version = "0.11.0" +features = [ "history" ] + +[dependencies.rand] +workspace = true + +[dependencies.rand_chacha] +workspace = true + +[dependencies.ratatui] +version = "0.29.0" + +[dependencies.toml] +workspace = true + +[dependencies.tui-input] +version = "0.11.1" + +[dev-dependencies.leo-test-framework] +path = "../tests/test-framework" + +[dev-dependencies.serial_test] +version = "3.1.1" + +[dev-dependencies.tempfile] +workspace = true diff --git a/interpreter/src/cursor.rs b/interpreter/src/cursor.rs new file mode 100644 index 0000000000..624e7dd209 --- /dev/null +++ b/interpreter/src/cursor.rs @@ -0,0 +1,2756 @@ +// Copyright (C) 2019-2024 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 super::*; + +use leo_ast::{ + AccessExpression, + AssertVariant, + BinaryOperation, + Block, + CoreConstant, + CoreFunction, + Expression, + Function, + GroupLiteral, + IntegerType, + Literal, + Statement, + Type, + UnaryOperation, + Variant, +}; +use leo_errors::{InterpreterHalt, Result}; +use leo_span::{Span, Symbol, sym}; + +use snarkvm::prelude::{ + CastLossy as _, + Closure as SvmClosure, + Double as _, + Finalize as SvmFinalize, + Function as SvmFunctionParam, + Inverse as _, + Network as _, + Pow as _, + ProgramID, + Square as _, + SquareRoot as _, + TestnetV0, + ToBits, +}; + +use indexmap::{IndexMap, IndexSet}; +use rand::Rng as _; +use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; +use std::{cmp::Ordering, collections::HashMap, fmt, mem, str::FromStr as _}; + +pub type Closure = SvmClosure; +pub type Finalize = SvmFinalize; +pub type SvmFunction = SvmFunctionParam; + +/// Names associated to values in a function being executed. +#[derive(Clone, Debug)] +pub struct FunctionContext { + program: Symbol, + pub caller: SvmAddress, + names: HashMap, + accumulated_futures: Future, + is_async: bool, +} + +/// A stack of contexts, building with the function call stack. +#[derive(Clone, Debug, Default)] +pub struct ContextStack { + contexts: Vec, + current_len: usize, +} + +impl ContextStack { + fn len(&self) -> usize { + self.current_len + } + + fn push(&mut self, program: Symbol, caller: SvmAddress, is_async: bool) { + if self.current_len == self.contexts.len() { + self.contexts.push(FunctionContext { + program, + caller, + names: HashMap::new(), + accumulated_futures: Default::default(), + is_async, + }); + } + self.contexts[self.current_len].accumulated_futures.0.clear(); + self.contexts[self.current_len].names.clear(); + self.contexts[self.current_len].caller = caller; + self.contexts[self.current_len].program = program; + self.contexts[self.current_len].is_async = is_async; + self.current_len += 1; + } + + pub fn pop(&mut self) { + // We never actually pop the underlying Vec + // so we can reuse the storage of the hash + // tables. + assert!(self.len() > 0); + self.current_len -= 1; + self.contexts[self.current_len].names.clear(); + } + + /// Get the future accumulated by awaiting futures in the current function call. + /// + /// If the current code being interpreted is not in an async function, this + /// will of course be empty. + fn get_future(&mut self) -> Future { + assert!(self.len() > 0); + mem::take(&mut self.contexts[self.current_len - 1].accumulated_futures) + } + + fn set(&mut self, symbol: Symbol, value: Value) { + assert!(self.current_len > 0); + self.last_mut().unwrap().names.insert(symbol, value); + } + + pub fn add_future(&mut self, future: Future) { + assert!(self.current_len > 0); + self.contexts[self.current_len - 1].accumulated_futures.0.extend(future.0); + } + + /// Are we currently in an async function? + fn is_async(&self) -> bool { + assert!(self.current_len > 0); + self.last().unwrap().is_async + } + + pub fn current_program(&self) -> Option { + self.last().map(|c| c.program) + } + + pub fn last(&self) -> Option<&FunctionContext> { + self.len().checked_sub(1).and_then(|i| self.contexts.get(i)) + } + + fn last_mut(&mut self) -> Option<&mut FunctionContext> { + self.len().checked_sub(1).and_then(|i| self.contexts.get_mut(i)) + } +} + +#[derive(Copy, Clone, Debug)] +pub enum AleoContext<'a> { + Closure(&'a Closure), + Function(&'a SvmFunction), + Finalize(&'a Finalize), +} + +/// A Leo construct to be evauated. +#[derive(Clone, Debug)] +pub enum Element<'a> { + /// A Leo statement. + Statement(&'a Statement), + + /// A Leo expression. + Expression(&'a Expression), + + /// A Leo block. + /// + /// We have a separate variant for Leo blocks for two reasons: + /// 1. In a ConditionalExpression, the `then` block is stored + /// as just a Block with no statement, and + /// 2. We need to remember if a Block came from a function body, + /// so that if such a block ends, we know to push a `Unit` to + /// the values stack. + Block { + block: &'a Block, + function_body: bool, + }, + + AleoExecution { + context: AleoContext<'a>, + registers: IndexMap, + instruction_index: usize, + }, + + DelayedCall(GlobalId), +} + +impl Element<'_> { + pub fn span(&self) -> Span { + use Element::*; + match self { + Statement(statement) => statement.span(), + Expression(expression) => expression.span(), + Block { block, .. } => block.span(), + AleoExecution { .. } | DelayedCall(..) => Default::default(), + } + } +} + +/// A frame of execution, keeping track of the Element next to +/// be executed and the number of steps we've done so far. +#[derive(Clone, Debug)] +pub struct Frame<'a> { + pub step: usize, + pub element: Element<'a>, + pub user_initiated: bool, +} + +/// Global values - such as mappings, functions, etc - +/// are identified by program and name. +#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] +pub struct GlobalId { + pub program: Symbol, + pub name: Symbol, +} + +impl fmt::Display for GlobalId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}/{}", self.program, self.name) + } +} + +#[derive(Clone, Debug)] +pub enum FunctionVariant<'a> { + Leo(&'a Function), + AleoClosure(&'a Closure), + AleoFunction(&'a SvmFunction), +} + +/// Tracks the current execution state - a cursor into the running program. +#[derive(Clone, Debug)] +pub struct Cursor<'a> { + /// Stack of execution frames, with the one currently to be executed on top. + pub frames: Vec>, + + /// Stack of values from evaluated expressions. + /// + /// Each time an expression completes evaluation, a value is pushed here. + pub values: Vec, + + /// All functions (or transitions or inlines) in any program being interpreted. + pub functions: HashMap>, + + /// Consts are stored here. + pub globals: HashMap, + + pub user_values: HashMap, + + pub mappings: HashMap>, + + /// For each struct type, we only need to remember the names of its members, in order. + pub structs: HashMap>, + + pub futures: Vec, + + pub contexts: ContextStack, + + pub signer: SvmAddress, + + pub rng: ChaCha20Rng, + + pub block_height: u32, + + pub really_async: bool, + + pub program: Option, +} + +impl<'a> Cursor<'a> { + /// `really_async` indicates we should really delay execution of async function calls until the user runs them. + pub fn new(really_async: bool, signer: SvmAddress, block_height: u32) -> Self { + Cursor { + frames: Default::default(), + values: Default::default(), + functions: Default::default(), + globals: Default::default(), + user_values: Default::default(), + mappings: Default::default(), + structs: Default::default(), + contexts: Default::default(), + futures: Default::default(), + rng: ChaCha20Rng::from_entropy(), + signer, + block_height, + really_async, + program: None, + } + } + + pub fn set_program(&mut self, program: &str) { + let p = program.strip_suffix(".aleo").unwrap_or(program); + self.program = Some(Symbol::intern(p)); + } + + pub fn current_program(&self) -> Option { + self.contexts.current_program().or(self.program) + } + + pub fn increment_step(&mut self) { + let Some(Frame { step, .. }) = self.frames.last_mut() else { + panic!("frame expected"); + }; + *step += 1; + } + + fn new_caller(&self) -> SvmAddress { + if let Some(function_context) = self.contexts.last() { + let program_id = ProgramID::::from_str(&format!("{}.aleo", function_context.program)) + .expect("should be able to create ProgramID"); + program_id.to_address().expect("should be able to convert to address") + } else { + self.signer + } + } + + fn pop_value(&mut self) -> Result { + match self.values.pop() { + Some(v) => Ok(v), + None => { + Err(InterpreterHalt::new("value expected - this may be a bug in the Leo interpreter".to_string()) + .into()) + } + } + } + + fn lookup(&self, name: Symbol) -> Option { + if let Some(context) = self.contexts.last() { + let option_value = + context.names.get(&name).or_else(|| self.globals.get(&GlobalId { program: context.program, name })); + if option_value.is_some() { + return option_value.cloned(); + } + }; + + self.user_values.get(&name).cloned() + } + + pub fn lookup_mapping(&self, program: Option, name: Symbol) -> Option<&HashMap> { + let Some(program) = program.or_else(|| self.current_program()) else { + panic!("no program for mapping lookup"); + }; + self.mappings.get(&GlobalId { program, name }) + } + + pub fn lookup_mapping_mut(&mut self, program: Option, name: Symbol) -> Option<&mut HashMap> { + let Some(program) = program.or_else(|| self.current_program()) else { + panic!("no program for mapping lookup"); + }; + self.mappings.get_mut(&GlobalId { program, name }) + } + + fn lookup_function(&self, program: Symbol, name: Symbol) -> Option> { + self.functions.get(&GlobalId { program, name }).cloned() + } + + fn set_variable(&mut self, symbol: Symbol, value: Value) { + if self.contexts.len() > 0 { + self.contexts.set(symbol, value); + } else { + self.user_values.insert(symbol, value); + } + } + + fn set_block_height(&mut self, block_height: u32) { + self.block_height = block_height; + } + + /// Execute the whole step of the current Element. + /// + /// That is, perform a step, and then finish all statements and expressions that have been pushed, + /// until we're ready for the next step of the current Element (if there is one). + pub fn whole_step(&mut self) -> Result { + let frames_len = self.frames.len(); + let initial_result = self.step()?; + if !initial_result.finished { + while self.frames.len() > frames_len { + self.step()?; + } + } + Ok(initial_result) + } + + /// Step `over` the current Element. + /// + /// That is, continue executing until the current Element is finished. + pub fn over(&mut self) -> Result { + let frames_len = self.frames.len(); + loop { + match self.frames.len().cmp(&frames_len) { + Ordering::Greater => { + self.step()?; + } + Ordering::Equal => { + let result = self.step()?; + if result.finished { + return Ok(result); + } + } + Ordering::Less => { + // This can happen if, for instance, a `return` was encountered, + // which means we exited the function we were evaluating and the + // frame stack was truncated. + return Ok(StepResult { finished: true, value: None }); + } + } + } + } + + pub fn step_block(&mut self, block: &'a Block, function_body: bool, step: usize) -> bool { + let len = self.frames.len(); + + let done = match step { + 0 => { + for statement in block.statements.iter().rev() { + self.frames.push(Frame { element: Element::Statement(statement), step: 0, user_initiated: false }); + } + false + } + 1 if function_body => { + self.values.push(Value::Unit); + self.contexts.pop(); + true + } + 1 => true, + _ => unreachable!(), + }; + + if done { + assert_eq!(len, self.frames.len()); + self.frames.pop(); + } else { + self.frames[len - 1].step += 1; + } + + done + } + + fn step_statement(&mut self, statement: &'a Statement, step: usize) -> Result { + let len = self.frames.len(); + + let mut push = |expression| { + self.frames.push(Frame { element: Element::Expression(expression), step: 0, user_initiated: false }) + }; + + let done = match statement { + Statement::Assert(assert) if step == 0 => { + match &assert.variant { + AssertVariant::Assert(x) => push(x), + AssertVariant::AssertEq(x, y) | AssertVariant::AssertNeq(x, y) => { + push(y); + push(x); + } + }; + false + } + Statement::Assert(assert) if step == 1 => { + match &assert.variant { + AssertVariant::Assert(..) => { + let value = self.pop_value()?; + match value { + Value::Bool(true) => {} + Value::Bool(false) => halt!(assert.span(), "assert failure"), + _ => tc_fail!(), + } + } + AssertVariant::AssertEq(..) | AssertVariant::AssertNeq(..) => { + let x = self.pop_value()?; + let y = self.pop_value()?; + let b = + if matches!(assert.variant, AssertVariant::AssertEq(..)) { x.eq(&y)? } else { x.neq(&y)? }; + if !b { + halt!(assert.span(), "assert failure"); + } + } + }; + true + } + Statement::Assign(assign) if step == 0 => { + push(&assign.value); + false + } + Statement::Assign(assign) if step == 1 => { + let value = self.values.pop().unwrap(); + let Expression::Identifier(id) = &assign.place else { tc_fail!() }; + self.set_variable(id.name, value); + true + } + Statement::Block(block) => return Ok(self.step_block(block, false, step)), + Statement::Conditional(conditional) if step == 0 => { + push(&conditional.condition); + false + } + Statement::Conditional(conditional) if step == 1 => { + match self.pop_value()? { + Value::Bool(true) => self.frames.push(Frame { + step: 0, + element: Element::Block { block: &conditional.then, function_body: false }, + user_initiated: false, + }), + Value::Bool(false) => { + if let Some(otherwise) = conditional.otherwise.as_ref() { + self.frames.push(Frame { + step: 0, + element: Element::Statement(otherwise), + user_initiated: false, + }) + } + } + _ => tc_fail!(), + }; + false + } + Statement::Conditional(_) if step == 2 => true, + Statement::Console(_) => todo!(), + Statement::Const(const_) if step == 0 => { + push(&const_.value); + false + } + Statement::Const(const_) if step == 1 => { + let value = self.pop_value()?; + self.set_variable(const_.place.name, value); + true + } + Statement::Definition(definition) if step == 0 => { + push(&definition.value); + false + } + Statement::Definition(definition) if step == 1 => { + let value = self.pop_value()?; + match &definition.place { + Expression::Identifier(id) => self.set_variable(id.name, value), + Expression::Tuple(tuple) => { + let Value::Tuple(rhs) = value else { + tc_fail!(); + }; + for (name, val) in tuple.elements.iter().zip(rhs.into_iter()) { + let Expression::Identifier(id) = name else { + tc_fail!(); + }; + self.set_variable(id.name, val); + } + } + _ => tc_fail!(), + } + true + } + Statement::Expression(expression) if step == 0 => { + push(&expression.expression); + false + } + Statement::Expression(_) if step == 1 => { + self.values.pop(); + true + } + Statement::Iteration(iteration) if step == 0 => { + assert!(!iteration.inclusive); + push(&iteration.stop); + push(&iteration.start); + false + } + Statement::Iteration(iteration) => { + // Currently there actually isn't a syntax in Leo for inclusive ranges. + let stop = self.pop_value()?; + let start = self.pop_value()?; + if start.eq(&stop)? { + true + } else { + let new_start = start.inc_wrapping(); + self.set_variable(iteration.variable.name, start); + self.frames.push(Frame { + step: 0, + element: Element::Block { block: &iteration.block, function_body: false }, + user_initiated: false, + }); + self.values.push(new_start); + self.values.push(stop); + false + } + } + Statement::Return(return_) if step == 0 => { + push(&return_.expression); + false + } + Statement::Return(_) if step == 1 => loop { + let last_frame = self.frames.last().expect("a frame should be present"); + match last_frame.element { + Element::Expression(Expression::Call(_)) | Element::DelayedCall(_) => { + if self.contexts.is_async() { + // Get rid of the Unit we previously pushed, and replace it with a Future. + self.values.pop(); + self.values.push(Value::Future(self.contexts.get_future())); + } + self.contexts.pop(); + return Ok(true); + } + _ => { + self.frames.pop(); + } + } + }, + _ => unreachable!(), + }; + + if done { + assert_eq!(len, self.frames.len()); + self.frames.pop(); + } else { + self.frames[len - 1].step += 1; + } + + Ok(done) + } + + fn step_expression(&mut self, expression: &'a Expression, step: usize) -> Result { + let len = self.frames.len(); + + macro_rules! push { + () => { + |expression| { + self.frames.push(Frame { element: Element::Expression(expression), step: 0, user_initiated: false }) + } + }; + } + + if let Some(value) = match expression { + Expression::Access(AccessExpression::Array(array)) if step == 0 => { + push!()(&*array.index); + push!()(&*array.array); + None + } + Expression::Access(AccessExpression::Array(array)) if step == 1 => { + let span = array.span(); + let array = self.pop_value()?; + let index = self.pop_value()?; + + let index_usize: usize = match index { + Value::U8(x) => x.into(), + Value::U16(x) => x.into(), + Value::U32(x) => x.try_into().expect_tc(span)?, + Value::U64(x) => x.try_into().expect_tc(span)?, + Value::U128(x) => x.try_into().expect_tc(span)?, + Value::I8(x) => x.try_into().expect_tc(span)?, + Value::I16(x) => x.try_into().expect_tc(span)?, + Value::I32(x) => x.try_into().expect_tc(span)?, + Value::I64(x) => x.try_into().expect_tc(span)?, + Value::I128(x) => x.try_into().expect_tc(span)?, + _ => halt!(expression.span(), "invalid array index {index}"), + }; + let Value::Array(vec_array) = array else { tc_fail!() }; + Some(vec_array.get(index_usize).expect_tc(span)?.clone()) + } + Expression::Access(AccessExpression::AssociatedConstant(constant)) if step == 0 => { + let Type::Identifier(type_ident) = constant.ty else { + tc_fail!(); + }; + let Some(core_constant) = CoreConstant::from_symbols(type_ident.name, constant.name.name) else { + halt!(constant.span(), "Unknown constant {constant}"); + }; + match core_constant { + CoreConstant::GroupGenerator => Some(Value::generator()), + } + } + Expression::Access(AccessExpression::Member(access)) => match &*access.inner { + Expression::Identifier(identifier) if identifier.name == sym::SelfLower => match access.name.name { + sym::signer => Some(Value::Address(self.signer)), + sym::caller => { + if let Some(function_context) = self.contexts.last() { + Some(Value::Address(function_context.caller)) + } else { + Some(Value::Address(self.signer)) + } + } + _ => halt!(access.span(), "unknown member of self"), + }, + Expression::Identifier(identifier) if identifier.name == sym::block => match access.name.name { + sym::height => Some(Value::U32(self.block_height)), + _ => halt!(access.span(), "unknown member of block"), + }, + + // Otherwise, we just have a normal struct member access. + _ if step == 0 => { + push!()(&*access.inner); + None + } + _ if step == 1 => { + let Some(Value::Struct(struct_)) = self.values.pop() else { + tc_fail!(); + }; + let value = struct_.contents.get(&access.name.name).cloned(); + if value.is_none() { + tc_fail!(); + } + value + } + _ => unreachable!("we've actually covered all possible patterns above"), + }, + Expression::Access(AccessExpression::AssociatedFunction(function)) if step == 0 => { + let Some(core_function) = CoreFunction::from_symbols(function.variant.name, function.name.name) else { + halt!(function.span(), "Unkown core function {function}"); + }; + + // We want to push expressions for each of the arguments... except for mappings, + // because we don't look them up as Values. + match core_function { + CoreFunction::MappingGet | CoreFunction::MappingRemove | CoreFunction::MappingContains => { + push!()(&function.arguments[1]); + } + CoreFunction::MappingGetOrUse | CoreFunction::MappingSet => { + push!()(&function.arguments[2]); + push!()(&function.arguments[1]); + } + CoreFunction::CheatCodePrintMapping => { + // Do nothing, as we don't need to evaluate the mapping. + } + _ => function.arguments.iter().rev().for_each(push!()), + } + None + } + Expression::Access(AccessExpression::AssociatedFunction(function)) if step == 1 => { + let Some(core_function) = CoreFunction::from_symbols(function.variant.name, function.name.name) else { + halt!(function.span(), "Unkown core function {function}"); + }; + + let span = function.span(); + + let value = self.evaluate_core_function(core_function.clone(), &function.arguments, span)?; + + if let CoreFunction::FutureAwait = core_function { + // For an await, we have one extra step - first we must evaluate the delayed call. + None + } else { + Some(value) + } + } + Expression::Access(AccessExpression::AssociatedFunction(function)) if step == 2 => { + let Some(core_function) = CoreFunction::from_symbols(function.variant.name, function.name.name) else { + halt!(function.span(), "Unkown core function {function}"); + }; + assert!(core_function == CoreFunction::FutureAwait); + Some(Value::Unit) + } + Expression::Access(AccessExpression::Tuple(tuple_access)) if step == 0 => { + push!()(&*tuple_access.tuple); + None + } + Expression::Access(AccessExpression::Tuple(tuple_access)) if step == 1 => { + let Some(value) = self.values.pop() else { tc_fail!() }; + let Value::Tuple(tuple) = value else { + halt!(tuple_access.span(), "Type error"); + }; + if let Some(result) = tuple.get(tuple_access.index.value()) { + Some(result.clone()) + } else { + halt!(tuple_access.span(), "Tuple index out of range"); + } + } + Expression::Array(array) if step == 0 => { + array.elements.iter().rev().for_each(push!()); + None + } + Expression::Array(array) if step == 1 => { + let len = self.values.len(); + let array_values = self.values.drain(len - array.elements.len()..).collect(); + Some(Value::Array(array_values)) + } + Expression::Binary(binary) if step == 0 => { + push!()(&binary.right); + push!()(&binary.left); + None + } + Expression::Binary(binary) if step == 1 => { + let rhs = self.pop_value()?; + let lhs = self.pop_value()?; + Some(evaluate_binary(binary.span, binary.op, lhs, rhs)?) + } + Expression::Call(call) if step == 0 => { + call.arguments.iter().rev().for_each(push!()); + None + } + Expression::Call(call) if step == 1 => { + let len = self.values.len(); + let (program, name) = match &*call.function { + Expression::Identifier(id) => { + let maybe_program = call.program.or_else(|| self.current_program()); + if let Some(program) = maybe_program { + (program, id.name) + } else { + halt!(call.span, "No current program"); + } + } + Expression::Locator(locator) => (locator.program.name.name, locator.name), + _ => tc_fail!(), + }; + // It's a bit cheesy to collect the arguments into a Vec first, but it's the easiest way + // to handle lifetimes here. + let arguments: Vec = self.values.drain(len - call.arguments.len()..).collect(); + self.do_call( + program, + name, + arguments.into_iter(), + false, // finalize + call.span(), + )?; + None + } + Expression::Call(_call) if step == 2 => Some(self.pop_value()?), + Expression::Cast(cast) if step == 0 => { + push!()(&*cast.expression); + None + } + Expression::Cast(cast) if step == 1 => { + let span = cast.span(); + let arg = self.pop_value()?; + match arg.cast(&cast.type_) { + Some(value) => Some(value), + None => return Err(InterpreterHalt::new_spanned("cast failure".to_string(), span).into()), + } + } + Expression::Err(_) => todo!(), + Expression::Identifier(identifier) if step == 0 => { + Some(self.lookup(identifier.name).expect_tc(identifier.span())?) + } + Expression::Literal(literal) if step == 0 => Some(match literal { + Literal::Boolean(b, ..) => Value::Bool(*b), + Literal::Integer(IntegerType::U8, s, ..) => Value::U8(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::U16, s, ..) => Value::U16(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::U32, s, ..) => Value::U32(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::U64, s, ..) => Value::U64(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::U128, s, ..) => Value::U128(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::I8, s, ..) => Value::I8(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::I16, s, ..) => Value::I16(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::I32, s, ..) => Value::I32(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::I64, s, ..) => Value::I64(s.parse().expect_tc(literal.span())?), + Literal::Integer(IntegerType::I128, s, ..) => Value::I128(s.parse().expect_tc(literal.span())?), + Literal::Field(s, ..) => Value::Field(format!("{s}field").parse().expect_tc(literal.span())?), + Literal::Group(group_literal) => match &**group_literal { + GroupLiteral::Single(s, ..) => Value::Group(format!("{s}group").parse().expect_tc(literal.span())?), + GroupLiteral::Tuple(_group_tuple) => todo!(), + }, + Literal::Address(s, ..) => { + if s.ends_with(".aleo") { + let program_id = ProgramID::from_str(s)?; + Value::Address(program_id.to_address()?) + } else { + Value::Address(s.parse().expect_tc(literal.span())?) + } + } + Literal::Scalar(s, ..) => Value::Scalar(format!("{s}scalar").parse().expect_tc(literal.span())?), + Literal::String(..) => tc_fail!(), + }), + Expression::Locator(_locator) => todo!(), + Expression::Struct(struct_) if step == 0 => { + struct_.members.iter().flat_map(|init| init.expression.as_ref()).for_each(push!()); + None + } + Expression::Struct(struct_) if step == 1 => { + // Collect all the key/value pairs into a HashMap. + let mut contents_tmp = HashMap::with_capacity(struct_.members.len()); + for initializer in struct_.members.iter() { + let name = initializer.identifier.name; + let value = if initializer.expression.is_some() { + self.pop_value()? + } else { + self.lookup(name).expect_tc(struct_.span())? + }; + contents_tmp.insert(name, value); + } + + // And now put them into an IndexMap in the correct order. + let program = self.current_program().expect("there should be a current program"); + let id = GlobalId { program, name: struct_.name.name }; + let struct_type = self.structs.get(&id).expect_tc(struct_.span())?; + let contents = struct_type + .iter() + .map(|sym| (*sym, contents_tmp.remove(sym).expect("we just inserted this"))) + .collect(); + + Some(Value::Struct(StructContents { name: struct_.name.name, contents })) + } + Expression::Ternary(ternary) if step == 0 => { + push!()(&*ternary.condition); + None + } + Expression::Ternary(ternary) if step == 1 => { + let condition = self.pop_value()?; + match condition { + Value::Bool(true) => push!()(&*ternary.if_true), + Value::Bool(false) => push!()(&*ternary.if_false), + _ => halt!(ternary.span(), "Invalid type for ternary expression {ternary}"), + } + None + } + Expression::Ternary(_) if step == 2 => Some(self.pop_value()?), + Expression::Tuple(tuple) if step == 0 => { + tuple.elements.iter().rev().for_each(push!()); + None + } + Expression::Tuple(tuple) if step == 1 => { + let len = self.values.len(); + let tuple_values = self.values.drain(len - tuple.elements.len()..).collect(); + Some(Value::Tuple(tuple_values)) + } + Expression::Unary(unary) if step == 0 => { + push!()(&*unary.receiver); + None + } + Expression::Unary(unary) if step == 1 => { + let value = self.pop_value()?; + Some(evaluate_unary(unary.span, unary.op, value)?) + } + Expression::Unit(_) if step == 0 => Some(Value::Unit), + x => unreachable!("Unexpected expression {x}"), + } { + assert_eq!(self.frames.len(), len); + self.frames.pop(); + self.values.push(value); + Ok(true) + } else { + self.frames[len - 1].step += 1; + Ok(false) + } + } + + /// Execute one step of the current element. + /// + /// Many Leo constructs require multiple steps. For instance, when executing a conditional, + /// the first step will push the condition expression to the stack. Once that has executed + /// and we've returned to the conditional, we push the `then` or `otherwise` block to the + /// stack. Once that has executed and we've returned to the conditional, the final step + /// does nothing. + pub fn step(&mut self) -> Result { + if self.frames.is_empty() { + return Err(InterpreterHalt::new("no execution frames available".into()).into()); + } + + let Frame { element, step, user_initiated } = self.frames.last().expect("there should be a frame"); + let user_initiated = *user_initiated; + match element { + Element::Block { block, function_body } => { + let finished = self.step_block(block, *function_body, *step); + Ok(StepResult { finished, value: None }) + } + Element::Statement(statement) => { + let finished = self.step_statement(statement, *step)?; + Ok(StepResult { finished, value: None }) + } + Element::Expression(expression) => { + let finished = self.step_expression(expression, *step)?; + let value = match (finished, user_initiated) { + (false, _) => None, + (true, false) => self.values.last().cloned(), + (true, true) => self.values.pop(), + }; + let maybe_future = if let Some(Value::Tuple(vals)) = &value { vals.last() } else { value.as_ref() }; + + if let Some(Value::Future(future)) = &maybe_future { + if user_initiated && !future.0.is_empty() { + self.futures.push(future.clone()); + } + } + Ok(StepResult { finished, value }) + } + Element::AleoExecution { .. } => { + self.step_aleo()?; + Ok(StepResult { finished: true, value: None }) + } + Element::DelayedCall(gid) if *step == 0 => { + match self.lookup_function(gid.program, gid.name).expect("function should exist") { + FunctionVariant::Leo(function) => { + assert!(function.variant == Variant::AsyncFunction); + let len = self.values.len(); + let values: Vec = self.values.drain(len - function.input.len()..).collect(); + self.contexts.push( + gid.program, + self.signer, + true, // is_async + ); + let param_names = function.input.iter().map(|input| input.identifier.name); + for (name, value) in param_names.zip(values) { + self.set_variable(name, value); + } + self.frames.last_mut().unwrap().step = 1; + self.frames.push(Frame { + step: 0, + element: Element::Block { block: &function.block, function_body: true }, + user_initiated: false, + }); + Ok(StepResult { finished: false, value: None }) + } + FunctionVariant::AleoFunction(function) => { + let Some(finalize_f) = function.finalize_logic() else { + panic!("must have finalize logic for a delayed call"); + }; + let len = self.values.len(); + let values_iter = self.values.drain(len - finalize_f.inputs().len()..); + self.contexts.push( + gid.program, + self.signer, + true, // is_async + ); + self.frames.last_mut().unwrap().step = 1; + self.frames.push(Frame { + step: 0, + element: Element::AleoExecution { + context: AleoContext::Finalize(finalize_f), + registers: values_iter.enumerate().map(|(i, v)| (i as u64, v)).collect(), + instruction_index: 0, + }, + user_initiated: false, + }); + Ok(StepResult { finished: false, value: None }) + } + FunctionVariant::AleoClosure(..) => panic!("A call to a closure can't be delayed"), + } + } + Element::DelayedCall(_gid) => { + assert_eq!(*step, 1); + let value = self.values.pop(); + self.frames.pop(); + Ok(StepResult { finished: true, value }) + } + } + } + + pub fn do_call( + &mut self, + function_program: Symbol, + function_name: Symbol, + arguments: impl Iterator, + finalize: bool, + span: Span, + ) -> Result<()> { + let Some(function_variant) = self.lookup_function(function_program, function_name) else { + halt!(span, "unknown function {function_program}.aleo/{function_name}"); + }; + match function_variant { + FunctionVariant::Leo(function) => { + let caller = if matches!(function.variant, Variant::Transition | Variant::AsyncTransition) { + self.new_caller() + } else { + self.signer + }; + if self.really_async && function.variant == Variant::AsyncFunction { + // Don't actually run the call now. + let async_ex = AsyncExecution { + function: GlobalId { name: function_name, program: function_program }, + arguments: arguments.collect(), + }; + self.values.push(Value::Future(Future(vec![async_ex]))); + } else { + let is_async = function.variant == Variant::AsyncFunction; + self.contexts.push(function_program, caller, is_async); + let param_names = function.input.iter().map(|input| input.identifier.name); + for (name, value) in param_names.zip(arguments) { + self.set_variable(name, value); + } + self.frames.push(Frame { + step: 0, + element: Element::Block { block: &function.block, function_body: true }, + user_initiated: false, + }); + } + } + FunctionVariant::AleoClosure(closure) => { + self.contexts.push(function_program, self.signer, false); + let context = AleoContext::Closure(closure); + self.frames.push(Frame { + step: 0, + element: Element::AleoExecution { + context, + registers: arguments.enumerate().map(|(i, v)| (i as u64, v)).collect(), + instruction_index: 0, + }, + user_initiated: false, + }); + } + FunctionVariant::AleoFunction(function) => { + let caller = self.new_caller(); + self.contexts.push(function_program, caller, false); + let context = if finalize { + let Some(finalize_f) = function.finalize_logic() else { + panic!("finalize call with no finalize logic"); + }; + AleoContext::Finalize(finalize_f) + } else { + AleoContext::Function(function) + }; + self.frames.push(Frame { + step: 0, + element: Element::AleoExecution { + context, + registers: arguments.enumerate().map(|(i, v)| (i as u64, v)).collect(), + instruction_index: 0, + }, + user_initiated: false, + }); + } + } + + Ok(()) + } + + pub fn evaluate_core_function( + &mut self, + core_function: CoreFunction, + arguments: &[Expression], + span: Span, + ) -> Result { + macro_rules! apply { + ($func: expr, $value: ident, $to: ident) => {{ + let v = self.pop_value()?; + let bits = v.$to(); + Value::$value($func(&bits).expect_tc(span)?) + }}; + } + + macro_rules! apply_cast { + ($func: expr, $value: ident, $to: ident) => {{ + let v = self.pop_value()?; + let bits = v.$to(); + let group = $func(&bits).expect_tc(span)?; + let x = group.to_x_coordinate(); + Value::$value(x.cast_lossy()) + }}; + } + + macro_rules! apply_cast_int { + ($func: expr, $value: ident, $int_ty: ident, $to: ident) => {{ + let v = self.pop_value()?; + let bits = v.$to(); + let group = $func(&bits).expect_tc(span)?; + let x = group.to_x_coordinate(); + let bits = x.to_bits_le(); + let mut result: $int_ty = 0; + for bit in 0..std::cmp::min($int_ty::BITS as usize, bits.len()) { + let setbit = (if bits[bit] { 1 } else { 0 }) << bit; + result |= setbit; + } + Value::$value(result) + }}; + } + + macro_rules! apply_cast2 { + ($func: expr, $value: ident) => {{ + let Value::Scalar(randomizer) = self.pop_value()? else { + tc_fail!(); + }; + let v = self.pop_value()?; + let bits = v.to_bits_le(); + let group = $func(&bits, &randomizer).expect_tc(span)?; + let x = group.to_x_coordinate(); + Value::$value(x.cast_lossy()) + }}; + } + + let value = match core_function { + CoreFunction::BHP256CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_bhp256, Address) + } + CoreFunction::BHP256CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_bhp256, Field) + } + CoreFunction::BHP256CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_bhp256, Group) + } + CoreFunction::BHP256HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_bhp256, Address, to_bits_le) + } + CoreFunction::BHP256HashToField => apply!(TestnetV0::hash_bhp256, Field, to_bits_le), + CoreFunction::BHP256HashToGroup => apply!(TestnetV0::hash_to_group_bhp256, Group, to_bits_le), + CoreFunction::BHP256HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, I8, i8, to_bits_le) + } + CoreFunction::BHP256HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, I16, i16, to_bits_le) + } + CoreFunction::BHP256HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, I32, i32, to_bits_le) + } + CoreFunction::BHP256HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, I64, i64, to_bits_le) + } + CoreFunction::BHP256HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, I128, i128, to_bits_le) + } + CoreFunction::BHP256HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, U8, u8, to_bits_le) + } + CoreFunction::BHP256HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, U16, u16, to_bits_le) + } + CoreFunction::BHP256HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, U32, u32, to_bits_le) + } + CoreFunction::BHP256HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, U64, u64, to_bits_le) + } + CoreFunction::BHP256HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp256, U128, u128, to_bits_le) + } + CoreFunction::BHP256HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_bhp256, Scalar, to_bits_le) + } + CoreFunction::BHP512CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_bhp512, Address) + } + CoreFunction::BHP512CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_bhp512, Field) + } + CoreFunction::BHP512CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_bhp512, Group) + } + CoreFunction::BHP512HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_bhp512, Address, to_bits_le) + } + CoreFunction::BHP512HashToField => apply!(TestnetV0::hash_bhp512, Field, to_bits_le), + CoreFunction::BHP512HashToGroup => apply!(TestnetV0::hash_to_group_bhp512, Group, to_bits_le), + CoreFunction::BHP512HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, I8, i8, to_bits_le) + } + CoreFunction::BHP512HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, I16, i16, to_bits_le) + } + CoreFunction::BHP512HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, I32, i32, to_bits_le) + } + CoreFunction::BHP512HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, I64, i64, to_bits_le) + } + CoreFunction::BHP512HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, I128, i128, to_bits_le) + } + CoreFunction::BHP512HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, U8, u8, to_bits_le) + } + CoreFunction::BHP512HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, U16, u16, to_bits_le) + } + CoreFunction::BHP512HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, U32, u32, to_bits_le) + } + CoreFunction::BHP512HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, U64, u64, to_bits_le) + } + CoreFunction::BHP512HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp512, U128, u128, to_bits_le) + } + CoreFunction::BHP512HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_bhp512, Scalar, to_bits_le) + } + CoreFunction::BHP768CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_bhp768, Address) + } + CoreFunction::BHP768CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_bhp768, Field) + } + CoreFunction::BHP768CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_bhp768, Group) + } + CoreFunction::BHP768HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_bhp768, Address, to_bits_le) + } + CoreFunction::BHP768HashToField => apply!(TestnetV0::hash_bhp768, Field, to_bits_le), + CoreFunction::BHP768HashToGroup => apply!(TestnetV0::hash_to_group_bhp768, Group, to_bits_le), + CoreFunction::BHP768HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, I8, i8, to_bits_le) + } + CoreFunction::BHP768HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, I16, i16, to_bits_le) + } + CoreFunction::BHP768HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, I32, i32, to_bits_le) + } + CoreFunction::BHP768HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, I64, i64, to_bits_le) + } + CoreFunction::BHP768HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, I128, i128, to_bits_le) + } + CoreFunction::BHP768HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, U8, u8, to_bits_le) + } + CoreFunction::BHP768HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, U16, u16, to_bits_le) + } + CoreFunction::BHP768HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, U32, u32, to_bits_le) + } + CoreFunction::BHP768HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, U64, u64, to_bits_le) + } + CoreFunction::BHP768HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp768, U128, u128, to_bits_le) + } + CoreFunction::BHP768HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_bhp768, Scalar, to_bits_le) + } + CoreFunction::BHP1024CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_bhp1024, Address) + } + CoreFunction::BHP1024CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_bhp1024, Field) + } + CoreFunction::BHP1024CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_bhp1024, Group) + } + CoreFunction::BHP1024HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_bhp1024, Address, to_bits_le) + } + CoreFunction::BHP1024HashToField => apply!(TestnetV0::hash_bhp1024, Field, to_bits_le), + CoreFunction::BHP1024HashToGroup => apply!(TestnetV0::hash_to_group_bhp1024, Group, to_bits_le), + CoreFunction::BHP1024HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, I8, i8, to_bits_le) + } + CoreFunction::BHP1024HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, I16, i16, to_bits_le) + } + CoreFunction::BHP1024HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, I32, i32, to_bits_le) + } + CoreFunction::BHP1024HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, I64, i64, to_bits_le) + } + CoreFunction::BHP1024HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, I128, i128, to_bits_le) + } + CoreFunction::BHP1024HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, U8, u8, to_bits_le) + } + CoreFunction::BHP1024HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, U16, u16, to_bits_le) + } + CoreFunction::BHP1024HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, U32, u32, to_bits_le) + } + CoreFunction::BHP1024HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, U64, u64, to_bits_le) + } + CoreFunction::BHP1024HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_bhp1024, U128, u128, to_bits_le) + } + CoreFunction::BHP1024HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_bhp1024, Scalar, to_bits_le) + } + CoreFunction::ChaChaRandAddress => Value::Address(self.rng.gen()), + CoreFunction::ChaChaRandBool => Value::Bool(self.rng.gen()), + CoreFunction::ChaChaRandField => Value::Field(self.rng.gen()), + CoreFunction::ChaChaRandGroup => Value::Group(self.rng.gen()), + CoreFunction::ChaChaRandI8 => Value::I8(self.rng.gen()), + CoreFunction::ChaChaRandI16 => Value::I16(self.rng.gen()), + CoreFunction::ChaChaRandI32 => Value::I32(self.rng.gen()), + CoreFunction::ChaChaRandI64 => Value::I64(self.rng.gen()), + CoreFunction::ChaChaRandI128 => Value::I128(self.rng.gen()), + CoreFunction::ChaChaRandU8 => Value::U8(self.rng.gen()), + CoreFunction::ChaChaRandU16 => Value::U16(self.rng.gen()), + CoreFunction::ChaChaRandU32 => Value::U32(self.rng.gen()), + CoreFunction::ChaChaRandU64 => Value::U64(self.rng.gen()), + CoreFunction::ChaChaRandU128 => Value::U128(self.rng.gen()), + CoreFunction::ChaChaRandScalar => Value::Scalar(self.rng.gen()), + CoreFunction::Keccak256HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::Keccak256HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::Keccak256HashToGroup => { + apply!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + Group, + to_bits_le + ) + } + CoreFunction::Keccak256HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::Keccak256HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + + CoreFunction::Keccak256HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::Keccak256HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::Keccak256HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::Keccak256HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::Keccak256HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::Keccak256HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::Keccak256HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::Keccak256HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::Keccak256HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_keccak256(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::Keccak384HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::Keccak384HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::Keccak384HashToGroup => { + apply!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + Group, + to_bits_le + ) + } + CoreFunction::Keccak384HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::Keccak384HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + CoreFunction::Keccak384HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::Keccak384HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::Keccak384HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::Keccak384HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::Keccak384HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::Keccak384HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::Keccak384HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::Keccak384HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::Keccak384HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak384(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::Keccak512HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::Keccak512HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::Keccak512HashToGroup => { + apply!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + Group, + to_bits_le + ) + } + CoreFunction::Keccak512HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::Keccak512HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + CoreFunction::Keccak512HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::Keccak512HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::Keccak512HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::Keccak512HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::Keccak512HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::Keccak512HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::Keccak512HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::Keccak512HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::Keccak512HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_keccak512(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::Pedersen64CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_ped64, Address) + } + CoreFunction::Pedersen64CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_ped64, Field) + } + CoreFunction::Pedersen64CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_ped64, Group) + } + CoreFunction::Pedersen64HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_ped64, Address, to_bits_le) + } + CoreFunction::Pedersen64HashToField => apply!(TestnetV0::hash_ped64, Field, to_bits_le), + CoreFunction::Pedersen64HashToGroup => apply!(TestnetV0::hash_to_group_ped64, Group, to_bits_le), + CoreFunction::Pedersen64HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I8, i8, to_bits_le) + } + CoreFunction::Pedersen64HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I16, i16, to_bits_le) + } + CoreFunction::Pedersen64HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I32, i32, to_bits_le) + } + CoreFunction::Pedersen64HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I64, i64, to_bits_le) + } + CoreFunction::Pedersen64HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I128, i128, to_bits_le) + } + CoreFunction::Pedersen64HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U8, u8, to_bits_le) + } + CoreFunction::Pedersen64HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U16, u16, to_bits_le) + } + CoreFunction::Pedersen64HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U32, u32, to_bits_le) + } + CoreFunction::Pedersen64HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U64, u64, to_bits_le) + } + CoreFunction::Pedersen64HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U128, u128, to_bits_le) + } + CoreFunction::Pedersen64HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_ped64, Scalar, to_bits_le) + } + CoreFunction::Pedersen128HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_ped128, Address, to_bits_le) + } + CoreFunction::Pedersen128HashToField => { + apply_cast!(TestnetV0::hash_to_group_ped128, Field, to_bits_le) + } + CoreFunction::Pedersen128HashToGroup => { + apply_cast!(TestnetV0::hash_to_group_ped128, Group, to_bits_le) + } + CoreFunction::Pedersen128HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, I8, i8, to_bits_le) + } + CoreFunction::Pedersen128HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I16, i16, to_bits_le) + } + CoreFunction::Pedersen128HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, I32, i32, to_bits_le) + } + CoreFunction::Pedersen128HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, I64, i64, to_bits_le) + } + CoreFunction::Pedersen128HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, I128, i128, to_bits_le) + } + CoreFunction::Pedersen128HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, U8, u8, to_bits_le) + } + CoreFunction::Pedersen128HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U16, u16, to_bits_le) + } + CoreFunction::Pedersen128HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, U32, u32, to_bits_le) + } + CoreFunction::Pedersen128HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_ped64, U64, u64, to_bits_le) + } + CoreFunction::Pedersen128HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_ped128, U128, u128, to_bits_le) + } + CoreFunction::Pedersen128HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_ped128, Scalar, to_bits_le) + } + CoreFunction::Pedersen128CommitToAddress => { + apply_cast2!(TestnetV0::commit_to_group_ped128, Address) + } + CoreFunction::Pedersen128CommitToField => { + apply_cast2!(TestnetV0::commit_to_group_ped128, Field) + } + CoreFunction::Pedersen128CommitToGroup => { + apply_cast2!(TestnetV0::commit_to_group_ped128, Group) + } + CoreFunction::Poseidon2HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_psd2, Address, to_fields) + } + CoreFunction::Poseidon2HashToField => { + apply!(TestnetV0::hash_psd2, Field, to_fields) + } + CoreFunction::Poseidon2HashToGroup => { + apply_cast!(TestnetV0::hash_to_group_psd2, Group, to_fields) + } + CoreFunction::Poseidon2HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, I8, i8, to_fields) + } + CoreFunction::Poseidon2HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, I16, i16, to_fields) + } + CoreFunction::Poseidon2HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, I32, i32, to_fields) + } + CoreFunction::Poseidon2HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, I64, i64, to_fields) + } + CoreFunction::Poseidon2HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, I128, i128, to_fields) + } + CoreFunction::Poseidon2HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, U8, u8, to_fields) + } + CoreFunction::Poseidon2HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, U16, u16, to_fields) + } + CoreFunction::Poseidon2HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, U32, u32, to_fields) + } + CoreFunction::Poseidon2HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, U64, u64, to_fields) + } + CoreFunction::Poseidon2HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd2, U128, u128, to_fields) + } + CoreFunction::Poseidon2HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_psd4, Scalar, to_fields) + } + CoreFunction::Poseidon4HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_psd4, Address, to_fields) + } + CoreFunction::Poseidon4HashToField => { + apply!(TestnetV0::hash_psd4, Field, to_fields) + } + CoreFunction::Poseidon4HashToGroup => { + apply_cast!(TestnetV0::hash_to_group_psd4, Group, to_fields) + } + CoreFunction::Poseidon4HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, I8, i8, to_fields) + } + CoreFunction::Poseidon4HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, I16, i16, to_fields) + } + CoreFunction::Poseidon4HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, I32, i32, to_fields) + } + CoreFunction::Poseidon4HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, I64, i64, to_fields) + } + CoreFunction::Poseidon4HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, I128, i128, to_fields) + } + CoreFunction::Poseidon4HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, U8, u8, to_fields) + } + CoreFunction::Poseidon4HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, U16, u16, to_fields) + } + CoreFunction::Poseidon4HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, U32, u32, to_fields) + } + CoreFunction::Poseidon4HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, U64, u64, to_fields) + } + CoreFunction::Poseidon4HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd4, U128, u128, to_fields) + } + CoreFunction::Poseidon4HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_psd4, Scalar, to_fields) + } + CoreFunction::Poseidon8HashToAddress => { + apply_cast!(TestnetV0::hash_to_group_psd8, Address, to_fields) + } + CoreFunction::Poseidon8HashToField => { + apply!(TestnetV0::hash_psd8, Field, to_fields) + } + CoreFunction::Poseidon8HashToGroup => { + apply_cast!(TestnetV0::hash_to_group_psd8, Group, to_fields) + } + CoreFunction::Poseidon8HashToI8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, I8, i8, to_fields) + } + CoreFunction::Poseidon8HashToI16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, I16, i16, to_fields) + } + CoreFunction::Poseidon8HashToI32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, I32, i32, to_fields) + } + CoreFunction::Poseidon8HashToI64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, I64, i64, to_fields) + } + CoreFunction::Poseidon8HashToI128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, I128, i128, to_fields) + } + CoreFunction::Poseidon8HashToU8 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, U8, u8, to_fields) + } + CoreFunction::Poseidon8HashToU16 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, U16, u16, to_fields) + } + CoreFunction::Poseidon8HashToU32 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, U32, u32, to_fields) + } + CoreFunction::Poseidon8HashToU64 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, U64, u64, to_fields) + } + CoreFunction::Poseidon8HashToU128 => { + apply_cast_int!(TestnetV0::hash_to_group_psd8, U128, u128, to_fields) + } + CoreFunction::Poseidon8HashToScalar => { + apply_cast!(TestnetV0::hash_to_group_psd8, Scalar, to_fields) + } + CoreFunction::SHA3_256HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::SHA3_256HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::SHA3_256HashToGroup => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + Group, + to_bits_le + ), + CoreFunction::SHA3_256HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::SHA3_256HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + CoreFunction::SHA3_256HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::SHA3_256HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::SHA3_256HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::SHA3_256HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::SHA3_256HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::SHA3_256HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::SHA3_256HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::SHA3_256HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::SHA3_256HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp256(&TestnetV0::hash_sha3_256(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::SHA3_384HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::SHA3_384HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::SHA3_384HashToGroup => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + Group, + to_bits_le + ), + CoreFunction::SHA3_384HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::SHA3_384HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + CoreFunction::SHA3_384HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::SHA3_384HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::SHA3_384HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::SHA3_384HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::SHA3_384HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::SHA3_384HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::SHA3_384HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::SHA3_384HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::SHA3_384HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_384(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::SHA3_512HashToAddress => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + Address, + to_bits_le + ), + CoreFunction::SHA3_512HashToField => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + Field, + to_bits_le + ), + CoreFunction::SHA3_512HashToGroup => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + Group, + to_bits_le + ), + CoreFunction::SHA3_512HashToI8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + I8, + i8, + to_bits_le + ), + CoreFunction::SHA3_512HashToI16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + I16, + i16, + to_bits_le + ), + CoreFunction::SHA3_512HashToI32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + I32, + i32, + to_bits_le + ), + CoreFunction::SHA3_512HashToI64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + I64, + i64, + to_bits_le + ), + CoreFunction::SHA3_512HashToI128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + I128, + i128, + to_bits_le + ), + CoreFunction::SHA3_512HashToU8 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + U8, + u8, + to_bits_le + ), + CoreFunction::SHA3_512HashToU16 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + U16, + u16, + to_bits_le + ), + CoreFunction::SHA3_512HashToU32 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + U32, + u32, + to_bits_le + ), + CoreFunction::SHA3_512HashToU64 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + U64, + u64, + to_bits_le + ), + CoreFunction::SHA3_512HashToU128 => apply_cast_int!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + U128, + u128, + to_bits_le + ), + CoreFunction::SHA3_512HashToScalar => apply_cast!( + |v| TestnetV0::hash_to_group_bhp512(&TestnetV0::hash_sha3_512(v).expect_tc(span)?), + Scalar, + to_bits_le + ), + CoreFunction::MappingGet => { + let key = self.values.pop().expect_tc(span)?; + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + match self.lookup_mapping(program, name).and_then(|mapping| mapping.get(&key)) { + Some(v) => v.clone(), + None => halt!(span, "map lookup failure"), + } + } + CoreFunction::MappingGetOrUse => { + let use_value = self.values.pop().expect_tc(span)?; + let key = self.values.pop().expect_tc(span)?; + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + match self.lookup_mapping(program, name).and_then(|mapping| mapping.get(&key)) { + Some(v) => v.clone(), + None => use_value, + } + } + CoreFunction::MappingSet => { + let value = self.pop_value()?; + let key = self.pop_value()?; + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + if let Some(mapping) = self.lookup_mapping_mut(program, name) { + mapping.insert(key, value); + } else { + tc_fail!(); + } + Value::Unit + } + CoreFunction::MappingRemove => { + let key = self.pop_value()?; + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + if let Some(mapping) = self.lookup_mapping_mut(program, name) { + mapping.remove(&key); + } else { + tc_fail!(); + } + Value::Unit + } + CoreFunction::MappingContains => { + let key = self.pop_value()?; + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + if let Some(mapping) = self.lookup_mapping_mut(program, name) { + Value::Bool(mapping.contains_key(&key)) + } else { + tc_fail!(); + } + } + CoreFunction::GroupToXCoordinate => { + let Value::Group(g) = self.pop_value()? else { + tc_fail!(); + }; + Value::Field(g.to_x_coordinate()) + } + CoreFunction::GroupToYCoordinate => { + let Value::Group(g) = self.pop_value()? else { + tc_fail!(); + }; + Value::Field(g.to_y_coordinate()) + } + CoreFunction::SignatureVerify => todo!(), + CoreFunction::FutureAwait => { + let value = self.pop_value()?; + let Value::Future(future) = value else { + halt!(span, "Invalid value for await: {value}"); + }; + for async_execution in future.0 { + self.values.extend(async_execution.arguments.into_iter()); + self.frames.push(Frame { + step: 0, + element: Element::DelayedCall(async_execution.function), + user_initiated: false, + }); + } + Value::Unit + } + CoreFunction::CheatCodePrintMapping => { + let (program, name) = match &arguments[0] { + Expression::Identifier(id) => (None, id.name), + Expression::Locator(locator) => (Some(locator.program.name.name), locator.name), + _ => tc_fail!(), + }; + if let Some(mapping) = self.lookup_mapping(program, name) { + // TODO: What is the appropriate way to print this to the console. + // Print the name of the mapping. + println!( + "Mapping: {}", + if let Some(program) = program { format!("{}/{}", program, name) } else { name.to_string() } + ); + // Print the contents of the mapping. + for (key, value) in mapping { + println!(" {} -> {}", key, value); + } + } else { + tc_fail!(); + } + Value::Unit + } + CoreFunction::CheatCodeSetBlockHeight => { + let Value::U32(height) = self.pop_value()? else { + tc_fail!(); + }; + self.set_block_height(height); + Value::Unit + } + }; + + Ok(value) + } +} + +#[derive(Clone, Debug)] +pub struct StepResult { + /// Has this element completely finished running? + pub finished: bool, + + /// If the element was an expression, here's its value. + pub value: Option, +} + +/// Evaluate a binary operation. +pub fn evaluate_binary(span: Span, op: BinaryOperation, lhs: Value, rhs: Value) -> Result { + let value = match op { + BinaryOperation::Add => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_add(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_add(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_add(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_add(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_add(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_add(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_add(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_add(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_add(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_add(y).map(Value::I128), + (Value::Group(x), Value::Group(y)) => Some(Value::Group(x + y)), + (Value::Field(x), Value::Field(y)) => Some(Value::Field(x + y)), + (Value::Scalar(x), Value::Scalar(y)) => Some(Value::Scalar(x + y)), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "add overflow"); + }; + value + } + BinaryOperation::AddWrapped => match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => Value::U8(x.wrapping_add(y)), + (Value::U16(x), Value::U16(y)) => Value::U16(x.wrapping_add(y)), + (Value::U32(x), Value::U32(y)) => Value::U32(x.wrapping_add(y)), + (Value::U64(x), Value::U64(y)) => Value::U64(x.wrapping_add(y)), + (Value::U128(x), Value::U128(y)) => Value::U128(x.wrapping_add(y)), + (Value::I8(x), Value::I8(y)) => Value::I8(x.wrapping_add(y)), + (Value::I16(x), Value::I16(y)) => Value::I16(x.wrapping_add(y)), + (Value::I32(x), Value::I32(y)) => Value::I32(x.wrapping_add(y)), + (Value::I64(x), Value::I64(y)) => Value::I64(x.wrapping_add(y)), + (Value::I128(x), Value::I128(y)) => Value::I128(x.wrapping_add(y)), + _ => halt!(span, "Type error"), + }, + BinaryOperation::And => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(x && y), + _ => halt!(span, "Type error"), + }, + BinaryOperation::BitwiseAnd => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(x & y), + (Value::U8(x), Value::U8(y)) => Value::U8(x & y), + (Value::U16(x), Value::U16(y)) => Value::U16(x & y), + (Value::U32(x), Value::U32(y)) => Value::U32(x & y), + (Value::U64(x), Value::U64(y)) => Value::U64(x & y), + (Value::U128(x), Value::U128(y)) => Value::U128(x & y), + (Value::I8(x), Value::I8(y)) => Value::I8(x & y), + (Value::I16(x), Value::I16(y)) => Value::I16(x & y), + (Value::I32(x), Value::I32(y)) => Value::I32(x & y), + (Value::I64(x), Value::I64(y)) => Value::I64(x & y), + (Value::I128(x), Value::I128(y)) => Value::I128(x & y), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Div => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_div(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_div(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_div(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_div(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_div(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_div(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_div(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_div(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_div(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_div(y).map(Value::I128), + (Value::Field(x), Value::Field(y)) => y.inverse().map(|y| Value::Field(x * y)).ok(), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "div overflow"); + }; + value + } + BinaryOperation::DivWrapped => match (lhs, rhs) { + (Value::U8(_), Value::U8(0)) + | (Value::U16(_), Value::U16(0)) + | (Value::U32(_), Value::U32(0)) + | (Value::U64(_), Value::U64(0)) + | (Value::U128(_), Value::U128(0)) + | (Value::I8(_), Value::I8(0)) + | (Value::I16(_), Value::I16(0)) + | (Value::I32(_), Value::I32(0)) + | (Value::I64(_), Value::I64(0)) + | (Value::I128(_), Value::I128(0)) => halt!(span, "divide by 0"), + (Value::U8(x), Value::U8(y)) => Value::U8(x.wrapping_div(y)), + (Value::U16(x), Value::U16(y)) => Value::U16(x.wrapping_div(y)), + (Value::U32(x), Value::U32(y)) => Value::U32(x.wrapping_div(y)), + (Value::U64(x), Value::U64(y)) => Value::U64(x.wrapping_div(y)), + (Value::U128(x), Value::U128(y)) => Value::U128(x.wrapping_div(y)), + (Value::I8(x), Value::I8(y)) => Value::I8(x.wrapping_div(y)), + (Value::I16(x), Value::I16(y)) => Value::I16(x.wrapping_div(y)), + (Value::I32(x), Value::I32(y)) => Value::I32(x.wrapping_div(y)), + (Value::I64(x), Value::I64(y)) => Value::I64(x.wrapping_div(y)), + (Value::I128(x), Value::I128(y)) => Value::I128(x.wrapping_div(y)), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Eq => Value::Bool(lhs.eq(&rhs)?), + BinaryOperation::Gte => Value::Bool(lhs.gte(&rhs)?), + BinaryOperation::Gt => Value::Bool(lhs.gt(&rhs)?), + BinaryOperation::Lte => Value::Bool(lhs.lte(&rhs)?), + BinaryOperation::Lt => Value::Bool(lhs.lt(&rhs)?), + BinaryOperation::Mod => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_rem(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_rem(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_rem(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_rem(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_rem(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_rem(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_rem(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_rem(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_rem(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_rem(y).map(Value::I128), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "mod overflow"); + }; + value + } + BinaryOperation::Mul => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_mul(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_mul(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_mul(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_mul(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_mul(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_mul(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_mul(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_mul(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_mul(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_mul(y).map(Value::I128), + (Value::Field(x), Value::Field(y)) => Some(Value::Field(x * y)), + (Value::Group(x), Value::Scalar(y)) => Some(Value::Group(x * y)), + (Value::Scalar(x), Value::Group(y)) => Some(Value::Group(x * y)), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "mul overflow"); + }; + value + } + BinaryOperation::MulWrapped => match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => Value::U8(x.wrapping_mul(y)), + (Value::U16(x), Value::U16(y)) => Value::U16(x.wrapping_mul(y)), + (Value::U32(x), Value::U32(y)) => Value::U32(x.wrapping_mul(y)), + (Value::U64(x), Value::U64(y)) => Value::U64(x.wrapping_mul(y)), + (Value::U128(x), Value::U128(y)) => Value::U128(x.wrapping_mul(y)), + (Value::I8(x), Value::I8(y)) => Value::I8(x.wrapping_mul(y)), + (Value::I16(x), Value::I16(y)) => Value::I16(x.wrapping_mul(y)), + (Value::I32(x), Value::I32(y)) => Value::I32(x.wrapping_mul(y)), + (Value::I64(x), Value::I64(y)) => Value::I64(x.wrapping_mul(y)), + (Value::I128(x), Value::I128(y)) => Value::I128(x.wrapping_mul(y)), + (Value::Field(_), Value::Field(_)) => todo!(), + _ => halt!(span, "Type error"), + }, + + BinaryOperation::Nand => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(!(x & y)), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Neq => Value::Bool(lhs.neq(&rhs)?), + BinaryOperation::Nor => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(!(x | y)), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Or => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(x | y), + _ => halt!(span, "Type error"), + }, + BinaryOperation::BitwiseOr => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(x | y), + (Value::U8(x), Value::U8(y)) => Value::U8(x | y), + (Value::U16(x), Value::U16(y)) => Value::U16(x | y), + (Value::U32(x), Value::U32(y)) => Value::U32(x | y), + (Value::U64(x), Value::U64(y)) => Value::U64(x | y), + (Value::U128(x), Value::U128(y)) => Value::U128(x | y), + (Value::I8(x), Value::I8(y)) => Value::I8(x | y), + (Value::I16(x), Value::I16(y)) => Value::I16(x | y), + (Value::I32(x), Value::I32(y)) => Value::I32(x | y), + (Value::I64(x), Value::I64(y)) => Value::I64(x | y), + (Value::I128(x), Value::I128(y)) => Value::I128(x | y), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Pow => { + if let (Value::Field(x), Value::Field(y)) = (&lhs, &rhs) { + Value::Field(x.pow(y)) + } else { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => tc_fail!(), + }; + + let Some(value) = (match lhs { + Value::U8(x) => x.checked_pow(rhs).map(Value::U8), + Value::U16(x) => x.checked_pow(rhs).map(Value::U16), + Value::U32(x) => x.checked_pow(rhs).map(Value::U32), + Value::U64(x) => x.checked_pow(rhs).map(Value::U64), + Value::U128(x) => x.checked_pow(rhs).map(Value::U128), + Value::I8(x) => x.checked_pow(rhs).map(Value::I8), + Value::I16(x) => x.checked_pow(rhs).map(Value::I16), + Value::I32(x) => x.checked_pow(rhs).map(Value::I32), + Value::I64(x) => x.checked_pow(rhs).map(Value::I64), + Value::I128(x) => x.checked_pow(rhs).map(Value::I128), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "pow overflow"); + }; + value + } + } + BinaryOperation::PowWrapped => { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => halt!(span, "Type error"), + }; + + match lhs { + Value::U8(x) => Value::U8(x.wrapping_pow(rhs)), + Value::U16(x) => Value::U16(x.wrapping_pow(rhs)), + Value::U32(x) => Value::U32(x.wrapping_pow(rhs)), + Value::U64(x) => Value::U64(x.wrapping_pow(rhs)), + Value::U128(x) => Value::U128(x.wrapping_pow(rhs)), + Value::I8(x) => Value::I8(x.wrapping_pow(rhs)), + Value::I16(x) => Value::I16(x.wrapping_pow(rhs)), + Value::I32(x) => Value::I32(x.wrapping_pow(rhs)), + Value::I64(x) => Value::I64(x.wrapping_pow(rhs)), + Value::I128(x) => Value::I128(x.wrapping_pow(rhs)), + _ => halt!(span, "Type error"), + } + } + BinaryOperation::Rem => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_rem(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_rem(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_rem(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_rem(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_rem(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_rem(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_rem(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_rem(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_rem(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_rem(y).map(Value::I128), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "rem error"); + }; + value + } + BinaryOperation::RemWrapped => match (lhs, rhs) { + (Value::U8(_), Value::U8(0)) + | (Value::U16(_), Value::U16(0)) + | (Value::U32(_), Value::U32(0)) + | (Value::U64(_), Value::U64(0)) + | (Value::U128(_), Value::U128(0)) + | (Value::I8(_), Value::I8(0)) + | (Value::I16(_), Value::I16(0)) + | (Value::I32(_), Value::I32(0)) + | (Value::I64(_), Value::I64(0)) + | (Value::I128(_), Value::I128(0)) => halt!(span, "rem by 0"), + (Value::U8(x), Value::U8(y)) => Value::U8(x.wrapping_rem(y)), + (Value::U16(x), Value::U16(y)) => Value::U16(x.wrapping_rem(y)), + (Value::U32(x), Value::U32(y)) => Value::U32(x.wrapping_rem(y)), + (Value::U64(x), Value::U64(y)) => Value::U64(x.wrapping_rem(y)), + (Value::U128(x), Value::U128(y)) => Value::U128(x.wrapping_rem(y)), + (Value::I8(x), Value::I8(y)) => Value::I8(x.wrapping_rem(y)), + (Value::I16(x), Value::I16(y)) => Value::I16(x.wrapping_rem(y)), + (Value::I32(x), Value::I32(y)) => Value::I32(x.wrapping_rem(y)), + (Value::I64(x), Value::I64(y)) => Value::I64(x.wrapping_rem(y)), + (Value::I128(x), Value::I128(y)) => Value::I128(x.wrapping_rem(y)), + _ => halt!(span, "Type error"), + }, + BinaryOperation::Shl => { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => halt!(span, "Type error"), + }; + match lhs { + Value::U8(_) | Value::I8(_) if rhs >= 8 => halt!(span, "shl overflow"), + Value::U16(_) | Value::I16(_) if rhs >= 16 => halt!(span, "shl overflow"), + Value::U32(_) | Value::I32(_) if rhs >= 32 => halt!(span, "shl overflow"), + Value::U64(_) | Value::I64(_) if rhs >= 64 => halt!(span, "shl overflow"), + Value::U128(_) | Value::I128(_) if rhs >= 128 => halt!(span, "shl overflow"), + _ => {} + } + + // Aleo's shl halts if set bits are shifted out. + let shifted = lhs.simple_shl(rhs); + let reshifted = shifted.simple_shr(rhs); + if lhs.eq(&reshifted)? { + shifted + } else { + halt!(span, "shl overflow"); + } + } + + BinaryOperation::ShlWrapped => { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => halt!(span, "Type error"), + }; + match lhs { + Value::U8(x) => Value::U8(x.wrapping_shl(rhs)), + Value::U16(x) => Value::U16(x.wrapping_shl(rhs)), + Value::U32(x) => Value::U32(x.wrapping_shl(rhs)), + Value::U64(x) => Value::U64(x.wrapping_shl(rhs)), + Value::U128(x) => Value::U128(x.wrapping_shl(rhs)), + Value::I8(x) => Value::I8(x.wrapping_shl(rhs)), + Value::I16(x) => Value::I16(x.wrapping_shl(rhs)), + Value::I32(x) => Value::I32(x.wrapping_shl(rhs)), + Value::I64(x) => Value::I64(x.wrapping_shl(rhs)), + Value::I128(x) => Value::I128(x.wrapping_shl(rhs)), + _ => halt!(span, "Type error"), + } + } + + BinaryOperation::Shr => { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => halt!(span, "Type error"), + }; + + match lhs { + Value::U8(_) | Value::I8(_) if rhs >= 8 => halt!(span, "shr overflow"), + Value::U16(_) | Value::I16(_) if rhs >= 16 => halt!(span, "shr overflow"), + Value::U32(_) | Value::I32(_) if rhs >= 32 => halt!(span, "shr overflow"), + Value::U64(_) | Value::I64(_) if rhs >= 64 => halt!(span, "shr overflow"), + Value::U128(_) | Value::I128(_) if rhs >= 128 => halt!(span, "shr overflow"), + _ => {} + } + + lhs.simple_shr(rhs) + } + + BinaryOperation::ShrWrapped => { + let rhs: u32 = match rhs { + Value::U8(y) => y.into(), + Value::U16(y) => y.into(), + Value::U32(y) => y, + _ => halt!(span, "Type error"), + }; + + match lhs { + Value::U8(x) => Value::U8(x.wrapping_shr(rhs)), + Value::U16(x) => Value::U16(x.wrapping_shr(rhs)), + Value::U32(x) => Value::U32(x.wrapping_shr(rhs)), + Value::U64(x) => Value::U64(x.wrapping_shr(rhs)), + Value::U128(x) => Value::U128(x.wrapping_shr(rhs)), + Value::I8(x) => Value::I8(x.wrapping_shr(rhs)), + Value::I16(x) => Value::I16(x.wrapping_shr(rhs)), + Value::I32(x) => Value::I32(x.wrapping_shr(rhs)), + Value::I64(x) => Value::I64(x.wrapping_shr(rhs)), + Value::I128(x) => Value::I128(x.wrapping_shr(rhs)), + _ => halt!(span, "Type error"), + } + } + + BinaryOperation::Sub => { + let Some(value) = (match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => x.checked_sub(y).map(Value::U8), + (Value::U16(x), Value::U16(y)) => x.checked_sub(y).map(Value::U16), + (Value::U32(x), Value::U32(y)) => x.checked_sub(y).map(Value::U32), + (Value::U64(x), Value::U64(y)) => x.checked_sub(y).map(Value::U64), + (Value::U128(x), Value::U128(y)) => x.checked_sub(y).map(Value::U128), + (Value::I8(x), Value::I8(y)) => x.checked_sub(y).map(Value::I8), + (Value::I16(x), Value::I16(y)) => x.checked_sub(y).map(Value::I16), + (Value::I32(x), Value::I32(y)) => x.checked_sub(y).map(Value::I32), + (Value::I64(x), Value::I64(y)) => x.checked_sub(y).map(Value::I64), + (Value::I128(x), Value::I128(y)) => x.checked_sub(y).map(Value::I128), + (Value::Group(x), Value::Group(y)) => Some(Value::Group(x - y)), + (Value::Field(x), Value::Field(y)) => Some(Value::Field(x - y)), + _ => halt!(span, "Type error"), + }) else { + halt!(span, "sub overflow"); + }; + value + } + + BinaryOperation::SubWrapped => match (lhs, rhs) { + (Value::U8(x), Value::U8(y)) => Value::U8(x.wrapping_sub(y)), + (Value::U16(x), Value::U16(y)) => Value::U16(x.wrapping_sub(y)), + (Value::U32(x), Value::U32(y)) => Value::U32(x.wrapping_sub(y)), + (Value::U64(x), Value::U64(y)) => Value::U64(x.wrapping_sub(y)), + (Value::U128(x), Value::U128(y)) => Value::U128(x.wrapping_sub(y)), + (Value::I8(x), Value::I8(y)) => Value::I8(x.wrapping_sub(y)), + (Value::I16(x), Value::I16(y)) => Value::I16(x.wrapping_sub(y)), + (Value::I32(x), Value::I32(y)) => Value::I32(x.wrapping_sub(y)), + (Value::I64(x), Value::I64(y)) => Value::I64(x.wrapping_sub(y)), + (Value::I128(x), Value::I128(y)) => Value::I128(x.wrapping_sub(y)), + _ => halt!(span, "Type error"), + }, + + BinaryOperation::Xor => match (lhs, rhs) { + (Value::Bool(x), Value::Bool(y)) => Value::Bool(x ^ y), + (Value::U8(x), Value::U8(y)) => Value::U8(x ^ y), + (Value::U16(x), Value::U16(y)) => Value::U16(x ^ y), + (Value::U32(x), Value::U32(y)) => Value::U32(x ^ y), + (Value::U64(x), Value::U64(y)) => Value::U64(x ^ y), + (Value::U128(x), Value::U128(y)) => Value::U128(x ^ y), + (Value::I8(x), Value::I8(y)) => Value::I8(x ^ y), + (Value::I16(x), Value::I16(y)) => Value::I16(x ^ y), + (Value::I32(x), Value::I32(y)) => Value::I32(x ^ y), + (Value::I64(x), Value::I64(y)) => Value::I64(x ^ y), + (Value::I128(x), Value::I128(y)) => Value::I128(x ^ y), + _ => halt!(span, "Type error"), + }, + }; + Ok(value) +} + +/// Evaluate a unary operation. +pub fn evaluate_unary(span: Span, op: UnaryOperation, value: Value) -> Result { + let value_result = match op { + UnaryOperation::Abs => match value { + Value::I8(x) => { + if x == i8::MIN { + halt!(span, "abs overflow"); + } else { + Value::I8(x.abs()) + } + } + Value::I16(x) => { + if x == i16::MIN { + halt!(span, "abs overflow"); + } else { + Value::I16(x.abs()) + } + } + Value::I32(x) => { + if x == i32::MIN { + halt!(span, "abs overflow"); + } else { + Value::I32(x.abs()) + } + } + Value::I64(x) => { + if x == i64::MIN { + halt!(span, "abs overflow"); + } else { + Value::I64(x.abs()) + } + } + Value::I128(x) => { + if x == i128::MIN { + halt!(span, "abs overflow"); + } else { + Value::I128(x.abs()) + } + } + _ => halt!(span, "Type error"), + }, + UnaryOperation::AbsWrapped => match value { + Value::I8(x) => Value::I8(x.unsigned_abs() as i8), + Value::I16(x) => Value::I16(x.unsigned_abs() as i16), + Value::I32(x) => Value::I32(x.unsigned_abs() as i32), + Value::I64(x) => Value::I64(x.unsigned_abs() as i64), + Value::I128(x) => Value::I128(x.unsigned_abs() as i128), + _ => halt!(span, "Type error"), + }, + UnaryOperation::Double => match value { + Value::Field(x) => Value::Field(x.double()), + Value::Group(x) => Value::Group(x.double()), + _ => halt!(span, "Type error"), + }, + UnaryOperation::Inverse => match value { + Value::Field(x) => { + let Ok(y) = x.inverse() else { + halt!(span, "attempt to invert 0field"); + }; + Value::Field(y) + } + _ => halt!(span, "Can only invert fields"), + }, + UnaryOperation::Negate => match value { + Value::I8(x) => match x.checked_neg() { + None => halt!(span, "negation overflow"), + Some(y) => Value::I8(y), + }, + Value::I16(x) => match x.checked_neg() { + None => halt!(span, "negation overflow"), + Some(y) => Value::I16(y), + }, + Value::I32(x) => match x.checked_neg() { + None => halt!(span, "negation overflow"), + Some(y) => Value::I32(y), + }, + Value::I64(x) => match x.checked_neg() { + None => halt!(span, "negation overflow"), + Some(y) => Value::I64(y), + }, + Value::I128(x) => match x.checked_neg() { + None => halt!(span, "negation overflow"), + Some(y) => Value::I128(y), + }, + Value::Group(x) => Value::Group(-x), + Value::Field(x) => Value::Field(-x), + _ => halt!(span, "Type error"), + }, + UnaryOperation::Not => match value { + Value::Bool(x) => Value::Bool(!x), + Value::U8(x) => Value::U8(!x), + Value::U16(x) => Value::U16(!x), + Value::U32(x) => Value::U32(!x), + Value::U64(x) => Value::U64(!x), + Value::U128(x) => Value::U128(!x), + Value::I8(x) => Value::I8(!x), + Value::I16(x) => Value::I16(!x), + Value::I32(x) => Value::I32(!x), + Value::I64(x) => Value::I64(!x), + Value::I128(x) => Value::I128(!x), + _ => halt!(span, "Type error"), + }, + UnaryOperation::Square => match value { + Value::Field(x) => Value::Field(x.square()), + _ => halt!(span, "Can only square fields"), + }, + UnaryOperation::SquareRoot => match value { + Value::Field(x) => { + let Ok(y) = x.square_root() else { + halt!(span, "square root failure"); + }; + Value::Field(y) + } + _ => halt!(span, "Can only apply square_root to fields"), + }, + UnaryOperation::ToXCoordinate => match value { + Value::Group(x) => Value::Field(x.to_x_coordinate()), + _ => tc_fail!(), + }, + UnaryOperation::ToYCoordinate => match value { + Value::Group(x) => Value::Field(x.to_y_coordinate()), + _ => tc_fail!(), + }, + }; + + Ok(value_result) +} diff --git a/interpreter/src/cursor_aleo.rs b/interpreter/src/cursor_aleo.rs new file mode 100644 index 0000000000..506117dc36 --- /dev/null +++ b/interpreter/src/cursor_aleo.rs @@ -0,0 +1,1030 @@ +// Copyright (C) 2019-2024 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 super::*; + +use leo_ast::{BinaryOperation, CoreFunction, IntegerType, Type, UnaryOperation}; + +use snarkvm::{ + prelude::{ + Access, + Boolean, + Field, + Identifier, + Literal, + LiteralType, + Network as _, + PlaintextType, + Register, + TestnetV0, + ToBits as _, + ToBytes as _, + integers::Integer, + }, + synthesizer::{Command, Instruction}, +}; +use snarkvm_synthesizer_program::{CallOperator, CastType, Operand}; + +use rand::Rng as _; +use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; +use std::mem; + +impl Cursor<'_> { + fn mapping_by_call_operator(&self, operator: &CallOperator) -> Option<&HashMap> { + let (program, name) = match operator { + CallOperator::Locator(locator) => { + (Some(snarkvm_identifier_to_symbol(locator.name())), snarkvm_identifier_to_symbol(locator.resource())) + } + CallOperator::Resource(id) => (None, snarkvm_identifier_to_symbol(id)), + }; + self.lookup_mapping(program, name) + } + + fn get_register(&self, reg: &Register) -> &Value { + let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.frames.last() else { + panic!(); + }; + match reg { + Register::Locator(index) => { + registers.get(index).expect("valid .aleo code doesn't access undefined registers") + } + Register::Access(index, accesses) => { + let mut current_value = + registers.get(index).expect("valid .aleo code doesn't access undefined registers"); + for access in accesses.iter() { + match access { + Access::Member(id) => { + if let Value::Struct(current_struct) = current_value { + let name = snarkvm_identifier_to_symbol(id); + current_value = current_struct.contents.get(&name).expect("struct missing member"); + } else { + tc_fail!(); + } + } + Access::Index(index) => { + if let Value::Array(current_array) = current_value { + current_value = current_array.get(**index as usize).expect("array index out of bounds"); + } else { + tc_fail!(); + } + } + } + } + + current_value + } + } + } + + fn set_register(&mut self, reg: Register, value: Value) { + let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.frames.last_mut() else { + panic!(); + }; + + match reg { + Register::Locator(index) => { + registers.insert(index, value); + } + Register::Access(_, _) => todo!(), + } + } + + fn instructions_len(&self) -> usize { + let Some(Frame { element: Element::AleoExecution { context, .. }, .. }) = self.frames.last() else { + panic!(); + }; + match context { + AleoContext::Closure(closure) => closure.instructions().len(), + AleoContext::Function(function) => function.instructions().len(), + AleoContext::Finalize(finalize) => finalize.commands().len(), + } + } + + fn increment_instruction_index(&mut self) { + let Some(Frame { element: Element::AleoExecution { instruction_index, .. }, .. }) = self.frames.last_mut() + else { + panic!(); + }; + *instruction_index += 1; + } + + fn execution_complete(&self) -> bool { + let Some(Frame { element: Element::AleoExecution { instruction_index, .. }, .. }) = self.frames.last() else { + panic!(); + }; + *instruction_index >= self.instructions_len() + } + + fn next_instruction(&self) -> Option<&Instruction> { + let Some(Frame { element: Element::AleoExecution { instruction_index, context, .. }, .. }) = self.frames.last() + else { + panic!(); + }; + match context { + AleoContext::Closure(closure) => closure.instructions().get(*instruction_index), + AleoContext::Function(function) => function.instructions().get(*instruction_index), + AleoContext::Finalize(_) => None, + } + } + + fn next_command(&self) -> Option<&Command> { + let Some(Frame { element: Element::AleoExecution { instruction_index, context, .. }, .. }) = self.frames.last() + else { + panic!(); + }; + match context { + AleoContext::Closure(_) | AleoContext::Function(_) => None, + AleoContext::Finalize(finalize) => finalize.commands().get(*instruction_index), + } + } + + fn operand_value(&self, operand: &Operand) -> Value { + match operand { + Operand::Literal(literal) => match literal { + Literal::Address(x) => Value::Address(*x), + Literal::Boolean(x) => Value::Bool(**x), + Literal::Field(x) => Value::Field(*x), + Literal::Group(x) => Value::Group(*x), + Literal::I8(x) => Value::I8(**x), + Literal::I16(x) => Value::I16(**x), + Literal::I32(x) => Value::I32(**x), + Literal::I64(x) => Value::I64(**x), + Literal::I128(x) => Value::I128(**x), + Literal::U8(x) => Value::U8(**x), + Literal::U16(x) => Value::U16(**x), + Literal::U32(x) => Value::U32(**x), + Literal::U64(x) => Value::U64(**x), + Literal::U128(x) => Value::U128(**x), + Literal::Scalar(x) => Value::Scalar(*x), + Literal::Signature(_) => todo!(), + Literal::String(_) => todo!(), + }, + Operand::Register(register) => self.get_register(register).clone(), + Operand::ProgramID(_) => todo!(), + Operand::Signer => Value::Address(self.signer), + Operand::Caller => { + if let Some(function_context) = self.contexts.last() { + Value::Address(function_context.caller) + } else { + Value::Address(self.signer) + } + } + Operand::BlockHeight => Value::U32(self.block_height), + Operand::NetworkID => todo!(), + } + } + + fn step_aleo_instruction(&mut self, instruction: Instruction) -> Result<()> { + // The Aleo VM code is a linear sequence of instructions, so we don't need to keep + // a stack of Elements (except for calls). Just run instructions in order. + use Instruction::*; + + let Some(Frame { step, .. }) = self.frames.last() else { + panic!("frame expected"); + }; + + macro_rules! unary { + ($svm_op: expr, $op: ident) => {{ + let operand = self.operand_value(&$svm_op.operands()[0]); + let value = evaluate_unary(Default::default(), UnaryOperation::$op, operand)?; + self.increment_instruction_index(); + (value, $svm_op.destinations()[0].clone()) + }}; + } + + macro_rules! binary { + ($svm_op: expr, $op: ident) => {{ + let operand0 = self.operand_value(&$svm_op.operands()[0]); + let operand1 = self.operand_value(&$svm_op.operands()[1]); + let value = evaluate_binary(Default::default(), BinaryOperation::$op, operand0, operand1)?; + self.increment_instruction_index(); + (value, $svm_op.destinations()[0].clone()) + }}; + } + + macro_rules! commit_function { + ($commit: expr, + $to_address: ident, + $to_field: ident, + $to_group: ident, + ) => {{ + let core_function = match $commit.destination_type() { + LiteralType::Address => CoreFunction::$to_address, + LiteralType::Field => CoreFunction::$to_field, + LiteralType::Group => CoreFunction::$to_group, + _ => panic!("invalid commit destination type"), + }; + + let randomizer_value = self.operand_value(&$commit.operands()[0]); + let operand_value = self.operand_value(&$commit.operands()[1]); + self.values.push(randomizer_value); + self.values.push(operand_value); + let value = self.evaluate_core_function(core_function, &[], Span::default())?; + self.increment_instruction_index(); + (value, $commit.destinations()[0].clone()) + }}; + } + + macro_rules! hash_function { + ($hash: expr, + $to_address: ident, + $to_field: ident, + $to_group: ident, + $to_i8: ident, + $to_i16: ident, + $to_i32: ident, + $to_i64: ident, + $to_i128: ident, + $to_u8: ident, + $to_u16: ident, + $to_u32: ident, + $to_u64: ident, + $to_u128: ident, + $to_scalar: ident, + ) => {{ + let core_function = match $hash.destination_type() { + PlaintextType::Literal(LiteralType::Address) => CoreFunction::$to_address, + PlaintextType::Literal(LiteralType::Field) => CoreFunction::$to_field, + PlaintextType::Literal(LiteralType::Group) => CoreFunction::$to_group, + PlaintextType::Literal(LiteralType::I8) => CoreFunction::$to_i8, + PlaintextType::Literal(LiteralType::I16) => CoreFunction::$to_i16, + PlaintextType::Literal(LiteralType::I32) => CoreFunction::$to_i32, + PlaintextType::Literal(LiteralType::I64) => CoreFunction::$to_i64, + PlaintextType::Literal(LiteralType::I128) => CoreFunction::$to_i128, + PlaintextType::Literal(LiteralType::U8) => CoreFunction::$to_u8, + PlaintextType::Literal(LiteralType::U16) => CoreFunction::$to_u16, + PlaintextType::Literal(LiteralType::U32) => CoreFunction::$to_u32, + PlaintextType::Literal(LiteralType::U64) => CoreFunction::$to_u64, + PlaintextType::Literal(LiteralType::U128) => CoreFunction::$to_u128, + PlaintextType::Literal(LiteralType::Scalar) => CoreFunction::$to_scalar, + _ => panic!("invalid hash destination type"), + }; + let operand_value = self.operand_value(&$hash.operands()[0]); + self.values.push(operand_value); + let value = self.evaluate_core_function(core_function, &[], Span::default())?; + self.increment_instruction_index(); + (value, $hash.destinations()[0].clone()) + }}; + } + + let (value, destination) = match instruction { + Abs(abs) => unary!(abs, Abs), + AbsWrapped(abs_wrapped) => unary!(abs_wrapped, AbsWrapped), + Add(add) => binary!(add, Add), + AddWrapped(add_wrapped) => binary!(add_wrapped, AddWrapped), + And(and) => binary!(and, BitwiseAnd), + AssertEq(assert_eq) => { + let operand0 = self.operand_value(&assert_eq.operands()[0]); + let operand1 = self.operand_value(&assert_eq.operands()[1]); + if operand0.neq(&operand1)? { + halt_no_span!("assertion failure: {operand0} != {operand1}"); + } + self.increment_instruction_index(); + return Ok(()); + } + AssertNeq(assert_neq) => { + let operand0 = self.operand_value(&assert_neq.operands()[0]); + let operand1 = self.operand_value(&assert_neq.operands()[1]); + if operand0.eq(&operand1)? { + halt_no_span!("assertion failure: {operand0} != {operand1}"); + } + self.increment_instruction_index(); + return Ok(()); + } + Async(async_) if *step == 0 => { + let program = self.contexts.current_program().expect("there should be a program"); + let name = snarkvm_identifier_to_symbol(async_.function_name()); + let arguments: Vec = async_.operands().iter().map(|op| self.operand_value(op)).collect(); + if self.really_async { + self.increment_instruction_index(); + let async_ex = AsyncExecution { function: GlobalId { name, program }, arguments }; + (Value::Future(Future(vec![async_ex])), async_.destinations()[0].clone()) + } else { + self.do_call( + program, + name, + arguments.into_iter(), + true, // finalize + Span::default(), + )?; + self.increment_step(); + return Ok(()); + } + } + Call(call) if *step == 0 => { + let (program, name) = match call.operator() { + CallOperator::Locator(locator) => ( + snarkvm_identifier_to_symbol(locator.resource()), + snarkvm_identifier_to_symbol(locator.program_id().name()), + ), + CallOperator::Resource(id) => ( + snarkvm_identifier_to_symbol(id), + self.contexts.current_program().expect("there should be a program"), + ), + }; + let arguments: Vec = call.operands().iter().map(|op| self.operand_value(op)).collect(); + self.do_call( + program, + name, + arguments.into_iter(), + false, // finalize + Span::default(), + )?; + self.increment_step(); + return Ok(()); + } + Async(async_) if *step == 1 => { + // We've done a call, and the result is on the value stack. + self.values.pop(); + self.set_register(async_.destinations()[0].clone(), Value::Future(Future(Vec::new()))); + self.increment_instruction_index(); + return Ok(()); + } + Call(call) if *step == 1 => { + // We've done a call, and the result is on the value stack. + let Some(result) = self.values.pop() else { + panic!("should have a result"); + }; + if call.destinations().len() == 1 { + self.set_register(call.destinations()[0].clone(), result); + } else { + let Value::Tuple(tuple) = result else { + panic!("function returning multiple values should create a tuple"); + }; + for (dest, value) in call.destinations().iter().zip(tuple.into_iter()) { + self.set_register(dest.clone(), value); + } + } + self.increment_instruction_index(); + return Ok(()); + } + Call(_) | Async(_) => unreachable!("all cases covered above"), + Cast(cast) => { + let destination = cast.destinations()[0].clone(); + + self.increment_instruction_index(); + + let make_struct = |program, name_identifier| { + let name = snarkvm_identifier_to_symbol(name_identifier); + let id = GlobalId { program, name }; + let struct_type = self.structs.get(&id).expect("struct type should exist"); + let operands = cast.operands().iter().map(|op| self.operand_value(op)); + Value::Struct(StructContents { + name, + contents: struct_type.iter().cloned().zip(operands).collect(), + }) + }; + + match cast.cast_type() { + CastType::GroupXCoordinate => { + let Value::Group(g) = self.operand_value(&cast.operands()[0]) else { + tc_fail!(); + }; + let value = Value::Field(g.to_x_coordinate()); + (value, destination) + } + CastType::GroupYCoordinate => { + let Value::Group(g) = self.operand_value(&cast.operands()[0]) else { + tc_fail!(); + }; + let value = Value::Field(g.to_y_coordinate()); + (value, destination) + } + CastType::Plaintext(PlaintextType::Array(_array)) => { + let value = Value::Array(cast.operands().iter().map(|op| self.operand_value(op)).collect()); + (value, destination) + } + CastType::Plaintext(PlaintextType::Literal(literal_type)) => { + let operand = self.operand_value(&cast.operands()[0]); + let value = match operand.cast(&snarkvm_literal_type_to_type(*literal_type)) { + Some(value) => value, + None => halt_no_span!("cast failure"), + }; + (value, destination) + } + CastType::Record(struct_name) | CastType::Plaintext(PlaintextType::Struct(struct_name)) => { + let program = self.contexts.current_program().expect("there should be a current program"); + let value = make_struct(program, struct_name); + (value, destination) + } + CastType::ExternalRecord(locator) => { + let program = snarkvm_identifier_to_symbol(locator.program_id().name()); + let value = make_struct(program, locator.name()); + (value, destination) + } + } + } + CastLossy(cast_lossy) => { + match cast_lossy.cast_type() { + CastType::Plaintext(PlaintextType::Literal(literal_type)) => { + // This is the only variant supported for lossy casts. + let operand = self.operand_value(&cast_lossy.operands()[0]); + let operand_literal = value_to_snarkvm_literal(operand); + let result_literal = match operand_literal.cast_lossy(*literal_type) { + Ok(result_literal) => result_literal, + Err(_) => halt_no_span!("cast failure"), + }; + let destination = cast_lossy.destinations()[0].clone(); + self.increment_instruction_index(); + (snarkvm_literal_to_value(result_literal), destination) + } + _ => tc_fail!(), + } + } + CommitBHP256(commit) => { + commit_function!(commit, BHP256CommitToAddress, BHP256CommitToField, BHP256CommitToGroup,) + } + CommitBHP512(commit) => { + commit_function!(commit, BHP512CommitToAddress, BHP512CommitToField, BHP512CommitToGroup,) + } + CommitBHP768(commit) => { + commit_function!(commit, BHP768CommitToAddress, BHP768CommitToField, BHP768CommitToGroup,) + } + CommitBHP1024(commit) => { + commit_function!(commit, BHP1024CommitToAddress, BHP1024CommitToField, BHP1024CommitToGroup,) + } + CommitPED64(commit) => { + commit_function!(commit, Pedersen64CommitToAddress, Pedersen64CommitToField, Pedersen64CommitToGroup,) + } + CommitPED128(commit) => { + commit_function!(commit, Pedersen128CommitToAddress, Pedersen128CommitToField, Pedersen128CommitToGroup,) + } + Div(div) => binary!(div, Div), + DivWrapped(div_wrapped) => binary!(div_wrapped, DivWrapped), + Double(double) => unary!(double, Double), + GreaterThan(gt) => binary!(gt, Gt), + GreaterThanOrEqual(gte) => binary!(gte, Gte), + HashBHP256(hash) => hash_function!( + hash, + BHP256HashToAddress, + BHP256HashToField, + BHP256HashToGroup, + BHP256HashToI8, + BHP256HashToI16, + BHP256HashToI32, + BHP256HashToI64, + BHP256HashToI128, + BHP256HashToU8, + BHP256HashToU16, + BHP256HashToU32, + BHP256HashToU64, + BHP256HashToU128, + BHP256HashToScalar, + ), + HashBHP512(hash) => hash_function!( + hash, + BHP512HashToAddress, + BHP512HashToField, + BHP512HashToGroup, + BHP512HashToI8, + BHP512HashToI16, + BHP512HashToI32, + BHP512HashToI64, + BHP512HashToI128, + BHP512HashToU8, + BHP512HashToU16, + BHP512HashToU32, + BHP512HashToU64, + BHP512HashToU128, + BHP512HashToScalar, + ), + HashBHP768(hash) => hash_function!( + hash, + BHP768HashToAddress, + BHP768HashToField, + BHP768HashToGroup, + BHP768HashToI8, + BHP768HashToI16, + BHP768HashToI32, + BHP768HashToI64, + BHP768HashToI128, + BHP768HashToU8, + BHP768HashToU16, + BHP768HashToU32, + BHP768HashToU64, + BHP768HashToU128, + BHP768HashToScalar, + ), + HashBHP1024(hash) => hash_function!( + hash, + BHP1024HashToAddress, + BHP1024HashToField, + BHP1024HashToGroup, + BHP1024HashToI8, + BHP1024HashToI16, + BHP1024HashToI32, + BHP1024HashToI64, + BHP1024HashToI128, + BHP1024HashToU8, + BHP1024HashToU16, + BHP1024HashToU32, + BHP1024HashToU64, + BHP1024HashToU128, + BHP1024HashToScalar, + ), + HashKeccak256(hash) => hash_function!( + hash, + Keccak256HashToAddress, + Keccak256HashToField, + Keccak256HashToGroup, + Keccak256HashToI8, + Keccak256HashToI16, + Keccak256HashToI32, + Keccak256HashToI64, + Keccak256HashToI128, + Keccak256HashToU8, + Keccak256HashToU16, + Keccak256HashToU32, + Keccak256HashToU64, + Keccak256HashToU128, + Keccak256HashToScalar, + ), + HashKeccak384(hash) => hash_function!( + hash, + Keccak384HashToAddress, + Keccak384HashToField, + Keccak384HashToGroup, + Keccak384HashToI8, + Keccak384HashToI16, + Keccak384HashToI32, + Keccak384HashToI64, + Keccak384HashToI128, + Keccak384HashToU8, + Keccak384HashToU16, + Keccak384HashToU32, + Keccak384HashToU64, + Keccak384HashToU128, + Keccak384HashToScalar, + ), + HashKeccak512(hash) => hash_function!( + hash, + Keccak512HashToAddress, + Keccak512HashToField, + Keccak512HashToGroup, + Keccak512HashToI8, + Keccak512HashToI16, + Keccak512HashToI32, + Keccak512HashToI64, + Keccak512HashToI128, + Keccak512HashToU8, + Keccak512HashToU16, + Keccak512HashToU32, + Keccak512HashToU64, + Keccak512HashToU128, + Keccak512HashToScalar, + ), + HashPED64(hash) => hash_function!( + hash, + Pedersen64HashToAddress, + Pedersen64HashToField, + Pedersen64HashToGroup, + Pedersen64HashToI8, + Pedersen64HashToI16, + Pedersen64HashToI32, + Pedersen64HashToI64, + Pedersen64HashToI128, + Pedersen64HashToU8, + Pedersen64HashToU16, + Pedersen64HashToU32, + Pedersen64HashToU64, + Pedersen64HashToU128, + Pedersen64HashToScalar, + ), + HashPED128(hash) => hash_function!( + hash, + Pedersen128HashToAddress, + Pedersen128HashToField, + Pedersen128HashToGroup, + Pedersen128HashToI8, + Pedersen128HashToI16, + Pedersen128HashToI32, + Pedersen128HashToI64, + Pedersen128HashToI128, + Pedersen128HashToU8, + Pedersen128HashToU16, + Pedersen128HashToU32, + Pedersen128HashToU64, + Pedersen128HashToU128, + Pedersen128HashToScalar, + ), + HashPSD2(hash) => hash_function!( + hash, + Poseidon2HashToAddress, + Poseidon2HashToField, + Poseidon2HashToGroup, + Poseidon2HashToI8, + Poseidon2HashToI16, + Poseidon2HashToI32, + Poseidon2HashToI64, + Poseidon2HashToI128, + Poseidon2HashToU8, + Poseidon2HashToU16, + Poseidon2HashToU32, + Poseidon2HashToU64, + Poseidon2HashToU128, + Poseidon2HashToScalar, + ), + HashPSD4(hash) => hash_function!( + hash, + Poseidon4HashToAddress, + Poseidon4HashToField, + Poseidon4HashToGroup, + Poseidon4HashToI8, + Poseidon4HashToI16, + Poseidon4HashToI32, + Poseidon4HashToI64, + Poseidon4HashToI128, + Poseidon4HashToU8, + Poseidon4HashToU16, + Poseidon4HashToU32, + Poseidon4HashToU64, + Poseidon4HashToU128, + Poseidon4HashToScalar, + ), + HashPSD8(hash) => hash_function!( + hash, + Poseidon8HashToAddress, + Poseidon8HashToField, + Poseidon8HashToGroup, + Poseidon8HashToI8, + Poseidon8HashToI16, + Poseidon8HashToI32, + Poseidon8HashToI64, + Poseidon8HashToI128, + Poseidon8HashToU8, + Poseidon8HashToU16, + Poseidon8HashToU32, + Poseidon8HashToU64, + Poseidon8HashToU128, + Poseidon8HashToScalar, + ), + HashSha3_256(hash) => hash_function!( + hash, + SHA3_256HashToAddress, + SHA3_256HashToField, + SHA3_256HashToGroup, + SHA3_256HashToI8, + SHA3_256HashToI16, + SHA3_256HashToI32, + SHA3_256HashToI64, + SHA3_256HashToI128, + SHA3_256HashToU8, + SHA3_256HashToU16, + SHA3_256HashToU32, + SHA3_256HashToU64, + SHA3_256HashToU128, + SHA3_256HashToScalar, + ), + HashSha3_384(hash) => hash_function!( + hash, + SHA3_384HashToAddress, + SHA3_384HashToField, + SHA3_384HashToGroup, + SHA3_384HashToI8, + SHA3_384HashToI16, + SHA3_384HashToI32, + SHA3_384HashToI64, + SHA3_384HashToI128, + SHA3_384HashToU8, + SHA3_384HashToU16, + SHA3_384HashToU32, + SHA3_384HashToU64, + SHA3_384HashToU128, + SHA3_384HashToScalar, + ), + HashSha3_512(hash) => hash_function!( + hash, + SHA3_512HashToAddress, + SHA3_512HashToField, + SHA3_512HashToGroup, + SHA3_512HashToI8, + SHA3_512HashToI16, + SHA3_512HashToI32, + SHA3_512HashToI64, + SHA3_512HashToI128, + SHA3_512HashToU8, + SHA3_512HashToU16, + SHA3_512HashToU32, + SHA3_512HashToU64, + SHA3_512HashToU128, + SHA3_512HashToScalar, + ), + HashManyPSD2(_) | HashManyPSD4(_) | HashManyPSD8(_) => panic!("these instructions don't exist yet"), + Inv(inv) => unary!(inv, Inverse), + IsEq(eq) => binary!(eq, Eq), + IsNeq(neq) => binary!(neq, Neq), + LessThan(lt) => binary!(lt, Lt), + LessThanOrEqual(lte) => binary!(lte, Lte), + Modulo(modulo) => binary!(modulo, Mod), + Mul(mul) => binary!(mul, Mul), + MulWrapped(mul_wrapped) => binary!(mul_wrapped, MulWrapped), + Nand(nand) => binary!(nand, Nand), + Neg(neg) => unary!(neg, Negate), + Nor(nor) => binary!(nor, Nor), + Not(not) => unary!(not, Not), + Or(or) => binary!(or, BitwiseOr), + Pow(pow) => binary!(pow, Pow), + PowWrapped(pow_wrapped) => binary!(pow_wrapped, PowWrapped), + Rem(rem) => binary!(rem, Rem), + RemWrapped(rem_wrapped) => binary!(rem_wrapped, RemWrapped), + Shl(shl) => binary!(shl, Shl), + ShlWrapped(shl_wrapped) => binary!(shl_wrapped, ShlWrapped), + Shr(shr) => binary!(shr, Shr), + ShrWrapped(shr_wrapped) => binary!(shr_wrapped, ShrWrapped), + SignVerify(_) => todo!(), + Square(square) => unary!(square, Square), + SquareRoot(sqrt) => unary!(sqrt, SquareRoot), + Sub(sub) => binary!(sub, Sub), + SubWrapped(sub_wrapped) => binary!(sub_wrapped, SubWrapped), + Ternary(ternary) => { + let condition = self.operand_value(&ternary.operands()[0]); + let result = match condition { + Value::Bool(true) => &ternary.operands()[1], + Value::Bool(false) => &ternary.operands()[2], + _ => panic!(), + }; + self.increment_instruction_index(); + (self.operand_value(result), ternary.destinations()[0].clone()) + } + Xor(xor) => binary!(xor, Xor), + }; + + self.set_register(destination, value); + + Ok(()) + } + + fn outputs(&self) -> Vec { + let Some(Frame { element, .. }) = self.frames.last() else { + panic!("frame expected"); + }; + let Element::AleoExecution { context, .. } = element else { + panic!("aleo execution expected"); + }; + + let mut result = match context { + AleoContext::Closure(closure) => { + closure.outputs().iter().map(|output| self.operand_value(output.operand())).collect() + } + AleoContext::Function(function) => { + function.outputs().iter().map(|output| self.operand_value(output.operand())).collect() + } + AleoContext::Finalize(_finalize) => Vec::new(), + }; + + if result.is_empty() { + result.push(Value::Unit); + } + result + } + + fn step_aleo_command(&mut self, command: Command) -> Result<()> { + use Command::*; + + let (value, destination) = match command { + Instruction(instruction) => { + self.step_aleo_instruction(instruction)?; + return Ok(()); + } + Await(await_) => { + let Value::Future(future) = self.get_register(await_.register()) else { + halt_no_span!("attempted to await a non-future"); + }; + self.contexts.add_future(future.clone()); + self.increment_instruction_index(); + return Ok(()); + } + Contains(contains) => { + let mapping = self.mapping_by_call_operator(contains.mapping()).expect("mapping should be present"); + let key = self.operand_value(contains.key()); + let result = Value::Bool(mapping.contains_key(&key)); + self.increment_instruction_index(); + (result, contains.destination().clone()) + } + Get(get) => { + let key = self.operand_value(get.key()); + let value = self.mapping_by_call_operator(get.mapping()).and_then(|mapping| mapping.get(&key)).cloned(); + self.increment_instruction_index(); + + match value { + Some(v) => (v, get.destination().clone()), + None => halt_no_span!("map access failure: {key}"), + } + } + GetOrUse(get_or_use) => { + let key = self.operand_value(get_or_use.key()); + let value = + self.mapping_by_call_operator(get_or_use.mapping()).and_then(|mapping| mapping.get(&key)).cloned(); + + let use_value = value.unwrap_or_else(|| self.operand_value(get_or_use.default())); + self.increment_instruction_index(); + + (use_value, get_or_use.destination().clone()) + } + Remove(remove) => { + let key = self.operand_value(remove.key()); + let mapping_name = snarkvm_identifier_to_symbol(remove.mapping_name()); + let maybe_mapping = self.lookup_mapping_mut(None, mapping_name); + match maybe_mapping { + None => halt_no_span!("no such mapping {mapping_name}"), + Some(mapping) => { + mapping.remove(&key); + } + } + self.increment_instruction_index(); + return Ok(()); + } + Set(set) => { + let key = self.operand_value(set.key()); + let value = self.operand_value(set.value()); + let mapping_name = snarkvm_identifier_to_symbol(set.mapping_name()); + let maybe_mapping = self.lookup_mapping_mut(None, mapping_name); + match maybe_mapping { + None => halt_no_span!("no such mapping {mapping_name}"), + Some(mapping) => { + mapping.insert(key, value); + } + } + self.increment_instruction_index(); + return Ok(()); + } + RandChaCha(rand) => { + // If there are operands, they are additional seeds. + let mut bits = Vec::new(); + for value in rand.operands().iter().map(|op| self.operand_value(op)) { + value.write_bits_le(&mut bits); + } + let field: Field = self.rng.gen(); + field.write_bits_le(&mut bits); + let seed_vec = TestnetV0::hash_bhp1024(&bits)?.to_bytes_le()?; + let mut seed = [0u8; 32]; + seed.copy_from_slice(&seed_vec[..32]); + let mut rng = ChaCha20Rng::from_seed(seed); + let value = match rand.destination_type() { + LiteralType::Address => Value::Address(rng.gen()), + LiteralType::Boolean => Value::Bool(rng.gen()), + LiteralType::Field => Value::Field(rng.gen()), + LiteralType::Group => Value::Group(rng.gen()), + LiteralType::I8 => Value::I8(rng.gen()), + LiteralType::I16 => Value::I16(rng.gen()), + LiteralType::I32 => Value::I32(rng.gen()), + LiteralType::I64 => Value::I64(rng.gen()), + LiteralType::I128 => Value::I128(rng.gen()), + LiteralType::U8 => Value::U8(rng.gen()), + LiteralType::U16 => Value::U16(rng.gen()), + LiteralType::U32 => Value::U32(rng.gen()), + LiteralType::U64 => Value::U64(rng.gen()), + LiteralType::U128 => Value::U128(rng.gen()), + LiteralType::Scalar => Value::Scalar(rng.gen()), + LiteralType::Signature => halt_no_span!("Cannot create a random signature"), + LiteralType::String => halt_no_span!("Cannot create a random string"), + }; + self.increment_instruction_index(); + (value, rand.destination().clone()) + } + BranchEq(branch_eq) => { + let first = self.operand_value(branch_eq.first()); + let second = self.operand_value(branch_eq.second()); + if first.eq(&second)? { + self.branch(branch_eq.position()); + } else { + self.increment_instruction_index(); + } + return Ok(()); + } + BranchNeq(branch_neq) => { + let first = self.operand_value(branch_neq.first()); + let second = self.operand_value(branch_neq.second()); + if first.neq(&second)? { + self.branch(branch_neq.position()); + } else { + self.increment_instruction_index(); + } + return Ok(()); + } + Position(_) => return Ok(()), + }; + + self.set_register(destination, value); + + Ok(()) + } + + fn branch(&mut self, label: &Identifier) { + let Some(Frame { + element: Element::AleoExecution { instruction_index, context: AleoContext::Finalize(finalize), .. }, + .. + }) = self.frames.last_mut() + else { + panic!(); + }; + for (i, cmd) in finalize.commands().iter().enumerate() { + if let Command::Position(position) = cmd { + if position.name() == label { + *instruction_index = i; + return; + } + } + } + panic!("branch to nonexistent label {}", label); + } + + pub fn step_aleo(&mut self) -> Result<()> { + if let Some(command) = self.next_command().cloned() { + self.step_aleo_command(command)?; + } else if let Some(instruction) = self.next_instruction().cloned() { + self.step_aleo_instruction(instruction)?; + } + + if self.execution_complete() { + let mut outputs = self.outputs(); + self.frames.pop(); + self.contexts.pop(); + if outputs.len() > 1 { + self.values.push(Value::Tuple(outputs)); + } else { + self.values.push(mem::take(&mut outputs[0])); + } + } + + Ok(()) + } +} + +fn snarkvm_literal_type_to_type(snarkvm_type: LiteralType) -> Type { + use Type::*; + match snarkvm_type { + LiteralType::Address => Address, + LiteralType::Boolean => Boolean, + LiteralType::Field => Field, + LiteralType::Group => Group, + LiteralType::I8 => Integer(IntegerType::I8), + LiteralType::I16 => Integer(IntegerType::I16), + LiteralType::I32 => Integer(IntegerType::I32), + LiteralType::I64 => Integer(IntegerType::I64), + LiteralType::I128 => Integer(IntegerType::I128), + LiteralType::U8 => Integer(IntegerType::U8), + LiteralType::U16 => Integer(IntegerType::U16), + LiteralType::U32 => Integer(IntegerType::U32), + LiteralType::U64 => Integer(IntegerType::U64), + LiteralType::U128 => Integer(IntegerType::U128), + LiteralType::Scalar => Scalar, + LiteralType::Signature => todo!(), + LiteralType::String => todo!(), + } +} + +fn snarkvm_literal_to_value(literal: Literal) -> Value { + match literal { + Literal::Address(x) => Value::Address(x), + Literal::Boolean(x) => Value::Bool(*x), + Literal::Field(x) => Value::Field(x), + Literal::Group(x) => Value::Group(x), + Literal::I8(x) => Value::I8(*x), + Literal::I16(x) => Value::I16(*x), + Literal::I32(x) => Value::I32(*x), + Literal::I64(x) => Value::I64(*x), + Literal::I128(x) => Value::I128(*x), + Literal::U8(x) => Value::U8(*x), + Literal::U16(x) => Value::U16(*x), + Literal::U32(x) => Value::U32(*x), + Literal::U64(x) => Value::U64(*x), + Literal::U128(x) => Value::U128(*x), + Literal::Scalar(x) => Value::Scalar(x), + Literal::Signature(_) | Literal::String(_) => tc_fail!(), + } +} + +fn value_to_snarkvm_literal(value: Value) -> Literal { + match value { + Value::Bool(x) => Literal::Boolean(Boolean::new(x)), + Value::U8(x) => Literal::U8(Integer::new(x)), + Value::U16(x) => Literal::U16(Integer::new(x)), + Value::U32(x) => Literal::U32(Integer::new(x)), + Value::U64(x) => Literal::U64(Integer::new(x)), + Value::U128(x) => Literal::U128(Integer::new(x)), + Value::I8(x) => Literal::I8(Integer::new(x)), + Value::I16(x) => Literal::I16(Integer::new(x)), + Value::I32(x) => Literal::I32(Integer::new(x)), + Value::I64(x) => Literal::I64(Integer::new(x)), + Value::I128(x) => Literal::I128(Integer::new(x)), + Value::Group(x) => Literal::Group(x), + Value::Field(x) => Literal::Field(x), + Value::Scalar(x) => Literal::Scalar(x), + Value::Address(x) => Literal::Address(x), + Value::Array(_) | Value::Tuple(_) | Value::Unit | Value::Future(_) | Value::Struct(_) => tc_fail!(), + } +} diff --git a/interpreter/src/dialoguer_input.rs b/interpreter/src/dialoguer_input.rs new file mode 100644 index 0000000000..67926ddfb1 --- /dev/null +++ b/interpreter/src/dialoguer_input.rs @@ -0,0 +1,62 @@ +// Copyright (C) 2019-2024 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 super::ui::{Ui, UserData}; + +use colored::*; + +pub struct DialoguerUi { + history: dialoguer::BasicHistory, +} + +impl DialoguerUi { + pub fn new() -> Self { + DialoguerUi { history: dialoguer::BasicHistory::new() } + } +} + +impl Ui for DialoguerUi { + fn display_user_data(&mut self, data: &UserData<'_>) { + if let Some(result) = data.result { + println!("{}: {}", "Result".bold(), result.bright_cyan()); + } + println!("{}", data.message); + if let Some(highlight_span) = data.highlight { + let first = data.code.get(0..highlight_span.0).expect("spans should be valid"); + let second = data.code.get(highlight_span.0..highlight_span.1).expect("spans should be valid"); + let third = data.code.get(highlight_span.1..).expect("spans should be valid"); + println!("{first}{}{third}", second.red()); + } else { + println!("{}", data.code); + } + + for (i, future) in data.futures.iter().enumerate() { + println!("{i:>4}: {future}"); + } + + for (i, watchpoint) in data.watchpoints.iter().enumerate() { + println!("{i:>4}: {watchpoint}"); + } + } + + fn receive_user_input(&mut self) -> String { + dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default()) + .with_prompt("Command?") + .history_with(&mut self.history) + .interact_text() + .unwrap() + } +} diff --git a/interpreter/src/interpreter.rs b/interpreter/src/interpreter.rs new file mode 100644 index 0000000000..efc7f51178 --- /dev/null +++ b/interpreter/src/interpreter.rs @@ -0,0 +1,453 @@ +// Copyright (C) 2019-2024 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 super::*; + +use leo_errors::{CompilerError, InterpreterHalt, LeoError, Result, emitter::Handler}; + +/// Contains the state of interpretation, in the form of the `Cursor`, +/// as well as information needed to interact with the user, like +/// the breakpoints. +pub struct Interpreter { + pub cursor: Cursor<'static>, + actions: Vec, + handler: Handler, + node_builder: NodeBuilder, + breakpoints: Vec, + pub watchpoints: Vec, + saved_cursors: Vec>, + filename_to_program: HashMap, + parsed_inputs: u32, +} + +#[derive(Clone, Debug)] +pub struct Breakpoint { + pub program: String, + pub line: usize, +} + +#[derive(Clone, Debug)] +pub struct Watchpoint { + pub code: String, + pub last_result: Option, +} + +#[derive(Clone, Debug)] +pub enum InterpreterAction { + LeoInterpretInto(String), + LeoInterpretOver(String), + Watch(String), + RunFuture(usize), + Breakpoint(Breakpoint), + PrintRegister(u64), + Into, + Over, + Step, + Run, +} + +impl Interpreter { + pub fn new<'a, P: 'a + AsRef, Q: 'a + AsRef>( + leo_source_files: impl IntoIterator, + aleo_source_files: impl IntoIterator, + signer: SvmAddress, + block_height: u32, + ) -> Result { + Self::new_impl( + &mut leo_source_files.into_iter().map(|p| p.as_ref()), + &mut aleo_source_files.into_iter().map(|p| p.as_ref()), + signer, + block_height, + ) + } + + fn get_ast(path: &Path, handler: &Handler, node_builder: &NodeBuilder) -> Result { + let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?; + let filename = FileName::Real(path.to_path_buf()); + let source_file = with_session_globals(|s| s.source_map.new_source(&text, filename)); + leo_parser::parse_ast::(handler, node_builder, &text, source_file.start_pos) + } + + fn new_impl( + leo_source_files: &mut dyn Iterator, + aleo_source_files: &mut dyn Iterator, + signer: SvmAddress, + block_height: u32, + ) -> Result { + let handler = Handler::default(); + let node_builder = Default::default(); + let mut cursor: Cursor<'_> = Cursor::new( + true, // really_async + signer, + block_height, + ); + let mut filename_to_program = HashMap::new(); + for path in leo_source_files { + let ast = Self::get_ast(path, &handler, &node_builder)?; + // TODO: This leak is silly. + let ast = Box::leak(Box::new(ast)); + for (&program, scope) in ast.ast.program_scopes.iter() { + filename_to_program.insert(path.to_path_buf(), program.to_string()); + for (name, function) in scope.functions.iter() { + cursor.functions.insert(GlobalId { program, name: *name }, FunctionVariant::Leo(function)); + } + + for (name, composite) in scope.structs.iter() { + cursor.structs.insert( + GlobalId { program, name: *name }, + composite.members.iter().map(|member| member.identifier.name).collect(), + ); + } + + for (name, _mapping) in scope.mappings.iter() { + cursor.mappings.insert(GlobalId { program, name: *name }, HashMap::new()); + } + + for (name, const_declaration) in scope.consts.iter() { + cursor.frames.push(Frame { + step: 0, + element: Element::Expression(&const_declaration.value), + user_initiated: false, + }); + cursor.over()?; + let value = cursor.values.pop().unwrap(); + cursor.globals.insert(GlobalId { program, name: *name }, value); + } + } + } + + for path in aleo_source_files { + let aleo_program = Self::get_aleo_program(path)?; + // TODO: Another goofy leak. + let aleo_program = Box::leak(Box::new(aleo_program)); + let program = snarkvm_identifier_to_symbol(aleo_program.id().name()); + filename_to_program.insert(path.to_path_buf(), program.to_string()); + + for (name, struct_type) in aleo_program.structs().iter() { + cursor.structs.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + struct_type.members().keys().map(snarkvm_identifier_to_symbol).collect(), + ); + } + + for (name, record_type) in aleo_program.records().iter() { + cursor.structs.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + record_type.entries().keys().map(snarkvm_identifier_to_symbol).collect(), + ); + } + + for (name, _mapping) in aleo_program.mappings().iter() { + cursor.mappings.insert(GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, HashMap::new()); + } + + for (name, function) in aleo_program.functions().iter() { + cursor.functions.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + FunctionVariant::AleoFunction(function), + ); + } + + for (name, closure) in aleo_program.closures().iter() { + cursor.functions.insert( + GlobalId { program, name: snarkvm_identifier_to_symbol(name) }, + FunctionVariant::AleoClosure(closure), + ); + } + } + + Ok(Interpreter { + cursor, + handler, + node_builder, + actions: Vec::new(), + breakpoints: Vec::new(), + watchpoints: Vec::new(), + saved_cursors: Vec::new(), + filename_to_program, + parsed_inputs: 0, + }) + } + + pub fn save_cursor(&mut self) { + self.saved_cursors.push(self.cursor.clone()); + } + + /// Returns false if there was no saved cursor to restore. + pub fn restore_cursor(&mut self) -> bool { + if let Some(old_cursor) = self.saved_cursors.pop() { + self.cursor = old_cursor; + true + } else { + false + } + } + + fn get_aleo_program(path: &Path) -> Result> { + let text = fs::read_to_string(path).map_err(|e| CompilerError::file_read_error(path, e))?; + let program = text.parse()?; + Ok(program) + } + + /// Returns true if any watchpoints changed. + pub fn update_watchpoints(&mut self) -> Result { + let mut changed = false; + let safe_cursor = self.cursor.clone(); + + for i in 0..self.watchpoints.len() { + let code = self.watchpoints[i].code.clone(); + let new_value = match self.action(InterpreterAction::LeoInterpretOver(code)) { + Ok(None) => None, + Ok(Some(ret)) => Some(ret.to_string()), + Err(LeoError::InterpreterHalt(halt)) => { + self.cursor = safe_cursor.clone(); + Some(halt.to_string()) + } + Err(e) => return Err(e), + }; + if self.watchpoints[i].last_result != new_value { + changed = true; + self.watchpoints[i].last_result = new_value; + } + } + Ok(changed) + } + + pub fn action(&mut self, act: InterpreterAction) -> Result> { + use InterpreterAction::*; + + let ret = match &act { + RunFuture(n) => { + let future = self.cursor.futures.remove(*n); + for async_exec in future.0.into_iter().rev() { + self.cursor.values.extend(async_exec.arguments); + self.cursor.frames.push(Frame { + step: 0, + element: Element::DelayedCall(async_exec.function), + user_initiated: true, + }); + } + self.cursor.step()? + } + LeoInterpretInto(s) | LeoInterpretOver(s) => { + let filename = FileName::Custom(format!("user_input{:04}", self.parsed_inputs)); + self.parsed_inputs += 1; + let source_file = with_session_globals(|globals| globals.source_map.new_source(s, filename)); + let s = s.trim(); + if s.ends_with(';') { + let statement = leo_parser::parse_statement::( + &self.handler, + &self.node_builder, + s, + source_file.start_pos, + ) + .map_err(|_e| { + LeoError::InterpreterHalt(InterpreterHalt::new("failed to parse statement".into())) + })?; + // TODO: This leak is silly. + let stmt = Box::leak(Box::new(statement)); + + // The spans of the code the user wrote at the REPL are meaningless, so get rid of them. + self.cursor.frames.push(Frame { step: 0, element: Element::Statement(stmt), user_initiated: true }); + } else { + let expression = leo_parser::parse_expression::( + &self.handler, + &self.node_builder, + s, + source_file.start_pos, + ) + .map_err(|e| { + LeoError::InterpreterHalt(InterpreterHalt::new(format!("Failed to parse expression: {e}"))) + })?; + // TODO: This leak is silly. + let expr = Box::leak(Box::new(expression)); + + // The spans of the code the user wrote at the REPL are meaningless, so get rid of them. + self.cursor.frames.push(Frame { + step: 0, + element: Element::Expression(expr), + user_initiated: true, + }); + }; + + if matches!(act, LeoInterpretOver(..)) { + self.cursor.over()? + } else { + StepResult { finished: false, value: None } + } + } + + Step => self.cursor.whole_step()?, + + Into => self.cursor.step()?, + + Over => self.cursor.over()?, + + Breakpoint(breakpoint) => { + self.breakpoints.push(breakpoint.clone()); + StepResult { finished: false, value: None } + } + + Watch(code) => { + self.watchpoints.push(Watchpoint { code: code.clone(), last_result: None }); + StepResult { finished: false, value: None } + } + + PrintRegister(register_index) => { + let Some(Frame { element: Element::AleoExecution { registers, .. }, .. }) = self.cursor.frames.last() + else { + halt_no_span!("cannot print register - not currently interpreting Aleo VM code"); + }; + + if let Some(value) = registers.get(register_index) { + StepResult { finished: false, value: Some(value.clone()) } + } else { + halt_no_span!("no such register {register_index}"); + } + } + + Run => { + while !self.cursor.frames.is_empty() { + if let Some((program, line)) = self.current_program_and_line() { + if self.breakpoints.iter().any(|bp| bp.program == program && bp.line == line) { + return Ok(None); + } + } + self.cursor.step()?; + if self.update_watchpoints()? { + return Ok(None); + } + } + StepResult { finished: false, value: None } + } + }; + + self.actions.push(act); + + Ok(ret.value) + } + + pub fn view_current(&self) -> Option { + if let Some(span) = self.current_span() { + if span != Default::default() { + return with_session_globals(|s| s.source_map.contents_of_span(span)); + } + } + + Some(match self.cursor.frames.last()?.element { + Element::Statement(statement) => format!("{statement}"), + Element::Expression(expression) => format!("{expression}"), + Element::Block { block, .. } => format!("{block}"), + Element::DelayedCall(gid) => format!("Delayed call to {gid}"), + Element::AleoExecution { context, instruction_index, .. } => match context { + AleoContext::Closure(closure) => closure.instructions().get(instruction_index).map(|i| format!("{i}")), + AleoContext::Function(function) => { + function.instructions().get(instruction_index).map(|i| format!("{i}")) + } + AleoContext::Finalize(finalize) => finalize.commands().get(instruction_index).map(|i| format!("{i}")), + } + .unwrap_or_else(|| "...".to_string()), + }) + } + + pub fn view_current_in_context(&self) -> Option<(impl Display, usize, usize)> { + if let Some(Frame { element: Element::AleoExecution { context, instruction_index, .. }, .. }) = + self.cursor.frames.last() + { + // For Aleo VM code, there are no spans; just print out the code without referring to the source code. + + fn write_all( + items: impl Iterator, + instruction_index: usize, + result: &mut String, + start: &mut usize, + stop: &mut usize, + ) { + for (i, item) in items.enumerate() { + if i == instruction_index { + *start = result.len(); + } + writeln!(result, " {item}").expect("write shouldn't fail"); + if i == instruction_index { + *stop = result.len(); + } + } + } + + let mut result = String::new(); + let mut start: usize = 0usize; + let mut stop: usize = 0usize; + + match context { + AleoContext::Closure(closure) => { + writeln!(&mut result, "closure {}", closure.name()).expect("write shouldn't fail"); + write_all(closure.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); + write_all(closure.instructions().iter(), *instruction_index, &mut result, &mut start, &mut stop); + write_all(closure.outputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); + } + AleoContext::Function(function) => { + writeln!(&mut result, "function {}", function.name()).expect("write shouldn't fail"); + write_all(function.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); + write_all(function.instructions().iter(), *instruction_index, &mut result, &mut start, &mut stop); + write_all(function.outputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); + } + AleoContext::Finalize(finalize) => { + writeln!(&mut result, "finalize {}", finalize.name()).expect("write shouldn't fail"); + write_all(finalize.inputs().iter(), usize::MAX, &mut result, &mut 0usize, &mut 0usize); + write_all(finalize.commands().iter(), *instruction_index, &mut result, &mut start, &mut stop); + } + } + + Some((result, start, stop)) + } else { + // For Leo code, we use spans to print the original source code. + let span = self.current_span()?; + if span == Default::default() { + return None; + } + with_session_globals(|s| { + let source_file = s.source_map.find_source_file(span.lo)?; + let first_span = Span::new(source_file.start_pos, span.lo); + let last_span = Span::new(span.hi, source_file.end_pos); + let mut result = String::new(); + result.push_str(&s.source_map.contents_of_span(first_span)?); + let start = result.len(); + result.push_str(&s.source_map.contents_of_span(span)?); + let stop = result.len(); + result.push_str(&s.source_map.contents_of_span(last_span)?); + Some((result, start, stop)) + }) + } + } + + fn current_program_and_line(&self) -> Option<(String, usize)> { + if let Some(span) = self.current_span() { + if let Some(location) = with_session_globals(|s| s.source_map.span_to_location(span)) { + let line = location.line_start; + if let FileName::Real(name) = &location.source_file.name { + if let Some(program) = self.filename_to_program.get(name) { + return Some((program.clone(), line)); + } + } + } + } + None + } + + fn current_span(&self) -> Option { + self.cursor.frames.last().map(|f| f.element.span()) + } +} diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs new file mode 100644 index 0000000000..cdbd9d6ac6 --- /dev/null +++ b/interpreter/src/lib.rs @@ -0,0 +1,267 @@ +// Copyright (C) 2019-2024 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 leo_ast::{Ast, Node as _, NodeBuilder}; +use leo_errors::{InterpreterHalt, LeoError, Result}; +use leo_span::{Span, source_map::FileName, symbol::with_session_globals}; + +use snarkvm::prelude::{Program, TestnetV0}; + +use std::{ + collections::HashMap, + fmt::{Display, Write as _}, + fs, + path::{Path, PathBuf}, +}; + +#[cfg(test)] +mod test; + +mod util; +use util::*; + +mod cursor; +use cursor::*; + +mod interpreter; +use interpreter::*; + +mod cursor_aleo; + +mod value; +use value::*; + +mod ui; +use ui::Ui; + +mod dialoguer_input; + +mod ratatui_ui; + +const INTRO: &str = "This is the Leo Interpreter. Try the command `#help`."; + +const HELP: &str = " +You probably want to start by running a function or transition. +For instance +#into program.aleo/main() +Once a function is running, commands include +#into to evaluate into the next expression or statement; +#step to take one step towards evaluating the current expression or statement; +#over to complete evaluating the current expression or statement; +#run to finish evaluating +#quit to quit the interpreter. + +You can set a breakpoint with +#break program_name line_number + +When executing Aleo VM code, you can print the value of a register like this: +#print 2 + +Some of the commands may be run with one letter abbreviations, such as #i. + +Note that this interpreter is not line oriented as in many common debuggers; +rather it is oriented around expressions and statements. +As you step into code, individual expressions or statements will +be evaluated one by one, including arguments of function calls. + +You may simply enter Leo expressions or statements on the command line +to evaluate. For instance, if you want to see the value of a variable w: +w +If you want to set w to a new value: +w = z + 2u8; + +Note that statements (like the assignment above) must end with a semicolon. + +If there are futures available to be executed, they will be listed by +numerical index, and you may run them using `#future` (or `#f`); for instance +#future 0 + +The interpreter begins in a global context, not in any Leo program. You can set +the current program with + +#set_program program_name + +This allows you to refer to structs and other items in the indicated program. + +The interpreter may enter an invalid state, often due to Leo code entered at the +REPL. In this case, you may use the command + +#restore + +Which will restore to the last saved state of the interpreter. Any time you +enter Leo code at the prompt, interpreter state is saved. + +Input history is available - use the up and down arrow keys. +"; + +fn parse_breakpoint(s: &str) -> Option { + let strings: Vec<&str> = s.split_whitespace().collect(); + if strings.len() == 2 { + if let Ok(line) = strings[1].parse::() { + let program = strings[0].strip_suffix(".aleo").unwrap_or(strings[0]).to_string(); + return Some(Breakpoint { program, line }); + } + } + None +} + +/// Load all the Leo source files indicated and open the interpreter +/// to commands from the user. +pub fn interpret( + leo_filenames: &[PathBuf], + aleo_filenames: &[PathBuf], + signer: SvmAddress, + block_height: u32, + tui: bool, +) -> Result<()> { + let mut interpreter = Interpreter::new(leo_filenames.iter(), aleo_filenames.iter(), signer, block_height)?; + + let mut user_interface: Box = + if tui { Box::new(ratatui_ui::RatatuiUi::new()) } else { Box::new(dialoguer_input::DialoguerUi::new()) }; + + let mut code = String::new(); + let mut futures = Vec::new(); + let mut watchpoints = Vec::new(); + let mut message = INTRO.to_string(); + let mut result = String::new(); + + loop { + code.clear(); + futures.clear(); + watchpoints.clear(); + + let (code, highlight) = if let Some((code, lo, hi)) = interpreter.view_current_in_context() { + (code.to_string(), Some((lo, hi))) + } else if let Some(v) = interpreter.view_current() { + (v.to_string(), None) + } else { + ("".to_string(), None) + }; + + futures.extend(interpreter.cursor.futures.iter().map(|f| f.to_string())); + + interpreter.update_watchpoints()?; + + watchpoints.extend(interpreter.watchpoints.iter().map(|watchpoint| { + format!("{:>15} = {}", watchpoint.code, if let Some(s) = &watchpoint.last_result { &**s } else { "?" }) + })); + + let user_data = ui::UserData { + code: &code, + highlight, + message: &message, + futures: &futures, + watchpoints: &watchpoints, + result: if result.is_empty() { None } else { Some(&result) }, + }; + + user_interface.display_user_data(&user_data); + + message.clear(); + result.clear(); + + let user_input = user_interface.receive_user_input(); + + let (command, rest) = tokenize_user_input(&user_input); + + let action = match (command, rest) { + ("", "") => continue, + ("#h" | "#help", "") => { + message = HELP.to_string(); + continue; + } + ("#i" | "#into", "") => InterpreterAction::Into, + ("#i" | "#into", rest) => InterpreterAction::LeoInterpretInto(rest.into()), + ("#s" | "#step", "") => InterpreterAction::Step, + ("#o" | "#over", "") => InterpreterAction::Over, + ("#r" | "#run", "") => InterpreterAction::Run, + ("#q" | "#quit", "") => return Ok(()), + ("#f" | "#future", rest) => { + if let Ok(num) = rest.trim().parse::() { + if num >= interpreter.cursor.futures.len() { + message = "No such future.".to_string(); + continue; + } + InterpreterAction::RunFuture(num) + } else { + message = "Failed to parse future.".to_string(); + continue; + } + } + ("#restore", "") => { + if !interpreter.restore_cursor() { + message = "No saved state to restore".to_string(); + } + continue; + } + ("#b" | "#break", rest) => { + let Some(breakpoint) = parse_breakpoint(rest) else { + message = "Failed to parse breakpoint".to_string(); + continue; + }; + InterpreterAction::Breakpoint(breakpoint) + } + ("#p" | "#print", rest) => { + let without_r = rest.strip_prefix("r").unwrap_or(rest); + if let Ok(num) = without_r.parse::() { + InterpreterAction::PrintRegister(num) + } else { + message = "Failed to parse register number".to_string(); + continue; + } + } + ("#w" | "#watch", rest) => InterpreterAction::Watch(rest.to_string()), + ("#set_program", rest) => { + interpreter.cursor.set_program(rest); + continue; + } + ("", rest) => InterpreterAction::LeoInterpretOver(rest.to_string()), + _ => { + message = "Failed to parse command".to_string(); + continue; + } + }; + + if matches!(action, InterpreterAction::LeoInterpretInto(..) | InterpreterAction::LeoInterpretOver(..)) { + interpreter.save_cursor(); + } + + match interpreter.action(action) { + Ok(Some(value)) => { + result = value.to_string(); + } + Ok(None) => {} + Err(LeoError::InterpreterHalt(interpreter_halt)) => { + message = format!("Halted: {interpreter_halt}"); + } + Err(e) => return Err(e), + } + } +} + +fn tokenize_user_input(input: &str) -> (&str, &str) { + let input = input.trim(); + + if !input.starts_with("#") { + return ("", input); + } + + let Some((first, rest)) = input.split_once(' ') else { + return (input, ""); + }; + + (first.trim(), rest.trim()) +} diff --git a/interpreter/src/ratatui_ui.rs b/interpreter/src/ratatui_ui.rs new file mode 100644 index 0000000000..06a02e1c29 --- /dev/null +++ b/interpreter/src/ratatui_ui.rs @@ -0,0 +1,370 @@ +// Copyright (C) 2019-2024 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 super::ui::{Ui, UserData}; + +use std::{cmp, collections::VecDeque, io::Stdout, mem}; + +use crossterm::event::{self, Event, KeyCode, KeyModifiers}; +use ratatui::{ + Frame, + Terminal, + prelude::{ + Buffer, + Constraint, + CrosstermBackend, + Direction, + Layout, + Line, + Modifier, + Rect, + Span, + Style, + Stylize as _, + }, + text::Text, + widgets::{Block, Paragraph, Widget}, +}; + +#[derive(Default)] +struct DrawData { + code: String, + highlight: Option<(usize, usize)>, + result: String, + watchpoints: Vec, + message: String, + prompt: Prompt, +} + +pub struct RatatuiUi { + terminal: Terminal>, + data: DrawData, +} + +impl Drop for RatatuiUi { + fn drop(&mut self) { + ratatui::restore(); + } +} + +impl RatatuiUi { + pub fn new() -> Self { + RatatuiUi { terminal: ratatui::init(), data: Default::default() } + } +} + +fn append_lines<'a>( + lines: &mut Vec>, + mut last_chunk: Option>, + string: &'a str, + style: Style, +) -> Option> { + let mut line_iter = string.lines().peekable(); + while let Some(line) = line_iter.next() { + let this_span = Span::styled(line, style); + let mut real_last_chunk = mem::take(&mut last_chunk).unwrap_or_else(|| Line::raw("")); + real_last_chunk.push_span(this_span); + if line_iter.peek().is_some() { + lines.push(real_last_chunk); + } else if string.ends_with('\n') { + lines.push(real_last_chunk); + return None; + } else { + return Some(real_last_chunk); + } + } + + last_chunk +} + +fn code_text(s: &str, highlight: Option<(usize, usize)>) -> (Text, usize) { + let Some((lo, hi)) = highlight else { + return (Text::from(s), 0); + }; + + let s1 = s.get(..lo).expect("should be able to split text"); + let s2 = s.get(lo..hi).expect("should be able to split text"); + let s3 = s.get(hi..).expect("should be able to split text"); + + let mut lines = Vec::new(); + + let s1_chunk = append_lines(&mut lines, None, s1, Style::default()); + let line = lines.len(); + let s2_chunk = append_lines(&mut lines, s1_chunk, s2, Style::new().red()); + let s3_chunk = append_lines(&mut lines, s2_chunk, s3, Style::default()); + + if let Some(chunk) = s3_chunk { + lines.push(chunk); + } + + (Text::from(lines), line) +} + +struct DebuggerLayout { + code: Rect, + result: Rect, + watchpoints: Rect, + user_input: Rect, + message: Rect, +} + +impl DebuggerLayout { + fn new(total: Rect) -> Self { + let overall_layout = Layout::default() + .direction(Direction::Vertical) + .constraints([ + Constraint::Fill(1), // Code + Constraint::Length(6), // Result and watchpoints + Constraint::Length(3), // Message + Constraint::Length(3), // User input + ]) + .split(total); + let code = overall_layout[0]; + let middle = overall_layout[1]; + let message = overall_layout[2]; + let user_input = overall_layout[3]; + + let middle = Layout::default() + .direction(Direction::Horizontal) + .constraints([Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(middle); + + DebuggerLayout { code, result: middle[0], watchpoints: middle[1], user_input, message } + } +} + +#[derive(Debug, Default)] +struct Prompt { + history: VecDeque, + history_index: usize, + current: String, + cursor: usize, +} + +impl<'a> Widget for &'a Prompt { + fn render(self, area: Rect, buf: &mut Buffer) { + let mut plain = || { + Text::raw(&self.current).render(area, buf); + }; + + if self.cursor >= self.current.len() { + let span1 = Span::raw(&self.current); + let span2 = Span::styled(" ", Style::new().add_modifier(Modifier::REVERSED)); + Text::from(Line::from_iter([span1, span2])).render(area, buf); + return; + } + + let Some(pre) = self.current.get(..self.cursor) else { + plain(); + return; + }; + + let Some(c) = self.current.get(self.cursor..self.cursor + 1) else { + plain(); + return; + }; + + let Some(post) = self.current.get(self.cursor + 1..) else { + plain(); + return; + }; + + Text::from(Line::from_iter([ + Span::raw(pre), + Span::styled(c, Style::new().add_modifier(Modifier::REVERSED)), + Span::raw(post), + ])) + .render(area, buf); + } +} + +impl Prompt { + fn handle_key(&mut self, key: KeyCode, control: bool) -> Option { + match (key, control) { + (KeyCode::Enter, _) => { + self.history.push_back(mem::take(&mut self.current)); + self.history_index = self.history.len(); + return self.history.back().cloned(); + } + (KeyCode::Backspace, _) => self.backspace(), + (KeyCode::Left, _) => self.left(), + (KeyCode::Right, _) => self.right(), + (KeyCode::Up, _) => self.history_prev(), + (KeyCode::Down, _) => self.history_next(), + (KeyCode::Delete, _) => self.delete(), + (KeyCode::Char(c), false) => self.new_character(c), + (KeyCode::Char('a'), true) => self.beginning_of_line(), + (KeyCode::Char('e'), true) => self.end_of_line(), + _ => {} + } + + None + } + + fn new_character(&mut self, c: char) { + if self.cursor >= self.current.len() { + self.current.push(c); + self.cursor = self.current.len(); + } else { + let Some(pre) = self.current.get(..self.cursor) else { + return; + }; + let Some(post) = self.current.get(self.cursor..) else { + return; + }; + let mut with_char = format!("{pre}{c}"); + self.cursor = with_char.len(); + with_char.push_str(post); + self.current = with_char; + } + self.check_history(); + } + + fn right(&mut self) { + self.cursor = cmp::min(self.cursor + 1, self.current.len()); + } + + fn left(&mut self) { + self.cursor = self.cursor.saturating_sub(1); + } + + fn backspace(&mut self) { + if self.cursor == 0 { + return; + } + + if self.cursor >= self.current.len() { + self.current.pop(); + self.cursor = self.current.len(); + return; + } + + let Some(pre) = self.current.get(..self.cursor - 1) else { + return; + }; + let Some(post) = self.current.get(self.cursor..) else { + return; + }; + self.cursor -= 1; + + let s = format!("{pre}{post}"); + + self.current = s; + + self.check_history(); + } + + fn delete(&mut self) { + if self.cursor + 1 >= self.current.len() { + return; + } + + let Some(pre) = self.current.get(..self.cursor) else { + return; + }; + let Some(post) = self.current.get(self.cursor + 1..) else { + return; + }; + + let s = format!("{pre}{post}"); + + self.current = s; + + self.check_history(); + } + + fn beginning_of_line(&mut self) { + self.cursor = 0; + } + + fn end_of_line(&mut self) { + self.cursor = self.current.len(); + } + + fn history_next(&mut self) { + self.history_index += 1; + if self.history_index > self.history.len() { + self.history_index = 0; + } + self.current = self.history.get(self.history_index).cloned().unwrap_or(String::new()); + } + + fn history_prev(&mut self) { + if self.history_index == 0 { + self.history_index = self.history.len(); + } else { + self.history_index -= 1; + } + self.current = self.history.get(self.history_index).cloned().unwrap_or(String::new()); + } + + fn check_history(&mut self) { + const MAX_HISTORY: usize = 50; + + while self.history.len() > MAX_HISTORY { + self.history.pop_front(); + } + + self.history_index = self.history.len(); + } +} + +fn render_titled(frame: &mut Frame, widget: W, title: &str, area: Rect) { + let block = Block::bordered().title(title); + frame.render_widget(widget, block.inner(area)); + frame.render_widget(block, area); +} + +impl DrawData { + fn draw(&mut self, frame: &mut Frame) { + let layout = DebuggerLayout::new(frame.area()); + + let (code, line) = code_text(&self.code, self.highlight); + let p = Paragraph::new(code).scroll((line.saturating_sub(4) as u16, 0)); + render_titled(frame, p, "code", layout.code); + + render_titled(frame, Text::raw(&self.result), "Result", layout.result); + + render_titled(frame, Text::from_iter(self.watchpoints.iter().map(|s| &**s)), "Watchpoints", layout.watchpoints); + + render_titled(frame, Text::raw(&self.message), "Message", layout.message); + + render_titled(frame, &self.prompt, "Command:", layout.user_input); + } +} + +impl Ui for RatatuiUi { + fn display_user_data(&mut self, data: &UserData<'_>) { + self.data.code = data.code.to_string(); + self.data.highlight = data.highlight; + self.data.result = data.result.map(|s| s.to_string()).unwrap_or_default(); + self.data.watchpoints.clear(); + self.data.watchpoints.extend(data.watchpoints.iter().enumerate().map(|(i, s)| format!("{i:>2} {s}"))); + self.data.message = data.message.to_string(); + } + + fn receive_user_input(&mut self) -> String { + loop { + self.terminal.draw(|frame| self.data.draw(frame)).expect("failed to draw frame"); + if let Event::Key(key_event) = event::read().expect("event") { + let control = key_event.modifiers.contains(KeyModifiers::CONTROL); + if let Some(string) = self.data.prompt.handle_key(key_event.code, control) { + return string; + } + } + } + } +} diff --git a/interpreter/src/test/mod.rs b/interpreter/src/test/mod.rs new file mode 100644 index 0000000000..2e828f5ae6 --- /dev/null +++ b/interpreter/src/test/mod.rs @@ -0,0 +1,25 @@ +// Copyright (C) 2019-2024 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 serial_test::serial; + +mod runner; + +#[test] +#[serial] +pub fn tests() { + leo_test_framework::run_tests(&runner::InterpreterRunner, "interpreter"); +} diff --git a/interpreter/src/test/runner.rs b/interpreter/src/test/runner.rs new file mode 100644 index 0000000000..98fde3ebfb --- /dev/null +++ b/interpreter/src/test/runner.rs @@ -0,0 +1,98 @@ +// Copyright (C) 2019-2024 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 crate::*; + +use snarkvm::prelude::{Address, PrivateKey}; + +use leo_span::symbol::create_session_if_not_set_then; +use leo_test_framework::runner::{Namespace, ParseType, Runner, Test}; + +use std::{fs, path::PathBuf, str::FromStr as _}; + +pub struct LeoNamespace; + +impl Namespace for LeoNamespace { + fn parse_type(&self) -> ParseType { + ParseType::Whole + } + + fn run_test(&self, test: Test) -> Result { + create_session_if_not_set_then(|_| run_leo_test(test).map(|v| toml::Value::String(format!("{v}")))) + } +} + +pub struct AleoNamespace; + +impl Namespace for AleoNamespace { + fn parse_type(&self) -> ParseType { + ParseType::Whole + } + + fn run_test(&self, test: Test) -> Result { + create_session_if_not_set_then(|_| run_aleo_test(test).map(|v| toml::Value::String(format!("{v}")))) + } +} + +pub struct InterpreterRunner; + +impl Runner for InterpreterRunner { + fn resolve_namespace(&self, name: &str) -> Option> { + match name { + "Leo" => Some(Box::new(LeoNamespace)), + "Aleo" => Some(Box::new(AleoNamespace)), + _ => None, + } + } +} + +fn run_leo_test(test: Test) -> Result { + let tempdir = tempfile::tempdir().map_err(|e| format!("{e}"))?; + let mut filename = PathBuf::from(tempdir.path()); + filename.push("main.leo"); + fs::write(&filename, &test.content).map_err(|e| format!("{e}"))?; + + let private_key: PrivateKey = + PrivateKey::from_str(leo_package::TEST_PRIVATE_KEY).expect("should be able to parse private key"); + let address = Address::try_from(&private_key).expect("should be able to create address"); + let empty: [&PathBuf; 0] = []; + let mut interpreter = Interpreter::new([filename].iter(), empty, address, 0).map_err(|e| format!("{e}"))?; + let v = interpreter.action(InterpreterAction::LeoInterpretOver("test.aleo/main()".into())); + println!("got {v:?}"); + match v { + Err(e) => Err(format!("{e}")), + Ok(None) => Err("no value received".to_string()), + Ok(Some(v)) => Ok(v), + } +} + +fn run_aleo_test(test: Test) -> Result { + let tempdir = tempfile::tempdir().map_err(|e| format!("{e}"))?; + let mut filename = PathBuf::from(tempdir.path()); + filename.push("main.aleo"); + fs::write(&filename, &test.content).map_err(|e| format!("{e}"))?; + + let private_key: PrivateKey = + PrivateKey::from_str(leo_package::TEST_PRIVATE_KEY).expect("should be able to parse private key"); + let address = Address::try_from(&private_key).expect("should be able to create address"); + let empty: [&PathBuf; 0] = []; + let mut interpreter = Interpreter::new(empty, [filename].iter(), address, 0).map_err(|e| format!("{e}"))?; + match interpreter.action(InterpreterAction::LeoInterpretOver("test.aleo/main()".into())) { + Err(e) => Err(format!("{e}")), + Ok(None) => Err("no value received".to_string()), + Ok(Some(v)) => Ok(v), + } +} diff --git a/interpreter/src/ui.rs b/interpreter/src/ui.rs new file mode 100644 index 0000000000..abbc31e58b --- /dev/null +++ b/interpreter/src/ui.rs @@ -0,0 +1,30 @@ +// Copyright (C) 2019-2024 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 . + +pub struct UserData<'a> { + pub code: &'a str, + pub highlight: Option<(usize, usize)>, + pub message: &'a str, + pub futures: &'a [String], + pub watchpoints: &'a [String], + pub result: Option<&'a str>, +} + +pub trait Ui { + fn display_user_data(&mut self, data: &UserData<'_>); + + fn receive_user_input(&mut self) -> String; +} diff --git a/interpreter/src/util.rs b/interpreter/src/util.rs new file mode 100644 index 0000000000..e68bc5cdaa --- /dev/null +++ b/interpreter/src/util.rs @@ -0,0 +1,75 @@ +// Copyright (C) 2019-2024 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 leo_errors::{InterpreterHalt, Result}; +use leo_span::{Span, Symbol}; + +use snarkvm::prelude::{Identifier, TestnetV0}; + +#[macro_export] +macro_rules! tc_fail { + () => { + panic!("type checker failure") + }; +} + +#[macro_export] +macro_rules! halt_no_span { + ($($x:tt)*) => { + return Err(InterpreterHalt::new(format!($($x)*)).into()) + } +} + +#[macro_export] +macro_rules! halt { + ($span: expr) => { + return Err(InterpreterHalt::new_spanned(String::new(), $span).into()) + + }; + + ($span: expr, $($x:tt)*) => { + return Err(InterpreterHalt::new_spanned(format!($($x)*), $span).into()) + }; +} + +pub trait ExpectTc { + type T; + fn expect_tc(self, span: Span) -> Result; +} + +impl ExpectTc for Option { + type T = T; + + fn expect_tc(self, span: Span) -> Result { + match self { + Some(t) => Ok(t), + None => Err(InterpreterHalt::new_spanned("type failure".into(), span).into()), + } + } +} + +impl ExpectTc for Result { + type T = T; + + fn expect_tc(self, span: Span) -> Result { + self.map_err(|_e| InterpreterHalt::new_spanned("type failure".into(), span).into()) + } +} + +pub fn snarkvm_identifier_to_symbol(id: &Identifier) -> Symbol { + let s = id.to_string(); + Symbol::intern(&s) +} diff --git a/interpreter/src/value.rs b/interpreter/src/value.rs new file mode 100644 index 0000000000..954de329e7 --- /dev/null +++ b/interpreter/src/value.rs @@ -0,0 +1,488 @@ +// Copyright (C) 2019-2024 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 super::*; + +use leo_ast::{IntegerType, Type}; +use leo_span::Symbol; + +use snarkvm::prelude::{ + Address as SvmAddressParam, + Boolean as SvmBooleanParam, + Cast, + Field as SvmFieldParam, + FromBits as _, + Group as SvmGroupParam, + Identifier as SvmIdentifierParam, + Literal, + Plaintext, + Scalar as SvmScalarParam, + // Signature as SvmSignatureParam, + TestnetV0, + ToBits, + integers::Integer as SvmIntegerParam, +}; + +use indexmap::IndexMap; +use std::{ + fmt, + hash::{Hash, Hasher}, + str::FromStr as _, +}; + +pub type SvmAddress = SvmAddressParam; +type SvmBoolean = SvmBooleanParam; +type SvmField = SvmFieldParam; +type SvmGroup = SvmGroupParam; +type SvmIdentifier = SvmIdentifierParam; +type SvmInteger = SvmIntegerParam; +type SvmScalar = SvmScalarParam; +// type SvmSignature = SvmSignatureParam; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct StructContents { + pub name: Symbol, + pub contents: IndexMap, +} + +impl Hash for StructContents { + fn hash(&self, state: &mut H) { + self.name.hash(state); + for (_symbol, value) in self.contents.iter() { + value.hash(state); + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq, Hash)] +pub struct AsyncExecution { + pub function: GlobalId, + pub arguments: Vec, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] +pub struct Future(pub Vec); + +impl fmt::Display for Future { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Future")?; + if !self.0.is_empty() { + write!(f, " with calls to ")?; + let mut names = self.0.iter().map(|async_ex| async_ex.function).peekable(); + while let Some(name) = names.next() { + write!(f, "{name}")?; + if names.peek().is_some() { + write!(f, ", ")?; + } + } + } + Ok(()) + } +} + +/// A Leo value of any type. +/// +/// Mappings and functions aren't considered values. +#[derive(Clone, Debug, Default, Eq, PartialEq, Hash)] +pub enum Value { + #[default] + Unit, + Bool(bool), + U8(u8), + U16(u16), + U32(u32), + U64(u64), + U128(u128), + I8(i8), + I16(i16), + I32(i32), + I64(i64), + I128(i128), + Group(SvmGroup), + Field(SvmField), + Scalar(SvmScalar), + Array(Vec), + // Signature(Box), + Tuple(Vec), + Address(SvmAddress), + Future(Future), + Struct(StructContents), + // String(()), +} + +impl fmt::Display for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use Value::*; + match self { + Unit => write!(f, "()"), + + Bool(x) => write!(f, "{x}"), + U8(x) => write!(f, "{x}u8"), + U16(x) => write!(f, "{x}u16"), + U32(x) => write!(f, "{x}u32"), + U64(x) => write!(f, "{x}u64"), + U128(x) => write!(f, "{x}u128"), + I8(x) => write!(f, "{x}i8"), + I16(x) => write!(f, "{x}i16"), + I32(x) => write!(f, "{x}i32"), + I64(x) => write!(f, "{x}i64"), + I128(x) => write!(f, "{x}i128"), + Group(x) => write!(f, "{x}"), + Field(x) => write!(f, "{x}"), + Scalar(x) => write!(f, "{x}"), + Array(x) => { + write!(f, "[")?; + let mut iter = x.iter().peekable(); + while let Some(value) = iter.next() { + write!(f, "{value}")?; + if iter.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, "]") + } + Struct(StructContents { name, contents }) => { + write!(f, "{name} {{")?; + let mut iter = contents.iter().peekable(); + while let Some((member_name, value)) = iter.next() { + write!(f, "{member_name}: {value}")?; + if iter.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, "}}") + } + Tuple(x) => { + write!(f, "(")?; + let mut iter = x.iter().peekable(); + while let Some(value) = iter.next() { + write!(f, "{value}")?; + if iter.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, ")") + } + Address(x) => write!(f, "{x}"), + Future(future) => write!(f, "{future}"), + // Signature(x) => write!(f, "{x}"), + // String(_) => todo!(), + } + } +} + +impl ToBits for Value { + fn write_bits_le(&self, vec: &mut Vec) { + use Value::*; + + let plaintext: Plaintext = match self { + Bool(x) => Literal::Boolean(SvmBoolean::new(*x)).into(), + U8(x) => Literal::U8(SvmInteger::new(*x)).into(), + U16(x) => Literal::U16(SvmInteger::new(*x)).into(), + U32(x) => Literal::U32(SvmInteger::new(*x)).into(), + U64(x) => Literal::U64(SvmInteger::new(*x)).into(), + U128(x) => Literal::U128(SvmInteger::new(*x)).into(), + I8(x) => Literal::I8(SvmInteger::new(*x)).into(), + I16(x) => Literal::I16(SvmInteger::new(*x)).into(), + I32(x) => Literal::I32(SvmInteger::new(*x)).into(), + I64(x) => Literal::I64(SvmInteger::new(*x)).into(), + I128(x) => Literal::I128(SvmInteger::new(*x)).into(), + Group(x) => Literal::Group(*x).into(), + Field(x) => Literal::Field(*x).into(), + Scalar(x) => Literal::Scalar(*x).into(), + Address(x) => Literal::Address(*x).into(), + Struct(StructContents { name: _, contents }) => { + (contents.len() as u8).write_bits_le(vec); + for (name, value) in contents.iter() { + let name_s = name.to_string(); + let identifier = SvmIdentifier::from_str(&name_s).expect("identifier should parse"); + identifier.size_in_bits().write_bits_le(vec); + identifier.write_bits_le(vec); + let value_bits = value.to_bits_le(); + (value_bits.len() as u16).write_bits_le(vec); + vec.extend_from_slice(&value_bits); + } + return; + } + + Array(array) => { + for element in array.iter() { + let bits = element.to_bits_le(); + (bits.len() as u16).write_bits_le(vec); + vec.extend_from_slice(&bits); + } + return; + } + _ => tc_fail!(), + }; + + plaintext.write_bits_le(vec); + } + + fn write_bits_be(&self, _vec: &mut Vec) { + todo!() + } +} + +impl Value { + pub fn to_fields(&self) -> Vec { + let mut bits = self.to_bits_le(); + bits.push(true); + bits.chunks(SvmField::SIZE_IN_DATA_BITS) + .map(|bits| SvmField::from_bits_le(bits).expect("conversion should work")) + .collect() + } + + pub fn gte(&self, rhs: &Self) -> Result { + rhs.gt(self).map(|v| !v) + } + + pub fn lte(&self, rhs: &Self) -> Result { + rhs.lt(self).map(|v| !v) + } + + pub fn lt(&self, rhs: &Self) -> Result { + use Value::*; + Ok(match (self, rhs) { + (U8(x), U8(y)) => x < y, + (U16(x), U16(y)) => x < y, + (U32(x), U32(y)) => x < y, + (U64(x), U64(y)) => x < y, + (U128(x), U128(y)) => x < y, + (I8(x), I8(y)) => x < y, + (I16(x), I16(y)) => x < y, + (I32(x), I32(y)) => x < y, + (I64(x), I64(y)) => x < y, + (I128(x), I128(y)) => x < y, + (Field(x), Field(y)) => x < y, + (a, b) => halt_no_span!("Type failure: {a} < {b}"), + }) + } + + pub fn gt(&self, rhs: &Self) -> Result { + use Value::*; + Ok(match (self, rhs) { + (U8(x), U8(y)) => x > y, + (U16(x), U16(y)) => x > y, + (U32(x), U32(y)) => x > y, + (U64(x), U64(y)) => x > y, + (U128(x), U128(y)) => x > y, + (I8(x), I8(y)) => x > y, + (I16(x), I16(y)) => x > y, + (I32(x), I32(y)) => x > y, + (I64(x), I64(y)) => x > y, + (I128(x), I128(y)) => x > y, + (Field(x), Field(y)) => x > y, + (a, b) => halt_no_span!("Type failure: {a} > {b}"), + }) + } + + pub fn neq(&self, rhs: &Self) -> Result { + self.eq(rhs).map(|v| !v) + } + + /// Are the values equal, according to SnarkVM? + /// + /// We use this rather than the Eq trait so we can + /// fail when comparing values of different types, + /// rather than just returning false. + pub fn eq(&self, rhs: &Self) -> Result { + use Value::*; + Ok(match (self, rhs) { + (Unit, Unit) => true, + (Bool(x), Bool(y)) => x == y, + (U8(x), U8(y)) => x == y, + (U16(x), U16(y)) => x == y, + (U32(x), U32(y)) => x == y, + (U64(x), U64(y)) => x == y, + (U128(x), U128(y)) => x == y, + (I8(x), I8(y)) => x == y, + (I16(x), I16(y)) => x == y, + (I32(x), I32(y)) => x == y, + (I64(x), I64(y)) => x == y, + (I128(x), I128(y)) => x == y, + (Field(x), Field(y)) => x == y, + (Group(x), Group(y)) => x == y, + (Array(x), Array(y)) => { + if x.len() != y.len() { + return Ok(false); + } + for (lhs, rhs) in x.iter().zip(y.iter()) { + match lhs.eq(rhs) { + Ok(true) => {} + Ok(false) => return Ok(false), + Err(e) => return Err(e), + } + } + true + } + (a, b) => halt_no_span!("Type failure: {a} == {b}"), + }) + } + + pub fn inc_wrapping(&self) -> Self { + match self { + Value::U8(x) => Value::U8(x.wrapping_add(1)), + Value::U16(x) => Value::U16(x.wrapping_add(1)), + Value::U32(x) => Value::U32(x.wrapping_add(1)), + Value::U64(x) => Value::U64(x.wrapping_add(1)), + Value::U128(x) => Value::U128(x.wrapping_add(1)), + Value::I8(x) => Value::I8(x.wrapping_add(1)), + Value::I16(x) => Value::I16(x.wrapping_add(1)), + Value::I32(x) => Value::I32(x.wrapping_add(1)), + Value::I64(x) => Value::I64(x.wrapping_add(1)), + Value::I128(x) => Value::I128(x.wrapping_add(1)), + _ => tc_fail!(), + } + } + + /// Return the group generator. + pub fn generator() -> Self { + Value::Group(SvmGroup::generator()) + } + + /// Doesn't correspond to Aleo's shl, because it + /// does not fail when set bits are shifted out. + pub fn simple_shl(&self, shift: u32) -> Self { + match self { + Value::U8(x) => Value::U8(x << shift), + Value::U16(x) => Value::U16(x << shift), + Value::U32(x) => Value::U32(x << shift), + Value::U64(x) => Value::U64(x << shift), + Value::U128(x) => Value::U128(x << shift), + Value::I8(x) => Value::I8(x << shift), + Value::I16(x) => Value::I16(x << shift), + Value::I32(x) => Value::I32(x << shift), + Value::I64(x) => Value::I64(x << shift), + Value::I128(x) => Value::I128(x << shift), + _ => tc_fail!(), + } + } + + pub fn simple_shr(&self, shift: u32) -> Self { + match self { + Value::U8(x) => Value::U8(x >> shift), + Value::U16(x) => Value::U16(x >> shift), + Value::U32(x) => Value::U32(x >> shift), + Value::U64(x) => Value::U64(x >> shift), + Value::U128(x) => Value::U128(x >> shift), + Value::I8(x) => Value::I8(x >> shift), + Value::I16(x) => Value::I16(x >> shift), + Value::I32(x) => Value::I32(x >> shift), + Value::I64(x) => Value::I64(x >> shift), + Value::I128(x) => Value::I128(x >> shift), + _ => tc_fail!(), + } + } + + /// Convert to the given type if possible under Aleo casting rules. + pub fn cast(self, cast_type: &Type) -> Option { + match self { + Value::Bool(b) => really_cast(SvmBoolean::new(b), cast_type), + Value::U8(x) => really_cast(SvmInteger::new(x), cast_type), + Value::U16(x) => really_cast(SvmInteger::new(x), cast_type), + Value::U32(x) => really_cast(SvmInteger::new(x), cast_type), + Value::U64(x) => really_cast(SvmInteger::new(x), cast_type), + Value::U128(x) => really_cast(SvmInteger::new(x), cast_type), + Value::I8(x) => really_cast(SvmInteger::new(x), cast_type), + Value::I16(x) => really_cast(SvmInteger::new(x), cast_type), + Value::I32(x) => really_cast(SvmInteger::new(x), cast_type), + Value::I64(x) => really_cast(SvmInteger::new(x), cast_type), + Value::I128(x) => really_cast(SvmInteger::new(x), cast_type), + Value::Group(g) => really_cast(g.to_x_coordinate(), cast_type), + Value::Field(f) => really_cast(f, cast_type), + Value::Scalar(s) => really_cast(s, cast_type), + Value::Address(a) => really_cast(a.to_group().to_x_coordinate(), cast_type), + _ => None, + } + } +} + +fn really_cast(c: C, cast_type: &Type) -> Option +where + C: Cast + + Cast + + Cast + + Cast + + Cast + + Cast + + Cast> + + Cast> + + Cast> + + Cast> + + Cast> + + Cast> + + Cast> + + Cast> + + Cast> + + Cast>, +{ + use Type::*; + + let value = match cast_type { + Address => Value::Address(c.cast().ok()?), + Boolean => Value::Bool({ + let b: SvmBoolean = c.cast().ok()?; + *b + }), + Field => Value::Field(c.cast().ok()?), + Group => Value::Group(c.cast().ok()?), + Integer(IntegerType::U8) => Value::U8({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::U16) => Value::U16({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::U32) => Value::U32({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::U64) => Value::U64({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::U128) => Value::U128({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::I8) => Value::I8({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::I16) => Value::I16({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::I32) => Value::I32({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::I64) => Value::I64({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Integer(IntegerType::I128) => Value::I128({ + let i: SvmInteger = c.cast().ok()?; + *i + }), + Scalar => Value::Scalar(c.cast().ok()?), + + _ => tc_fail!(), + }; + Some(value) +} diff --git a/leo/cli/cli.rs b/leo/cli/cli.rs index b98836a8c8..1b290bfa6e 100644 --- a/leo/cli/cli.rs +++ b/leo/cli/cli.rs @@ -82,6 +82,11 @@ enum Commands { #[clap(flatten)] command: LeoBuild, }, + #[clap(about = "Debug the current package via the interpreter")] + Debug { + #[clap(flatten)] + command: LeoDebug, + }, #[clap(about = "Add a new on-chain or local dependency to the current package.")] Add { #[clap(flatten)] @@ -138,6 +143,7 @@ pub fn run_with_args(cli: CLI) -> Result<()> { Commands::Account { command } => command.try_execute(context), Commands::New { command } => command.try_execute(context), Commands::Build { command } => command.try_execute(context), + Commands::Debug { command } => command.try_execute(context), Commands::Query { command } => command.try_execute(context), Commands::Clean { command } => command.try_execute(context), Commands::Deploy { command } => command.try_execute(context), diff --git a/leo/cli/commands/debug.rs b/leo/cli/commands/debug.rs new file mode 100644 index 0000000000..ed904d21e2 --- /dev/null +++ b/leo/cli/commands/debug.rs @@ -0,0 +1,154 @@ +// Copyright (C) 2019-2024 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 std::{fs, path::PathBuf}; + +use snarkvm::prelude::{Network, ProgramID, TestnetV0}; + +#[cfg(not(feature = "only_testnet"))] +use snarkvm::prelude::{CanaryV0, MainnetV0}; + +use leo_errors::UtilError; +use leo_retriever::{Manifest, NetworkName, Retriever}; +use leo_span::Symbol; + +use super::*; + +/// Debugs an Aleo program through the interpreter. +#[derive(Parser, Debug)] +pub struct LeoDebug { + #[arg(long, help = "Use these source files instead of finding source files through the project structure.", num_args = 1..)] + pub(crate) paths: Vec, + + #[arg(long, help = "The block height, accessible via block.height.", default_value = "0")] + pub(crate) block_height: u32, + + #[arg(long, action, help = "Use the text user interface.")] + pub(crate) tui: bool, + + #[clap(flatten)] + pub(crate) compiler_options: BuildOptions, +} + +impl Command for LeoDebug { + type Input = ::Output; + type Output = (); + + fn log_span(&self) -> Span { + tracing::span!(tracing::Level::INFO, "Leo") + } + + fn prelude(&self, context: Context) -> Result { + if self.paths.is_empty() { + (LeoBuild { options: self.compiler_options.clone() }).execute(context) + } else { + Ok(()) + } + } + + fn apply(self, context: Context, _: Self::Input) -> Result { + // Parse the network. + let network = NetworkName::try_from(context.get_network(&self.compiler_options.network)?)?; + match network { + NetworkName::TestnetV0 => handle_debug::(&self, context), + NetworkName::MainnetV0 => { + #[cfg(feature = "only_testnet")] + panic!("Mainnet chosen with only_testnet feature"); + #[cfg(not(feature = "only_testnet"))] + return handle_debug::(&self, context); + } + NetworkName::CanaryV0 => { + #[cfg(feature = "only_testnet")] + panic!("Canary chosen with only_testnet feature"); + #[cfg(not(feature = "only_testnet"))] + return handle_debug::(&self, context); + } + } + } +} + +fn handle_debug(command: &LeoDebug, context: Context) -> Result<()> { + if command.paths.is_empty() { + // Get the package path. + let package_path = context.dir()?; + let home_path = context.home()?; + + // Get the program id. + let manifest = Manifest::read_from_dir(&package_path)?; + let program_id = ProgramID::::from_str(manifest.program())?; + + // Get the private key. + let private_key = context.get_private_key(&None)?; + let address = Address::try_from(&private_key)?; + + // Retrieve all local dependencies in post order + let main_sym = Symbol::intern(&program_id.name().to_string()); + let mut retriever = Retriever::::new( + main_sym, + &package_path, + &home_path, + context.get_endpoint(&command.compiler_options.endpoint)?.to_string(), + ) + .map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?; + let mut local_dependencies = + retriever.retrieve().map_err(|err| UtilError::failed_to_retrieve_dependencies(err, Default::default()))?; + + // Push the main program at the end of the list. + local_dependencies.push(main_sym); + + let paths: Vec = local_dependencies + .into_iter() + .map(|dependency| { + let base_path = retriever.get_context(&dependency).full_path(); + base_path.join("src/main.leo") + }) + .collect(); + + let imports_directory = package_path.join("build/imports"); + + let aleo_paths: Vec = if let Ok(dir) = fs::read_dir(imports_directory) { + dir.flat_map(|maybe_filename| maybe_filename.ok()) + .filter(|entry| entry.file_type().ok().map(|filetype| filetype.is_file()).unwrap_or(false)) + .flat_map(|entry| { + let path = entry.path(); + if path.extension().map(|e| e == "aleo").unwrap_or(false) { Some(path) } else { None } + }) + .collect() + } else { + Vec::new() + }; + + leo_interpreter::interpret(&paths, &aleo_paths, address, command.block_height, command.tui) + } else { + let private_key: PrivateKey = PrivateKey::from_str(leo_package::TEST_PRIVATE_KEY)?; + let address = Address::try_from(&private_key)?; + + let leo_paths: Vec = command + .paths + .iter() + .filter(|path_str| path_str.ends_with(".leo")) + .map(|path_str| path_str.into()) + .collect(); + let aleo_paths: Vec = command + .paths + .iter() + .filter(|path_str| !path_str.ends_with(".leo")) + .map(|path_str| path_str.into()) + .collect(); + + leo_interpreter::interpret(&leo_paths, &aleo_paths, address, command.block_height, command.tui) + } +} diff --git a/leo/cli/commands/mod.rs b/leo/cli/commands/mod.rs index d22cd8683c..55bc8faccb 100644 --- a/leo/cli/commands/mod.rs +++ b/leo/cli/commands/mod.rs @@ -26,6 +26,9 @@ pub use build::LeoBuild; pub mod clean; pub use clean::LeoClean; +pub mod debug; +pub use debug::LeoDebug; + pub mod deploy; pub use deploy::Deploy; diff --git a/leo/package/src/lib.rs b/leo/package/src/lib.rs index 6d069fdfee..2d99ded342 100644 --- a/leo/package/src/lib.rs +++ b/leo/package/src/lib.rs @@ -31,6 +31,8 @@ use std::{fs, fs::ReadDir, path::PathBuf}; pub static LEO_FILE_EXTENSION: &str = ".leo"; +pub static TEST_PRIVATE_KEY: &str = "APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH"; + pub(crate) fn parse_file_paths(directory: ReadDir, file_paths: &mut Vec) -> Result<()> { for file_entry in directory { let file_entry = file_entry.map_err(PackageError::failed_to_get_leo_file_entry)?; diff --git a/leo/package/src/package.rs b/leo/package/src/package.rs index 63bae5fa79..04a74b2b11 100644 --- a/leo/package/src/package.rs +++ b/leo/package/src/package.rs @@ -15,6 +15,7 @@ // along with the Leo library. If not, see . use crate::{ + TEST_PRIVATE_KEY, root::{Env, Gitignore}, source::{MainFile, SourceDirectory}, }; @@ -148,11 +149,7 @@ impl Package { // Create the .env file. // Include the private key of validator 0 for ease of use with local devnets, as it will automatically be seeded with funds. - Env::::new( - Some(PrivateKey::::from_str("APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH")?), - endpoint, - )? - .write_to(&path)?; + Env::::new(Some(PrivateKey::::from_str(TEST_PRIVATE_KEY)?), endpoint)?.write_to(&path)?; // Create a manifest. let manifest = Manifest::default(package_name); diff --git a/notes b/notes new file mode 100644 index 0000000000..d7b0935124 --- /dev/null +++ b/notes @@ -0,0 +1,8 @@ + +call deposit(100u64), type failure + +cannot print register credits.aleo/transfer_public + +after set_program, message or print out symbols + +set self.signer diff --git a/tests/expectations/compiler/core/cheatcodes/invalid_cheatcodes_fail.out b/tests/expectations/compiler/core/cheatcodes/invalid_cheatcodes_fail.out new file mode 100644 index 0000000000..4205dced3d --- /dev/null +++ b/tests/expectations/compiler/core/cheatcodes/invalid_cheatcodes_fail.out @@ -0,0 +1,24 @@ +namespace = "Compile" +expectation = "Fail" +outputs = [""" +Error [ETYC0372005]: Unknown variable `Yo` + --> compiler-test:13:34 + | + 13 | CheatCode::print_mapping(Yo); + | ^^ +Error [ETYC0372005]: Unknown variable `account` + --> compiler-test:14:34 + | + 14 | CheatCode::print_mapping(test_dep.aleo/account); + | ^^^^^^^^^^^^^^^^^^^^^ +Error [ETYC0372003]: Expected type `u32` but type `u64` was found + --> compiler-test:15:37 + | + 15 | CheatCode::set_block_height(1u64); + | ^^^^ +Error [ETYC0372003]: Expected type `u32` but type `u64` was found + --> compiler-test:16:37 + | + 16 | CheatCode::set_block_height(a); + | ^ +"""] diff --git a/tests/expectations/compiler/core/cheatcodes/valid_cheatcodes.out b/tests/expectations/compiler/core/cheatcodes/valid_cheatcodes.out new file mode 100644 index 0000000000..ff52b82d9e --- /dev/null +++ b/tests/expectations/compiler/core/cheatcodes/valid_cheatcodes.out @@ -0,0 +1,44 @@ +namespace = "Compile" +expectation = "Pass" +outputs = [[{ compile = [ + { initial_symbol_table = "0fbe7b86610386bfb1c7f0f211b2043baae706b9195008f8553511968f9297e7", type_checked_symbol_table = "efc3324af11b2f3645010266f1a871d799b81b07bec594fa88402b3f6fe1330b", unrolled_symbol_table = "efc3324af11b2f3645010266f1a871d799b81b07bec594fa88402b3f6fe1330b", initial_ast = "472f984ad224e345de6a6a8cb7c4986b0bf8fa288713c38a506b41bad280faa5", unrolled_ast = "472f984ad224e345de6a6a8cb7c4986b0bf8fa288713c38a506b41bad280faa5", ssa_ast = "ff6501ea72e6a46b15d71a89b71181851fba9aa2e6ee2a36d70218ad1a089a68", flattened_ast = "ba4154876562575fc3f8b6106a3ed4ab331382a4538ebc9630c82ed9be48176b", destructured_ast = "a995365c129f150bc361a571e5a0810f014a62c170d39e904b7de473bcdac50f", inlined_ast = "3a2f11285208b9bd75048be921a23504d9389ae81e2bdc96f631943cfa4349c6", dce_ast = "ed19a1a5455d89e6a59914923e69d600b0fde7fa91cae652d70756eb59365e03", bytecode = """ +program test_dep.aleo; + +record yeets: + owner as address.private; + val as u32.private; + +mapping Yo: + key as u32.public; + value as u32.public; + +function main_dep: + input r0 as u32.private; + async main_dep r0 1u32 into r1; + cast self.caller 1u32 into r2 as yeets.record; + output r2 as yeets.record; + output r1 as test_dep.aleo/main_dep.future; + +finalize main_dep: + input r0 as u32.public; + input r1 as u32.public; + set r1 into Yo[r0]; +""", errors = "", warnings = "" }, + { initial_symbol_table = "1ff3afb19b60e93b29bcf302b325d787717fc9f72dc76ebf0e2f16a88c61f8e1", type_checked_symbol_table = "34fca725cd812896570be3b52571fda6af6ae081e686f1414d3c356e3a96f568", unrolled_symbol_table = "34fca725cd812896570be3b52571fda6af6ae081e686f1414d3c356e3a96f568", initial_ast = "308e8bf01c3d58f3833b54d7bd297cc34a47754b723fdb727b06aafd88c7322c", unrolled_ast = "cbebc3742af33ad850a585eb37f0e50dd4182317f89bf229083826d3a41a7719", ssa_ast = "c29e889749cbd936b56a07f85d7fa1cc932901ef0b89c5d9f81badf262122286", flattened_ast = "37eb161a0cfc90d08f16ea37e2a815476b11e7b03adf57361c97217807a49e58", destructured_ast = "36ad597d27bc588495a6168d7fabfd8750b8efab765f39992851530b48e04712", inlined_ast = "0152ae3ca21c99c5c59eb80d72832fcd8cb830f13ab4dfab8137af9dfaa5e43e", dce_ast = "0152ae3ca21c99c5c59eb80d72832fcd8cb830f13ab4dfab8137af9dfaa5e43e", bytecode = """ +import test_dep.aleo; +program test.aleo; + +mapping account: + key as address.public; + value as u64.public; + +function main: + input r0 as u32.public; + async main r0 into r1; + output r1 as test.aleo/main.future; + +finalize main: + input r0 as u32.public; + assert.eq true true; +""", errors = "", warnings = "" }, +] }]] diff --git a/tests/expectations/interpreter/arithmetic.out b/tests/expectations/interpreter/arithmetic.out new file mode 100644 index 0000000000..6e13df9810 --- /dev/null +++ b/tests/expectations/interpreter/arithmetic.out @@ -0,0 +1,3 @@ +namespace = "Leo" +expectation = "Pass" +outputs = ["1712u32"] diff --git a/tests/expectations/interpreter/hash.out b/tests/expectations/interpreter/hash.out new file mode 100644 index 0000000000..6874c9b7bc --- /dev/null +++ b/tests/expectations/interpreter/hash.out @@ -0,0 +1,3 @@ +namespace = "Leo" +expectation = "Pass" +outputs = ["7649508962193807282860816486231709561414143880166292770947785297592281622433field"] diff --git a/tests/test-framework/src/error.rs b/tests/test-framework/src/error.rs index 1a0862dea6..81c44beb99 100644 --- a/tests/test-framework/src/error.rs +++ b/tests/test-framework/src/error.rs @@ -48,6 +48,8 @@ fn toml_to_string(x: &Value) -> String { s.push('\n'); } s + } else if let Some(s) = x.as_str() { + s.to_string() } else { toml::to_string(x).expect("serialization failed") } diff --git a/tests/tests/compiler/core/cheatcodes/invalid_cheatcodes_fail.leo b/tests/tests/compiler/core/cheatcodes/invalid_cheatcodes_fail.leo new file mode 100644 index 0000000000..0c7bcc3e0e --- /dev/null +++ b/tests/tests/compiler/core/cheatcodes/invalid_cheatcodes_fail.leo @@ -0,0 +1,43 @@ +/* +namespace = "Compile" +expectation = "Fail" +*/ + +// The 'test_dep' program. +program test_dep.aleo { + mapping Yo: u32 => u32; + record yeets { + owner: address, + val: u32, + } + + async transition main_dep(a:u32) -> (yeets, Future) { + let f: Future = finalize_main_dep(a, 1u32); + let l: yeets = yeets {owner: self.caller, val: 1u32}; + return (l, f); + } + + async function finalize_main_dep(a:u32, b:u32) { + Mapping::set(Yo, a, b); + let c:u32 = a + b; + } +} + +// --- Next Program --- // + +import test_dep.aleo; + +program test.aleo { + mapping account: address => u64; + + async transition main(public a: u64) -> Future { + return finish(a); + } + + async function finish(public a: u64) { + CheatCode::print_mapping(Yo); + CheatCode::print_mapping(test_dep.aleo/account); + CheatCode::set_block_height(1u64); + CheatCode::set_block_height(a); + } +} diff --git a/tests/tests/compiler/core/cheatcodes/valid_cheatcodes.leo b/tests/tests/compiler/core/cheatcodes/valid_cheatcodes.leo new file mode 100644 index 0000000000..050d3ae4f7 --- /dev/null +++ b/tests/tests/compiler/core/cheatcodes/valid_cheatcodes.leo @@ -0,0 +1,43 @@ +/* +namespace = "Compile" +expectation = "Pass" +*/ + +// The 'test_dep' program. +program test_dep.aleo { + mapping Yo: u32 => u32; + record yeets { + owner: address, + val: u32, + } + + async transition main_dep(a:u32) -> (yeets, Future) { + let f: Future = finalize_main_dep(a, 1u32); + let l: yeets = yeets {owner: self.caller, val: 1u32}; + return (l, f); + } + + async function finalize_main_dep(a:u32, b:u32) { + Mapping::set(Yo, a, b); + let c:u32 = a + b; + } +} + +// --- Next Program --- // + +import test_dep.aleo; + +program test.aleo { + mapping account: address => u64; + + async transition main(public a: u32) -> Future { + return finish(a); + } + + async function finish(public a: u32) { + CheatCode::print_mapping(account); + CheatCode::print_mapping(test_dep.aleo/Yo); + CheatCode::set_block_height(1u32); + CheatCode::set_block_height(a); + } +} diff --git a/tests/tests/interpreter/arithmetic.leo b/tests/tests/interpreter/arithmetic.leo new file mode 100644 index 0000000000..676f82bae1 --- /dev/null +++ b/tests/tests/interpreter/arithmetic.leo @@ -0,0 +1,15 @@ +/* +namespace = "Leo" +expectation = "Pass" +*/ + +program test.aleo { + function f(x: u32, y: u32) -> u32 { + return x + (17u32 * y); + + } + + transition main() -> u32 { + return f(12u32, 100u32); + } +} diff --git a/tests/tests/interpreter/hash.leo b/tests/tests/interpreter/hash.leo new file mode 100644 index 0000000000..bdffddf13b --- /dev/null +++ b/tests/tests/interpreter/hash.leo @@ -0,0 +1,15 @@ +/* +namespace = "Leo" +expectation = "Pass" +*/ + +program test.aleo { + transition main() -> field { + let a: field = 1234567890field; + return BHP256::hash_to_field(a) + * SHA3_256::hash_to_field(a) + * Poseidon2::hash_to_field(a) + * Poseidon4::hash_to_field(a) + * Poseidon8::hash_to_field(a); + } +}