1use 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#[derive(Debug, Copy, Clone, Default)]
110pub struct NotSupportedEcal;
111
112#[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 panic_context: PanicContext,
139 ecal_state: Ecal,
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
144pub struct InterpreterParams {
145 pub gas_price: Word,
147 pub gas_costs: GasCosts,
149 pub max_inputs: u16,
151 pub contract_max_size: u64,
153 pub tx_offset: usize,
155 pub max_message_data_length: u64,
157 pub chain_id: ChainId,
159 pub fee_params: FeeParameters,
161 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 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#[derive(Debug, Clone, PartialEq, Eq)]
206pub(crate) enum PanicContext {
207 None,
209 ContractId(ContractId),
211}
212
213impl<M: Memory, S, Tx, Ecal> Interpreter<M, S, Tx, Ecal> {
214 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 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 pub const fn registers(&self) -> &[Word] {
230 &self.registers
231 }
232
233 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 pub const fn debugger(&self) -> &Debugger {
244 &self.debugger
245 }
246
247 pub fn transaction(&self) -> &Tx {
249 &self.tx
250 }
251
252 pub fn initial_balances(&self) -> &InitialBalances {
254 &self.initial_balances
255 }
256
257 pub fn max_inputs(&self) -> u16 {
259 self.interpreter_params.max_inputs
260 }
261
262 pub fn gas_price(&self) -> Word {
264 self.interpreter_params.gas_price
265 }
266
267 #[cfg(feature = "test-helpers")]
268 pub fn set_gas_price(&mut self, gas_price: u64) {
270 self.interpreter_params.gas_price = gas_price;
271 }
272
273 pub fn gas_costs(&self) -> &GasCosts {
275 &self.interpreter_params.gas_costs
276 }
277
278 pub fn fee_params(&self) -> &FeeParameters {
280 &self.interpreter_params.fee_params
281 }
282
283 pub fn base_asset_id(&self) -> &AssetId {
285 &self.interpreter_params.base_asset_id
286 }
287
288 pub fn contract_max_size(&self) -> u64 {
290 self.interpreter_params.contract_max_size
291 }
292
293 pub fn tx_offset(&self) -> usize {
295 self.interpreter_params.tx_offset
296 }
297
298 pub fn max_message_data_length(&self) -> u64 {
300 self.interpreter_params.max_message_data_length
301 }
302
303 pub fn chain_id(&self) -> ChainId {
305 self.interpreter_params.chain_id
306 }
307
308 pub fn receipts(&self) -> &[Receipt] {
310 self.receipts.as_ref().as_slice()
311 }
312
313 pub fn compute_receipts_root(&self) -> Bytes32 {
315 self.receipts.root()
316 }
317
318 #[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 #[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 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
371pub 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 fn as_script(&self) -> Option<&Script> {
388 None
389 }
390
391 fn as_script_mut(&mut self) -> Option<&mut Script> {
393 None
394 }
395
396 fn as_create(&self) -> Option<&Create> {
398 None
399 }
400
401 fn as_create_mut(&mut self) -> Option<&mut Create> {
403 None
404 }
405
406 fn as_upgrade(&self) -> Option<&Upgrade> {
408 None
409 }
410
411 fn as_upgrade_mut(&mut self) -> Option<&mut Upgrade> {
413 None
414 }
415
416 fn as_upload(&self) -> Option<&Upload> {
418 None
419 }
420
421 fn as_upload_mut(&mut self) -> Option<&mut Upload> {
423 None
424 }
425
426 fn as_blob(&self) -> Option<&Blob> {
428 None
429 }
430
431 fn as_blob_mut(&mut self) -> Option<&mut Blob> {
433 None
434 }
435
436 fn transaction_type() -> Word;
439
440 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 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 #[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 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 Output::Change {
507 asset_id, amount, ..
508 } if revert => {
509 *amount = initial_balances.non_retryable[asset_id];
510 Ok(())
511 }
512
513 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 Output::Change {
523 asset_id, amount, ..
524 } => {
525 *amount = balances[asset_id];
526 Ok(())
527 }
528
529 Output::Variable { amount, .. } if revert => {
531 *amount = 0;
532 Ok(())
533 }
534
535 _ => 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#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
613pub struct InitialBalances {
614 pub non_retryable: NonRetryableFreeBalances,
616 pub retryable: Option<RetryableAmount>,
618}
619
620pub trait CheckedMetadata {
622 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 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}