Skip to main content

neo_types/
error.rs

1// Copyright (c) 2025-2026 R3E Network
2// Licensed under the MIT License
3
4use std::fmt;
5use std::string::String;
6
7/// Neo N3 Error type
8///
9/// Represents all possible error conditions that can occur during
10/// Neo N3 smart contract execution.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum NeoError {
13    /// Operation is not valid in the current context
14    InvalidOperation,
15    /// Argument has an invalid value or type
16    InvalidArgument,
17    /// Type mismatch encountered during execution
18    InvalidType,
19    /// Index or offset is out of valid bounds
20    OutOfBounds,
21    /// Attempted division by zero
22    DivisionByZero,
23    /// Arithmetic overflow occurred
24    Overflow,
25    /// Arithmetic underflow occurred
26    Underflow,
27    /// Dereferenced a null or invalid reference
28    NullReference,
29    /// Internal state is invalid or corrupted
30    InvalidState,
31    /// Application-specific error with custom message
32    Custom(String),
33    /// L6: a wasm32 cross-call syscall (System.Contract.Call,
34    /// System.Runtime.LoadScript, System.Contract.CallNative) was
35    /// invoked, but the wasm32 cross-call executor is not yet
36    /// implemented. Replaces the v0.8.0 B4 panic-loud sites with
37    /// a structured error.
38    Wasm32CrossCallUnavailable { syscall: &'static str },
39}
40
41impl fmt::Display for NeoError {
42    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
43        match self {
44            NeoError::InvalidOperation => write!(f, "Invalid operation: the requested operation cannot be performed in the current context"),
45            NeoError::InvalidArgument => write!(f, "Invalid argument: one or more arguments have invalid values or types"),
46            NeoError::InvalidType => write!(f, "Invalid type: type mismatch encountered during execution"),
47            NeoError::OutOfBounds => write!(f, "Out of bounds: index or offset exceeds valid range"),
48            NeoError::DivisionByZero => write!(f, "Division by zero: cannot divide by zero"),
49            NeoError::Overflow => write!(f, "Overflow: arithmetic operation resulted in overflow"),
50            NeoError::Underflow => write!(f, "Underflow: arithmetic operation resulted in underflow"),
51            NeoError::NullReference => write!(f, "Null reference: attempted to access a null or invalid reference"),
52            NeoError::InvalidState => write!(f, "Invalid state: internal state is invalid or corrupted"),
53            NeoError::Custom(msg) => write!(f, "{}", msg),
54            NeoError::Wasm32CrossCallUnavailable { syscall } => {
55                write!(f, "wasm32 cross-call unavailable: {syscall}")
56            }
57        }
58    }
59}
60
61impl NeoError {
62    /// Creates a new custom error with the given message.
63    ///
64    /// # Examples
65    ///
66    /// ```
67    /// use neo_types::NeoError;
68    ///
69    /// let err = NeoError::new("Custom error message");
70    /// ```
71    pub fn new(message: &str) -> Self {
72        NeoError::Custom(message.to_string())
73    }
74
75    /// Returns a stable status code for this error variant.
76    ///
77    /// These codes are used by generated export wrappers to avoid silently
78    /// treating failures as successful `0` values.
79    pub fn status_code(&self) -> i64 {
80        match self {
81            NeoError::InvalidOperation => 1,
82            NeoError::InvalidArgument => 2,
83            NeoError::InvalidType => 3,
84            NeoError::OutOfBounds => 4,
85            NeoError::DivisionByZero => 5,
86            NeoError::Overflow => 6,
87            NeoError::Underflow => 7,
88            NeoError::NullReference => 8,
89            NeoError::InvalidState => 9,
90            NeoError::Custom(_) => 10,
91            NeoError::Wasm32CrossCallUnavailable { .. } => 11,
92        }
93    }
94
95    /// Returns the error message if this is a custom error, otherwise returns a generic description.
96    pub fn message(&self) -> &str {
97        match self {
98            NeoError::Custom(msg) => msg,
99            NeoError::Wasm32CrossCallUnavailable { syscall } => {
100                // `message()` returns `&str`, so it cannot allocate the
101                // "<prefix>: <syscall>" form that `Display` builds. Since the
102                // syscall is a `&'static str` from a closed set, return a
103                // matching static literal per known syscall.
104                match *syscall {
105                    "System.Contract.Call" => "wasm32 cross-call unavailable: System.Contract.Call",
106                    "System.Runtime.LoadScript" => {
107                        "wasm32 cross-call unavailable: System.Runtime.LoadScript"
108                    }
109                    "System.Contract.CallNative" => {
110                        "wasm32 cross-call unavailable: System.Contract.CallNative"
111                    }
112                    _ => "wasm32 cross-call unavailable",
113                }
114            }
115            _ => self.as_str(),
116        }
117    }
118
119    /// Returns a static string description of the error variant.
120    pub fn as_str(&self) -> &'static str {
121        match self {
122            NeoError::InvalidOperation => "InvalidOperation",
123            NeoError::InvalidArgument => "InvalidArgument",
124            NeoError::InvalidType => "InvalidType",
125            NeoError::OutOfBounds => "OutOfBounds",
126            NeoError::DivisionByZero => "DivisionByZero",
127            NeoError::Overflow => "Overflow",
128            NeoError::Underflow => "Underflow",
129            NeoError::NullReference => "NullReference",
130            NeoError::InvalidState => "InvalidState",
131            NeoError::Custom(_) => "Custom",
132            NeoError::Wasm32CrossCallUnavailable { .. } => "Wasm32CrossCallUnavailable",
133        }
134    }
135}
136
137impl std::error::Error for NeoError {}
138
139// D16: ergonomic `From` conversions so `?` works across common stdlib error
140// types encountered when parsing integers/bytes in contract code.
141impl From<std::num::TryFromIntError> for NeoError {
142    fn from(_: std::num::TryFromIntError) -> Self {
143        NeoError::Overflow
144    }
145}
146
147impl From<std::num::ParseIntError> for NeoError {
148    fn from(_: std::num::ParseIntError) -> Self {
149        NeoError::InvalidArgument
150    }
151}
152
153/// Neo N3 Result type
154pub type NeoResult<T> = Result<T, NeoError>;