use crate::{
call::CallFrame,
checked_transaction::{
BlobCheckedMetadata,
CheckPredicateParams,
},
constraints::reg_key::*,
consts::*,
context::Context,
error::SimpleResult,
state::Debugger,
};
use alloc::vec::Vec;
use core::{
mem,
ops::Index,
};
use fuel_asm::{
Flags,
PanicReason,
};
use fuel_tx::{
field,
Blob,
Chargeable,
Create,
Executable,
FeeParameters,
GasCosts,
Output,
PrepareSign,
Receipt,
Script,
Transaction,
TransactionRepr,
UniqueIdentifier,
Upgrade,
Upload,
ValidityError,
};
use fuel_types::{
AssetId,
Bytes32,
ChainId,
ContractId,
Word,
};
mod alu;
mod balances;
mod blob;
mod blockchain;
mod constructors;
pub mod contract;
mod crypto;
pub mod diff;
mod executors;
mod flow;
mod gas;
mod initialization;
mod internal;
mod log;
mod memory;
mod metadata;
mod post_execution;
mod receipts;
mod debug;
mod ecal;
use crate::profiler::Profiler;
#[cfg(feature = "profile-gas")]
use crate::profiler::InstructionLocation;
pub use balances::RuntimeBalances;
pub use ecal::{
EcalHandler,
PredicateErrorEcal,
};
pub use executors::predicates;
pub use memory::{
Memory,
MemoryInstance,
MemoryRange,
};
use crate::checked_transaction::{
CreateCheckedMetadata,
EstimatePredicates,
IntoChecked,
NonRetryableFreeBalances,
RetryableAmount,
ScriptCheckedMetadata,
UpgradeCheckedMetadata,
UploadCheckedMetadata,
};
#[cfg(feature = "test-helpers")]
pub use self::receipts::ReceiptsCtx;
#[cfg(not(feature = "test-helpers"))]
use self::receipts::ReceiptsCtx;
#[derive(Debug, Copy, Clone, Default)]
pub struct NotSupportedEcal;
#[derive(Debug, Clone)]
pub struct Interpreter<M, S, Tx = (), Ecal = NotSupportedEcal> {
registers: [Word; VM_REGISTER_COUNT],
memory: M,
frames: Vec<CallFrame>,
receipts: ReceiptsCtx,
tx: Tx,
initial_balances: InitialBalances,
input_contracts: alloc::collections::BTreeSet<ContractId>,
input_contracts_index_to_output_index: alloc::collections::BTreeMap<u16, u16>,
storage: S,
debugger: Debugger,
context: Context,
balances: RuntimeBalances,
profiler: Profiler,
interpreter_params: InterpreterParams,
panic_context: PanicContext,
ecal_state: Ecal,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InterpreterParams {
pub gas_price: Word,
pub gas_costs: GasCosts,
pub max_inputs: u16,
pub contract_max_size: u64,
pub tx_offset: usize,
pub max_message_data_length: u64,
pub chain_id: ChainId,
pub fee_params: FeeParameters,
pub base_asset_id: AssetId,
}
#[cfg(feature = "test-helpers")]
impl Default for InterpreterParams {
fn default() -> Self {
Self {
gas_price: 0,
gas_costs: Default::default(),
max_inputs: fuel_tx::TxParameters::DEFAULT.max_inputs(),
contract_max_size: fuel_tx::ContractParameters::DEFAULT.contract_max_size(),
tx_offset: fuel_tx::TxParameters::DEFAULT.tx_offset(),
max_message_data_length: fuel_tx::PredicateParameters::DEFAULT
.max_message_data_length(),
chain_id: ChainId::default(),
fee_params: FeeParameters::default(),
base_asset_id: Default::default(),
}
}
}
impl InterpreterParams {
pub fn new<T: Into<CheckPredicateParams>>(gas_price: Word, params: T) -> Self {
let params: CheckPredicateParams = params.into();
Self {
gas_price,
gas_costs: params.gas_costs,
max_inputs: params.max_inputs,
contract_max_size: params.contract_max_size,
tx_offset: params.tx_offset,
max_message_data_length: params.max_message_data_length,
chain_id: params.chain_id,
fee_params: params.fee_params,
base_asset_id: params.base_asset_id,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum PanicContext {
None,
ContractId(ContractId),
}
impl<M: Memory, S, Tx, Ecal> Interpreter<M, S, Tx, Ecal> {
pub fn memory(&self) -> &MemoryInstance {
self.memory.as_ref()
}
}
impl<M: AsMut<MemoryInstance>, S, Tx, Ecal> Interpreter<M, S, Tx, Ecal> {
pub fn memory_mut(&mut self) -> &mut MemoryInstance {
self.memory.as_mut()
}
}
impl<M, S, Tx, Ecal> Interpreter<M, S, Tx, Ecal> {
pub const fn registers(&self) -> &[Word] {
&self.registers
}
pub fn registers_mut(&mut self) -> &mut [Word] {
&mut self.registers
}
pub(crate) fn call_stack(&self) -> &[CallFrame] {
self.frames.as_slice()
}
pub const fn debugger(&self) -> &Debugger {
&self.debugger
}
pub fn transaction(&self) -> &Tx {
&self.tx
}
pub fn initial_balances(&self) -> &InitialBalances {
&self.initial_balances
}
pub fn max_inputs(&self) -> u16 {
self.interpreter_params.max_inputs
}
pub fn gas_price(&self) -> Word {
self.interpreter_params.gas_price
}
#[cfg(feature = "test-helpers")]
pub fn set_gas_price(&mut self, gas_price: u64) {
self.interpreter_params.gas_price = gas_price;
}
pub fn gas_costs(&self) -> &GasCosts {
&self.interpreter_params.gas_costs
}
pub fn fee_params(&self) -> &FeeParameters {
&self.interpreter_params.fee_params
}
pub fn base_asset_id(&self) -> &AssetId {
&self.interpreter_params.base_asset_id
}
pub fn contract_max_size(&self) -> u64 {
self.interpreter_params.contract_max_size
}
pub fn tx_offset(&self) -> usize {
self.interpreter_params.tx_offset
}
pub fn max_message_data_length(&self) -> u64 {
self.interpreter_params.max_message_data_length
}
pub fn chain_id(&self) -> ChainId {
self.interpreter_params.chain_id
}
pub fn receipts(&self) -> &[Receipt] {
self.receipts.as_ref().as_slice()
}
pub fn compute_receipts_root(&self) -> Bytes32 {
self.receipts.root()
}
#[cfg(any(test, feature = "test-helpers"))]
pub fn receipts_mut(&mut self) -> &mut ReceiptsCtx {
&mut self.receipts
}
pub(crate) fn contract_id(&self) -> Option<ContractId> {
self.frames.last().map(|frame| *frame.to())
}
#[cfg(feature = "profile-any")]
pub const fn profiler(&self) -> &Profiler {
&self.profiler
}
}
pub(crate) fn flags(flag: Reg<FLAG>) -> Flags {
Flags::from_bits_truncate(*flag)
}
pub(crate) fn is_wrapping(flag: Reg<FLAG>) -> bool {
flags(flag).contains(Flags::WRAPPING)
}
pub(crate) fn is_unsafe_math(flag: Reg<FLAG>) -> bool {
flags(flag).contains(Flags::UNSAFEMATH)
}
#[cfg(feature = "profile-gas")]
fn current_location(
current_contract: Option<ContractId>,
pc: crate::constraints::reg_key::Reg<{ crate::constraints::reg_key::PC }>,
is: crate::constraints::reg_key::Reg<{ crate::constraints::reg_key::IS }>,
) -> InstructionLocation {
let offset = (*pc).saturating_sub(*is);
InstructionLocation::new(current_contract, offset)
}
impl<M, S, Tx, Ecal> AsRef<S> for Interpreter<M, S, Tx, Ecal> {
fn as_ref(&self) -> &S {
&self.storage
}
}
impl<M, S, Tx, Ecal> AsMut<S> for Interpreter<M, S, Tx, Ecal> {
fn as_mut(&mut self) -> &mut S {
&mut self.storage
}
}
pub trait ExecutableTransaction:
Default
+ Clone
+ Chargeable
+ Executable
+ IntoChecked
+ EstimatePredicates
+ UniqueIdentifier
+ field::Outputs
+ field::Witnesses
+ Into<Transaction>
+ PrepareSign
+ fuel_types::canonical::Serialize
{
fn as_script(&self) -> Option<&Script> {
None
}
fn as_script_mut(&mut self) -> Option<&mut Script> {
None
}
fn as_create(&self) -> Option<&Create> {
None
}
fn as_create_mut(&mut self) -> Option<&mut Create> {
None
}
fn as_upgrade(&self) -> Option<&Upgrade> {
None
}
fn as_upgrade_mut(&mut self) -> Option<&mut Upgrade> {
None
}
fn as_upload(&self) -> Option<&Upload> {
None
}
fn as_upload_mut(&mut self) -> Option<&mut Upload> {
None
}
fn as_blob(&self) -> Option<&Blob> {
None
}
fn as_blob_mut(&mut self) -> Option<&mut Blob> {
None
}
fn transaction_type() -> Word;
fn replace_variable_output(
&mut self,
idx: usize,
output: Output,
) -> SimpleResult<()> {
if !output.is_variable() {
return Err(PanicReason::ExpectedOutputVariable.into());
}
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))
.ok_or(PanicReason::OutputNotFound)?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn update_outputs<I>(
&mut self,
revert: bool,
used_gas: Word,
initial_balances: &InitialBalances,
balances: &I,
gas_costs: &GasCosts,
fee_params: &FeeParameters,
base_asset_id: &AssetId,
gas_price: Word,
) -> Result<(), ValidityError>
where
I: for<'a> Index<&'a AssetId, Output = Word>,
{
let gas_refund = self
.refund_fee(gas_costs, fee_params, used_gas, gas_price)
.ok_or(ValidityError::GasCostsCoinsOverflow)?;
self.outputs_mut().iter_mut().try_for_each(|o| match o {
Output::Change {
asset_id, amount, ..
} if revert && asset_id == base_asset_id => initial_balances.non_retryable
[base_asset_id]
.checked_add(gas_refund)
.map(|v| *amount = v)
.ok_or(ValidityError::BalanceOverflow),
Output::Change {
asset_id, amount, ..
} if revert => {
*amount = initial_balances.non_retryable[asset_id];
Ok(())
}
Output::Change {
asset_id, amount, ..
} if asset_id == base_asset_id => balances[asset_id]
.checked_add(gas_refund)
.map(|v| *amount = v)
.ok_or(ValidityError::BalanceOverflow),
Output::Change {
asset_id, amount, ..
} => {
*amount = balances[asset_id];
Ok(())
}
Output::Variable { amount, .. } if revert => {
*amount = 0;
Ok(())
}
_ => Ok(()),
})
}
}
impl ExecutableTransaction for Create {
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 transaction_type() -> Word {
TransactionRepr::Script as Word
}
}
impl ExecutableTransaction for Upgrade {
fn as_upgrade(&self) -> Option<&Upgrade> {
Some(self)
}
fn as_upgrade_mut(&mut self) -> Option<&mut Upgrade> {
Some(self)
}
fn transaction_type() -> Word {
TransactionRepr::Upgrade as Word
}
}
impl ExecutableTransaction for Upload {
fn as_upload(&self) -> Option<&Upload> {
Some(self)
}
fn as_upload_mut(&mut self) -> Option<&mut Upload> {
Some(self)
}
fn transaction_type() -> Word {
TransactionRepr::Upload as Word
}
}
impl ExecutableTransaction for Blob {
fn as_blob(&self) -> Option<&Blob> {
Some(self)
}
fn as_blob_mut(&mut self) -> Option<&mut Blob> {
Some(self)
}
fn transaction_type() -> Word {
TransactionRepr::Blob as Word
}
}
#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
pub struct InitialBalances {
pub non_retryable: NonRetryableFreeBalances,
pub retryable: Option<RetryableAmount>,
}
pub trait CheckedMetadata {
fn balances(&self) -> InitialBalances;
}
impl CheckedMetadata for ScriptCheckedMetadata {
fn balances(&self) -> InitialBalances {
InitialBalances {
non_retryable: self.non_retryable_balances.clone(),
retryable: Some(self.retryable_balance),
}
}
}
impl CheckedMetadata for CreateCheckedMetadata {
fn balances(&self) -> InitialBalances {
InitialBalances {
non_retryable: self.free_balances.clone(),
retryable: None,
}
}
}
impl CheckedMetadata for UpgradeCheckedMetadata {
fn balances(&self) -> InitialBalances {
InitialBalances {
non_retryable: self.free_balances.clone(),
retryable: None,
}
}
}
impl CheckedMetadata for UploadCheckedMetadata {
fn balances(&self) -> InitialBalances {
InitialBalances {
non_retryable: self.free_balances.clone(),
retryable: None,
}
}
}
impl CheckedMetadata for BlobCheckedMetadata {
fn balances(&self) -> InitialBalances {
InitialBalances {
non_retryable: self.free_balances.clone(),
retryable: None,
}
}
}
pub(crate) struct InputContracts<'vm> {
input_contracts: &'vm alloc::collections::BTreeSet<ContractId>,
panic_context: &'vm mut PanicContext,
}
impl<'vm> InputContracts<'vm> {
pub fn new(
input_contracts: &'vm alloc::collections::BTreeSet<ContractId>,
panic_context: &'vm mut PanicContext,
) -> Self {
Self {
input_contracts,
panic_context,
}
}
pub fn check(&mut self, contract: &ContractId) -> SimpleResult<()> {
if !self.input_contracts.contains(contract) {
*self.panic_context = PanicContext::ContractId(*contract);
Err(PanicReason::ContractNotInInputs.into())
} else {
Ok(())
}
}
}