From cbdf55af9d575f7aed2886d59efed4c31e6f4a89 Mon Sep 17 00:00:00 2001 From: Michael Benfield Date: Wed, 23 Oct 2024 14:29:23 -0700 Subject: [PATCH 1/4] Interpreter. --- Cargo.lock | 2 +- Cargo.toml | 2 +- interpreter/Cargo.toml | 2 +- interpreter/src/cursor.rs | 6 ++++++ interpreter/src/lib.rs | 7 +++++++ 5 files changed, 16 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2a468f7e13..95a4f21ee3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1563,7 +1563,7 @@ dependencies = [ [[package]] name = "leo-interpreter" -version = "2.3.1" +version = "2.4.0" dependencies = [ "colored", "dialoguer", diff --git a/Cargo.toml b/Cargo.toml index 4e1e4bcd93..2bebb2e881 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,7 +63,7 @@ version = "2.4.0" [workspace.dependencies.leo-interpreter] path = "./interpreter" -version = "2.3.0" +version = "2.4.0" [workspace.dependencies.leo-package] path = "./leo/package" diff --git a/interpreter/Cargo.toml b/interpreter/Cargo.toml index 2a1b22be98..85a9b5877a 100644 --- a/interpreter/Cargo.toml +++ b/interpreter/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "leo-interpreter" -version = "2.3.1" +version = "2.4.0" authors = [ "The Leo Team " ] description = "Interpreter for the Leo programming language" homepage = "https://leo-lang.org" diff --git a/interpreter/src/cursor.rs b/interpreter/src/cursor.rs index 492f4d9541..f19d94a1e7 100644 --- a/interpreter/src/cursor.rs +++ b/interpreter/src/cursor.rs @@ -284,6 +284,7 @@ impl<'a> Cursor<'a> { signer, block_height, really_async, +<<<<<<< HEAD program: None, } } @@ -296,6 +297,11 @@ impl<'a> Cursor<'a> { self.contexts.current_program().or(self.program) } +======= + } + } + +>>>>>>> dd68615d86 (Interpreter.) pub fn increment_step(&mut self) { let Some(Frame { step, .. }) = self.frames.last_mut() else { panic!("frame expected"); diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index aee0a59586..d925900c3f 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -68,7 +68,11 @@ You can set a breakpoint with When executing Aleo VM code, you can print the value of a register like this: #print 2 +<<<<<<< HEAD Some of the commands may be run with one letter abbreviations, such as #i. +======= +You may also use one letter abbreviations for these commands, such as #i. +>>>>>>> dd68615d86 (Interpreter.) Note that this interpreter is not line oriented as in many common debuggers; rather it is oriented around expressions and statements. @@ -87,6 +91,7 @@ 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 +<<<<<<< HEAD The interpreter begins in a global context, not in any Leo program. You can set the current program with @@ -102,6 +107,8 @@ REPL. In this case, you may use the command Which will restore to the last saved state of the interpreter. Any time you enter Leo code at the prompt, interpreter state is saved. +======= +>>>>>>> dd68615d86 (Interpreter.) Input history is available - use the up and down arrow keys. "; From 3e941d5b000fe8f22b5a5b346d39dc06c9f4e848 Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Tue, 3 Dec 2024 18:20:20 -0800 Subject: [PATCH 2/4] Implement basic cheat codes --- compiler/ast/src/functions/core_function.rs | 13 +++++- .../src/code_generation/visit_expressions.rs | 4 ++ .../eliminate_expression.rs | 7 ++- compiler/passes/src/type_checking/checker.rs | 10 +++++ compiler/span/src/symbol.rs | 5 +++ interpreter/src/cursor.rs | 36 +++++++++++++++ .../cheatcodes/invalid_cheatcodes_fail.out | 24 ++++++++++ .../core/cheatcodes/valid_cheatcodes.out | 44 +++++++++++++++++++ .../cheatcodes/invalid_cheatcodes_fail.leo | 43 ++++++++++++++++++ .../core/cheatcodes/valid_cheatcodes.leo | 43 ++++++++++++++++++ 10 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 tests/expectations/compiler/core/cheatcodes/invalid_cheatcodes_fail.out create mode 100644 tests/expectations/compiler/core/cheatcodes/valid_cheatcodes.out create mode 100644 tests/tests/compiler/core/cheatcodes/invalid_cheatcodes_fail.leo create mode 100644 tests/tests/compiler/core/cheatcodes/valid_cheatcodes.leo diff --git a/compiler/ast/src/functions/core_function.rs b/compiler/ast/src/functions/core_function.rs index 3732666df4..e2bc67e4b8 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, } } @@ -870,7 +879,9 @@ impl CoreFunction { | CoreFunction::ChaChaRandScalar | CoreFunction::MappingSet | CoreFunction::MappingRemove - | CoreFunction::MappingContains => true, + | CoreFunction::MappingContains + | CoreFunction::CheatCodePrintMapping + | CoreFunction::CheatCodeSetBlockHeight => true, CoreFunction::BHP256CommitToAddress | CoreFunction::BHP256CommitToField | CoreFunction::BHP256CommitToGroup 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 ff24e68aad..00fb0d9d40 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/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/interpreter/src/cursor.rs b/interpreter/src/cursor.rs index f19d94a1e7..f852e98015 100644 --- a/interpreter/src/cursor.rs +++ b/interpreter/src/cursor.rs @@ -367,6 +367,10 @@ impl<'a> Cursor<'a> { } } + 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, @@ -716,6 +720,9 @@ impl<'a> Cursor<'a> { 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 @@ -2164,6 +2171,35 @@ impl<'a> Cursor<'a> { } 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) 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/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); + } +} From 1d9a76acaeba44b5614f376872fa4cd5829a178a Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 6 Dec 2024 07:33:57 -0800 Subject: [PATCH 3/4] Address feedback --- compiler/ast/src/functions/core_function.rs | 8 ++++---- interpreter/src/cursor.rs | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/ast/src/functions/core_function.rs b/compiler/ast/src/functions/core_function.rs index e2bc67e4b8..6af63f333c 100644 --- a/compiler/ast/src/functions/core_function.rs +++ b/compiler/ast/src/functions/core_function.rs @@ -879,9 +879,7 @@ impl CoreFunction { | CoreFunction::ChaChaRandScalar | CoreFunction::MappingSet | CoreFunction::MappingRemove - | CoreFunction::MappingContains - | CoreFunction::CheatCodePrintMapping - | CoreFunction::CheatCodeSetBlockHeight => true, + | CoreFunction::MappingContains => true, CoreFunction::BHP256CommitToAddress | CoreFunction::BHP256CommitToField | CoreFunction::BHP256CommitToGroup @@ -1112,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/interpreter/src/cursor.rs b/interpreter/src/cursor.rs index f852e98015..83c8ef1c62 100644 --- a/interpreter/src/cursor.rs +++ b/interpreter/src/cursor.rs @@ -2186,7 +2186,7 @@ impl<'a> Cursor<'a> { ); // Print the contents of the mapping. for (key, value) in mapping { - println!(" {:?} -> {:?}", key, value); + println!(" {} -> {}", key, value); } } else { tc_fail!(); From a5ea6b8c81abb1fcf1e586d67d1d81e8450df5cd Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu <23022326+d0cd@users.noreply.github.com> Date: Fri, 6 Dec 2024 07:49:25 -0800 Subject: [PATCH 4/4] Cleanup --- interpreter/src/cursor.rs | 6 ------ interpreter/src/lib.rs | 7 ------- 2 files changed, 13 deletions(-) diff --git a/interpreter/src/cursor.rs b/interpreter/src/cursor.rs index 83c8ef1c62..6777fcd7e4 100644 --- a/interpreter/src/cursor.rs +++ b/interpreter/src/cursor.rs @@ -284,7 +284,6 @@ impl<'a> Cursor<'a> { signer, block_height, really_async, -<<<<<<< HEAD program: None, } } @@ -297,11 +296,6 @@ impl<'a> Cursor<'a> { self.contexts.current_program().or(self.program) } -======= - } - } - ->>>>>>> dd68615d86 (Interpreter.) pub fn increment_step(&mut self) { let Some(Frame { step, .. }) = self.frames.last_mut() else { panic!("frame expected"); diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index d925900c3f..aee0a59586 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -68,11 +68,7 @@ You can set a breakpoint with When executing Aleo VM code, you can print the value of a register like this: #print 2 -<<<<<<< HEAD Some of the commands may be run with one letter abbreviations, such as #i. -======= -You may also use one letter abbreviations for these commands, such as #i. ->>>>>>> dd68615d86 (Interpreter.) Note that this interpreter is not line oriented as in many common debuggers; rather it is oriented around expressions and statements. @@ -91,7 +87,6 @@ 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 -<<<<<<< HEAD The interpreter begins in a global context, not in any Leo program. You can set the current program with @@ -107,8 +102,6 @@ REPL. In this case, you may use the command Which will restore to the last saved state of the interpreter. Any time you enter Leo code at the prompt, interpreter state is saved. -======= ->>>>>>> dd68615d86 (Interpreter.) Input history is available - use the up and down arrow keys. ";