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