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/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/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/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..6777fcd7e4 100644 --- a/interpreter/src/cursor.rs +++ b/interpreter/src/cursor.rs @@ -361,6 +361,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, @@ -710,6 +714,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 @@ -2158,6 +2165,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); + } +}