1use fuel_asm::{
4 PanicInstruction,
5 PanicReason,
6 RawInstruction,
7 Word,
8};
9use fuel_tx::ValidityError;
10
11use crate::checked_transaction::CheckError;
12use alloc::{
13 format,
14 string::{
15 String,
16 ToString,
17 },
18};
19use core::{
20 convert::Infallible,
21 fmt,
22};
23
24use crate::storage::predicate;
25
26#[derive(Debug, derive_more::Display)]
28#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
29pub enum InterpreterError<StorageError> {
30 #[display(fmt = "Execution error: {_0:?}")]
33 PanicInstruction(PanicInstruction),
34 #[display(fmt = "Execution error: {_0:?}")]
38 Panic(PanicReason),
39 #[display(fmt = "Failed to check the transaction: {_0:?}")]
41 CheckError(CheckError),
42 #[display(fmt = "Execution error")]
45 NoTransactionInitialized,
46 #[display(fmt = "Execution error")]
47 DebugStateNotInitialized,
49 #[display(fmt = "Storage error: {}", _0)]
51 Storage(StorageError),
52 #[display(fmt = "Bug: {_0}")]
54 Bug(Bug),
55 #[display(
57 fmt = "The transaction's gas price is wrong: expected {expected}, got {actual}"
58 )]
59 ReadyTransactionWrongGasPrice {
60 expected: Word,
62 actual: Word,
64 },
65}
66
67impl<StorageError> InterpreterError<StorageError> {
68 pub fn from_runtime(
70 error: RuntimeError<StorageError>,
71 instruction: RawInstruction,
72 ) -> Self {
73 match error {
74 RuntimeError::Recoverable(reason) => {
75 Self::PanicInstruction(PanicInstruction::error(reason, instruction))
76 }
77 _ => Self::from(error),
78 }
79 }
80
81 pub const fn panic_reason(&self) -> Option<PanicReason> {
83 match self {
84 Self::PanicInstruction(result) => Some(*result.reason()),
85 Self::Panic(reason) => Some(*reason),
86 _ => None,
87 }
88 }
89
90 pub const fn instruction(&self) -> Option<&RawInstruction> {
92 match self {
93 Self::PanicInstruction(result) => Some(result.instruction()),
94 _ => None,
95 }
96 }
97
98 pub fn instruction_result(&self) -> Option<PanicInstruction> {
101 match self {
102 Self::PanicInstruction(r) => Some(*r),
103 _ => None,
104 }
105 }
106}
107
108impl<StorageError> InterpreterError<StorageError>
109where
110 StorageError: fmt::Debug,
111{
112 pub fn erase_generics(&self) -> InterpreterError<String> {
114 match self {
115 Self::Storage(e) => InterpreterError::Storage(format!("{e:?}")),
116 Self::PanicInstruction(e) => InterpreterError::PanicInstruction(*e),
117 Self::Panic(e) => InterpreterError::Panic(*e),
118 Self::NoTransactionInitialized => InterpreterError::NoTransactionInitialized,
119 Self::DebugStateNotInitialized => InterpreterError::DebugStateNotInitialized,
120 Self::Bug(e) => InterpreterError::Bug(e.clone()),
121 Self::CheckError(e) => InterpreterError::CheckError(e.clone()),
122 InterpreterError::ReadyTransactionWrongGasPrice { expected, actual } => {
123 InterpreterError::ReadyTransactionWrongGasPrice {
124 expected: *expected,
125 actual: *actual,
126 }
127 }
128 }
129 }
130}
131
132impl<StorageError> From<RuntimeError<StorageError>> for InterpreterError<StorageError> {
133 fn from(error: RuntimeError<StorageError>) -> Self {
134 match error {
135 RuntimeError::Recoverable(e) => Self::Panic(e),
136 RuntimeError::Bug(e) => Self::Bug(e),
137 RuntimeError::Storage(e) => Self::Storage(e),
138 }
139 }
140}
141
142impl<StorageError> PartialEq for InterpreterError<StorageError>
143where
144 StorageError: PartialEq,
145{
146 fn eq(&self, other: &Self) -> bool {
147 match (self, other) {
148 (Self::PanicInstruction(s), Self::PanicInstruction(o)) => s == o,
149 (Self::Panic(s), Self::Panic(o)) => s == o,
150 (Self::NoTransactionInitialized, Self::NoTransactionInitialized) => true,
151 (Self::Storage(a), Self::Storage(b)) => a == b,
152 (Self::DebugStateNotInitialized, Self::DebugStateNotInitialized) => true,
153
154 _ => false,
155 }
156 }
157}
158
159impl<StorageError> From<Bug> for InterpreterError<StorageError> {
160 fn from(bug: Bug) -> Self {
161 Self::Bug(bug)
162 }
163}
164
165impl<StorageError> From<Infallible> for InterpreterError<StorageError> {
166 fn from(_: Infallible) -> Self {
167 unreachable!()
168 }
169}
170
171impl<StorageError> From<ValidityError> for InterpreterError<StorageError> {
172 fn from(err: ValidityError) -> Self {
173 Self::CheckError(CheckError::Validity(err))
174 }
175}
176
177#[derive(Debug)]
180#[must_use]
181pub enum RuntimeError<StorageError> {
182 Recoverable(PanicReason),
184 Bug(Bug),
186 Storage(StorageError),
188}
189
190impl<StorageError> RuntimeError<StorageError> {
191 pub const fn is_recoverable(&self) -> bool {
193 matches!(self, Self::Recoverable(_))
194 }
195
196 pub const fn must_halt(&self) -> bool {
198 !self.is_recoverable()
199 }
200}
201
202impl<StorageError: PartialEq> PartialEq for RuntimeError<StorageError> {
203 fn eq(&self, other: &Self) -> bool {
204 match (self, other) {
205 (RuntimeError::Recoverable(a), RuntimeError::Recoverable(b)) => a == b,
206 (RuntimeError::Bug(a), RuntimeError::Bug(b)) => a == b,
207 (RuntimeError::Storage(a), RuntimeError::Storage(b)) => a == b,
208 _ => false,
209 }
210 }
211}
212
213impl<StorageError: core::fmt::Debug> fmt::Display for RuntimeError<StorageError> {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 match self {
216 Self::Recoverable(reason) => write!(f, "Recoverable error: {}", reason),
217 Self::Bug(err) => write!(f, "Bug: {}", err),
218 Self::Storage(err) => write!(f, "Unrecoverable storage error: {:?}", err),
219 }
220 }
221}
222
223impl<StorageError> From<PanicReason> for RuntimeError<StorageError> {
224 fn from(value: PanicReason) -> Self {
225 Self::Recoverable(value)
226 }
227}
228
229impl<StorageError> From<core::array::TryFromSliceError> for RuntimeError<StorageError> {
230 fn from(value: core::array::TryFromSliceError) -> Self {
231 Self::Recoverable(value.into())
232 }
233}
234
235impl<StorageError> From<Bug> for RuntimeError<StorageError> {
236 fn from(bug: Bug) -> Self {
237 Self::Bug(bug)
238 }
239}
240
241impl<StorageError> From<Infallible> for RuntimeError<StorageError> {
242 fn from(_: Infallible) -> Self {
243 unreachable!()
244 }
245}
246
247#[derive(Debug, Clone, PartialEq, derive_more::Display)]
249#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
250pub enum PredicateVerificationFailed {
251 #[display(fmt = "Predicate {index} used less than the required amount of gas")]
253 GasMismatch {
254 index: usize,
256 },
257 #[display(fmt = "Insufficient gas available for predicate {index}")]
259 OutOfGas {
260 index: usize,
262 },
263 #[display(fmt = "Predicate {index} owner invalid, doesn't match code root")]
265 InvalidOwner {
266 index: usize,
268 },
269 #[display(fmt = "Predicate {index} failed to evaluate")]
271 False {
272 index: usize,
274 },
275 #[display(fmt = "Predicate {index} failed to evaluate")]
277 GasNotSpecified {
278 index: usize,
280 },
281 #[display(fmt = "Transaction exceeds total gas allowance {_0:?}")]
283 TransactionExceedsTotalGasAllowance(Word),
284 #[display(fmt = "Cumulative gas computation overflowed the u64 accumulator")]
286 GasOverflow,
287 #[display(fmt = "Invalid interpreter state reached unexpectedly")]
289 Bug(Bug),
290 #[display(fmt = "Execution error: {instruction:?} in input predicate {index}")]
292 PanicInstruction {
293 index: usize,
295 instruction: PanicInstruction,
297 },
298 #[display(fmt = "Execution error: {reason:?} in input predicate {index}")]
300 Panic {
301 index: usize,
303 reason: PanicReason,
305 },
306 #[display(fmt = "Predicate {index} attempted to access storage")]
308 Storage {
309 index: usize,
311 },
312}
313
314impl PredicateVerificationFailed {
315 pub(crate) fn interpreter_error(
318 index: usize,
319 error: InterpreterError<predicate::PredicateStorageError>,
320 ) -> Self {
321 match error {
322 error if error.panic_reason() == Some(PanicReason::OutOfGas) => {
323 Self::OutOfGas { index }
324 }
325 InterpreterError::Panic(reason) => Self::Panic { index, reason },
326 InterpreterError::PanicInstruction(instruction) => {
327 Self::PanicInstruction { index, instruction }
328 }
329 InterpreterError::Bug(bug) => Self::Bug(bug),
330 InterpreterError::Storage(_) => Self::Storage { index },
331 _ => Self::False { index },
332 }
333 }
334}
335
336impl From<Bug> for PredicateVerificationFailed {
337 fn from(bug: Bug) -> Self {
338 Self::Bug(bug)
339 }
340}
341
342#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumMessage)]
344#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
345pub enum BugVariant {
346 #[strum(
348 message = "The context gas cannot overflow since it was created by a valid transaction and the total gas does not increase - hence, it always fits a word."
349 )]
350 ContextGasOverflow,
351
352 #[strum(
354 message = "The context gas cannot underflow since any script should halt upon gas exhaustion."
355 )]
356 ContextGasUnderflow,
357
358 #[strum(
360 message = "The gas consumption cannot exceed the gas context since it is capped by the transaction gas limit."
361 )]
362 GlobalGasUnderflow,
363
364 #[strum(message = "The global gas cannot ever be less than the context gas. ")]
366 GlobalGasLessThanContext,
367
368 #[strum(message = "The stack pointer cannot overflow under checked operations.")]
370 StackPointerOverflow,
371
372 #[strum(message = "Contract size doesn't fit into a word.")]
375 CodeSizeOverflow,
376
377 #[strum(message = "Refund cannot be computed in the current vm state.")]
379 UncomputableRefund,
380
381 #[strum(message = "Receipts context is full, cannot add new receipts.")]
383 ReceiptsCtxFull,
384
385 #[strum(message = "Witness index is out of bounds.")]
387 WitnessIndexOutOfBounds,
388
389 #[strum(
391 message = "The witness subsection index is higher than the total number of parts."
392 )]
393 NextSubsectionIndexIsHigherThanTotalNumberOfParts,
394
395 #[strum(message = "Input index more than u16::MAX was used internally.")]
397 InputIndexMoreThanU16Max,
398 #[strum(message = "Transaction owner index is out of bounds.")]
399 TransactionOwnerIndexOutOfBounds,
401 #[strum(message = "The `Input::Owner` at the given index is missing an owner.")]
402 TransactionOwnerInputHasNoOwner {
404 index: usize,
406 },
407}
408
409impl fmt::Display for BugVariant {
410 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
411 use strum::EnumMessage;
412 if let Some(msg) = self.get_message() {
413 write!(f, "{}", msg)
414 } else {
415 write!(f, "{:?}", self)
416 }
417 }
418}
419
420#[derive(Debug, Clone)]
425#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
426#[must_use]
427pub struct Bug {
428 location: String,
430
431 variant: BugVariant,
433
434 inner_message: Option<String>,
436
437 #[cfg(feature = "backtrace")]
440 bt: backtrace::Backtrace,
441}
442
443impl Bug {
444 #[track_caller]
447 pub fn new(variant: BugVariant) -> Self {
448 let caller = core::panic::Location::caller();
449 let location = format!("{}:{}:{}", caller.file(), caller.line(), caller.column());
450 Self {
451 location,
452 variant,
453 inner_message: None,
454 #[cfg(feature = "backtrace")]
455 bt: backtrace::Backtrace::new(),
456 }
457 }
458
459 pub fn with_message<E: ToString>(mut self, error: E) -> Self {
461 self.inner_message = Some(error.to_string());
462 self
463 }
464}
465
466impl PartialEq for Bug {
467 fn eq(&self, other: &Self) -> bool {
468 self.location == other.location
469 }
470}
471
472#[cfg(feature = "backtrace")]
473mod bt {
474 use super::*;
475 use backtrace::Backtrace;
476
477 impl Bug {
478 pub const fn bt(&self) -> &Backtrace {
480 &self.bt
481 }
482 }
483}
484
485impl fmt::Display for Bug {
486 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487 use percent_encoding::{
488 NON_ALPHANUMERIC,
489 utf8_percent_encode,
490 };
491
492 let issue_title = format!("Bug report: {:?} in {}", self.variant, self.location);
493
494 let issue_body = format!(
495 "Error: {:?} {}\nLocation: {}\nVersion: {} {}\n",
496 self.variant,
497 self.inner_message
498 .as_ref()
499 .map(|msg| format!("({msg})"))
500 .unwrap_or_default(),
501 self.location,
502 env!("CARGO_PKG_NAME"),
503 env!("CARGO_PKG_VERSION")
504 );
505
506 write!(
507 f,
508 concat!(
509 "Encountered a bug! Please report this using the following link: ",
510 "https://github.com/FuelLabs/fuel-vm/issues/new",
511 "?title={}",
512 "&body={}",
513 "\n\n",
514 "{:?} error in {}: {} {}\n",
515 ),
516 utf8_percent_encode(&issue_title, NON_ALPHANUMERIC),
517 utf8_percent_encode(&issue_body, NON_ALPHANUMERIC),
518 self.variant,
519 self.location,
520 self.variant,
521 self.inner_message
522 .as_ref()
523 .map(|msg| format!("({msg})"))
524 .unwrap_or_default(),
525 )?;
526
527 #[cfg(feature = "backtrace")]
528 {
529 write!(f, "\nBacktrace:\n{:?}\n", self.bt)?;
530 }
531
532 Ok(())
533 }
534}
535
536#[derive(Debug, Clone, PartialEq)]
539#[must_use]
540pub enum PanicOrBug {
541 Panic(PanicReason),
543 Bug(Bug),
545}
546
547impl From<PanicReason> for PanicOrBug {
548 fn from(panic: PanicReason) -> Self {
549 Self::Panic(panic)
550 }
551}
552
553impl From<Bug> for PanicOrBug {
554 fn from(bug: Bug) -> Self {
555 Self::Bug(bug)
556 }
557}
558
559impl<StorageError> From<PanicOrBug> for RuntimeError<StorageError> {
560 fn from(value: PanicOrBug) -> Self {
561 match value {
562 PanicOrBug::Panic(reason) => Self::Recoverable(reason),
563 PanicOrBug::Bug(bug) => Self::Bug(bug),
564 }
565 }
566}
567
568impl<StorageError> From<PanicOrBug> for InterpreterError<StorageError> {
569 fn from(value: PanicOrBug) -> Self {
570 match value {
571 PanicOrBug::Panic(reason) => Self::Panic(reason),
572 PanicOrBug::Bug(bug) => Self::Bug(bug),
573 }
574 }
575}
576
577pub type SimpleResult<T> = Result<T, PanicOrBug>;
579
580pub type IoResult<T, S> = Result<T, RuntimeError<S>>;
582
583#[cfg(test)]
584mod tests {
585 use super::*;
586
587 #[test]
588 fn bug_report_message() {
589 let bug = Bug::new(BugVariant::ContextGasOverflow).with_message("Test message");
590 let text = format!("{}", bug);
591 assert!(text.contains(file!()));
592 assert!(text.contains("https://github.com/FuelLabs/fuel-vm/issues/new"));
593 assert!(text.contains("ContextGasOverflow"));
594 assert!(text.contains("Test message"));
595 }
596}