diff --git a/src/interpreter.rs b/src/interpreter.rs index f96bb476dc..5e1640f3f9 100644 --- a/src/interpreter.rs +++ b/src/interpreter.rs @@ -15,7 +15,7 @@ use fuel_tx::{ Receipt, Script, ScriptCheckedMetadata, Transaction, TransactionFee, TransactionRepr, UniqueIdentifier, }; use fuel_types::bytes::{SerializableVec, SizedBytes}; -use fuel_types::{Address, AssetId, Word}; +use fuel_types::{Address, AssetId, ContractId, Word}; mod alu; mod balances; @@ -46,7 +46,6 @@ use crate::profiler::InstructionLocation; pub use balances::RuntimeBalances; pub use memory::MemoryRange; -#[derive(Debug, Clone)] /// VM interpreter. /// /// The internal state of the VM isn't expose because the intended usage is to @@ -55,6 +54,7 @@ pub use memory::MemoryRange; /// /// These can be obtained with the help of a [`crate::transactor::Transactor`] /// or a client implementation. +#[derive(Debug, Clone)] pub struct Interpreter { registers: [Word; VM_REGISTER_COUNT], memory: Vec, @@ -69,6 +69,21 @@ pub struct Interpreter { #[cfg(feature = "profile-any")] profiler: Profiler, params: ConsensusParameters, + /// `PanicContext` after the latest execution. It is consumed by `append_panic_receipt` + /// and is `PanicContext::None` after consumption. + panic_context: PanicContext, +} + +/// Sometimes it is possible to add some additional context information +/// regarding panic reasons to simplify debugging. +// TODO: Move this enum into `fuel-tx` and use it inside of the `Receipt::Panic` as meta +// information. Maybe better to have `Vec` to provide more information. +#[derive(Debug, Clone)] +pub(crate) enum PanicContext { + /// No additional information. + None, + /// `ContractId` retrieved during instruction execution. + ContractId(ContractId), } impl Interpreter { diff --git a/src/interpreter/blockchain.rs b/src/interpreter/blockchain.rs index 4518d9a151..43ca720f42 100644 --- a/src/interpreter/blockchain.rs +++ b/src/interpreter/blockchain.rs @@ -5,11 +5,12 @@ use crate::error::{Bug, BugId, BugVariant, RuntimeError}; use crate::storage::InterpreterStorage; use fuel_asm::PanicReason; -use fuel_tx::{Input, Output, Receipt}; +use fuel_tx::{Output, Receipt}; use fuel_types::bytes::{self, Deserializable}; use fuel_types::{Address, AssetId, Bytes32, Bytes8, ContractId, RegisterId, Word}; use crate::arith::{add_usize, checked_add_usize, checked_add_word, checked_sub_word}; +use crate::interpreter::PanicContext; use core::slice; impl Interpreter @@ -67,6 +68,7 @@ where // the contract must be declared in the transaction inputs if !self.transaction().input_contracts().any(|id| id == contract_id) { + self.panic_context = PanicContext::ContractId(contract_id.clone()); return Err(PanicReason::ContractNotInInputs.into()); }; @@ -157,12 +159,8 @@ where // Safety: Memory bounds are checked by the interpreter let contract = unsafe { ContractId::as_ref_unchecked(&self.memory[b..bx]) }; - if !self - .transaction() - .inputs() - .iter() - .any(|input| matches!(input, Input::Contract { contract_id, .. } if contract_id == contract)) - { + if !self.transaction().input_contracts().any(|input| input == contract) { + self.panic_context = PanicContext::ContractId(contract.clone()); return Err(PanicReason::ContractNotInInputs.into()); } diff --git a/src/interpreter/constructors.rs b/src/interpreter/constructors.rs index decf48e81f..5e44afdfde 100644 --- a/src/interpreter/constructors.rs +++ b/src/interpreter/constructors.rs @@ -3,6 +3,7 @@ use super::{ExecutableTransaction, Interpreter, RuntimeBalances}; use crate::consts::*; use crate::context::Context; +use crate::interpreter::PanicContext; use crate::state::Debugger; use crate::storage::MemoryStorage; @@ -35,6 +36,7 @@ where #[cfg(feature = "profile-any")] profiler: Profiler::default(), params, + panic_context: PanicContext::None, } } diff --git a/src/interpreter/contract.rs b/src/interpreter/contract.rs index 11f608c3b2..673654032b 100644 --- a/src/interpreter/contract.rs +++ b/src/interpreter/contract.rs @@ -1,6 +1,7 @@ use super::{ExecutableTransaction, Interpreter}; use crate::consts::*; use crate::error::RuntimeError; +use crate::interpreter::PanicContext; use crate::storage::InterpreterStorage; use fuel_asm::{PanicReason, RegisterId, Word}; @@ -45,6 +46,7 @@ where let contract = unsafe { ContractId::as_ref_unchecked(&self.memory[c..cx]) }; if !self.transaction().input_contracts().any(|input| contract == input) { + self.panic_context = PanicContext::ContractId(contract.clone()); return Err(PanicReason::ContractNotInInputs.into()); } @@ -81,6 +83,7 @@ where .input_contracts() .any(|contract| &destination == contract) { + self.panic_context = PanicContext::ContractId(destination); return Err(PanicReason::ContractNotInInputs.into()); } diff --git a/src/interpreter/flow.rs b/src/interpreter/flow.rs index 589a05aef4..a053c9d163 100644 --- a/src/interpreter/flow.rs +++ b/src/interpreter/flow.rs @@ -3,6 +3,7 @@ use crate::arith; use crate::call::{Call, CallFrame}; use crate::consts::*; use crate::error::RuntimeError; +use crate::interpreter::PanicContext; use crate::state::ProgramState; use crate::storage::InterpreterStorage; @@ -128,16 +129,15 @@ where let pc = self.registers[REG_PC]; let is = self.registers[REG_IS]; - let receipt = Receipt::panic(self.internal_contract_or_default(), result, pc, is); + let mut receipt = Receipt::panic(self.internal_contract_or_default(), result, pc, is); - let receipt = match result.reason() { - PanicReason::ContractNotInInputs => { - let call = Call::try_from(&self.memory[self.registers[result.instruction().ra()] as usize..]) - .expect("append panic receipt error"); - receipt.with_panic_contract_id(Some(*call.to())) + match self.panic_context { + PanicContext::None => {} + PanicContext::ContractId(contract_id) => { + receipt = receipt.with_panic_contract_id(Some(contract_id)); } - _ => receipt, }; + self.panic_context = PanicContext::None; self.append_receipt(receipt); } @@ -173,6 +173,7 @@ where .input_contracts() .any(|contract| call.to() == contract) { + self.panic_context = PanicContext::ContractId(call.to().clone()); return Err(PanicReason::ContractNotInInputs.into()); } diff --git a/tests/blockchain.rs b/tests/blockchain.rs index 786099f244..8b3f63c133 100644 --- a/tests/blockchain.rs +++ b/tests/blockchain.rs @@ -11,7 +11,7 @@ use fuel_asm::PanicReason::{ ArithmeticOverflow, ContractNotInInputs, ErrorFlag, ExpectedUnallocatedStack, MemoryOverflow, }; use fuel_tx::field::{Outputs, Script as ScriptField}; -use fuel_vm::util::test_helpers::{check_expected_reason_for_opcodes, check_reason_for_transaction}; +use fuel_vm::util::test_helpers::check_expected_reason_for_opcodes; const SET_STATUS_REG: RegisterId = 0x39; // log2(VM_MAX_MEM) - used to set a pointer to the memory boundary via SHL: 1<, expected_reason: PanicReason, should_patc .expect("failed to check tx"); } - check_reason_for_transaction(client, tx_deploy_loader, expected_reason); + let receipts = client.transact(tx_deploy_loader); + if let Receipt::Panic { + id: _, + reason, + contract_id: actual_contract_id, + .. + } = receipts.get(0).expect("No receipt") + { + assert_eq!( + &expected_reason, + reason.reason(), + "Expected {}, found {}", + expected_reason, + reason.reason() + ); + match expected_reason { + PanicReason::ContractNotInInputs => { + assert!(actual_contract_id.is_some()); + assert_ne!(actual_contract_id, &Some(contract_id)); + } + _ => {} + }; + } else { + panic!("Script should have panicked"); + } } #[test]