Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Receipt::ScriptReturn to run_program #50

Merged
merged 16 commits into from
Dec 15, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions src/backtrace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//! 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_types::{ContractId, Word};

#[derive(Debug)]
pub struct Backtrace {
call_stack: Vec<CallFrame>,
contract: ContractId,
registers: [Word; VM_REGISTER_COUNT],
memory: Vec<u8>,
result: InstructionResult,
}

impl Backtrace {
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 mut registers = [0; VM_REGISTER_COUNT];

registers.copy_from_slice(vm.registers());

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

pub fn call_stack(&self) -> &[CallFrame] {
self.call_stack.as_slice()
}

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

pub const fn registers(&self) -> &[Word] {
&self.registers
}

pub fn memory(&self) -> &[u8] {
self.memory.as_slice()
}

pub const fn result(&self) -> &InstructionResult {
&self.result
}

pub fn into_inner(
self,
) -> (
Vec<CallFrame>,
ContractId,
[Word; VM_REGISTER_COUNT],
Vec<u8>,
InstructionResult,
) {
let Self {
call_stack,
contract,
registers,
memory,
result,
} = self;

(call_stack, contract, registers, memory, result)
}
}
9 changes: 6 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 Down Expand Up @@ -85,12 +88,12 @@ 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)
}
Expand Down
129 changes: 42 additions & 87 deletions src/client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::error::{Backtrace, InterpreterError};
use crate::interpreter::Interpreter;
//! In-memory client implementation

use crate::backtrace::Backtrace;
use crate::error::InterpreterError;
use crate::transactor::Transactor;

use fuel_tx::{Receipt, Transaction};

Expand All @@ -8,112 +11,64 @@ mod storage;
pub use storage::MemoryStorage;

#[derive(Debug, Default, Clone)]
pub struct MemoryClient {
storage: MemoryStorage,
pub struct MemoryClient<'a> {
transactor: Transactor<'a, MemoryStorage>,
}

impl From<MemoryStorage> for MemoryClient {
impl<'a> From<MemoryStorage> for MemoryClient<'a> {
fn from(s: MemoryStorage) -> Self {
Self::new(s)
}
}

impl AsRef<MemoryStorage> for MemoryClient {
impl<'a> AsRef<MemoryStorage> for MemoryClient<'a> {
fn as_ref(&self) -> &MemoryStorage {
&self.storage
self.transactor.as_ref()
}
}

impl AsMut<MemoryStorage> for MemoryClient {
impl<'a> AsMut<MemoryStorage> for MemoryClient<'a> {
fn as_mut(&mut self) -> &mut MemoryStorage {
&mut self.storage
self.transactor.as_mut()
}
}

impl MemoryClient {
pub const fn new(storage: MemoryStorage) -> Self {
Self { storage }
impl<'a> MemoryClient<'a> {
pub fn new(storage: MemoryStorage) -> Self {
Self {
transactor: Transactor::new(storage),
}
}

fn transition_internal<F, E, I: IntoIterator<Item = Transaction>>(
&mut self,
err: F,
txs: I,
) -> Result<Vec<Receipt>, E>
where
F: Fn(&Interpreter<&mut MemoryStorage>, InterpreterError) -> E,
{
let mut interpreter = Interpreter::with_storage(&mut self.storage);

// A transaction that is considered valid will be reverted if the runtime
// returns an error.
//
// The exception is `InterpreterError::ValidationError` since this means the
// whole set of transactions must also fail.
//
// All the other variants of `InterpreterError` are supressed in this function
// and they will produce isolated `RVRT` operations.
let receipts = txs.into_iter().try_fold(vec![], |mut receipts, tx| {
match interpreter.transact(tx) {
Ok(state) => {
receipts.extend(state.receipts());

if !state.receipts().iter().any(|r| matches!(r, Receipt::Revert { .. })) {
interpreter.as_mut().commit();
} else {
interpreter.as_mut().revert();
}

Ok(receipts)
}

Err(InterpreterError::ValidationError(e)) => {
let err = err(&interpreter, e.into());
interpreter.as_mut().rollback();

Err(err)
}

Err(InterpreterError::OpcodeRepresentationUnimplemented(op)) => {
let err = err(&interpreter, InterpreterError::OpcodeRepresentationUnimplemented(op));
interpreter.as_mut().rollback();

Err(err)
}

Err(InterpreterError::OpcodeInvalid(op)) => {
let err = err(&interpreter, InterpreterError::OpcodeInvalid(op));
interpreter.as_mut().rollback();

Err(err)
}

// TODO VM is to return a `RVRT` receipt on runtime error. This way, the return of
// `transact` should be `Err` only if `InterpreterError::ValidationError` happens
Err(_) => {
interpreter.as_mut().revert();

Ok(receipts)
}
}
})?;

self.storage.persist();
pub fn backtrace(&self) -> Option<Backtrace> {
self.transactor.backtrace()
}

Ok(receipts)
pub fn receipts(&self) -> Option<&[Receipt]> {
self.transactor.receipts()
}

pub fn transition<I: IntoIterator<Item = Transaction>>(
&mut self,
txs: I,
) -> Result<Vec<Receipt>, InterpreterError> {
self.transition_internal(|_, e| e, txs)
pub fn transact(&mut self, tx: Transaction) -> Result<&[Receipt], InterpreterError> {
self.transactor.transact(tx);

match self.transactor.result() {
Ok(state) if state.should_revert() => self.transactor.as_mut().revert(),

Ok(_) => self.transactor.as_mut().commit(),

Err(e) => {
self.transactor.as_mut().rollback();

return Err(e);
}
};

self.transactor
.receipts()
.ok_or(InterpreterError::NoTransactionInitialized)
}

pub fn transition_with_backtrace<I: IntoIterator<Item = Transaction>>(
&mut self,
txs: I,
) -> Result<Vec<Receipt>, Backtrace> {
self.transition_internal(|interpreter, e| e.backtrace(interpreter), txs)
pub fn persist(&mut self) {
self.as_mut().persist();
}
}
2 changes: 2 additions & 0 deletions src/consts.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! VM parameters

use fuel_tx::consts::*;
use fuel_types::{Bytes32, Color, Word};

Expand Down
2 changes: 2 additions & 0 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! VM runtime context definitions

use fuel_tx::Transaction;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down
2 changes: 2 additions & 0 deletions src/contract.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Chain contract definition

use crate::crypto;
use crate::error::InterpreterError;

Expand Down
2 changes: 2 additions & 0 deletions src/crypto.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Crypto implementations for the instructions

use fuel_merkle::binary::MerkleTree;
use fuel_merkle::common::StorageMap;
use fuel_types::{Bytes32, Bytes64};
Expand Down
Loading