forest/shim/
error.rs

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