1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// Copyright 2021-2023 Protocol Labs
// SPDX-License-Identifier: Apache-2.0, MIT
//! This module contains code used to convert errors to and from wasmtime traps.
use anyhow::anyhow;
use fvm_shared::error::ExitCode;
use wasmtime::Trap;

use crate::call_manager::NO_DATA_BLOCK_ID;
use crate::kernel::{BlockId, ExecutionError};

/// Represents an actor "abort". Returning an [`Abort`] from a syscall will cause the currently
/// running actor to exit and may cause part or all of the actor call stack to unwind.
#[derive(Debug, thiserror::Error)]
pub enum Abort {
    /// The actor explicitly aborted with the given exit code, panicked, or ran out of memory.
    #[error("exit with code {0} ({2})")]
    Exit(ExitCode, String, BlockId),
    /// The actor ran out of gas. This will unwind the actor call stack until we reach an actor
    /// invocation with a gas limit _less_ than that of the caller's gas limit, or until we reach
    /// the top-level call.
    #[error("out of gas")]
    OutOfGas,
    /// The system failed with a fatal error indicating a bug in the FVM. This will abort the entire
    /// top-level message and record a
    /// [`SYS_ASSERTION_FAILED`][fvm_shared::ExitCode::SYS_ASSERTION_FAILED] exit code on-chain.
    #[error("fatal error: {0}")]
    Fatal(anyhow::Error),
}

impl Abort {
    /// Convert an execution error into an "abort". We can't directly convert because we need an
    /// exit code, not a syscall error number.
    pub fn from_error(code: ExitCode, e: ExecutionError) -> Self {
        match e {
            ExecutionError::Syscall(e) => Abort::Exit(
                code,
                format!(
                    "actor aborted with an invalid message: {} (code={:?})",
                    e.0, e.1
                ),
                0,
            ),
            ExecutionError::OutOfGas => Abort::OutOfGas,
            ExecutionError::Fatal(err) => Abort::Fatal(err),
        }
    }

    /// Just like from_error, but escalating syscall errors as fatal.
    pub fn from_error_as_fatal(e: ExecutionError) -> Self {
        match e {
            ExecutionError::OutOfGas => Abort::OutOfGas,
            ExecutionError::Fatal(e) => Abort::Fatal(e),
            ExecutionError::Syscall(e) => Abort::Fatal(anyhow!("unexpected syscall error: {}", e)),
        }
    }
}

/// Unwraps a trap error from an actor into an "abort".
impl From<anyhow::Error> for Abort {
    fn from(e: anyhow::Error) -> Self {
        if let Some(trap) = e.downcast_ref::<Trap>() {
            return match trap {
                | Trap::MemoryOutOfBounds
                | Trap::TableOutOfBounds
                | Trap::IndirectCallToNull
                | Trap::BadSignature
                | Trap::IntegerOverflow
                | Trap::IntegerDivisionByZero
                | Trap::BadConversionToInteger
                | Trap::UnreachableCodeReached

                // Should require the atomic feature to be enabled, but we might as well just
                // handle this.
                | Trap::HeapMisaligned
                | Trap::AtomicWaitNonSharedMemory

                // I think this is fatal? But I'm not sure.
                | Trap::StackOverflow => Abort::Exit(
                    ExitCode::SYS_ILLEGAL_INSTRUCTION,
                    trap.to_string(),
                    NO_DATA_BLOCK_ID,
                ),
                _ => Abort::Fatal(anyhow!("unexpected wasmtime trap: {}", trap)),
            };
        };

        match e.downcast::<Abort>() {
            Ok(abort) => abort,
            Err(e) => Abort::Fatal(e),
        }
    }
}