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 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#[derive(Debug, Copy, Clone, Default)]
107pub struct NotSupportedEcal;
108
109#[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 panic_context: PanicContext,
135 ecal_state: Ecal,
136 verifier: V,
137 owner_ptr: Option<Word>,
139 storage_slot_cache:
144 hashbrown::HashMap<ContractId, hashbrown::HashMap<Bytes32, Option<Vec<u8>>>>,
145}
146
147#[derive(Debug, Clone, PartialEq, Eq)]
149pub struct InterpreterParams {
150 pub gas_price: Word,
152 pub gas_costs: GasCosts,
154 pub max_inputs: u16,
156 pub contract_max_size: u64,
158 pub tx_offset: usize,
160 pub max_message_data_length: u64,
162 pub max_storage_slot_length: u64,
164 pub chain_id: ChainId,
166 pub fee_params: FeeParameters,
168 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 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#[derive(Debug, Clone, PartialEq, Eq)]
216pub(crate) enum PanicContext {
217 None,
219 ContractId(ContractId),
221}
222
223impl<M: Memory, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V> {
224 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 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 pub const fn registers(&self) -> &[Word] {
240 &self.registers
241 }
242
243 pub fn registers_mut(&mut self) -> &mut [Word] {
245 &mut self.registers
246 }
247
248 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 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 pub const fn debugger(&self) -> &Debugger {
276 &self.debugger
277 }
278
279 pub fn transaction(&self) -> &Tx {
281 &self.tx
282 }
283
284 pub fn initial_balances(&self) -> &InitialBalances {
286 &self.initial_balances
287 }
288
289 pub fn max_inputs(&self) -> u16 {
291 self.interpreter_params.max_inputs
292 }
293
294 pub fn gas_price(&self) -> Word {
296 self.interpreter_params.gas_price
297 }
298
299 #[cfg(feature = "test-helpers")]
300 pub fn set_gas_price(&mut self, gas_price: u64) {
302 self.interpreter_params.gas_price = gas_price;
303 }
304
305 pub fn gas_costs(&self) -> &GasCosts {
307 &self.interpreter_params.gas_costs
308 }
309
310 pub fn fee_params(&self) -> &FeeParameters {
312 &self.interpreter_params.fee_params
313 }
314
315 pub fn base_asset_id(&self) -> &AssetId {
317 &self.interpreter_params.base_asset_id
318 }
319
320 pub fn contract_max_size(&self) -> u64 {
322 self.interpreter_params.contract_max_size
323 }
324
325 pub fn tx_offset(&self) -> usize {
327 self.interpreter_params.tx_offset
328 }
329
330 pub fn max_message_data_length(&self) -> u64 {
332 self.interpreter_params.max_message_data_length
333 }
334
335 pub fn chain_id(&self) -> ChainId {
337 self.interpreter_params.chain_id
338 }
339
340 pub fn receipts(&self) -> &[Receipt] {
342 self.receipts.as_ref().as_slice()
343 }
344
345 pub fn compute_receipts_root(&self) -> Bytes32 {
347 self.receipts.root()
348 }
349
350 #[cfg(any(test, feature = "test-helpers"))]
352 pub fn receipts_mut(&mut self) -> &mut ReceiptsCtx {
353 &mut self.receipts
354 }
355
356 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
386pub enum ExecutableTxType<'a> {
388 Script(&'a Script),
390 Create(&'a Create),
392 Blob(&'a Blob),
394 Upgrade(&'a Upgrade),
396 Upload(&'a Upload),
398}
399
400pub 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 fn as_script(&self) -> Option<&Script> {
417 None
418 }
419
420 fn as_script_mut(&mut self) -> Option<&mut Script> {
422 None
423 }
424
425 fn as_create(&self) -> Option<&Create> {
427 None
428 }
429
430 fn as_create_mut(&mut self) -> Option<&mut Create> {
432 None
433 }
434
435 fn as_upgrade(&self) -> Option<&Upgrade> {
437 None
438 }
439
440 fn as_upgrade_mut(&mut self) -> Option<&mut Upgrade> {
442 None
443 }
444
445 fn as_upload(&self) -> Option<&Upload> {
447 None
448 }
449
450 fn as_upload_mut(&mut self) -> Option<&mut Upload> {
452 None
453 }
454
455 fn as_blob(&self) -> Option<&Blob> {
457 None
458 }
459
460 fn as_blob_mut(&mut self) -> Option<&mut Blob> {
462 None
463 }
464
465 fn transaction_type() -> TransactionRepr;
467
468 fn executable_type(&self) -> ExecutableTxType<'_>;
470
471 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 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 #[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 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 Output::Change {
538 asset_id, amount, ..
539 } if revert => {
540 *amount = initial_balances.non_retryable[asset_id];
541 Ok(())
542 }
543
544 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 Output::Change {
554 asset_id, amount, ..
555 } => {
556 *amount = balances[asset_id];
557 Ok(())
558 }
559
560 Output::Variable { amount, .. } if revert => {
562 *amount = 0;
563 Ok(())
564 }
565
566 _ => 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#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
664pub struct InitialBalances {
665 pub non_retryable: NonRetryableFreeBalances,
667 pub retryable: Option<RetryableAmount>,
669}
670
671pub trait CheckedMetadata {
673 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}