use crate::checked_transaction::{Checked, IntoChecked};
use crate::consts::*;
use crate::context::Context;
use crate::crypto;
use crate::error::{Bug, BugId, BugVariant, InterpreterError, PredicateVerificationFailed, RuntimeError};
use crate::gas::GasCosts;
use crate::interpreter::{CheckedMetadata, ExecutableTransaction, InitialBalances, Interpreter, RuntimeBalances};
use crate::predicate::RuntimePredicate;
use crate::state::{ExecuteState, ProgramState};
use crate::state::{StateTransition, StateTransitionRef};
use crate::storage::{InterpreterStorage, PredicateStorage};
use crate::error::BugVariant::GlobalGasUnderflow;
use fuel_asm::{PanicReason, RegId};
use fuel_tx::{
field::{Outputs, ReceiptsRoot, Salt, Script as ScriptField, StorageSlots},
Chargeable, ConsensusParameters, Contract, Create, Input, Output, Receipt, ScriptExecutionResult,
};
use fuel_types::bytes::SerializableVec;
use fuel_types::Word;
#[derive(Debug, Clone, Copy)]
pub struct PredicatesChecked {
gas_used: Word,
}
impl PredicatesChecked {
pub fn gas_used(&self) -> Word {
self.gas_used
}
}
impl<T> Interpreter<PredicateStorage, T> {
pub fn check_predicates<Tx>(
checked: Checked<Tx>,
params: ConsensusParameters,
gas_costs: GasCosts,
) -> Result<PredicatesChecked, PredicateVerificationFailed>
where
Tx: ExecutableTransaction,
<Tx as IntoChecked>::Metadata: CheckedMetadata,
{
if !checked.transaction().check_predicate_owners() {
return Err(PredicateVerificationFailed::InvalidOwner);
}
let mut vm = Interpreter::with_storage(PredicateStorage::default(), params, gas_costs);
#[allow(clippy::needless_collect)]
let predicates: Vec<_> = (0..checked.transaction().inputs().len())
.filter_map(|i| RuntimePredicate::from_tx(¶ms, checked.transaction(), i))
.collect();
let tx_gas_limit = checked.transaction().limit();
let mut remaining_gas = tx_gas_limit;
vm.init_predicate(checked);
for predicate in predicates {
let mut vm = vm.clone();
vm.context = Context::Predicate { program: predicate };
vm.set_remaining_gas(remaining_gas);
if !matches!(vm.verify_predicate()?, ProgramState::Return(0x01)) {
return Err(PredicateVerificationFailed::False);
}
remaining_gas = vm.registers[RegId::GGAS];
}
Ok(PredicatesChecked {
gas_used: tx_gas_limit
.checked_sub(remaining_gas)
.ok_or_else(|| Bug::new(BugId::ID004, GlobalGasUnderflow))?,
})
}
}
impl<S, Tx> Interpreter<S, Tx>
where
S: InterpreterStorage,
Tx: ExecutableTransaction,
{
pub(crate) fn run_call(&mut self) -> Result<ProgramState, RuntimeError> {
loop {
if self.registers[RegId::PC] >= VM_MAX_RAM {
return Err(PanicReason::MemoryOverflow.into());
}
let state = self.execute().map_err(|e| {
e.panic_reason()
.map(RuntimeError::Recoverable)
.unwrap_or_else(|| RuntimeError::Halt(e.into()))
})?;
match state {
ExecuteState::Return(r) => {
return Ok(ProgramState::Return(r));
}
ExecuteState::ReturnData(d) => {
return Ok(ProgramState::ReturnData(d));
}
ExecuteState::Revert(r) => {
return Ok(ProgramState::Revert(r));
}
ExecuteState::Proceed => (),
#[cfg(feature = "debug")]
ExecuteState::DebugEvent(d) => {
return Ok(ProgramState::RunProgram(d));
}
}
}
}
}
impl<S, Tx> Interpreter<S, Tx>
where
S: InterpreterStorage,
{
fn _deploy(
create: &mut Create,
storage: &mut S,
initial_balances: InitialBalances,
params: &ConsensusParameters,
) -> Result<(), InterpreterError> {
let salt = create.salt();
let storage_slots = create.storage_slots();
let contract = Contract::try_from(&*create)?;
let root = contract.root();
let storage_root = Contract::initial_state_root(storage_slots.iter());
let id = contract.id(salt, &root, &storage_root);
if !create
.outputs()
.iter()
.any(|output| matches!(output, Output::ContractCreated { contract_id, state_root } if contract_id == &id && state_root == &storage_root))
{
return Err(InterpreterError::Panic(PanicReason::ContractNotInInputs));
}
if storage
.storage_contract_exists(&id)
.map_err(InterpreterError::from_io)?
{
return Err(InterpreterError::Panic(PanicReason::ContractIdAlreadyDeployed));
}
storage
.deploy_contract_with_id(salt, storage_slots, &contract, &root, &id)
.map_err(InterpreterError::from_io)?;
let remaining_gas = create.limit();
Self::finalize_outputs(
create,
false,
remaining_gas,
&initial_balances,
&RuntimeBalances::from(initial_balances.clone()),
params,
)?;
Ok(())
}
}
impl<S, Tx> Interpreter<S, Tx>
where
S: InterpreterStorage,
Tx: ExecutableTransaction,
{
fn update_transaction_outputs(&mut self) -> Result<(), InterpreterError> {
let outputs = self.transaction().outputs().len();
(0..outputs).try_for_each(|o| self.update_memory_output(o))?;
Ok(())
}
pub(crate) fn run(&mut self) -> Result<ProgramState, InterpreterError> {
let state = if let Some(create) = self.tx.as_create_mut() {
Self::_deploy(create, &mut self.storage, self.initial_balances.clone(), &self.params)?;
self.update_transaction_outputs()?;
ProgramState::Return(1)
} else {
if self.transaction().inputs().iter().any(|input| {
if let Input::Contract { contract_id, .. } = input {
!self.check_contract_exists(contract_id).unwrap_or(false)
} else {
false
}
}) {
return Err(InterpreterError::Panic(PanicReason::ContractNotFound));
}
if let Some(script) = self.transaction().as_script() {
let offset = (self.tx_offset() + script.script_offset()) as Word;
self.registers[RegId::PC] = offset;
self.registers[RegId::IS] = offset;
}
let program = if !self
.transaction()
.as_script()
.expect("It should be `Script` transaction")
.script()
.is_empty()
{
self.run_program()
} else {
let return_val = 1;
self.ret(return_val)?;
Ok(ProgramState::Return(return_val))
};
let gas_used = self
.transaction()
.limit()
.checked_sub(self.remaining_gas())
.ok_or_else(|| Bug::new(BugId::ID002, BugVariant::GlobalGasUnderflow))?;
let (status, program) = match program {
Ok(s) => {
let res = if let ProgramState::Revert(_) = &s {
ScriptExecutionResult::Revert
} else {
ScriptExecutionResult::Success
};
(res, s)
}
Err(e) => match e.instruction_result() {
Some(result) => {
self.append_panic_receipt(result);
(ScriptExecutionResult::Panic, ProgramState::Revert(0))
}
None => {
return Err(e);
}
},
};
let receipt = Receipt::script_result(status, gas_used);
self.append_receipt(receipt);
#[cfg(feature = "debug")]
if program.is_debug() {
self.debugger_set_last_state(program);
}
let receipts_root = if self.receipts().is_empty() {
EMPTY_RECEIPTS_MERKLE_ROOT.into()
} else {
crypto::ephemeral_merkle_root(self.receipts().iter().map(|r| r.clone().to_bytes()))
};
if let Some(script) = self.tx.as_script_mut() {
*script.receipts_root_mut() = receipts_root;
}
let revert = matches!(program, ProgramState::Revert(_));
let remaining_gas = self.remaining_gas();
Self::finalize_outputs(
&mut self.tx,
revert,
remaining_gas,
&self.initial_balances,
&self.balances,
&self.params,
)?;
self.update_transaction_outputs()?;
program
};
Ok(state)
}
pub(crate) fn run_program(&mut self) -> Result<ProgramState, InterpreterError> {
loop {
if self.registers[RegId::PC] >= VM_MAX_RAM {
return Err(InterpreterError::Panic(PanicReason::MemoryOverflow));
}
match self.execute()? {
ExecuteState::Return(r) => {
return Ok(ProgramState::Return(r));
}
ExecuteState::ReturnData(d) => {
return Ok(ProgramState::ReturnData(d));
}
ExecuteState::Revert(r) => {
return Ok(ProgramState::Revert(r));
}
ExecuteState::Proceed => (),
#[cfg(feature = "debug")]
ExecuteState::DebugEvent(d) => {
return Ok(ProgramState::RunProgram(d));
}
}
}
}
}
impl<S, Tx> Interpreter<S, Tx>
where
S: InterpreterStorage,
Tx: ExecutableTransaction,
<Tx as IntoChecked>::Metadata: CheckedMetadata,
{
pub fn transact_owned(
storage: S,
tx: Checked<Tx>,
params: ConsensusParameters,
gas_costs: GasCosts,
) -> Result<StateTransition<Tx>, InterpreterError> {
let mut interpreter = Interpreter::with_storage(storage, params, gas_costs);
interpreter
.transact(tx)
.map(ProgramState::from)
.map(|state| StateTransition::new(state, interpreter.tx, interpreter.receipts))
}
pub fn transact(&mut self, tx: Checked<Tx>) -> Result<StateTransitionRef<'_, Tx>, InterpreterError> {
let state_result = self.init_script(tx).and_then(|_| self.run());
#[cfg(feature = "profile-any")]
self.profiler.on_transaction(&state_result);
let state = state_result?;
Ok(StateTransitionRef::new(state, self.transaction(), self.receipts()))
}
}
impl<S, Tx> Interpreter<S, Tx>
where
S: InterpreterStorage,
{
pub fn deploy(&mut self, tx: Checked<Create>) -> Result<Create, InterpreterError> {
let (mut create, metadata) = tx.into();
Self::_deploy(&mut create, &mut self.storage, metadata.balances(), &self.params)?;
Ok(create)
}
}