forest/shim/
kernel.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3//! We have three goals for our error shims:
4//! - preserve upstream error _numbers_.
5//! - preserve upstream error messages.
6//! - allow _matching_ on specific errors.
7//!
8//! There are a couple of things that make this difficult:
9//! - `fvm_shared*::error::ErrorNumber` is `#[non_exhaustive]`
10//!
11//! We have designed with the following assumptions about the `fvm*` crates:
12//! - new error variants are append-only
13//! - error messages are consistent between crates
14
15use self::ErrorNumber as NShim;
16use self::SyscallError as EShim;
17use fvm_shared2::error::ErrorNumber as N2;
18use fvm_shared3::error::ErrorNumber as N3;
19use fvm_shared4::error::ErrorNumber as N4;
20use fvm2::kernel::SyscallError as E2;
21use fvm3::kernel::SyscallError as E3;
22use fvm4::kernel::SyscallError as E4;
23use num_traits::FromPrimitive;
24use std::fmt;
25use std::fmt::Debug;
26
27macro_rules! error_number {
28    ($($variant:ident),* $(,)?) => {
29        #[derive(Debug, Clone)]
30        pub enum ErrorNumber {
31            $($variant,)*
32            /// This is to catch `#[non_exhaustive]` upstream errors, and MUST NOT for be constructed
33            Unknown(u32),
34        }
35
36        impl From<N4> for ErrorNumber {
37            fn from(error: N4) -> Self {
38                match error {
39                    $(N4::$variant => Self::$variant,)*
40                    _ => Self::Unknown(error as u32),
41                }
42            }
43        }
44
45        impl fmt::Display for ErrorNumber {
46            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47                match self {
48                    $(Self::$variant => std::fmt::Display::fmt(&N4::$variant, f),)*
49                    Self::Unknown(u) => std::fmt::Debug::fmt(&u, f),
50                }
51            }
52        }
53    }
54}
55
56error_number! {
57    IllegalArgument,
58    IllegalOperation,
59    LimitExceeded,
60    AssertionFailed,
61    InsufficientFunds,
62    NotFound,
63    InvalidHandle,
64    IllegalCid,
65    IllegalCodec,
66    Serialization,
67    Forbidden,
68    BufferTooSmall,
69    ReadOnly,
70}
71
72impl From<N2> for ErrorNumber {
73    fn from(value: N2) -> Self {
74        let opt: Option<N4> = FromPrimitive::from_u32(value as u32);
75        match opt {
76            Some(err) => err.into(),
77            None => Self::Unknown(value as u32),
78        }
79    }
80}
81
82impl From<N3> for ErrorNumber {
83    fn from(value: N3) -> Self {
84        let opt: Option<N4> = FromPrimitive::from_u32(value as u32);
85        match opt {
86            Some(err) => err.into(),
87            None => Self::Unknown(value as u32),
88        }
89    }
90}
91
92#[derive(Debug, Clone, thiserror::Error)]
93#[error("syscall error: {message} (exit_code={number})")]
94pub struct SyscallError {
95    pub message: String,
96    pub number: NShim,
97}
98
99impl From<E2> for EShim {
100    fn from(value: E2) -> Self {
101        let E2(message, number) = value;
102        Self {
103            message,
104            number: number.into(),
105        }
106    }
107}
108
109impl From<E3> for EShim {
110    fn from(value: E3) -> Self {
111        let E3(message, number) = value;
112        Self {
113            message,
114            number: number.into(),
115        }
116    }
117}
118
119impl From<E4> for EShim {
120    fn from(value: E4) -> Self {
121        let E4(message, number) = value;
122        Self {
123            message,
124            number: number.into(),
125        }
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_n2_error_fmt() {
135        let shim: NShim = N2::IllegalArgument.into();
136
137        assert_eq!(format!("{shim}"), "illegal argument");
138    }
139
140    #[test]
141    fn test_n3_error_fmt() {
142        let shim: NShim = N3::ReadOnly.into();
143
144        assert_eq!(format!("{shim}"), "execution context is read-only");
145    }
146
147    #[test]
148    fn test_unknown_error_fmt() {
149        let shim: NShim = ErrorNumber::Unknown(23);
150
151        assert_eq!(format!("{shim}"), "23");
152    }
153}