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