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;
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#[derive(Debug, Copy, Clone, Default)]
106pub struct NotSupportedEcal;
107
108#[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 panic_context: PanicContext,
134 ecal_state: Ecal,
135 verifier: V,
136 owner_ptr: Option<Word>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq)]
142pub struct InterpreterParams {
143 pub gas_price: Word,
145 pub gas_costs: GasCosts,
147 pub max_inputs: u16,
149 pub contract_max_size: u64,
151 pub tx_offset: usize,
153 pub max_message_data_length: u64,
155 pub chain_id: ChainId,
157 pub fee_params: FeeParameters,
159 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 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#[derive(Debug, Clone, PartialEq, Eq)]
204pub(crate) enum PanicContext {
205 None,
207 ContractId(ContractId),
209}
210
211impl<M: Memory, S, Tx, Ecal, V> Interpreter<M, S, Tx, Ecal, V> {
212 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 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 pub const fn registers(&self) -> &[Word] {
228 &self.registers
229 }
230
231 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 pub const fn debugger(&self) -> &Debugger {
242 &self.debugger
243 }
244
245 pub fn transaction(&self) -> &Tx {
247 &self.tx
248 }
249
250 pub fn initial_balances(&self) -> &InitialBalances {
252 &self.initial_balances
253 }
254
255 pub fn max_inputs(&self) -> u16 {
257 self.interpreter_params.max_inputs
258 }
259
260 pub fn gas_price(&self) -> Word {
262 self.interpreter_params.gas_price
263 }
264
265 #[cfg(feature = "test-helpers")]
266 pub fn set_gas_price(&mut self, gas_price: u64) {
268 self.interpreter_params.gas_price = gas_price;
269 }
270
271 pub fn gas_costs(&self) -> &GasCosts {
273 &self.interpreter_params.gas_costs
274 }
275
276 pub fn fee_params(&self) -> &FeeParameters {
278 &self.interpreter_params.fee_params
279 }
280
281 pub fn base_asset_id(&self) -> &AssetId {
283 &self.interpreter_params.base_asset_id
284 }
285
286 pub fn contract_max_size(&self) -> u64 {
288 self.interpreter_params.contract_max_size
289 }
290
291 pub fn tx_offset(&self) -> usize {
293 self.interpreter_params.tx_offset
294 }
295
296 pub fn max_message_data_length(&self) -> u64 {
298 self.interpreter_params.max_message_data_length
299 }
300
301 pub fn chain_id(&self) -> ChainId {
303 self.interpreter_params.chain_id
304 }
305
306 pub fn receipts(&self) -> &[Receipt] {
308 self.receipts.as_ref().as_slice()
309 }
310
311 pub fn compute_receipts_root(&self) -> Bytes32 {
313 self.receipts.root()
314 }
315
316 #[cfg(any(test, feature = "test-helpers"))]
318 pub fn receipts_mut(&mut self) -> &mut ReceiptsCtx {
319 &mut self.receipts
320 }
321
322 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
352pub enum ExecutableTxType<'a> {
354 Script(&'a Script),
356 Create(&'a Create),
358 Blob(&'a Blob),
360 Upgrade(&'a Upgrade),
362 Upload(&'a Upload),
364}
365
366pub 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 fn as_script(&self) -> Option<&Script> {
383 None
384 }
385
386 fn as_script_mut(&mut self) -> Option<&mut Script> {
388 None
389 }
390
391 fn as_create(&self) -> Option<&Create> {
393 None
394 }
395
396 fn as_create_mut(&mut self) -> Option<&mut Create> {
398 None
399 }
400
401 fn as_upgrade(&self) -> Option<&Upgrade> {
403 None
404 }
405
406 fn as_upgrade_mut(&mut self) -> Option<&mut Upgrade> {
408 None
409 }
410
411 fn as_upload(&self) -> Option<&Upload> {
413 None
414 }
415
416 fn as_upload_mut(&mut self) -> Option<&mut Upload> {
418 None
419 }
420
421 fn as_blob(&self) -> Option<&Blob> {
423 None
424 }
425
426 fn as_blob_mut(&mut self) -> Option<&mut Blob> {
428 None
429 }
430
431 fn transaction_type() -> TransactionRepr;
433
434 fn executable_type(&self) -> ExecutableTxType<'_>;
436
437 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 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 #[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 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 Output::Change {
504 asset_id, amount, ..
505 } if revert => {
506 *amount = initial_balances.non_retryable[asset_id];
507 Ok(())
508 }
509
510 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 Output::Change {
520 asset_id, amount, ..
521 } => {
522 *amount = balances[asset_id];
523 Ok(())
524 }
525
526 Output::Variable { amount, .. } if revert => {
528 *amount = 0;
529 Ok(())
530 }
531
532 _ => 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#[derive(Default, Debug, Clone, Eq, PartialEq, Hash)]
630pub struct InitialBalances {
631 pub non_retryable: NonRetryableFreeBalances,
633 pub retryable: Option<RetryableAmount>,
635}
636
637pub trait CheckedMetadata {
639 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}