forest/shim/
error.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3use fvm_shared2::error::ExitCode as ExitCodeV2;
4use fvm_shared3::error::ExitCode as ExitCodeV3;
5use fvm_shared4::error::ExitCode as ExitCodeV4;
6use fvm_shared4::error::ExitCode as ExitCode_latest;
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use std::cmp::Ordering;
10use std::fmt;
11
12/// `Newtype` wrapper for the FVM `ExitCode`.
13///
14/// # Examples
15/// ```
16/// # use forest::doctest_private::ExitCode;
17/// let fvm2_success = fvm_shared2::error::ExitCode::new(0);
18/// let fvm3_success = fvm_shared3::error::ExitCode::new(0);
19///
20/// let shim_from_v2 = ExitCode::from(fvm2_success);
21/// let shim_from_v3 = ExitCode::from(fvm3_success);
22///
23/// assert_eq!(shim_from_v2, shim_from_v3);
24/// assert_eq!(shim_from_v2, fvm2_success.into());
25/// assert_eq!(shim_from_v3, fvm3_success.into());
26/// ```
27#[derive(PartialEq, Eq, Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
28pub struct ExitCode(#[schemars(with = "u32")] ExitCodeV4);
29
30impl PartialOrd for ExitCode {
31    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
32        Some(self.value().cmp(&other.value()))
33    }
34}
35
36impl fmt::Display for ExitCode {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        let name = match self.0 {
39            ExitCode_latest::SYS_SENDER_INVALID => Some("SysErrSenderInvalid"),
40            ExitCode_latest::SYS_SENDER_STATE_INVALID => Some("SysErrSenderStateInvalid"),
41            ExitCode_latest::SYS_ILLEGAL_INSTRUCTION => Some("SysErrIllegalInstruction"),
42            ExitCode_latest::SYS_INVALID_RECEIVER => Some("SysErrInvalidReceiver"),
43            ExitCode_latest::SYS_INSUFFICIENT_FUNDS => Some("SysErrInsufficientFunds"),
44            ExitCode_latest::SYS_OUT_OF_GAS => Some("SysErrOutOfGas"),
45            ExitCode_latest::SYS_ILLEGAL_EXIT_CODE => Some("SysErrIllegalExitCode"),
46            ExitCode_latest::SYS_ASSERTION_FAILED => Some("SysFatal"),
47            ExitCode_latest::SYS_MISSING_RETURN => Some("SysErrMissingReturn"),
48
49            ExitCode_latest::USR_ILLEGAL_ARGUMENT => Some("ErrIllegalArgument"),
50            ExitCode_latest::USR_NOT_FOUND => Some("ErrNotFound"),
51            ExitCode_latest::USR_FORBIDDEN => Some("ErrForbidden"),
52            ExitCode_latest::USR_INSUFFICIENT_FUNDS => Some("ErrInsufficientFunds"),
53            ExitCode_latest::USR_ILLEGAL_STATE => Some("ErrIllegalState"),
54            ExitCode_latest::USR_SERIALIZATION => Some("ErrSerialization"),
55            ExitCode_latest::USR_UNHANDLED_MESSAGE => Some("ErrUnhandledMessage"),
56            ExitCode_latest::USR_UNSPECIFIED => Some("ErrUnspecified"),
57            ExitCode_latest::USR_ASSERTION_FAILED => Some("ErrAssertionFailed"),
58            ExitCode_latest::USR_READ_ONLY => Some("ErrReadOnly"),
59            ExitCode_latest::USR_NOT_PAYABLE => Some("ErrNotPayable"),
60
61            _ => None,
62        };
63        if let Some(name) = name {
64            write!(f, "{}({})", name, self.value())
65        } else {
66            match self.value() {
67                code if code > ExitCode_latest::SYS_MISSING_RETURN.value()
68                    && code < ExitCode_latest::FIRST_USER_EXIT_CODE =>
69                {
70                    // We want to match Lotus display exit codes
71                    // See <https://github.com/filecoin-project/go-state-types/blob/v0.15.0/exitcode/names.go#L16-L21>
72                    write!(
73                        f,
74                        "SysErrReserved{}({})",
75                        code - (ExitCode_latest::SYS_ASSERTION_FAILED.value()),
76                        code
77                    )
78                }
79                _ => write!(f, "{}", self.value()),
80            }
81        }
82    }
83}
84
85impl ExitCode {
86    /// The lowest exit code that an actor may abort with.
87    pub const FIRST_USER_EXIT_CODE: u32 = ExitCode_latest::FIRST_USER_EXIT_CODE;
88
89    /// Message execution (including sub-calls) used more gas than the specified limit.
90    pub const SYS_OUT_OF_GAS: Self = Self::new(ExitCode_latest::SYS_OUT_OF_GAS);
91
92    /// The message sender didn't have the requisite funds.
93    pub const SYS_INSUFFICIENT_FUNDS: Self = Self::new(ExitCode_latest::SYS_INSUFFICIENT_FUNDS);
94
95    /// The initial range of exit codes is reserved for system errors.
96    /// Actors may define codes starting with this one.
97    pub const FIRST_ACTOR_ERROR_CODE: u32 = 16;
98
99    pub fn value(&self) -> u32 {
100        self.0.value()
101    }
102
103    pub fn is_success(&self) -> bool {
104        self.0.is_success()
105    }
106
107    pub const fn new(value: ExitCode_latest) -> Self {
108        Self(value)
109    }
110}
111
112impl From<u32> for ExitCode {
113    fn from(value: u32) -> Self {
114        Self(ExitCodeV4::new(value))
115    }
116}
117
118impl From<ExitCodeV4> for ExitCode {
119    fn from(value: ExitCodeV4) -> Self {
120        Self(value)
121    }
122}
123
124impl From<ExitCodeV3> for ExitCode {
125    fn from(value: ExitCodeV3) -> Self {
126        value.value().into()
127    }
128}
129
130impl From<ExitCodeV2> for ExitCode {
131    fn from(value: ExitCodeV2) -> Self {
132        value.value().into()
133    }
134}
135
136impl From<ExitCode> for ExitCodeV2 {
137    fn from(value: ExitCode) -> Self {
138        Self::new(value.0.value())
139    }
140}
141
142impl From<ExitCode> for ExitCodeV3 {
143    fn from(value: ExitCode) -> Self {
144        Self::new(value.0.value())
145    }
146}
147
148impl From<ExitCode> for ExitCodeV4 {
149    fn from(value: ExitCode) -> Self {
150        value.0
151    }
152}