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 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#[derive(Debug, Copy, Clone, Default)]
109pub struct NotSupportedEcal;
110
111#[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 panic_context: PanicContext,
137 ecal_state: Ecal,
138 verifier: V,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
143pub struct InterpreterParams {
144 pub gas_price: Word,
146 pub gas_costs: GasCosts,
148 pub max_inputs: u16,
150 pub contract_max_size: u64,
152 pub tx_offset: usize,
154 pub max_message_data_length: u64,
156 pub chain_id: ChainId,
158 pub fee_params: FeeParameters,
160 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 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#[derive(Debug, Clone, PartialEq, Eq)]
205pub(crate) enum PanicContext {
206 None,
208 ContractId(ContractId),
210}
211
212impl<M: Memory, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V> {
213 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 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 pub const fn registers(&self) -> &[Word] {
229 &self.registers
230 }
231
232 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 pub const fn debugger(&self) -> &Debugger {
243 &self.debugger
244 }
245
246 pub fn transaction(&self) -> &Tx {
248 &self.tx
249 }
250
251 pub fn initial_balances(&self) -> &InitialBalances {
253 &self.initial_balances
254 }
255
256 pub fn max_inputs(&self) -> u16 {
258 self.interpreter_params.max_inputs
259 }
260
261 pub fn gas_price(&self) -> Word {
263 self.interpreter_params.gas_price
264 }
265
266 #[cfg(feature = "test-helpers")]
267 pub fn set_gas_price(&mut self, gas_price: u64) {
269 self.interpreter_params.gas_price = gas_price;
270 }
271
272 pub fn gas_costs(&self) -> &GasCosts {
274 &self.interpreter_params.gas_costs
275 }
276
277 pub fn fee_params(&self) -> &FeeParameters {
279 &self.interpreter_params.fee_params
280 }
281
282 pub fn base_asset_id(&self) -> &AssetId {
284 &self.interpreter_params.base_asset_id
285 }
286
287 pub fn contract_max_size(&self) -> u64 {
289 self.interpreter_params.contract_max_size
290 }
291
292 pub fn tx_offset(&self) -> usize {
294 self.interpreter_params.tx_offset
295 }
296
297 pub fn max_message_data_length(&self) -> u64 {
299 self.interpreter_params.max_message_data_length
300 }
301
302 pub fn chain_id(&self) -> ChainId {
304 self.interpreter_params.chain_id
305 }
306
307 pub fn receipts(&self) -> &[Receipt] {
309 self.receipts.as_ref().as_slice()
310 }
311
312 pub fn compute_receipts_root(&self) -> Bytes32 {
314 self.receipts.root()
315 }
316
317 #[cfg(any(test, feature = "test-helpers"))]
319 pub fn receipts_mut(&mut self) -> &mut ReceiptsCtx {
320 &mut self.receipts
321 }
322
323 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
353pub enum ExecutableTxType<'a> {
355 Script(&'a Script),
357 Create(&'a Create),
359 Blob(&'a Blob),
361 Upgrade(&'a Upgrade),
363 Upload(&'a Upload),
365}
366
367pub 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 fn as_script(&self) -> Option<&Script> {
384 None
385 }
386
387 fn as_script_mut(&mut self) -> Option<&mut Script> {
389 None
390 }
391
392 fn as_create(&self) -> Option<&Create> {
394 None
395 }
396
397 fn as_create_mut(&mut self) -> Option<&mut Create> {
399 None
400 }
401
402 fn as_upgrade(&self) -> Option<&Upgrade> {
404 None
405 }
406
407 fn as_upgrade_mut(&mut self) -> Option<&mut Upgrade> {
409 None
410 }
411
412 fn as_upload(&self) -> Option<&Upload> {
414 None
415 }
416
417 fn as_upload_mut(&mut self) -> Option<&mut Upload> {
419 None
420 }
421
422 fn as_blob(&self) -> Option<&Blob> {
424 None
425 }
426
427 fn as_blob_mut(&mut self) -> Option<&mut Blob> {
429 None
430 }
431
432 fn transaction_type() -> TransactionRepr;
434
435 fn executable_type(&self) -> ExecutableTxType;
437
438 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 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 #[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 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 Output::Change {
505 asset_id, amount, ..
506 } if revert => {
507 *amount = initial_balances.non_retryable[asset_id];
508 Ok(())
509 }
510
511 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 Output::Change {
521 asset_id, amount, ..
522 } => {
523 *amount = balances[asset_id];
524 Ok(())
525 }
526
527 Output::Variable { amount, .. } if revert => {
529 *amount = 0;
530 Ok(())
531 }
532
533 _ => 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#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
631pub struct InitialBalances {
632 pub non_retryable: NonRetryableFreeBalances,
634 pub retryable: Option<RetryableAmount>,
636}
637
638pub trait CheckedMetadata {
640 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}