Skip to main content

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