fuel_vm/
interpreter.rs

1//! [`Interpreter`] implementation
2
3use crate::{
4    call::CallFrame,
5    checked_transaction::{
6        BlobCheckedMetadata,
7        CheckPredicateParams,
8    },
9    constraints::reg_key::*,
10    consts::*,
11    context::Context,
12    error::SimpleResult,
13    state::Debugger,
14};
15use alloc::vec::Vec;
16use core::{
17    mem,
18    ops::Index,
19};
20
21use fuel_asm::{
22    Flags,
23    PanicReason,
24};
25use fuel_tx::{
26    field,
27    Blob,
28    Chargeable,
29    Create,
30    Executable,
31    FeeParameters,
32    GasCosts,
33    Output,
34    PrepareSign,
35    Receipt,
36    Script,
37    Transaction,
38    TransactionRepr,
39    UniqueIdentifier,
40    Upgrade,
41    Upload,
42    ValidityError,
43};
44use fuel_types::{
45    AssetId,
46    Bytes32,
47    ChainId,
48    ContractId,
49    Word,
50};
51
52mod alu;
53mod balances;
54mod blob;
55mod blockchain;
56mod constructors;
57pub mod contract;
58mod crypto;
59pub mod diff;
60mod executors;
61mod flow;
62mod gas;
63mod initialization;
64mod internal;
65mod log;
66mod memory;
67mod metadata;
68mod post_execution;
69mod receipts;
70
71mod debug;
72mod ecal;
73
74use crate::profiler::Profiler;
75
76#[cfg(feature = "profile-gas")]
77use crate::profiler::InstructionLocation;
78
79pub use balances::RuntimeBalances;
80pub use ecal::{
81    EcalHandler,
82    PredicateErrorEcal,
83};
84pub use executors::predicates;
85pub use memory::{
86    Memory,
87    MemoryInstance,
88    MemoryRange,
89};
90
91use crate::checked_transaction::{
92    CreateCheckedMetadata,
93    EstimatePredicates,
94    IntoChecked,
95    NonRetryableFreeBalances,
96    RetryableAmount,
97    ScriptCheckedMetadata,
98    UpgradeCheckedMetadata,
99    UploadCheckedMetadata,
100};
101
102#[cfg(feature = "test-helpers")]
103pub use self::receipts::ReceiptsCtx;
104
105#[cfg(not(feature = "test-helpers"))]
106use self::receipts::ReceiptsCtx;
107
108/// ECAL opcode is not supported and return an error if you try to call.
109#[derive(Debug, Copy, Clone, Default)]
110pub struct NotSupportedEcal;
111
112/// VM interpreter.
113///
114/// The internal state of the VM isn't expose because the intended usage is to
115/// either inspect the resulting receipts after a transaction execution, or the
116/// resulting mutated transaction.
117///
118/// These can be obtained with the help of a [`crate::transactor::Transactor`]
119/// or a client implementation.
120#[derive(Debug, Clone)]
121pub struct Interpreter<M, S, Tx = (), Ecal = NotSupportedEcal> {
122    registers: [Word; VM_REGISTER_COUNT],
123    memory: M,
124    frames: Vec<CallFrame>,
125    receipts: ReceiptsCtx,
126    tx: Tx,
127    initial_balances: InitialBalances,
128    input_contracts: alloc::collections::BTreeSet<ContractId>,
129    input_contracts_index_to_output_index: alloc::collections::BTreeMap<u16, u16>,
130    storage: S,
131    debugger: Debugger,
132    context: Context,
133    balances: RuntimeBalances,
134    profiler: Profiler,
135    interpreter_params: InterpreterParams,
136    /// `PanicContext` after the latest execution. It is consumed by
137    /// `append_panic_receipt` and is `PanicContext::None` after consumption.
138    panic_context: PanicContext,
139    ecal_state: Ecal,
140}
141
142/// Interpreter parameters
143#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct InterpreterParams {
145    /// Gas Price
146    pub gas_price: Word,
147    /// Gas costs
148    pub gas_costs: GasCosts,
149    /// Maximum number of inputs
150    pub max_inputs: u16,
151    /// Maximum size of the contract in bytes
152    pub contract_max_size: u64,
153    /// Offset of the transaction data in the memory
154    pub tx_offset: usize,
155    /// Maximum length of the message data
156    pub max_message_data_length: u64,
157    /// Chain ID
158    pub chain_id: ChainId,
159    /// Fee parameters
160    pub fee_params: FeeParameters,
161    /// Base Asset ID
162    pub base_asset_id: AssetId,
163}
164
165#[cfg(feature = "test-helpers")]
166impl Default for InterpreterParams {
167    fn default() -> Self {
168        Self {
169            gas_price: 0,
170            gas_costs: Default::default(),
171            max_inputs: fuel_tx::TxParameters::DEFAULT.max_inputs(),
172            contract_max_size: fuel_tx::ContractParameters::DEFAULT.contract_max_size(),
173            tx_offset: fuel_tx::TxParameters::DEFAULT.tx_offset(),
174            max_message_data_length: fuel_tx::PredicateParameters::DEFAULT
175                .max_message_data_length(),
176            chain_id: ChainId::default(),
177            fee_params: FeeParameters::default(),
178            base_asset_id: Default::default(),
179        }
180    }
181}
182
183impl InterpreterParams {
184    /// Constructor for `InterpreterParams`
185    pub fn new<T: Into<CheckPredicateParams>>(gas_price: Word, params: T) -> Self {
186        let params: CheckPredicateParams = params.into();
187        Self {
188            gas_price,
189            gas_costs: params.gas_costs,
190            max_inputs: params.max_inputs,
191            contract_max_size: params.contract_max_size,
192            tx_offset: params.tx_offset,
193            max_message_data_length: params.max_message_data_length,
194            chain_id: params.chain_id,
195            fee_params: params.fee_params,
196            base_asset_id: params.base_asset_id,
197        }
198    }
199}
200
201/// Sometimes it is possible to add some additional context information
202/// regarding panic reasons to simplify debugging.
203// TODO: Move this enum into `fuel-tx` and use it inside of the `Receipt::Panic` as meta
204//  information. Maybe better to have `Vec<PanicContext>` to provide more information.
205#[derive(Debug, Clone, PartialEq, Eq)]
206pub(crate) enum PanicContext {
207    /// No additional information.
208    None,
209    /// `ContractId` retrieved during instruction execution.
210    ContractId(ContractId),
211}
212
213impl<M: Memory, S, Tx, Ecal> Interpreter<M, S, Tx, Ecal> {
214    /// Returns the current state of the VM memory
215    pub fn memory(&self) -> &MemoryInstance {
216        self.memory.as_ref()
217    }
218}
219
220impl<M: AsMut<MemoryInstance>, S, Tx, Ecal> Interpreter<M, S, Tx, Ecal> {
221    /// Returns mutable access to the vm memory
222    pub fn memory_mut(&mut self) -> &mut MemoryInstance {
223        self.memory.as_mut()
224    }
225}
226
227impl<M, S, Tx, Ecal> Interpreter<M, S, Tx, Ecal> {
228    /// Returns the current state of the registers
229    pub const fn registers(&self) -> &[Word] {
230        &self.registers
231    }
232
233    /// Returns mutable access to the registers
234    pub fn registers_mut(&mut self) -> &mut [Word] {
235        &mut self.registers
236    }
237
238    pub(crate) fn call_stack(&self) -> &[CallFrame] {
239        self.frames.as_slice()
240    }
241
242    /// Debug handler
243    pub const fn debugger(&self) -> &Debugger {
244        &self.debugger
245    }
246
247    /// The current transaction.
248    pub fn transaction(&self) -> &Tx {
249        &self.tx
250    }
251
252    /// The initial balances.
253    pub fn initial_balances(&self) -> &InitialBalances {
254        &self.initial_balances
255    }
256
257    /// Get max_inputs value
258    pub fn max_inputs(&self) -> u16 {
259        self.interpreter_params.max_inputs
260    }
261
262    /// Gas price for current block
263    pub fn gas_price(&self) -> Word {
264        self.interpreter_params.gas_price
265    }
266
267    #[cfg(feature = "test-helpers")]
268    /// Sets the gas price of the `Interpreter`
269    pub fn set_gas_price(&mut self, gas_price: u64) {
270        self.interpreter_params.gas_price = gas_price;
271    }
272
273    /// Gas costs for opcodes
274    pub fn gas_costs(&self) -> &GasCosts {
275        &self.interpreter_params.gas_costs
276    }
277
278    /// Get the Fee Parameters
279    pub fn fee_params(&self) -> &FeeParameters {
280        &self.interpreter_params.fee_params
281    }
282
283    /// Get the base Asset ID
284    pub fn base_asset_id(&self) -> &AssetId {
285        &self.interpreter_params.base_asset_id
286    }
287
288    /// Get contract_max_size value
289    pub fn contract_max_size(&self) -> u64 {
290        self.interpreter_params.contract_max_size
291    }
292
293    /// Get tx_offset value
294    pub fn tx_offset(&self) -> usize {
295        self.interpreter_params.tx_offset
296    }
297
298    /// Get max_message_data_length value
299    pub fn max_message_data_length(&self) -> u64 {
300        self.interpreter_params.max_message_data_length
301    }
302
303    /// Get the chain id
304    pub fn chain_id(&self) -> ChainId {
305        self.interpreter_params.chain_id
306    }
307
308    /// Receipts generated by a transaction execution.
309    pub fn receipts(&self) -> &[Receipt] {
310        self.receipts.as_ref().as_slice()
311    }
312
313    /// Compute current receipts root
314    pub fn compute_receipts_root(&self) -> Bytes32 {
315        self.receipts.root()
316    }
317
318    /// Mutable access to receipts for testing purposes.
319    #[cfg(any(test, feature = "test-helpers"))]
320    pub fn receipts_mut(&mut self) -> &mut ReceiptsCtx {
321        &mut self.receipts
322    }
323
324    pub(crate) fn contract_id(&self) -> Option<ContractId> {
325        self.frames.last().map(|frame| *frame.to())
326    }
327
328    /// Reference to the underlying profiler
329    #[cfg(feature = "profile-any")]
330    pub const fn profiler(&self) -> &Profiler {
331        &self.profiler
332    }
333}
334
335pub(crate) fn flags(flag: Reg<FLAG>) -> Flags {
336    Flags::from_bits_truncate(*flag)
337}
338
339pub(crate) fn is_wrapping(flag: Reg<FLAG>) -> bool {
340    flags(flag).contains(Flags::WRAPPING)
341}
342
343pub(crate) fn is_unsafe_math(flag: Reg<FLAG>) -> bool {
344    flags(flag).contains(Flags::UNSAFEMATH)
345}
346
347#[cfg(feature = "profile-gas")]
348fn current_location(
349    current_contract: Option<ContractId>,
350    pc: crate::constraints::reg_key::Reg<{ crate::constraints::reg_key::PC }>,
351    is: crate::constraints::reg_key::Reg<{ crate::constraints::reg_key::IS }>,
352) -> InstructionLocation {
353    // Safety: pc should always be above is, but fallback to zero here for weird cases,
354    //         as the profiling code should be robust against regards cases like this.
355    let offset = (*pc).saturating_sub(*is);
356    InstructionLocation::new(current_contract, offset)
357}
358
359impl<M, S, Tx, Ecal> AsRef<S> for Interpreter<M, S, Tx, Ecal> {
360    fn as_ref(&self) -> &S {
361        &self.storage
362    }
363}
364
365impl<M, S, Tx, Ecal> AsMut<S> for Interpreter<M, S, Tx, Ecal> {
366    fn as_mut(&mut self) -> &mut S {
367        &mut self.storage
368    }
369}
370
371/// The definition of the executable transaction supported by the `Interpreter`.
372pub trait ExecutableTransaction:
373    Default
374    + Clone
375    + Chargeable
376    + Executable
377    + IntoChecked
378    + EstimatePredicates
379    + UniqueIdentifier
380    + field::Outputs
381    + field::Witnesses
382    + Into<Transaction>
383    + PrepareSign
384    + fuel_types::canonical::Serialize
385{
386    /// Casts the `Self` transaction into `&Script` if any.
387    fn as_script(&self) -> Option<&Script> {
388        None
389    }
390
391    /// Casts the `Self` transaction into `&mut Script` if any.
392    fn as_script_mut(&mut self) -> Option<&mut Script> {
393        None
394    }
395
396    /// Casts the `Self` transaction into `&Create` if any.
397    fn as_create(&self) -> Option<&Create> {
398        None
399    }
400
401    /// Casts the `Self` transaction into `&mut Create` if any.
402    fn as_create_mut(&mut self) -> Option<&mut Create> {
403        None
404    }
405
406    /// Casts the `Self` transaction into `&Upgrade` if any.
407    fn as_upgrade(&self) -> Option<&Upgrade> {
408        None
409    }
410
411    /// Casts the `Self` transaction into `&mut Upgrade` if any.
412    fn as_upgrade_mut(&mut self) -> Option<&mut Upgrade> {
413        None
414    }
415
416    /// Casts the `Self` transaction into `&Upload` if any.
417    fn as_upload(&self) -> Option<&Upload> {
418        None
419    }
420
421    /// Casts the `Self` transaction into `&mut Upload` if any.
422    fn as_upload_mut(&mut self) -> Option<&mut Upload> {
423        None
424    }
425
426    /// Casts the `Self` transaction into `&Blob` if any.
427    fn as_blob(&self) -> Option<&Blob> {
428        None
429    }
430
431    /// Casts the `Self` transaction into `&mut Blob` if any.
432    fn as_blob_mut(&mut self) -> Option<&mut Blob> {
433        None
434    }
435
436    /// Returns the type of the transaction like `Transaction::Create` or
437    /// `Transaction::Script`.
438    fn transaction_type() -> Word;
439
440    /// Replaces the `Output::Variable` with the `output`(should be also
441    /// `Output::Variable`) by the `idx` index.
442    fn replace_variable_output(
443        &mut self,
444        idx: usize,
445        output: Output,
446    ) -> SimpleResult<()> {
447        if !output.is_variable() {
448            return Err(PanicReason::ExpectedOutputVariable.into());
449        }
450
451        // TODO increase the error granularity for this case - create a new variant of
452        // panic reason
453        self.outputs_mut()
454            .get_mut(idx)
455            .and_then(|o| match o {
456                Output::Variable { amount, .. } if amount == &0 => Some(o),
457                _ => None,
458            })
459            .map(|o| mem::replace(o, output))
460            .ok_or(PanicReason::OutputNotFound)?;
461        Ok(())
462    }
463
464    /// Update change and variable outputs.
465    ///
466    /// `revert` will signal if the execution was reverted. It will refund the unused gas
467    /// cost to the base asset and reset output changes to their `initial_balances`.
468    ///
469    /// `remaining_gas` expects the raw content of `$ggas`
470    ///
471    /// `initial_balances` contains the initial state of the free balances
472    ///
473    /// `balances` will contain the current state of the free balances
474    #[allow(clippy::too_many_arguments)]
475    fn update_outputs<I>(
476        &mut self,
477        revert: bool,
478        used_gas: Word,
479        initial_balances: &InitialBalances,
480        balances: &I,
481        gas_costs: &GasCosts,
482        fee_params: &FeeParameters,
483        base_asset_id: &AssetId,
484        gas_price: Word,
485    ) -> Result<(), ValidityError>
486    where
487        I: for<'a> Index<&'a AssetId, Output = Word>,
488    {
489        let gas_refund = self
490            .refund_fee(gas_costs, fee_params, used_gas, gas_price)
491            .ok_or(ValidityError::GasCostsCoinsOverflow)?;
492
493        self.outputs_mut().iter_mut().try_for_each(|o| match o {
494            // If revert, set base asset to initial balance and refund unused gas
495            //
496            // Note: the initial balance deducts the gas limit from base asset
497            Output::Change {
498                asset_id, amount, ..
499            } if revert && asset_id == base_asset_id => initial_balances.non_retryable
500                [base_asset_id]
501                .checked_add(gas_refund)
502                .map(|v| *amount = v)
503                .ok_or(ValidityError::BalanceOverflow),
504
505            // If revert, reset any non-base asset to its initial balance
506            Output::Change {
507                asset_id, amount, ..
508            } if revert => {
509                *amount = initial_balances.non_retryable[asset_id];
510                Ok(())
511            }
512
513            // The change for the base asset will be the available balance + unused gas
514            Output::Change {
515                asset_id, amount, ..
516            } if asset_id == base_asset_id => balances[asset_id]
517                .checked_add(gas_refund)
518                .map(|v| *amount = v)
519                .ok_or(ValidityError::BalanceOverflow),
520
521            // Set changes to the remainder provided balances
522            Output::Change {
523                asset_id, amount, ..
524            } => {
525                *amount = balances[asset_id];
526                Ok(())
527            }
528
529            // If revert, zeroes all variable output values
530            Output::Variable { amount, .. } if revert => {
531                *amount = 0;
532                Ok(())
533            }
534
535            // Other outputs are unaffected
536            _ => Ok(()),
537        })
538    }
539}
540
541impl ExecutableTransaction for Create {
542    fn as_create(&self) -> Option<&Create> {
543        Some(self)
544    }
545
546    fn as_create_mut(&mut self) -> Option<&mut Create> {
547        Some(self)
548    }
549
550    fn transaction_type() -> Word {
551        TransactionRepr::Create as Word
552    }
553}
554
555impl ExecutableTransaction for Script {
556    fn as_script(&self) -> Option<&Script> {
557        Some(self)
558    }
559
560    fn as_script_mut(&mut self) -> Option<&mut Script> {
561        Some(self)
562    }
563
564    fn transaction_type() -> Word {
565        TransactionRepr::Script as Word
566    }
567}
568
569impl ExecutableTransaction for Upgrade {
570    fn as_upgrade(&self) -> Option<&Upgrade> {
571        Some(self)
572    }
573
574    fn as_upgrade_mut(&mut self) -> Option<&mut Upgrade> {
575        Some(self)
576    }
577
578    fn transaction_type() -> Word {
579        TransactionRepr::Upgrade as Word
580    }
581}
582
583impl ExecutableTransaction for Upload {
584    fn as_upload(&self) -> Option<&Upload> {
585        Some(self)
586    }
587
588    fn as_upload_mut(&mut self) -> Option<&mut Upload> {
589        Some(self)
590    }
591
592    fn transaction_type() -> Word {
593        TransactionRepr::Upload as Word
594    }
595}
596
597impl ExecutableTransaction for Blob {
598    fn as_blob(&self) -> Option<&Blob> {
599        Some(self)
600    }
601
602    fn as_blob_mut(&mut self) -> Option<&mut Blob> {
603        Some(self)
604    }
605
606    fn transaction_type() -> Word {
607        TransactionRepr::Blob as Word
608    }
609}
610
611/// The initial balances of the transaction.
612#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
613pub struct InitialBalances {
614    /// See [`NonRetryableFreeBalances`].
615    pub non_retryable: NonRetryableFreeBalances,
616    /// See [`RetryableAmount`].
617    pub retryable: Option<RetryableAmount>,
618}
619
620/// Methods that should be implemented by the checked metadata of supported transactions.
621pub trait CheckedMetadata {
622    /// Returns the initial balances from the checked metadata of the transaction.
623    fn balances(&self) -> InitialBalances;
624}
625
626impl CheckedMetadata for ScriptCheckedMetadata {
627    fn balances(&self) -> InitialBalances {
628        InitialBalances {
629            non_retryable: self.non_retryable_balances.clone(),
630            retryable: Some(self.retryable_balance),
631        }
632    }
633}
634
635impl CheckedMetadata for CreateCheckedMetadata {
636    fn balances(&self) -> InitialBalances {
637        InitialBalances {
638            non_retryable: self.free_balances.clone(),
639            retryable: None,
640        }
641    }
642}
643
644impl CheckedMetadata for UpgradeCheckedMetadata {
645    fn balances(&self) -> InitialBalances {
646        InitialBalances {
647            non_retryable: self.free_balances.clone(),
648            retryable: None,
649        }
650    }
651}
652
653impl CheckedMetadata for UploadCheckedMetadata {
654    fn balances(&self) -> InitialBalances {
655        InitialBalances {
656            non_retryable: self.free_balances.clone(),
657            retryable: None,
658        }
659    }
660}
661
662impl CheckedMetadata for BlobCheckedMetadata {
663    fn balances(&self) -> InitialBalances {
664        InitialBalances {
665            non_retryable: self.free_balances.clone(),
666            retryable: None,
667        }
668    }
669}
670
671pub(crate) struct InputContracts<'vm> {
672    input_contracts: &'vm alloc::collections::BTreeSet<ContractId>,
673    panic_context: &'vm mut PanicContext,
674}
675
676impl<'vm> InputContracts<'vm> {
677    pub fn new(
678        input_contracts: &'vm alloc::collections::BTreeSet<ContractId>,
679        panic_context: &'vm mut PanicContext,
680    ) -> Self {
681        Self {
682            input_contracts,
683            panic_context,
684        }
685    }
686
687    /// Checks that the contract is declared in the transaction inputs.
688    pub fn check(&mut self, contract: &ContractId) -> SimpleResult<()> {
689        if !self.input_contracts.contains(contract) {
690            *self.panic_context = PanicContext::ContractId(*contract);
691            Err(PanicReason::ContractNotInInputs.into())
692        } else {
693            Ok(())
694        }
695    }
696}