From 4f8169e7e19858cb736a647c52a9564bd8030a24 Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Mon, 18 Nov 2024 15:23:37 -0300 Subject: [PATCH 1/8] forc test single-step until jump point instead of patching binary --- forc-test/src/execute.rs | 96 +++--- .../language/configurable_tests/Forc.lock | 13 + .../language/configurable_tests/Forc.toml | 8 + .../configurable_tests/json_abi_oracle.json | 284 ++++++++++++++++++ .../json_abi_oracle_new_encoding.json | 228 ++++++++++++++ .../language/configurable_tests/src/main.sw | 83 +++++ .../language/configurable_tests/test.toml | 1 + 7 files changed, 669 insertions(+), 44 deletions(-) create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.lock create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.toml create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle_new_encoding.json create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/src/main.sw create mode 100644 test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/test.toml diff --git a/forc-test/src/execute.rs b/forc-test/src/execute.rs index 76e89324903..a4ba7b6a219 100644 --- a/forc-test/src/execute.rs +++ b/forc-test/src/execute.rs @@ -5,6 +5,7 @@ use crate::TEST_METADATA_SEED; use forc_pkg::PkgTestEntry; use fuel_tx::{self as tx, output::contract::Contract, Chargeable, Finalizable}; use fuel_vm::error::InterpreterError; +use fuel_vm::fuel_asm; use fuel_vm::{ self as vm, checked_transaction::builder::TransactionBuilderExt, @@ -27,6 +28,8 @@ pub struct TestExecutor { pub tx: vm::checked_transaction::Ready, pub test_entry: PkgTestEntry, pub name: String, + pub ji_idx: usize, + pub test_offset: u32, } /// The result of executing a test with breakpoints enabled. @@ -49,7 +52,8 @@ impl TestExecutor { let storage = test_setup.storage().clone(); // Patch the bytecode to jump to the relevant test. - let bytecode = patch_test_bytecode(bytecode, test_offset).into_owned(); + //let bytecode = patch_test_bytecode(bytecode, test_offset).into_owned(); + let ji_idx = find_ji_instruction_index(bytecode); // Create a transaction to execute the test function. let script_input_data = vec![]; @@ -68,7 +72,7 @@ impl TestExecutor { let block_height = (u32::MAX >> 1).into(); let gas_price = 0; - let mut tx_builder = tx::TransactionBuilder::script(bytecode, script_input_data); + let mut tx_builder = tx::TransactionBuilder::script(bytecode.to_vec(), script_input_data); let params = maxed_consensus_params(); @@ -126,6 +130,8 @@ impl TestExecutor { tx, test_entry: test_entry.clone(), name, + ji_idx, + test_offset: (test_offset - ji_idx as u32) * Instruction::SIZE as u32 }) } @@ -192,14 +198,36 @@ impl TestExecutor { pub fn execute(&mut self) -> anyhow::Result { let start = std::time::Instant::now(); - let transition = self - .interpreter - .transact(self.tx.clone()) - .map_err(|err: InterpreterError<_>| anyhow::anyhow!(err))?; - let state = *transition.state(); + + self.interpreter.set_single_stepping(true); + + let mut state = { + let transition = self + .interpreter + .transact(self.tx.clone()) + .map_err(|err: InterpreterError<_>| anyhow::anyhow!(err))?; + *transition.state() + }; + + let jump_pc = (self.ji_idx * Instruction::SIZE) as u64; + + loop { + match state { + ProgramState::Return(_) | ProgramState::ReturnData(_) | ProgramState::Revert(_) => break, + ProgramState::RunProgram(eval) => { + if eval.breakpoint().unwrap().pc() == jump_pc { + self.interpreter.registers_mut()[3] += self.test_offset as u64; + self.interpreter.set_single_stepping(false); + } + + state = self.interpreter.resume().map_err(|err: InterpreterError<_>| anyhow::anyhow!(err))?; + }, + ProgramState::VerifyPredicate(_) => todo!(), + } + } let duration = start.elapsed(); - let (gas_used, logs) = Self::get_gas_and_receipts(transition.receipts().to_vec())?; + let (gas_used, logs) = Self::get_gas_and_receipts(self.interpreter.receipts().to_vec())?; let span = self.test_entry.span.clone(); let file_path = self.test_entry.file_path.clone(); let condition = self.test_entry.pass_condition.clone(); @@ -237,42 +265,22 @@ impl TestExecutor { } } -/// Given some bytecode and an instruction offset for some test's desired entry point, patch the -/// bytecode with a `JI` (jump) instruction to jump to the desired test. -/// -/// We want to splice in the `JI` only after the initial data section setup is complete, and only -/// if the entry point doesn't begin exactly after the data section setup. -/// -/// The following is how the beginning of the bytecode is laid out: -/// -/// ```ignore -/// [ 0] ji i(4 + 2) ; Jumps to the data section setup. -/// [ 1] noop -/// [ 2] DATA_SECTION_OFFSET[0..32] -/// [ 3] DATA_SECTION_OFFSET[32..64] -/// [ 4] CONFIGURABLES_OFFSET[0..32] -/// [ 5] CONFIGURABLES_OFFSET[32..64] -/// [ 6] lw $ds $is 1 ; The data section setup, i.e. where the first ji lands. -/// [ 7] add $$ds $$ds $is -/// [ 8] ; This is where we want to jump from to our test code! -/// ``` -fn patch_test_bytecode(bytecode: &[u8], test_offset: u32) -> std::borrow::Cow<[u8]> { - // Each instruction is 4 bytes, - // so we divide the total byte-size by 4 to get the instruction offset. - const PROGRAM_START_INST_OFFSET: u32 = (sway_core::PRELUDE_SIZE_IN_BYTES / 4) as u32; - const PROGRAM_START_BYTE_OFFSET: usize = sway_core::PRELUDE_SIZE_IN_BYTES; +fn find_ji_instruction_index(bytecode: &[u8]) -> usize { + // Search first `move $$locbase $sp` + // This will be `__entry` for script/predicate/contract using encoding v1; + // `main` for script/predicate using encoding v0; + // or the first function for libraries + // MOVE R59 $sp ;; [26, 236, 80, 0] + let a = vm::fuel_asm::op::move_(59, fuel_asm::RegId::SP).to_bytes(); - // If our desired entry point is the program start, no need to jump. - if test_offset == PROGRAM_START_INST_OFFSET { - return std::borrow::Cow::Borrowed(bytecode); - } + // for contracts using encoding v0 + // search the first `lw $r0 $fp i73` + // which is the start of the fn selector + // LW $writable $fp 0x49 ;; [93, 64, 96, 73] + let b = vm::fuel_asm::op::lw(fuel_asm::RegId::WRITABLE, fuel_asm::RegId::FP, 73).to_bytes(); - // Create the jump instruction and splice it into the bytecode. - let ji = vm::fuel_asm::op::ji(test_offset); - let ji_bytes = ji.to_bytes(); - let start = PROGRAM_START_BYTE_OFFSET; - let end = start + ji_bytes.len(); - let mut patched = bytecode.to_vec(); - patched.splice(start..end, ji_bytes); - std::borrow::Cow::Owned(patched) + bytecode.chunks(Instruction::SIZE).position(|instruction| { + let instruction: [u8; 4] = instruction.try_into().unwrap(); + instruction == a || instruction == b + }).unwrap() } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.lock b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.lock new file mode 100644 index 00000000000..688eb152964 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.lock @@ -0,0 +1,13 @@ +[[package]] +name = "configurable_tests" +source = "member" +dependencies = ["std"] + +[[package]] +name = "core" +source = "path+from-root-CEAD1EF3DC39BB76" + +[[package]] +name = "std" +source = "path+from-root-CEAD1EF3DC39BB76" +dependencies = ["core"] diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.toml new file mode 100644 index 00000000000..7fff3f8b575 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/Forc.toml @@ -0,0 +1,8 @@ +[project] +authors = ["Fuel Labs "] +entry = "main.sw" +license = "Apache-2.0" +name = "configurable_tests" + +[dependencies] +std = { path = "../../../../../../../sway-lib-std" } diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle.json new file mode 100644 index 00000000000..096b1d487c9 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle.json @@ -0,0 +1,284 @@ +{ + "configurables": [ + { + "configurableType": { + "name": "", + "type": 5, + "typeArguments": null + }, + "name": "BOOL", + "offset": 3392 + }, + { + "configurableType": { + "name": "", + "type": 13, + "typeArguments": null + }, + "name": "U8", + "offset": 3528 + }, + { + "configurableType": { + "name": "", + "type": 13, + "typeArguments": null + }, + "name": "ANOTHER_U8", + "offset": 3320 + }, + { + "configurableType": { + "name": "", + "type": 9, + "typeArguments": null + }, + "name": "U16", + "offset": 3472 + }, + { + "configurableType": { + "name": "", + "type": 11, + "typeArguments": null + }, + "name": "U32", + "offset": 3512 + }, + { + "configurableType": { + "name": "", + "type": 11, + "typeArguments": null + }, + "name": "U64", + "offset": 3520 + }, + { + "configurableType": { + "name": "", + "type": 10, + "typeArguments": null + }, + "name": "U256", + "offset": 3480 + }, + { + "configurableType": { + "name": "", + "type": 4, + "typeArguments": null + }, + "name": "B256", + "offset": 3360 + }, + { + "configurableType": { + "name": "", + "type": 8, + "typeArguments": [] + }, + "name": "CONFIGURABLE_STRUCT", + "offset": 3432 + }, + { + "configurableType": { + "name": "", + "type": 6, + "typeArguments": [] + }, + "name": "CONFIGURABLE_ENUM_A", + "offset": 3400 + }, + { + "configurableType": { + "name": "", + "type": 6, + "typeArguments": [] + }, + "name": "CONFIGURABLE_ENUM_B", + "offset": 3416 + }, + { + "configurableType": { + "name": "", + "type": 2, + "typeArguments": null + }, + "name": "ARRAY_BOOL", + "offset": 3328 + }, + { + "configurableType": { + "name": "", + "type": 3, + "typeArguments": null + }, + "name": "ARRAY_U64", + "offset": 3336 + }, + { + "configurableType": { + "name": "", + "type": 1, + "typeArguments": null + }, + "name": "TUPLE_BOOL_U64", + "offset": 3456 + }, + { + "configurableType": { + "name": "", + "type": 7, + "typeArguments": null + }, + "name": "STR_4", + "offset": 3448 + } + ], + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": { + "name": "", + "type": 0, + "typeArguments": null + } + } + ], + "loggedTypes": [], + "messagesTypes": [], + "types": [ + { + "components": [], + "type": "()", + "typeId": 0, + "typeParameters": null + }, + { + "components": [ + { + "name": "__tuple_element", + "type": 5, + "typeArguments": null + }, + { + "name": "__tuple_element", + "type": 12, + "typeArguments": null + } + ], + "type": "(_, _)", + "typeId": 1, + "typeParameters": null + }, + { + "components": [ + { + "name": "__array_element", + "type": 5, + "typeArguments": null + } + ], + "type": "[_; 3]", + "typeId": 2, + "typeParameters": null + }, + { + "components": [ + { + "name": "__array_element", + "type": 12, + "typeArguments": null + } + ], + "type": "[_; 3]", + "typeId": 3, + "typeParameters": null + }, + { + "components": null, + "type": "b256", + "typeId": 4, + "typeParameters": null + }, + { + "components": null, + "type": "bool", + "typeId": 5, + "typeParameters": null + }, + { + "components": [ + { + "name": "A", + "type": 5, + "typeArguments": null + }, + { + "name": "B", + "type": 12, + "typeArguments": null + } + ], + "type": "enum ConfigurableEnum", + "typeId": 6, + "typeParameters": null + }, + { + "components": null, + "type": "str[4]", + "typeId": 7, + "typeParameters": null + }, + { + "components": [ + { + "name": "a", + "type": 5, + "typeArguments": null + }, + { + "name": "b", + "type": 12, + "typeArguments": null + } + ], + "type": "struct ConfigurableStruct", + "typeId": 8, + "typeParameters": null + }, + { + "components": null, + "type": "u16", + "typeId": 9, + "typeParameters": null + }, + { + "components": null, + "type": "u256", + "typeId": 10, + "typeParameters": null + }, + { + "components": null, + "type": "u32", + "typeId": 11, + "typeParameters": null + }, + { + "components": null, + "type": "u64", + "typeId": 12, + "typeParameters": null + }, + { + "components": null, + "type": "u8", + "typeId": 13, + "typeParameters": null + } + ] +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle_new_encoding.json new file mode 100644 index 00000000000..2daeaffdc6a --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/json_abi_oracle_new_encoding.json @@ -0,0 +1,228 @@ +{ + "concreteTypes": [ + { + "concreteTypeId": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d", + "type": "()" + }, + { + "concreteTypeId": "c998ca9a5f221fe7b5c66ae70c8a9562b86d964408b00d17f883c906bc1fe4be", + "metadataTypeId": 0, + "type": "(bool, u64)" + }, + { + "concreteTypeId": "4926d35d1a5157936b0a29bc126b8aace6d911209a5c130e9b716b0c73643ea6", + "metadataTypeId": 1, + "type": "[bool; 3]" + }, + { + "concreteTypeId": "776fb5a3824169d6736138565fdc20aad684d9111266a5ff6d5c675280b7e199", + "metadataTypeId": 2, + "type": "[u64; 3]" + }, + { + "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b", + "type": "b256" + }, + { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "type": "bool" + }, + { + "concreteTypeId": "a2922861f03be8a650595dd76455b95383a61b46dd418f02607fa2e00dc39d5c", + "metadataTypeId": 3, + "type": "enum ConfigurableEnum" + }, + { + "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a", + "type": "str[4]" + }, + { + "concreteTypeId": "81fc10c4681a3271cf2d66b2ec6fbc8ed007a442652930844fcf11818c295bff", + "metadataTypeId": 4, + "type": "struct ConfigurableStruct" + }, + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "type": "u16" + }, + { + "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", + "type": "u256" + }, + { + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", + "type": "u32" + }, + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "type": "u8" + } + ], + "configurables": [ + { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "name": "BOOL", + "offset": 6896 + }, + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "name": "U8", + "offset": 7088 + }, + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "name": "ANOTHER_U8", + "offset": 6824 + }, + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "name": "U16", + "offset": 7032 + }, + { + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", + "name": "U32", + "offset": 7072 + }, + { + "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", + "name": "U64", + "offset": 7080 + }, + { + "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", + "name": "U256", + "offset": 7040 + }, + { + "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b", + "name": "B256", + "offset": 6864 + }, + { + "concreteTypeId": "81fc10c4681a3271cf2d66b2ec6fbc8ed007a442652930844fcf11818c295bff", + "name": "CONFIGURABLE_STRUCT", + "offset": 6984 + }, + { + "concreteTypeId": "a2922861f03be8a650595dd76455b95383a61b46dd418f02607fa2e00dc39d5c", + "name": "CONFIGURABLE_ENUM_A", + "offset": 6904 + }, + { + "concreteTypeId": "a2922861f03be8a650595dd76455b95383a61b46dd418f02607fa2e00dc39d5c", + "name": "CONFIGURABLE_ENUM_B", + "offset": 6944 + }, + { + "concreteTypeId": "4926d35d1a5157936b0a29bc126b8aace6d911209a5c130e9b716b0c73643ea6", + "name": "ARRAY_BOOL", + "offset": 6832 + }, + { + "concreteTypeId": "776fb5a3824169d6736138565fdc20aad684d9111266a5ff6d5c675280b7e199", + "name": "ARRAY_U64", + "offset": 6840 + }, + { + "concreteTypeId": "c998ca9a5f221fe7b5c66ae70c8a9562b86d964408b00d17f883c906bc1fe4be", + "name": "TUPLE_BOOL_U64", + "offset": 7016 + }, + { + "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a", + "name": "STR_4", + "offset": 7008 + }, + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "name": "NOT_USED", + "offset": 7000 + } + ], + "encodingVersion": "1", + "functions": [ + { + "attributes": null, + "inputs": [], + "name": "main", + "output": "2e38e77b22c314a449e91fafed92a43826ac6aa403ae6a8acb6cf58239fbaf5d" + } + ], + "loggedTypes": [], + "messagesTypes": [], + "metadataTypes": [ + { + "components": [ + { + "name": "__tuple_element", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "__tuple_element", + "typeId": 5 + } + ], + "metadataTypeId": 0, + "type": "(_, _)" + }, + { + "components": [ + { + "name": "__array_element", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + ], + "metadataTypeId": 1, + "type": "[_; 3]" + }, + { + "components": [ + { + "name": "__array_element", + "typeId": 5 + } + ], + "metadataTypeId": 2, + "type": "[_; 3]" + }, + { + "components": [ + { + "name": "A", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "B", + "typeId": 5 + }, + { + "name": "C", + "typeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b" + } + ], + "metadataTypeId": 3, + "type": "enum ConfigurableEnum" + }, + { + "components": [ + { + "name": "a", + "typeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + }, + { + "name": "b", + "typeId": 5 + } + ], + "metadataTypeId": 4, + "type": "struct ConfigurableStruct" + }, + { + "metadataTypeId": 5, + "type": "u64" + } + ], + "programType": "script", + "specVersion": "1" +} \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/src/main.sw new file mode 100644 index 00000000000..a0e5d11a345 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/src/main.sw @@ -0,0 +1,83 @@ +script; + +use std::hash::*; + +struct ConfigurableStruct { + a: bool, + b: u64, +} + +enum ConfigurableEnum { + A: bool, + B: u64, + C: b256 +} + +impl core::ops::Eq for ConfigurableEnum { + fn eq(self, other: ConfigurableEnum) -> bool { + match (self, other) { + (ConfigurableEnum::A(inner1), ConfigurableEnum::A(inner2)) => inner1 == inner2, + (ConfigurableEnum::B(inner1), ConfigurableEnum::B(inner2)) => inner1 == inner2, + _ => false, + } + } +} + +type AnotherU8 = u8; + +configurable { + BOOL: bool = true, + U8: u8 = 1, + ANOTHER_U8: AnotherU8 = 3, + U16: u16 = 2, + U32: u32 = 3, + U64: u32 = 4, + U256: u256 = 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu256, + B256: b256 = 0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB, + CONFIGURABLE_STRUCT: ConfigurableStruct = ConfigurableStruct { a: true, b: 5 }, + CONFIGURABLE_ENUM_A: ConfigurableEnum = ConfigurableEnum::A(true), + CONFIGURABLE_ENUM_B: ConfigurableEnum = ConfigurableEnum::B(12), + ARRAY_BOOL: [bool; 3] = [true, false, true], + ARRAY_U64: [u64; 3] = [9, 8, 7], + TUPLE_BOOL_U64: (bool, u64) = (true, 11), + STR_4: str[4] = __to_str_array("abcd"), + + NOT_USED: u8 = 1 +} + +fn main() { +} + +#[test] +fn t() { + assert(BOOL == true); + assert(U8 == 1); + assert(ANOTHER_U8 == 3); + assert(U16 == 2); + assert(U32 == 3); + assert(U64 == 4); + assert(U256 == 0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAu256); + assert(B256 == 0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB); + assert(CONFIGURABLE_STRUCT.a == true); + assert(CONFIGURABLE_STRUCT.b == 5); + assert(CONFIGURABLE_ENUM_A == ConfigurableEnum::A(true)); + assert(CONFIGURABLE_ENUM_B == ConfigurableEnum::B(12)); + assert(ARRAY_BOOL[0] == true); + assert(ARRAY_BOOL[1] == false); + assert(ARRAY_BOOL[2] == true); + assert(ARRAY_U64[0] == 9); + assert(ARRAY_U64[1] == 8); + assert(ARRAY_U64[2] == 7); + assert(TUPLE_BOOL_U64.0 == true); + assert(TUPLE_BOOL_U64.1 == 11); + assert(sha256_str_array(STR_4) == sha256("abcd")); + + // Assert address do not change + let addr_1 = asm(addr: __addr_of(&BOOL)) { + addr: u64 + }; + let addr_2 = asm(addr: __addr_of(&BOOL)) { + addr: u64 + }; + assert(addr_1 == addr_2); +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/test.toml new file mode 100644 index 00000000000..0f3f6d7e866 --- /dev/null +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_tests/test.toml @@ -0,0 +1 @@ +category = "unit_tests_pass" From fede3391ffea7e5b4883b8294c3a1f8cde5b020f Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Mon, 18 Nov 2024 15:25:53 -0300 Subject: [PATCH 2/8] fix import --- forc-test/src/execute.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/forc-test/src/execute.rs b/forc-test/src/execute.rs index a4ba7b6a219..a7ff1e57565 100644 --- a/forc-test/src/execute.rs +++ b/forc-test/src/execute.rs @@ -6,6 +6,7 @@ use forc_pkg::PkgTestEntry; use fuel_tx::{self as tx, output::contract::Contract, Chargeable, Finalizable}; use fuel_vm::error::InterpreterError; use fuel_vm::fuel_asm; +use fuel_vm::prelude::Instruction; use fuel_vm::{ self as vm, checked_transaction::builder::TransactionBuilderExt, From fc76bdbe2e80a87644e809eb746ae89ea7083b9c Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Mon, 18 Nov 2024 15:28:08 -0300 Subject: [PATCH 3/8] clippy and fmt issues --- forc-test/src/execute.rs | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/forc-test/src/execute.rs b/forc-test/src/execute.rs index a7ff1e57565..78a7bd8ef7f 100644 --- a/forc-test/src/execute.rs +++ b/forc-test/src/execute.rs @@ -132,7 +132,7 @@ impl TestExecutor { test_entry: test_entry.clone(), name, ji_idx, - test_offset: (test_offset - ji_idx as u32) * Instruction::SIZE as u32 + test_offset: (test_offset - ji_idx as u32) * Instruction::SIZE as u32, }) } @@ -214,15 +214,20 @@ impl TestExecutor { loop { match state { - ProgramState::Return(_) | ProgramState::ReturnData(_) | ProgramState::Revert(_) => break, + ProgramState::Return(_) | ProgramState::ReturnData(_) | ProgramState::Revert(_) => { + break + } ProgramState::RunProgram(eval) => { if eval.breakpoint().unwrap().pc() == jump_pc { self.interpreter.registers_mut()[3] += self.test_offset as u64; self.interpreter.set_single_stepping(false); } - state = self.interpreter.resume().map_err(|err: InterpreterError<_>| anyhow::anyhow!(err))?; - }, + state = self + .interpreter + .resume() + .map_err(|err: InterpreterError<_>| anyhow::anyhow!(err))?; + } ProgramState::VerifyPredicate(_) => todo!(), } } @@ -280,8 +285,11 @@ fn find_ji_instruction_index(bytecode: &[u8]) -> usize { // LW $writable $fp 0x49 ;; [93, 64, 96, 73] let b = vm::fuel_asm::op::lw(fuel_asm::RegId::WRITABLE, fuel_asm::RegId::FP, 73).to_bytes(); - bytecode.chunks(Instruction::SIZE).position(|instruction| { - let instruction: [u8; 4] = instruction.try_into().unwrap(); - instruction == a || instruction == b - }).unwrap() + bytecode + .chunks(Instruction::SIZE) + .position(|instruction| { + let instruction: [u8; 4] = instruction.try_into().unwrap(); + instruction == a || instruction == b + }) + .unwrap() } From 45ce12c8989c01c8bdf5148868aae8223294b57a Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Tue, 19 Nov 2024 10:01:54 -0300 Subject: [PATCH 4/8] correctly resume interpreter for tests --- forc-test/src/execute.rs | 54 ++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/forc-test/src/execute.rs b/forc-test/src/execute.rs index 78a7bd8ef7f..8a206757c76 100644 --- a/forc-test/src/execute.rs +++ b/forc-test/src/execute.rs @@ -7,6 +7,7 @@ use fuel_tx::{self as tx, output::contract::Contract, Chargeable, Finalizable}; use fuel_vm::error::InterpreterError; use fuel_vm::fuel_asm; use fuel_vm::prelude::Instruction; +use fuel_vm::prelude::RegId; use fuel_vm::{ self as vm, checked_transaction::builder::TransactionBuilderExt, @@ -29,8 +30,8 @@ pub struct TestExecutor { pub tx: vm::checked_transaction::Ready, pub test_entry: PkgTestEntry, pub name: String, - pub ji_idx: usize, - pub test_offset: u32, + pub jump_instruction_index: usize, + pub relative_jump_in_bytes: u32, } /// The result of executing a test with breakpoints enabled. @@ -45,16 +46,16 @@ pub enum DebugResult { impl TestExecutor { pub fn build( bytecode: &[u8], - test_offset: u32, + test_instruction_index: u32, test_setup: TestSetup, test_entry: &PkgTestEntry, name: String, ) -> anyhow::Result { let storage = test_setup.storage().clone(); - // Patch the bytecode to jump to the relevant test. - //let bytecode = patch_test_bytecode(bytecode, test_offset).into_owned(); - let ji_idx = find_ji_instruction_index(bytecode); + // Find the instruction which we will jump into the + // specified test + let jump_instruction_index = find_jump_instruction_index(bytecode); // Create a transaction to execute the test function. let script_input_data = vec![]; @@ -131,8 +132,8 @@ impl TestExecutor { tx, test_entry: test_entry.clone(), name, - ji_idx, - test_offset: (test_offset - ji_idx as u32) * Instruction::SIZE as u32, + jump_instruction_index, + relative_jump_in_bytes: (test_instruction_index - jump_instruction_index as u32) * Instruction::SIZE as u32, }) } @@ -200,35 +201,40 @@ impl TestExecutor { pub fn execute(&mut self) -> anyhow::Result { let start = std::time::Instant::now(); - self.interpreter.set_single_stepping(true); + // single-step until the jump-to-test instruction + let jump_pc = (self.jump_instruction_index * Instruction::SIZE) as u64; + self.interpreter.set_single_stepping(true); let mut state = { let transition = self .interpreter - .transact(self.tx.clone()) - .map_err(|err: InterpreterError<_>| anyhow::anyhow!(err))?; - *transition.state() + .transact(self.tx.clone()); + Ok(transition.unwrap().state().clone()) }; - let jump_pc = (self.ji_idx * Instruction::SIZE) as u64; - loop { match state { - ProgramState::Return(_) | ProgramState::ReturnData(_) | ProgramState::Revert(_) => { + // if the VM fails, we interpret as a revert + Err(_) => { + state = Ok(ProgramState::Revert(0)); + break; + } + Ok(ProgramState::Return(_) | ProgramState::ReturnData(_) | ProgramState::Revert(_)) => { break } - ProgramState::RunProgram(eval) => { - if eval.breakpoint().unwrap().pc() == jump_pc { - self.interpreter.registers_mut()[3] += self.test_offset as u64; - self.interpreter.set_single_stepping(false); + Ok(ProgramState::RunProgram(eval) | ProgramState::VerifyPredicate(eval)) => { + // time to jump into the specified test + if let Some(b) = eval.breakpoint() { + if b.pc() == jump_pc { + self.interpreter.registers_mut()[RegId::PC] += self.relative_jump_in_bytes as u64; + self.interpreter.set_single_stepping(false); + } } state = self .interpreter - .resume() - .map_err(|err: InterpreterError<_>| anyhow::anyhow!(err))?; + .resume(); } - ProgramState::VerifyPredicate(_) => todo!(), } } @@ -243,7 +249,7 @@ impl TestExecutor { file_path, duration, span, - state, + state: state.unwrap(), condition, logs, gas_used, @@ -271,7 +277,7 @@ impl TestExecutor { } } -fn find_ji_instruction_index(bytecode: &[u8]) -> usize { +fn find_jump_instruction_index(bytecode: &[u8]) -> usize { // Search first `move $$locbase $sp` // This will be `__entry` for script/predicate/contract using encoding v1; // `main` for script/predicate using encoding v0; From 59950e842eda1b455291f7213e36314604339744 Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Tue, 19 Nov 2024 10:05:02 -0300 Subject: [PATCH 5/8] clippy and fmt issues --- forc-test/src/execute.rs | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/forc-test/src/execute.rs b/forc-test/src/execute.rs index 8a206757c76..6da2e5ec16e 100644 --- a/forc-test/src/execute.rs +++ b/forc-test/src/execute.rs @@ -133,7 +133,8 @@ impl TestExecutor { test_entry: test_entry.clone(), name, jump_instruction_index, - relative_jump_in_bytes: (test_instruction_index - jump_instruction_index as u32) * Instruction::SIZE as u32, + relative_jump_in_bytes: (test_instruction_index - jump_instruction_index as u32) + * Instruction::SIZE as u32, }) } @@ -206,10 +207,8 @@ impl TestExecutor { self.interpreter.set_single_stepping(true); let mut state = { - let transition = self - .interpreter - .transact(self.tx.clone()); - Ok(transition.unwrap().state().clone()) + let transition = self.interpreter.transact(self.tx.clone()); + Ok(*transition.unwrap().state()) }; loop { @@ -219,21 +218,20 @@ impl TestExecutor { state = Ok(ProgramState::Revert(0)); break; } - Ok(ProgramState::Return(_) | ProgramState::ReturnData(_) | ProgramState::Revert(_)) => { - break - } + Ok( + ProgramState::Return(_) | ProgramState::ReturnData(_) | ProgramState::Revert(_), + ) => break, Ok(ProgramState::RunProgram(eval) | ProgramState::VerifyPredicate(eval)) => { // time to jump into the specified test if let Some(b) = eval.breakpoint() { - if b.pc() == jump_pc { - self.interpreter.registers_mut()[RegId::PC] += self.relative_jump_in_bytes as u64; + if b.pc() == jump_pc { + self.interpreter.registers_mut()[RegId::PC] += + self.relative_jump_in_bytes as u64; self.interpreter.set_single_stepping(false); } } - state = self - .interpreter - .resume(); + state = self.interpreter.resume(); } } } From e0435ef9786fb9e2d26ca240fe22a73f7e0ed2ec Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Tue, 19 Nov 2024 11:01:25 -0300 Subject: [PATCH 6/8] fix start_debugging --- .../src/server/handlers/handle_launch.rs | 3 + forc-test/src/execute.rs | 74 ++++++++++++------- 2 files changed, 52 insertions(+), 25 deletions(-) diff --git a/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs b/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs index 3ae3c2f3539..bb1da0d59c5 100644 --- a/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs +++ b/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs @@ -11,6 +11,7 @@ use sway_types::LineCol; impl DapServer { /// Handles a `launch` request. Returns true if the server should continue running. pub fn handle_launch(&mut self) -> Result { + dbg!(); // Build tests for the given path. let (pkg_to_debug, test_setup) = self.build_tests()?; let entries = pkg_to_debug.bytecode.entries.iter().filter_map(|entry| { @@ -20,6 +21,7 @@ impl DapServer { None }); + dbg!(); // Construct a TestExecutor for each test and store it let executors: Vec = entries .filter_map(|(entry, test_entry)| { @@ -42,6 +44,7 @@ impl DapServer { .collect(); self.state.init_executors(executors); + dbg!(); // Start debugging self.start_debugging_tests(false) } diff --git a/forc-test/src/execute.rs b/forc-test/src/execute.rs index 6da2e5ec16e..7eefa4935a8 100644 --- a/forc-test/src/execute.rs +++ b/forc-test/src/execute.rs @@ -138,20 +138,60 @@ impl TestExecutor { }) } + // single-step until the jump-to-test instruction, then + // jump into the first instruction of the test + fn single_step_until_test(&mut self) -> ProgramState { + let jump_pc = (self.jump_instruction_index * Instruction::SIZE) as u64; + + let old_single_stepping = self.interpreter.single_stepping(); + self.interpreter.set_single_stepping(true); + let mut state = { + let transition = self.interpreter.transact(self.tx.clone()); + Ok(*transition.unwrap().state()) + }; + + loop { + match state { + // if the VM fails, we interpret as a revert + Err(_) => { + break ProgramState::Revert(0); + } + Ok( + state @ ProgramState::Return(_) | state @ ProgramState::ReturnData(_) | state @ ProgramState::Revert(_), + ) => break state, + Ok(s @ ProgramState::RunProgram(eval) | s @ ProgramState::VerifyPredicate(eval)) => { + // time to jump into the specified test + if let Some(b) = eval.breakpoint() { + if b.pc() == jump_pc { + self.interpreter.registers_mut()[RegId::PC] += + self.relative_jump_in_bytes as u64; + self.interpreter.set_single_stepping(old_single_stepping); + break s; + } + } + + state = self.interpreter.resume(); + } + } + } + } + /// Execute the test with breakpoints enabled. pub fn start_debugging(&mut self) -> anyhow::Result { let start = std::time::Instant::now(); - let transition = self - .interpreter - .transact(self.tx.clone()) - .map_err(|err: InterpreterError<_>| anyhow::anyhow!(err))?; - let state = *transition.state(); + + let _ = self.single_step_until_test(); + let state = self.interpreter.resume() + .map_err(|err: InterpreterError<_>| { + anyhow::anyhow!("VM failed to resume. {:?}", err) + })?; if let ProgramState::RunProgram(DebugEval::Breakpoint(breakpoint)) = state { // A breakpoint was hit, so we tell the client to stop. return Ok(DebugResult::Breakpoint(breakpoint.pc())); } + let duration = start.elapsed(); - let (gas_used, logs) = Self::get_gas_and_receipts(transition.receipts().to_vec())?; + let (gas_used, logs) = Self::get_gas_and_receipts(self.interpreter.receipts().to_vec())?; let span = self.test_entry.span.clone(); let file_path = self.test_entry.file_path.clone(); let condition = self.test_entry.pass_condition.clone(); @@ -202,18 +242,11 @@ impl TestExecutor { pub fn execute(&mut self) -> anyhow::Result { let start = std::time::Instant::now(); - // single-step until the jump-to-test instruction - let jump_pc = (self.jump_instruction_index * Instruction::SIZE) as u64; - - self.interpreter.set_single_stepping(true); - let mut state = { - let transition = self.interpreter.transact(self.tx.clone()); - Ok(*transition.unwrap().state()) - }; + let mut state = Ok(self.single_step_until_test()); + // Run test until its end loop { match state { - // if the VM fails, we interpret as a revert Err(_) => { state = Ok(ProgramState::Revert(0)); break; @@ -221,16 +254,7 @@ impl TestExecutor { Ok( ProgramState::Return(_) | ProgramState::ReturnData(_) | ProgramState::Revert(_), ) => break, - Ok(ProgramState::RunProgram(eval) | ProgramState::VerifyPredicate(eval)) => { - // time to jump into the specified test - if let Some(b) = eval.breakpoint() { - if b.pc() == jump_pc { - self.interpreter.registers_mut()[RegId::PC] += - self.relative_jump_in_bytes as u64; - self.interpreter.set_single_stepping(false); - } - } - + Ok(ProgramState::RunProgram(_) | ProgramState::VerifyPredicate(_)) => { state = self.interpreter.resume(); } } From ed497258aa3fecb58ccddf9e67d69101d8effce0 Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Tue, 19 Nov 2024 11:06:05 -0300 Subject: [PATCH 7/8] clippy and fmt issues --- forc-test/src/execute.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/forc-test/src/execute.rs b/forc-test/src/execute.rs index 7eefa4935a8..a0423caba12 100644 --- a/forc-test/src/execute.rs +++ b/forc-test/src/execute.rs @@ -157,9 +157,13 @@ impl TestExecutor { break ProgramState::Revert(0); } Ok( - state @ ProgramState::Return(_) | state @ ProgramState::ReturnData(_) | state @ ProgramState::Revert(_), + state @ ProgramState::Return(_) + | state @ ProgramState::ReturnData(_) + | state @ ProgramState::Revert(_), ) => break state, - Ok(s @ ProgramState::RunProgram(eval) | s @ ProgramState::VerifyPredicate(eval)) => { + Ok( + s @ ProgramState::RunProgram(eval) | s @ ProgramState::VerifyPredicate(eval), + ) => { // time to jump into the specified test if let Some(b) = eval.breakpoint() { if b.pc() == jump_pc { @@ -179,9 +183,11 @@ impl TestExecutor { /// Execute the test with breakpoints enabled. pub fn start_debugging(&mut self) -> anyhow::Result { let start = std::time::Instant::now(); - + let _ = self.single_step_until_test(); - let state = self.interpreter.resume() + let state = self + .interpreter + .resume() .map_err(|err: InterpreterError<_>| { anyhow::anyhow!("VM failed to resume. {:?}", err) })?; From 3d7870e770c26a86cf2c86f9287ecd47415cd326 Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Tue, 19 Nov 2024 12:30:44 -0300 Subject: [PATCH 8/8] remove dbg --- forc-plugins/forc-debug/src/server/handlers/handle_launch.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs b/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs index bb1da0d59c5..3ae3c2f3539 100644 --- a/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs +++ b/forc-plugins/forc-debug/src/server/handlers/handle_launch.rs @@ -11,7 +11,6 @@ use sway_types::LineCol; impl DapServer { /// Handles a `launch` request. Returns true if the server should continue running. pub fn handle_launch(&mut self) -> Result { - dbg!(); // Build tests for the given path. let (pkg_to_debug, test_setup) = self.build_tests()?; let entries = pkg_to_debug.bytecode.entries.iter().filter_map(|entry| { @@ -21,7 +20,6 @@ impl DapServer { None }); - dbg!(); // Construct a TestExecutor for each test and store it let executors: Vec = entries .filter_map(|(entry, test_entry)| { @@ -44,7 +42,6 @@ impl DapServer { .collect(); self.state.init_executors(executors); - dbg!(); // Start debugging self.start_debugging_tests(false) }