use crate::call::CallFrame;
use crate::consts::*;
use crate::context::Context;
use crate::gas::GasCosts;
use crate::state::Debugger;
use fuel_asm::{PanicReason, RegId};
use std::collections::BTreeMap;
use std::io::Read;
use std::ops::Index;
use std::{io, mem};
use fuel_tx::{
field, Chargeable, CheckError, ConsensusParameters, Create, Executable, Output, Receipt, Script, Transaction,
TransactionFee, TransactionRepr, UniqueIdentifier,
};
use fuel_types::bytes::{SerializableVec, SizedBytes};
use fuel_types::{Address, AssetId, ContractId, Word};
mod alu;
mod balances;
mod blockchain;
mod constructors;
mod contract;
mod crypto;
pub mod diff;
mod executors;
mod flow;
mod frame;
mod gas;
mod initialization;
mod internal;
mod log;
mod memory;
mod metadata;
mod post_execution;
#[cfg(feature = "debug")]
mod debug;
#[cfg(feature = "profile-any")]
use crate::profiler::Profiler;
#[cfg(feature = "profile-gas")]
use crate::profiler::InstructionLocation;
pub use balances::RuntimeBalances;
pub use memory::MemoryRange;
use crate::checked_transaction::{CreateCheckedMetadata, IntoChecked, ScriptCheckedMetadata};
#[derive(Debug, Clone)]
pub struct Interpreter<S, Tx = ()> {
registers: [Word; VM_REGISTER_COUNT],
memory: Vec<u8>,
frames: Vec<CallFrame>,
receipts: Vec<Receipt>,
tx: Tx,
initial_balances: InitialBalances,
storage: S,
debugger: Debugger,
context: Context,
balances: RuntimeBalances,
gas_costs: GasCosts,
#[cfg(feature = "profile-any")]
profiler: Profiler,
params: ConsensusParameters,
panic_context: PanicContext,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum PanicContext {
None,
ContractId(ContractId),
}
impl<S, Tx> Interpreter<S, Tx> {
pub fn memory(&self) -> &[u8] {
self.memory.as_slice()
}
pub const fn registers(&self) -> &[Word] {
&self.registers
}
pub(crate) fn call_stack(&self) -> &[CallFrame] {
self.frames.as_slice()
}
pub const fn debugger(&self) -> &Debugger {
&self.debugger
}
pub(crate) fn is_unsafe_math(&self) -> bool {
self.registers[RegId::FLAG] & 0x01 == 0x01
}
pub(crate) fn is_wrapping(&self) -> bool {
self.registers[RegId::FLAG] & 0x02 == 0x02
}
pub fn transaction(&self) -> &Tx {
&self.tx
}
pub fn initial_balances(&self) -> &InitialBalances {
&self.initial_balances
}
pub const fn params(&self) -> &ConsensusParameters {
&self.params
}
pub fn gas_costs(&self) -> &GasCosts {
&self.gas_costs
}
pub fn receipts(&self) -> &[Receipt] {
self.receipts.as_slice()
}
#[cfg(feature = "profile-gas")]
fn current_location(&self) -> InstructionLocation {
InstructionLocation::new(
self.frames.last().map(|frame| *frame.to()),
self.registers[RegId::PC] - self.registers[RegId::IS],
)
}
#[cfg(feature = "profile-any")]
pub const fn profiler(&self) -> &Profiler {
&self.profiler
}
}
impl<S, Tx> AsRef<S> for Interpreter<S, Tx> {
fn as_ref(&self) -> &S {
&self.storage
}
}
impl<S, Tx> AsMut<S> for Interpreter<S, Tx> {
fn as_mut(&mut self) -> &mut S {
&mut self.storage
}
}
pub trait ExecutableTransaction:
Default
+ Clone
+ Chargeable
+ Executable
+ IntoChecked
+ UniqueIdentifier
+ field::Maturity
+ field::Inputs
+ field::Outputs
+ field::Witnesses
+ Into<Transaction>
+ SizedBytes
+ SerializableVec
{
fn as_script(&self) -> Option<&Script>;
fn as_script_mut(&mut self) -> Option<&mut Script>;
fn as_create(&self) -> Option<&Create>;
fn as_create_mut(&mut self) -> Option<&mut Create>;
fn transaction_type() -> Word;
fn output_to_mem(&mut self, idx: usize, buf: &mut [u8]) -> io::Result<usize> {
self.outputs_mut()
.get_mut(idx)
.ok_or_else(|| io::Error::new(io::ErrorKind::Other, "Invalid output idx"))
.and_then(|o| o.read(buf))
}
fn replace_message_output(&mut self, idx: usize, output: Output) -> Result<(), PanicReason> {
if !matches!(&output, Output::Message {
recipient,
..
} if recipient != &Address::zeroed())
{
return Err(PanicReason::OutputNotFound);
}
self.outputs_mut()
.get_mut(idx)
.and_then(|o| match o {
Output::Message { recipient, .. } if recipient == &Address::zeroed() => Some(o),
_ => None,
})
.map(|o| mem::replace(o, output))
.map(|_| ())
.ok_or(PanicReason::NonZeroMessageOutputRecipient)
}
fn replace_variable_output(&mut self, idx: usize, output: Output) -> Result<(), PanicReason> {
if !output.is_variable() {
return Err(PanicReason::ExpectedOutputVariable);
}
self.outputs_mut()
.get_mut(idx)
.and_then(|o| match o {
Output::Variable { amount, .. } if amount == &0 => Some(o),
_ => None,
})
.map(|o| mem::replace(o, output))
.map(|_| ())
.ok_or(PanicReason::OutputNotFound)
}
fn update_outputs<I>(
&mut self,
params: &ConsensusParameters,
revert: bool,
remaining_gas: Word,
initial_balances: &InitialBalances,
balances: &I,
) -> Result<(), CheckError>
where
I: for<'a> Index<&'a AssetId, Output = Word>,
{
let gas_refund = TransactionFee::gas_refund_value(params, remaining_gas, self.price())
.ok_or(CheckError::ArithmeticOverflow)?;
self.outputs_mut().iter_mut().try_for_each(|o| match o {
Output::Change { asset_id, amount, .. } if revert && asset_id == &AssetId::BASE => initial_balances
[&AssetId::BASE]
.checked_add(gas_refund)
.map(|v| *amount = v)
.ok_or(CheckError::ArithmeticOverflow),
Output::Change { asset_id, amount, .. } if revert => {
*amount = initial_balances[asset_id];
Ok(())
}
Output::Change { asset_id, amount, .. } if asset_id == &AssetId::BASE => balances[asset_id]
.checked_add(gas_refund)
.map(|v| *amount = v)
.ok_or(CheckError::ArithmeticOverflow),
Output::Change { asset_id, amount, .. } => {
*amount = balances[asset_id];
Ok(())
}
Output::Variable { amount, .. } if revert => {
*amount = 0;
Ok(())
}
_ => Ok(()),
})
}
fn find_output_contract(&self, input: usize) -> Option<(usize, &Output)> {
self.outputs().iter().enumerate().find(|(_idx, o)| {
matches!(o, Output::Contract {
input_index, ..
} if *input_index as usize == input)
})
}
}
impl ExecutableTransaction for Create {
fn as_script(&self) -> Option<&Script> {
None
}
fn as_script_mut(&mut self) -> Option<&mut Script> {
None
}
fn as_create(&self) -> Option<&Create> {
Some(self)
}
fn as_create_mut(&mut self) -> Option<&mut Create> {
Some(self)
}
fn transaction_type() -> Word {
TransactionRepr::Create as Word
}
}
impl ExecutableTransaction for Script {
fn as_script(&self) -> Option<&Script> {
Some(self)
}
fn as_script_mut(&mut self) -> Option<&mut Script> {
Some(self)
}
fn as_create(&self) -> Option<&Create> {
None
}
fn as_create_mut(&mut self) -> Option<&mut Create> {
None
}
fn transaction_type() -> Word {
TransactionRepr::Script as Word
}
}
pub type InitialBalances = BTreeMap<AssetId, Word>;
pub trait CheckedMetadata {
fn balances(self) -> InitialBalances;
fn gas_used_by_predicates(&self) -> Word;
fn set_gas_used_by_predicates(&mut self, gas_used: Word);
}
impl CheckedMetadata for ScriptCheckedMetadata {
fn balances(self) -> InitialBalances {
self.initial_free_balances
}
fn gas_used_by_predicates(&self) -> Word {
self.gas_used_by_predicates
}
fn set_gas_used_by_predicates(&mut self, gas_used: Word) {
self.gas_used_by_predicates = gas_used;
}
}
impl CheckedMetadata for CreateCheckedMetadata {
fn balances(self) -> InitialBalances {
self.initial_free_balances
}
fn gas_used_by_predicates(&self) -> Word {
self.gas_used_by_predicates
}
fn set_gas_used_by_predicates(&mut self, gas_used: Word) {
self.gas_used_by_predicates = gas_used;
}
}