Skip to content

Commit

Permalink
Add Receipt::ScriptReturn to run_program (#50)
Browse files Browse the repository at this point in the history
When a script is executed, it should return a receipt with its status
and used gas.

However, if there is a halt-error defined in
`InterpreterError::is_panic`, then the VM should be interrupted, no gas
should be consumed, and no new state/receipt should be returned.
  • Loading branch information
vlopes11 authored Dec 15, 2021
1 parent 82e6da3 commit d742b4e
Show file tree
Hide file tree
Showing 45 changed files with 1,323 additions and 713 deletions.
5 changes: 0 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,6 @@ name = "test-contract"
path = "tests/contract.rs"
required-features = ["random"]

[[test]]
name = "test-debug"
path = "tests/debug.rs"
required-features = ["debug"]

[[test]]
name = "test-encoding"
path = "tests/encoding.rs"
Expand Down
99 changes: 99 additions & 0 deletions src/backtrace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
//! Backtrace implementation to track script errors.
//!
//! As of the moment, doesn't support predicates.
use crate::call::CallFrame;
use crate::consts::*;
use crate::interpreter::Interpreter;

use fuel_asm::InstructionResult;
use fuel_tx::Transaction;
use fuel_types::{ContractId, Word};

#[derive(Debug)]
/// Runtime description derived from a VM error.
pub struct Backtrace {
call_stack: Vec<CallFrame>,
contract: ContractId,
registers: [Word; VM_REGISTER_COUNT],
memory: Vec<u8>,
result: InstructionResult,
tx: Transaction,
}

impl Backtrace {
/// Create a backtrace from a vm instance and instruction result.
///
/// This isn't copy-free and shouldn't be provided by default.
pub fn from_vm_error<S>(vm: &Interpreter<S>, result: InstructionResult) -> Self {
let call_stack = vm.call_stack().to_owned();
let contract = vm.internal_contract_or_default();
let memory = vm.memory().to_owned();
let tx = vm.transaction().clone();
let mut registers = [0; VM_REGISTER_COUNT];

registers.copy_from_slice(vm.registers());

Self {
call_stack,
contract,
registers,
memory,
result,
tx,
}
}

/// Call stack of the VM when the error occurred.
pub fn call_stack(&self) -> &[CallFrame] {
self.call_stack.as_slice()
}

/// Last contract of the context when the error occurred.
pub const fn contract(&self) -> &ContractId {
&self.contract
}

/// Register set when the error occurred.
pub const fn registers(&self) -> &[Word] {
&self.registers
}

/// Memory of the VM when the error occurred.
pub fn memory(&self) -> &[u8] {
self.memory.as_slice()
}

/// [`InstructionResult`] of the error that caused this backtrace.
pub const fn result(&self) -> &InstructionResult {
&self.result
}

/// [`Transaction`] state when the error occurred.
pub const fn tx(&self) -> &Transaction {
&self.tx
}

/// Expose the internal attributes of the backtrace.
pub fn into_inner(
self,
) -> (
Vec<CallFrame>,
ContractId,
[Word; VM_REGISTER_COUNT],
Vec<u8>,
InstructionResult,
Transaction,
) {
let Self {
call_stack,
contract,
registers,
memory,
result,
tx,
} = self;

(call_stack, contract, registers, memory, result, tx)
}
}
32 changes: 29 additions & 3 deletions src/call.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Inter-contract call supporting structures
use crate::consts::*;
use crate::contract::Contract;

use fuel_asm::PanicReason;
use fuel_types::bytes::{self, SizedBytes};
use fuel_types::{Color, ContractId, Word};

Expand All @@ -11,29 +14,38 @@ const WORD_SIZE: usize = mem::size_of::<Word>();

#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde-types", derive(serde::Serialize, serde::Deserialize))]
/// Call structure representation, composed of a called contract `to` and two
/// word arguments.
///
/// <https://github.com/FuelLabs/fuel-specs/blob/master/specs/vm/opcodes.md#call-call-contract>
pub struct Call {
to: ContractId,
a: Word,
b: Word,
}

impl Call {
/// Create a new call structure representation.
pub const fn new(to: ContractId, a: Word, b: Word) -> Self {
Self { to, a, b }
}

/// Called contract.
pub const fn to(&self) -> &ContractId {
&self.to
}

/// `a` argument.
pub const fn a(&self) -> Word {
self.a
}

/// `b` argument.
pub const fn b(&self) -> Word {
self.b
}

/// Expose the internal attributes of the call description.
pub const fn into_inner(self) -> (ContractId, Word, Word) {
(self.to, self.a, self.b)
}
Expand Down Expand Up @@ -85,18 +97,21 @@ impl io::Write for Call {
}

impl TryFrom<&[u8]> for Call {
type Error = io::Error;
type Error = PanicReason;

fn try_from(bytes: &[u8]) -> io::Result<Self> {
fn try_from(bytes: &[u8]) -> Result<Self, PanicReason> {
let mut call = Self::default();

call.write(bytes)?;
call.write(bytes).map_err(|_| PanicReason::MalformedCallStructure)?;

Ok(call)
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
/// Call frame representation in the VM stack.
///
/// <https://github.com/FuelLabs/fuel-specs/blob/master/specs/vm/main.md#call-frames>
pub struct CallFrame {
to: ContractId,
color: Color,
Expand All @@ -107,6 +122,7 @@ pub struct CallFrame {
}

impl CallFrame {
/// Create a new call frame.
pub const fn new(
to: ContractId,
color: Color,
Expand All @@ -125,42 +141,52 @@ impl CallFrame {
}
}

/// Contract code of the called (`to`) id.
pub fn code(&self) -> &[u8] {
self.code.as_ref()
}

/// Contract code memory offset.
pub const fn code_offset() -> usize {
ContractId::LEN + Color::LEN + WORD_SIZE * (3 + VM_REGISTER_COUNT)
}

/// `a` argument memory offset.
pub const fn a_offset() -> usize {
ContractId::LEN + Color::LEN + WORD_SIZE * (1 + VM_REGISTER_COUNT)
}

/// `b` argument memory offset.
pub const fn b_offset() -> usize {
ContractId::LEN + Color::LEN + WORD_SIZE * (2 + VM_REGISTER_COUNT)
}

/// Registers prior to the called execution.
pub const fn registers(&self) -> &[Word] {
&self.registers
}

/// Called contract id.
pub const fn to(&self) -> &ContractId {
&self.to
}

/// `a` argument.
pub const fn a(&self) -> Word {
self.a
}

/// `b` argument.
pub const fn b(&self) -> Word {
self.b
}

/// Gas context prior to the called execution.
pub const fn context_gas(&self) -> Word {
self.registers[REG_CGAS]
}

/// Color of forwarded coins.
pub const fn color(&self) -> &Color {
&self.color
}
Expand Down
Loading

0 comments on commit d742b4e

Please sign in to comment.