soroban_env_common/
error.rs

1use crate::xdr::{ScError, ScErrorCode, ScErrorType, ScVal};
2use crate::{
3    impl_wrapper_as_and_to_val, impl_wrapper_tag_based_constructors,
4    impl_wrapper_tag_based_valconvert, impl_wrapper_wasmi_conversions, Compare, ConversionError,
5    Env, SymbolError, Val,
6};
7use core::{
8    cmp::Ordering,
9    fmt::Debug,
10    hash::{Hash, Hasher},
11};
12
13/// Wrapper for a [Val] that is tagged with [Tag::Error], interpreting the
14/// [Val]'s body as a pair of a 28-bit error-type code and a 32-bit error
15/// code. The error-type codes correspond to the enumerated cases of
16/// [ScErrorType], and the error codes correspond to the code values stored in
17/// each variant of the [ScError] union.
18#[repr(transparent)]
19#[derive(Copy, Clone)]
20pub struct Error(Val);
21
22impl_wrapper_tag_based_valconvert!(Error);
23impl_wrapper_tag_based_constructors!(Error);
24impl_wrapper_as_and_to_val!(Error);
25impl_wrapper_wasmi_conversions!(Error);
26
27impl Hash for Error {
28    #[inline(always)]
29    fn hash<H: Hasher>(&self, state: &mut H) {
30        self.as_val().get_payload().hash(state);
31    }
32}
33
34impl PartialEq for Error {
35    #[inline(always)]
36    fn eq(&self, other: &Self) -> bool {
37        self.as_val().get_payload() == other.as_val().get_payload()
38    }
39}
40
41impl Eq for Error {}
42
43impl PartialOrd for Error {
44    #[inline(always)]
45    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
46        Some(self.cmp(other))
47    }
48}
49
50impl Ord for Error {
51    #[inline(always)]
52    fn cmp(&self, other: &Self) -> Ordering {
53        let self_tup = (self.as_val().get_minor(), self.as_val().get_major());
54        let other_tup = (other.as_val().get_minor(), other.as_val().get_major());
55        self_tup.cmp(&other_tup)
56    }
57}
58
59impl<E: Env> Compare<Error> for E {
60    type Error = E::Error;
61    fn compare(&self, a: &Error, b: &Error) -> Result<Ordering, Self::Error> {
62        Ok(a.cmp(b))
63    }
64}
65
66impl Debug for Error {
67    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68        let (min, maj) = (
69            self.as_val().get_minor() as i32,
70            self.as_val().get_major() as i32,
71        );
72        if let Ok(type_) = ScErrorType::try_from(min) {
73            if type_ == ScErrorType::Contract {
74                write!(f, "Error({}, #{})", type_.name(), maj)
75            } else if let Ok(code) = ScErrorCode::try_from(maj) {
76                write!(f, "Error({}, {})", type_.name(), code.name())
77            } else {
78                write!(f, "Error({}, #{})", type_.name(), maj)
79            }
80        } else {
81            if let Ok(code) = ScErrorCode::try_from(maj) {
82                write!(f, "Error(#{}, {})", min, code.name())
83            } else {
84                write!(f, "Error(#{}, #{})", min, maj)
85            }
86        }
87    }
88}
89
90impl<'a> From<&'a Error> for Error {
91    fn from(value: &'a Error) -> Self {
92        *value
93    }
94}
95
96impl TryFrom<Error> for ScError {
97    type Error = crate::xdr::Error;
98    fn try_from(er: Error) -> Result<Self, Self::Error> {
99        let type_: ScErrorType = (er.as_val().get_minor() as i32).try_into()?;
100        let u: u32 = er.as_val().get_major();
101        Ok(match type_ {
102            ScErrorType::Contract => ScError::Contract(u),
103            ScErrorType::WasmVm => ScError::WasmVm((u as i32).try_into()?),
104            ScErrorType::Context => ScError::Context((u as i32).try_into()?),
105            ScErrorType::Storage => ScError::Storage((u as i32).try_into()?),
106            ScErrorType::Object => ScError::Object((u as i32).try_into()?),
107            ScErrorType::Crypto => ScError::Crypto((u as i32).try_into()?),
108            ScErrorType::Events => ScError::Events((u as i32).try_into()?),
109            ScErrorType::Budget => ScError::Budget((u as i32).try_into()?),
110            ScErrorType::Value => ScError::Value((u as i32).try_into()?),
111            ScErrorType::Auth => ScError::Auth((u as i32).try_into()?),
112        })
113    }
114}
115
116impl TryFrom<Error> for ScVal {
117    type Error = crate::xdr::Error;
118    fn try_from(st: Error) -> Result<Self, crate::xdr::Error> {
119        Ok(ScVal::Error(<_ as TryInto<ScError>>::try_into(st)?))
120    }
121}
122
123impl TryFrom<&Error> for ScVal {
124    type Error = crate::xdr::Error;
125    fn try_from(value: &Error) -> Result<Self, crate::xdr::Error> {
126        (*value).try_into()
127    }
128}
129
130impl From<ScError> for Error {
131    fn from(er: ScError) -> Self {
132        Error::from_scerror(er)
133    }
134}
135
136impl From<(ScErrorType, ScErrorCode)> for Error {
137    fn from(value: (ScErrorType, ScErrorCode)) -> Self {
138        Error::from_type_and_code(value.0, value.1)
139    }
140}
141
142impl From<SymbolError> for Error {
143    fn from(_: SymbolError) -> Self {
144        Error::from_type_and_code(ScErrorType::Value, ScErrorCode::InvalidInput)
145    }
146}
147
148impl From<ConversionError> for Error {
149    fn from(_: ConversionError) -> Self {
150        Error::from_type_and_code(ScErrorType::Value, ScErrorCode::UnexpectedType)
151    }
152}
153
154impl From<crate::xdr::Error> for Error {
155    fn from(e: crate::xdr::Error) -> Self {
156        match e {
157            crate::xdr::Error::DepthLimitExceeded | crate::xdr::Error::LengthLimitExceeded => {
158                Error::from_type_and_code(ScErrorType::Context, ScErrorCode::ExceededLimit)
159            }
160            _ => Error::from_type_and_code(ScErrorType::Value, ScErrorCode::InvalidInput),
161        }
162    }
163}
164
165// This never happens, but it's needed for some impls of TryFromVal downstream
166// in the SDK that use the xdr::Error type.
167impl From<Error> for crate::xdr::Error {
168    fn from(_value: Error) -> Self {
169        crate::xdr::Error::Unsupported
170    }
171}
172
173#[cfg(feature = "wasmi")]
174impl From<wasmi::core::TrapCode> for Error {
175    fn from(code: wasmi::core::TrapCode) -> Self {
176        let ec = match code {
177            wasmi::core::TrapCode::UnreachableCodeReached => ScErrorCode::InvalidAction,
178
179            wasmi::core::TrapCode::MemoryOutOfBounds | wasmi::core::TrapCode::TableOutOfBounds => {
180                ScErrorCode::IndexBounds
181            }
182
183            wasmi::core::TrapCode::IndirectCallToNull => ScErrorCode::MissingValue,
184
185            wasmi::core::TrapCode::IntegerDivisionByZero
186            | wasmi::core::TrapCode::IntegerOverflow
187            | wasmi::core::TrapCode::BadConversionToInteger => ScErrorCode::ArithDomain,
188
189            wasmi::core::TrapCode::BadSignature => ScErrorCode::UnexpectedType,
190
191            wasmi::core::TrapCode::StackOverflow
192            | wasmi::core::TrapCode::OutOfFuel
193            | wasmi::core::TrapCode::GrowthOperationLimited => {
194                return Error::from_type_and_code(ScErrorType::Budget, ScErrorCode::ExceededLimit)
195            }
196        };
197        return Error::from_type_and_code(ScErrorType::WasmVm, ec);
198    }
199}
200
201#[cfg(feature = "wasmi")]
202impl From<wasmi::errors::FuncError> for Error {
203    fn from(err: wasmi::errors::FuncError) -> Self {
204        let ec = match err {
205            wasmi::errors::FuncError::ExportedFuncNotFound => ScErrorCode::MissingValue,
206            wasmi::errors::FuncError::MismatchingParameterType
207            | wasmi::errors::FuncError::MismatchingResultType => ScErrorCode::UnexpectedType,
208            wasmi::errors::FuncError::MismatchingParameterLen
209            | wasmi::errors::FuncError::MismatchingResultLen => ScErrorCode::UnexpectedSize,
210        };
211        return Error::from_type_and_code(ScErrorType::WasmVm, ec);
212    }
213}
214
215#[cfg(feature = "wasmi")]
216impl From<wasmi::Error> for Error {
217    fn from(e: wasmi::Error) -> Self {
218        const EXCEEDED_LIMIT: Error =
219            Error::from_type_and_code(ScErrorType::Budget, ScErrorCode::ExceededLimit);
220        const INDEX_BOUND: Error =
221            Error::from_type_and_code(ScErrorType::WasmVm, ScErrorCode::IndexBounds);
222
223        match e {
224            wasmi::Error::Memory(e) => match e {
225                wasmi::errors::MemoryError::OutOfBoundsAllocation
226                | wasmi::errors::MemoryError::OutOfBoundsGrowth => return EXCEEDED_LIMIT,
227                wasmi::errors::MemoryError::OutOfBoundsAccess => return INDEX_BOUND,
228                _ => (),
229            },
230            wasmi::Error::Table(e) => match e {
231                wasmi::errors::TableError::GrowOutOfBounds { .. } => return EXCEEDED_LIMIT,
232                wasmi::errors::TableError::AccessOutOfBounds { .. }
233                | wasmi::errors::TableError::CopyOutOfBounds => return INDEX_BOUND,
234                _ => (),
235            },
236            wasmi::Error::Instantiation(e) => match e {
237                wasmi::errors::InstantiationError::Memory(me) => match me {
238                    wasmi::errors::MemoryError::OutOfBoundsAllocation
239                    | wasmi::errors::MemoryError::OutOfBoundsGrowth => return EXCEEDED_LIMIT,
240                    wasmi::errors::MemoryError::OutOfBoundsAccess => return INDEX_BOUND,
241                    _ => (),
242                },
243                wasmi::errors::InstantiationError::Table(te) => match te {
244                    wasmi::errors::TableError::GrowOutOfBounds { .. } => return EXCEEDED_LIMIT,
245                    wasmi::errors::TableError::AccessOutOfBounds { .. }
246                    | wasmi::errors::TableError::CopyOutOfBounds => return INDEX_BOUND,
247                    _ => (),
248                },
249                _ => (),
250            },
251            wasmi::Error::Store(e) => {
252                if let wasmi::errors::FuelError::OutOfFuel = e {
253                    return EXCEEDED_LIMIT;
254                }
255            }
256            wasmi::Error::Trap(trap) => {
257                if let Some(code) = trap.trap_code() {
258                    return code.into();
259                }
260            }
261            wasmi::Error::Func(e) => {
262                return e.into();
263            }
264            wasmi::Error::Global(e) => {
265                if matches!(e, wasmi::errors::GlobalError::TypeMismatch { .. }) {
266                    return Error::from_type_and_code(
267                        ScErrorType::WasmVm,
268                        ScErrorCode::UnexpectedType,
269                    );
270                }
271            }
272            _ => (),
273        }
274
275        Error::from_type_and_code(ScErrorType::WasmVm, ScErrorCode::InvalidAction)
276    }
277}
278
279#[cfg(feature = "wasmi")]
280impl From<wasmparser::BinaryReaderError> for Error {
281    fn from(_: wasmparser::BinaryReaderError) -> Self {
282        Error::from_type_and_code(ScErrorType::WasmVm, ScErrorCode::InvalidInput)
283    }
284}
285
286impl Error {
287    // NB: we don't provide a "get_type" to avoid casting a bad bit-pattern into
288    // an ScErrorType. Instead we provide an "is_type" to check any specific
289    // bit-pattern.
290    #[inline(always)]
291    pub const fn is_type(&self, type_: ScErrorType) -> bool {
292        self.as_val().has_minor(type_ as u32)
293    }
294
295    #[inline(always)]
296    pub const fn is_code(&self, code: ScErrorCode) -> bool {
297        self.as_val().has_major(code as u32)
298    }
299
300    #[inline(always)]
301    pub const fn get_code(&self) -> u32 {
302        self.as_val().get_major()
303    }
304
305    #[inline(always)]
306    pub const fn from_contract_error(code: u32) -> Error {
307        unsafe { Self::from_major_minor(code, ScErrorType::Contract as u32) }
308    }
309
310    #[inline(always)]
311    pub const fn from_type_and_code(type_: ScErrorType, code: ScErrorCode) -> Error {
312        unsafe { Self::from_major_minor(code as u32, type_ as u32) }
313    }
314
315    #[inline(always)]
316    pub const fn from_scerror(sc: ScError) -> Error {
317        match sc {
318            ScError::Contract(u) => Self::from_contract_error(u),
319            ScError::WasmVm(code) => Self::from_type_and_code(ScErrorType::WasmVm, code),
320            ScError::Context(code) => Self::from_type_and_code(ScErrorType::Context, code),
321            ScError::Storage(code) => Self::from_type_and_code(ScErrorType::Storage, code),
322            ScError::Object(code) => Self::from_type_and_code(ScErrorType::Object, code),
323            ScError::Crypto(code) => Self::from_type_and_code(ScErrorType::Crypto, code),
324            ScError::Events(code) => Self::from_type_and_code(ScErrorType::Events, code),
325            ScError::Budget(code) => Self::from_type_and_code(ScErrorType::Budget, code),
326            ScError::Value(code) => Self::from_type_and_code(ScErrorType::Value, code),
327            ScError::Auth(code) => Self::from_type_and_code(ScErrorType::Auth, code),
328        }
329    }
330}
331
332impl From<core::convert::Infallible> for crate::Error {
333    fn from(x: core::convert::Infallible) -> Self {
334        match x {}
335    }
336}
337
338#[cfg(all(test, feature = "std"))]
339mod tests {
340    use super::*;
341
342    #[test]
343    fn error_ord_same_as_scerror() {
344        // The impl `Ord for Error` must agree with `Ord for ScError`,
345        // re https://github.com/stellar/rs-soroban-env/issues/743.
346        //
347        // This test creates pairs of corresponding ScError/Error values,
348        // puts them all into a list, and sorts them with each comparison function,
349        // then checks that both lists are sorted the same.
350
351        let mut xdr_vals = Vec::new();
352        for type_ in crate::xdr::ScErrorType::VARIANTS {
353            match type_ {
354                ScErrorType::Contract => {
355                    for i in 0..=512 {
356                        xdr_vals.push(ScError::Contract(i))
357                    }
358                }
359                ScErrorType::WasmVm => {
360                    for code in crate::xdr::ScErrorCode::VARIANTS {
361                        xdr_vals.push(ScError::WasmVm(code))
362                    }
363                }
364                ScErrorType::Context => {
365                    for code in crate::xdr::ScErrorCode::VARIANTS {
366                        xdr_vals.push(ScError::Context(code))
367                    }
368                }
369                ScErrorType::Storage => {
370                    for code in crate::xdr::ScErrorCode::VARIANTS {
371                        xdr_vals.push(ScError::Storage(code))
372                    }
373                }
374                ScErrorType::Object => {
375                    for code in crate::xdr::ScErrorCode::VARIANTS {
376                        xdr_vals.push(ScError::Object(code))
377                    }
378                }
379                ScErrorType::Crypto => {
380                    for code in crate::xdr::ScErrorCode::VARIANTS {
381                        xdr_vals.push(ScError::Crypto(code))
382                    }
383                }
384                ScErrorType::Events => {
385                    for code in crate::xdr::ScErrorCode::VARIANTS {
386                        xdr_vals.push(ScError::Events(code))
387                    }
388                }
389                ScErrorType::Budget => {
390                    for code in crate::xdr::ScErrorCode::VARIANTS {
391                        xdr_vals.push(ScError::Budget(code))
392                    }
393                }
394                ScErrorType::Value => {
395                    for code in crate::xdr::ScErrorCode::VARIANTS {
396                        xdr_vals.push(ScError::Value(code))
397                    }
398                }
399                ScErrorType::Auth => {
400                    for code in crate::xdr::ScErrorCode::VARIANTS {
401                        xdr_vals.push(ScError::Auth(code))
402                    }
403                }
404            }
405        }
406
407        let pairs: Vec<_> = xdr_vals
408            .iter()
409            .map(|xdr_val| {
410                let host_val = Error::try_from(xdr_val.clone()).unwrap();
411                (xdr_val, host_val)
412            })
413            .collect();
414
415        let mut pairs_xdr_sorted = pairs.clone();
416        let mut pairs_host_sorted = pairs_xdr_sorted.clone();
417
418        pairs_xdr_sorted.sort_by(|&(v1, _), &(v2, _)| v1.cmp(v2));
419
420        pairs_host_sorted.sort_by(|&(_, v1), &(_, v2)| v1.cmp(&v2));
421
422        assert_eq!(pairs_xdr_sorted, pairs_host_sorted);
423    }
424}