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 used less than the required amount of gas")]
253 GasMismatch,
254 #[display(fmt = "Insufficient gas available for single predicate")]
256 OutOfGas,
257 #[display(fmt = "Predicate owner invalid, doesn't match code root")]
259 InvalidOwner,
260 #[display(fmt = "Predicate failed to evaluate")]
262 False,
263 #[display(fmt = "Predicate failed to evaluate")]
265 GasNotSpecified,
266 #[display(fmt = "Transaction exceeds total gas allowance {_0:?}")]
268 TransactionExceedsTotalGasAllowance(Word),
269 #[display(fmt = "Cumulative gas computation overflowed the u64 accumulator")]
271 GasOverflow,
272 #[display(fmt = "Invalid interpreter state reached unexpectedly")]
274 Bug(Bug),
275 #[display(fmt = "Execution error: {_0:?}")]
277 PanicInstruction(PanicInstruction),
278 #[display(fmt = "Execution error: {_0:?}")]
280 Panic(PanicReason),
281 #[display(
283 fmt = "Predicate verification failed since it attempted to access storage"
284 )]
285 Storage,
286}
287
288impl From<InterpreterError<predicate::PredicateStorageError>>
289 for PredicateVerificationFailed
290{
291 fn from(error: InterpreterError<predicate::PredicateStorageError>) -> Self {
292 match error {
293 error if error.panic_reason() == Some(PanicReason::OutOfGas) => {
294 PredicateVerificationFailed::OutOfGas
295 }
296 InterpreterError::Panic(reason) => PredicateVerificationFailed::Panic(reason),
297 InterpreterError::PanicInstruction(result) => {
298 PredicateVerificationFailed::PanicInstruction(result)
299 }
300 InterpreterError::Bug(bug) => PredicateVerificationFailed::Bug(bug),
301 InterpreterError::Storage(_) => PredicateVerificationFailed::Storage,
302 _ => PredicateVerificationFailed::False,
303 }
304 }
305}
306
307impl From<Bug> for PredicateVerificationFailed {
308 fn from(bug: Bug) -> Self {
309 Self::Bug(bug)
310 }
311}
312
313impl From<PanicReason> for PredicateVerificationFailed {
314 fn from(reason: PanicReason) -> Self {
315 Self::Panic(reason)
316 }
317}
318
319impl From<PanicOrBug> for PredicateVerificationFailed {
320 fn from(err: PanicOrBug) -> Self {
321 match err {
322 PanicOrBug::Panic(reason) => Self::from(reason),
323 PanicOrBug::Bug(bug) => Self::Bug(bug),
324 }
325 }
326}
327
328#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::EnumMessage)]
330#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
331pub enum BugVariant {
332 #[strum(
334 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."
335 )]
336 ContextGasOverflow,
337
338 #[strum(
340 message = "The context gas cannot underflow since any script should halt upon gas exhaustion."
341 )]
342 ContextGasUnderflow,
343
344 #[strum(
346 message = "The gas consumption cannot exceed the gas context since it is capped by the transaction gas limit."
347 )]
348 GlobalGasUnderflow,
349
350 #[strum(message = "The global gas cannot ever be less than the context gas. ")]
352 GlobalGasLessThanContext,
353
354 #[strum(message = "The stack pointer cannot overflow under checked operations.")]
356 StackPointerOverflow,
357
358 #[strum(message = "Contract size doesn't fit into a word.")]
361 CodeSizeOverflow,
362
363 #[strum(message = "Refund cannot be computed in the current vm state.")]
365 UncomputableRefund,
366
367 #[strum(message = "Receipts context is full, cannot add new receipts.")]
369 ReceiptsCtxFull,
370
371 #[strum(message = "Witness index is out of bounds.")]
373 WitnessIndexOutOfBounds,
374
375 #[strum(
377 message = "The witness subsection index is higher than the total number of parts."
378 )]
379 NextSubsectionIndexIsHigherThanTotalNumberOfParts,
380}
381
382impl fmt::Display for BugVariant {
383 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
384 use strum::EnumMessage;
385 if let Some(msg) = self.get_message() {
386 write!(f, "{}", msg)
387 } else {
388 write!(f, "{:?}", self)
389 }
390 }
391}
392
393#[derive(Debug, Clone)]
398#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
399#[must_use]
400pub struct Bug {
401 location: String,
403
404 variant: BugVariant,
406
407 inner_message: Option<String>,
409
410 #[cfg(feature = "backtrace")]
413 bt: backtrace::Backtrace,
414}
415
416impl Bug {
417 #[track_caller]
420 pub fn new(variant: BugVariant) -> Self {
421 let caller = core::panic::Location::caller();
422 let location = format!("{}:{}:{}", caller.file(), caller.line(), caller.column());
423 Self {
424 location,
425 variant,
426 inner_message: None,
427 #[cfg(feature = "backtrace")]
428 bt: backtrace::Backtrace::new(),
429 }
430 }
431
432 pub fn with_message<E: ToString>(mut self, error: E) -> Self {
434 self.inner_message = Some(error.to_string());
435 self
436 }
437}
438
439impl PartialEq for Bug {
440 fn eq(&self, other: &Self) -> bool {
441 self.location == other.location
442 }
443}
444
445#[cfg(feature = "backtrace")]
446mod bt {
447 use super::*;
448 use backtrace::Backtrace;
449
450 impl Bug {
451 pub const fn bt(&self) -> &Backtrace {
453 &self.bt
454 }
455 }
456}
457
458impl fmt::Display for Bug {
459 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460 use percent_encoding::{
461 utf8_percent_encode,
462 NON_ALPHANUMERIC,
463 };
464
465 let issue_title = format!("Bug report: {:?} in {}", self.variant, self.location);
466
467 let issue_body = format!(
468 "Error: {:?} {}\nLocation: {}\nVersion: {} {}\n",
469 self.variant,
470 self.inner_message
471 .as_ref()
472 .map(|msg| format!("({msg})"))
473 .unwrap_or_default(),
474 self.location,
475 env!("CARGO_PKG_NAME"),
476 env!("CARGO_PKG_VERSION")
477 );
478
479 write!(
480 f,
481 concat!(
482 "Encountered a bug! Please report this using the following link: ",
483 "https://github.com/FuelLabs/fuel-vm/issues/new",
484 "?title={}",
485 "&body={}",
486 "\n\n",
487 "{:?} error in {}: {} {}\n",
488 ),
489 utf8_percent_encode(&issue_title, NON_ALPHANUMERIC),
490 utf8_percent_encode(&issue_body, NON_ALPHANUMERIC),
491 self.variant,
492 self.location,
493 self.variant,
494 self.inner_message
495 .as_ref()
496 .map(|msg| format!("({msg})"))
497 .unwrap_or_default(),
498 )?;
499
500 #[cfg(feature = "backtrace")]
501 {
502 write!(f, "\nBacktrace:\n{:?}\n", self.bt)?;
503 }
504
505 Ok(())
506 }
507}
508
509#[derive(Debug, Clone, PartialEq)]
512#[must_use]
513pub enum PanicOrBug {
514 Panic(PanicReason),
516 Bug(Bug),
518}
519
520impl From<PanicReason> for PanicOrBug {
521 fn from(panic: PanicReason) -> Self {
522 Self::Panic(panic)
523 }
524}
525
526impl From<Bug> for PanicOrBug {
527 fn from(bug: Bug) -> Self {
528 Self::Bug(bug)
529 }
530}
531
532impl<StorageError> From<PanicOrBug> for RuntimeError<StorageError> {
533 fn from(value: PanicOrBug) -> Self {
534 match value {
535 PanicOrBug::Panic(reason) => Self::Recoverable(reason),
536 PanicOrBug::Bug(bug) => Self::Bug(bug),
537 }
538 }
539}
540
541impl<StorageError> From<PanicOrBug> for InterpreterError<StorageError> {
542 fn from(value: PanicOrBug) -> Self {
543 match value {
544 PanicOrBug::Panic(reason) => Self::Panic(reason),
545 PanicOrBug::Bug(bug) => Self::Bug(bug),
546 }
547 }
548}
549
550pub type SimpleResult<T> = Result<T, PanicOrBug>;
552
553pub type IoResult<T, S> = Result<T, RuntimeError<S>>;
555
556#[cfg(test)]
557mod tests {
558 use super::*;
559
560 #[test]
561 fn bug_report_message() {
562 let bug = Bug::new(BugVariant::ContextGasOverflow).with_message("Test message");
563 let text = format!("{}", bug);
564 assert!(text.contains(file!()));
565 assert!(text.contains("https://github.com/FuelLabs/fuel-vm/issues/new"));
566 assert!(text.contains("ContextGasOverflow"));
567 assert!(text.contains("Test message"));
568 }
569}