near_vm_errors_v3/
lib.rs

1use std::fmt::{self, Error, Formatter};
2
3use borsh::{BorshDeserialize, BorshSerialize};
4use near_account_id::AccountId;
5use near_rpc_error_macro::RpcError;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
9pub enum VMError {
10    FunctionCallError(FunctionCallError),
11    /// Serialized external error from External trait implementation.
12    ExternalError(Vec<u8>),
13    /// An error that is caused by an operation on an inconsistent state.
14    /// E.g. an integer overflow by using a value from the given context.
15    InconsistentStateError(InconsistentStateError),
16    /// Error caused by caching.
17    CacheError(CacheError),
18}
19
20// TODO(4217): remove borsh serialization derives, once fix compilation caching.
21#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
22pub enum FunctionCallError {
23    /// Wasm compilation error
24    CompilationError(CompilationError),
25    /// Wasm binary env link error
26    LinkError {
27        msg: String,
28    },
29    /// Import/export resolve error
30    MethodResolveError(MethodResolveError),
31    /// A trap happened during execution of a binary
32    WasmTrap(WasmTrap),
33    WasmUnknownError {
34        debug_message: String,
35    },
36    HostError(HostError),
37    // Unused, can be reused by a future error but must be exactly one error to keep Nondeterministic
38    // error borsh serialized at correct index
39    _EVMError,
40    /// Non-deterministic error.
41    Nondeterministic(String),
42}
43
44/// Serializable version of `FunctionCallError`. Must never reorder/remove elements, can only
45/// add new variants at the end (but do that very carefully). This type must be never used
46/// directly, and must be converted to `ContractCallError` instead using `into()` converter.
47/// It describes stable serialization format, and only used by serialization logic.
48#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Serialize, Deserialize)]
49pub enum FunctionCallErrorSer {
50    /// Wasm compilation error
51    CompilationError(CompilationError),
52    /// Wasm binary env link error
53    LinkError {
54        msg: String,
55    },
56    /// Import/export resolve error
57    MethodResolveError(MethodResolveError),
58    /// A trap happened during execution of a binary
59    WasmTrap(WasmTrap),
60    WasmUnknownError,
61    HostError(HostError),
62    // Unused, can be reused by a future error but must be exactly one error to keep ExecutionError
63    // error borsh serialized at correct index
64    _EVMError,
65    ExecutionError(String),
66}
67
68#[derive(
69    Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize, RpcError,
70)]
71pub enum CacheError {
72    ReadError,
73    WriteError,
74    DeserializationError,
75    SerializationError { hash: [u8; 32] },
76}
77/// A kind of a trap happened during execution of a binary
78#[derive(
79    Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize, RpcError,
80)]
81pub enum WasmTrap {
82    /// An `unreachable` opcode was executed.
83    Unreachable,
84    /// Call indirect incorrect signature trap.
85    IncorrectCallIndirectSignature,
86    /// Memory out of bounds trap.
87    MemoryOutOfBounds,
88    /// Call indirect out of bounds trap.
89    CallIndirectOOB,
90    /// An arithmetic exception, e.g. divided by zero.
91    IllegalArithmetic,
92    /// Misaligned atomic access trap.
93    MisalignedAtomicAccess,
94    /// Indirect call to null.
95    IndirectCallToNull,
96    /// Stack overflow.
97    StackOverflow,
98    /// Generic trap.
99    GenericTrap,
100}
101
102#[derive(
103    Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize, RpcError,
104)]
105pub enum MethodResolveError {
106    MethodEmptyName,
107    MethodNotFound,
108    MethodInvalidSignature,
109}
110
111#[derive(
112    Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize, RpcError,
113)]
114pub enum CompilationError {
115    CodeDoesNotExist { account_id: AccountId },
116    PrepareError(PrepareError),
117    WasmerCompileError { msg: String },
118    UnsupportedCompiler { msg: String },
119}
120
121#[derive(
122    Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize, RpcError,
123)]
124/// Error that can occur while preparing or executing Wasm smart-contract.
125pub enum PrepareError {
126    /// Error happened while serializing the module.
127    Serialization,
128    /// Error happened while deserializing the module.
129    Deserialization,
130    /// Internal memory declaration has been found in the module.
131    InternalMemoryDeclared,
132    /// Gas instrumentation failed.
133    ///
134    /// This most likely indicates the module isn't valid.
135    GasInstrumentation,
136    /// Stack instrumentation failed.
137    ///
138    /// This  most likely indicates the module isn't valid.
139    StackHeightInstrumentation,
140    /// Error happened during instantiation.
141    ///
142    /// This might indicate that `start` function trapped, or module isn't
143    /// instantiable and/or unlinkable.
144    Instantiate,
145    /// Error creating memory.
146    Memory,
147}
148
149#[derive(
150    Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize, RpcError,
151)]
152pub enum HostError {
153    /// String encoding is bad UTF-16 sequence
154    BadUTF16,
155    /// String encoding is bad UTF-8 sequence
156    BadUTF8,
157    /// Exceeded the prepaid gas
158    GasExceeded,
159    /// Exceeded the maximum amount of gas allowed to burn per contract
160    GasLimitExceeded,
161    /// Exceeded the account balance
162    BalanceExceeded,
163    /// Tried to call an empty method name
164    EmptyMethodName,
165    /// Smart contract panicked
166    GuestPanic { panic_msg: String },
167    /// IntegerOverflow happened during a contract execution
168    IntegerOverflow,
169    /// `promise_idx` does not correspond to existing promises
170    InvalidPromiseIndex { promise_idx: u64 },
171    /// Actions can only be appended to non-joint promise.
172    CannotAppendActionToJointPromise,
173    /// Returning joint promise is currently prohibited
174    CannotReturnJointPromise,
175    /// Accessed invalid promise result index
176    InvalidPromiseResultIndex { result_idx: u64 },
177    /// Accessed invalid register id
178    InvalidRegisterId { register_id: u64 },
179    /// Iterator `iterator_index` was invalidated after its creation by performing a mutable operation on trie
180    IteratorWasInvalidated { iterator_index: u64 },
181    /// Accessed memory outside the bounds
182    MemoryAccessViolation,
183    /// VM Logic returned an invalid receipt index
184    InvalidReceiptIndex { receipt_index: u64 },
185    /// Iterator index `iterator_index` does not exist
186    InvalidIteratorIndex { iterator_index: u64 },
187    /// VM Logic returned an invalid account id
188    InvalidAccountId,
189    /// VM Logic returned an invalid method name
190    InvalidMethodName,
191    /// VM Logic provided an invalid public key
192    InvalidPublicKey,
193    /// `method_name` is not allowed in view calls
194    ProhibitedInView { method_name: String },
195    /// The total number of logs will exceed the limit.
196    NumberOfLogsExceeded { limit: u64 },
197    /// The storage key length exceeded the limit.
198    KeyLengthExceeded { length: u64, limit: u64 },
199    /// The storage value length exceeded the limit.
200    ValueLengthExceeded { length: u64, limit: u64 },
201    /// The total log length exceeded the limit.
202    TotalLogLengthExceeded { length: u64, limit: u64 },
203    /// The maximum number of promises within a FunctionCall exceeded the limit.
204    NumberPromisesExceeded { number_of_promises: u64, limit: u64 },
205    /// The maximum number of input data dependencies exceeded the limit.
206    NumberInputDataDependenciesExceeded { number_of_input_data_dependencies: u64, limit: u64 },
207    /// The returned value length exceeded the limit.
208    ReturnedValueLengthExceeded { length: u64, limit: u64 },
209    /// The contract size for DeployContract action exceeded the limit.
210    ContractSizeExceeded { size: u64, limit: u64 },
211    /// The host function was deprecated.
212    Deprecated { method_name: String },
213    /// General errors for ECDSA recover.
214    ECRecoverError { msg: String },
215    /// Deserialization error for alt_bn128 functions
216    #[cfg(feature = "protocol_feature_alt_bn128")]
217    AltBn128DeserializationError { msg: String },
218    /// Serialization error for alt_bn128 functions
219    #[cfg(feature = "protocol_feature_alt_bn128")]
220    AltBn128SerializationError { msg: String },
221}
222
223#[derive(Debug, Clone, PartialEq, BorshDeserialize, BorshSerialize, Deserialize, Serialize)]
224pub enum VMLogicError {
225    /// Errors coming from native Wasm VM.
226    HostError(HostError),
227    /// Serialized external error from External trait implementation.
228    ExternalError(Vec<u8>),
229    /// An error that is caused by an operation on an inconsistent state.
230    InconsistentStateError(InconsistentStateError),
231}
232
233impl std::error::Error for VMLogicError {}
234
235/// An error that is caused by an operation on an inconsistent state.
236/// E.g. a deserialization error or an integer overflow.
237#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, Deserialize, Serialize)]
238pub enum InconsistentStateError {
239    StorageError(String),
240    /// Math operation with a value from the state resulted in a integer overflow.
241    IntegerOverflow,
242}
243
244impl From<HostError> for VMLogicError {
245    fn from(err: HostError) -> Self {
246        VMLogicError::HostError(err)
247    }
248}
249
250impl From<InconsistentStateError> for VMLogicError {
251    fn from(err: InconsistentStateError) -> Self {
252        VMLogicError::InconsistentStateError(err)
253    }
254}
255
256impl From<PrepareError> for VMError {
257    fn from(err: PrepareError) -> Self {
258        VMError::FunctionCallError(FunctionCallError::CompilationError(
259            CompilationError::PrepareError(err),
260        ))
261    }
262}
263
264impl From<&VMLogicError> for VMError {
265    fn from(err: &VMLogicError) -> Self {
266        match err {
267            VMLogicError::HostError(h) => {
268                VMError::FunctionCallError(FunctionCallError::HostError(h.clone()))
269            }
270            VMLogicError::ExternalError(s) => VMError::ExternalError(s.clone()),
271            VMLogicError::InconsistentStateError(e) => VMError::InconsistentStateError(e.clone()),
272        }
273    }
274}
275
276impl fmt::Display for VMLogicError {
277    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
278        write!(f, "{:?}", self)
279    }
280}
281
282impl fmt::Display for PrepareError {
283    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
284        use PrepareError::*;
285        match self {
286            Serialization => write!(f, "Error happened while serializing the module."),
287            Deserialization => write!(f, "Error happened while deserializing the module."),
288            InternalMemoryDeclared => {
289                write!(f, "Internal memory declaration has been found in the module.")
290            }
291            GasInstrumentation => write!(f, "Gas instrumentation failed."),
292            StackHeightInstrumentation => write!(f, "Stack instrumentation failed."),
293            Instantiate => write!(f, "Error happened during instantiation."),
294            Memory => write!(f, "Error creating memory"),
295        }
296    }
297}
298
299impl fmt::Display for FunctionCallError {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
301        match self {
302            FunctionCallError::CompilationError(e) => e.fmt(f),
303            FunctionCallError::MethodResolveError(e) => e.fmt(f),
304            FunctionCallError::HostError(e) => e.fmt(f),
305            FunctionCallError::LinkError { msg } => write!(f, "{}", msg),
306            FunctionCallError::WasmTrap(trap) => write!(f, "WebAssembly trap: {}", trap),
307            FunctionCallError::WasmUnknownError { debug_message } => {
308                write!(f, "Unknown error during Wasm contract execution: {}", debug_message)
309            }
310            FunctionCallError::Nondeterministic(msg) => {
311                write!(f, "Nondeterministic error during contract execution: {}", msg)
312            }
313            FunctionCallError::_EVMError => unreachable!(),
314        }
315    }
316}
317
318impl fmt::Display for WasmTrap {
319    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
320        match self {
321            WasmTrap::Unreachable => write!(f, "An `unreachable` opcode was executed."),
322            WasmTrap::IncorrectCallIndirectSignature => {
323                write!(f, "Call indirect incorrect signature trap.")
324            }
325            WasmTrap::MemoryOutOfBounds => write!(f, "Memory out of bounds trap."),
326            WasmTrap::CallIndirectOOB => write!(f, "Call indirect out of bounds trap."),
327            WasmTrap::IllegalArithmetic => {
328                write!(f, "An arithmetic exception, e.g. divided by zero.")
329            }
330            WasmTrap::MisalignedAtomicAccess => write!(f, "Misaligned atomic access trap."),
331            WasmTrap::GenericTrap => write!(f, "Generic trap."),
332            WasmTrap::StackOverflow => write!(f, "Stack overflow."),
333            WasmTrap::IndirectCallToNull => write!(f, "Indirect call to null."),
334        }
335    }
336}
337
338impl fmt::Display for CompilationError {
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
340        match self {
341            CompilationError::CodeDoesNotExist { account_id } => {
342                write!(f, "cannot find contract code for account {}", account_id)
343            }
344            CompilationError::PrepareError(p) => write!(f, "PrepareError: {}", p),
345            CompilationError::WasmerCompileError { msg } => {
346                write!(f, "Wasmer compilation error: {}", msg)
347            }
348            CompilationError::UnsupportedCompiler { msg } => {
349                write!(f, "Unsupported compiler: {}", msg)
350            }
351        }
352    }
353}
354
355impl fmt::Display for MethodResolveError {
356    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
357        fmt::Debug::fmt(self, f)
358    }
359}
360
361impl fmt::Display for VMError {
362    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
363        match self {
364            VMError::FunctionCallError(err) => fmt::Display::fmt(err, f),
365            VMError::ExternalError(_err) => write!(f, "Serialized ExternalError"),
366            VMError::InconsistentStateError(err) => fmt::Display::fmt(err, f),
367            VMError::CacheError(err) => write!(f, "Cache error: {:?}", err),
368        }
369    }
370}
371
372impl std::fmt::Display for InconsistentStateError {
373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
374        match self {
375            InconsistentStateError::StorageError(err) => write!(f, "Storage error: {:?}", err),
376            InconsistentStateError::IntegerOverflow => write!(
377                f,
378                "Math operation with a value from the state resulted in a integer overflow.",
379            ),
380        }
381    }
382}
383
384impl std::fmt::Display for HostError {
385    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
386        use HostError::*;
387        match self {
388            BadUTF8 => write!(f, "String encoding is bad UTF-8 sequence."),
389            BadUTF16 => write!(f, "String encoding is bad UTF-16 sequence."),
390            GasExceeded => write!(f, "Exceeded the prepaid gas."),
391            GasLimitExceeded => write!(f, "Exceeded the maximum amount of gas allowed to burn per contract."),
392            BalanceExceeded => write!(f, "Exceeded the account balance."),
393            EmptyMethodName => write!(f, "Tried to call an empty method name."),
394            GuestPanic { panic_msg } => write!(f, "Smart contract panicked: {}", panic_msg),
395            IntegerOverflow => write!(f, "Integer overflow."),
396            InvalidIteratorIndex { iterator_index } => write!(f, "Iterator index {:?} does not exist", iterator_index),
397            InvalidPromiseIndex { promise_idx } => write!(f, "{:?} does not correspond to existing promises", promise_idx),
398            CannotAppendActionToJointPromise => write!(f, "Actions can only be appended to non-joint promise."),
399            CannotReturnJointPromise => write!(f, "Returning joint promise is currently prohibited."),
400            InvalidPromiseResultIndex { result_idx } => write!(f, "Accessed invalid promise result index: {:?}", result_idx),
401            InvalidRegisterId { register_id } => write!(f, "Accessed invalid register id: {:?}", register_id),
402            IteratorWasInvalidated { iterator_index } => write!(f, "Iterator {:?} was invalidated after its creation by performing a mutable operation on trie", iterator_index),
403            MemoryAccessViolation => write!(f, "Accessed memory outside the bounds."),
404            InvalidReceiptIndex { receipt_index } => write!(f, "VM Logic returned an invalid receipt index: {:?}", receipt_index),
405            InvalidAccountId => write!(f, "VM Logic returned an invalid account id"),
406            InvalidMethodName => write!(f, "VM Logic returned an invalid method name"),
407            InvalidPublicKey => write!(f, "VM Logic provided an invalid public key"),
408            ProhibitedInView { method_name } => write!(f, "{} is not allowed in view calls", method_name),
409            NumberOfLogsExceeded { limit } => write!(f, "The number of logs will exceed the limit {}", limit),
410            KeyLengthExceeded { length, limit } => write!(f, "The length of a storage key {} exceeds the limit {}", length, limit),
411            ValueLengthExceeded { length, limit } => write!(f, "The length of a storage value {} exceeds the limit {}", length, limit),
412            TotalLogLengthExceeded{ length, limit } => write!(f, "The length of a log message {} exceeds the limit {}", length, limit),
413            NumberPromisesExceeded { number_of_promises, limit } => write!(f, "The number of promises within a FunctionCall {} exceeds the limit {}", number_of_promises, limit),
414            NumberInputDataDependenciesExceeded { number_of_input_data_dependencies, limit } => write!(f, "The number of input data dependencies {} exceeds the limit {}", number_of_input_data_dependencies, limit),
415            ReturnedValueLengthExceeded { length, limit } => write!(f, "The length of a returned value {} exceeds the limit {}", length, limit),
416            ContractSizeExceeded { size, limit } => write!(f, "The size of a contract code in DeployContract action {} exceeds the limit {}", size, limit),
417            Deprecated {method_name}=> write!(f, "Attempted to call deprecated host function {}", method_name),
418            #[cfg(feature = "protocol_feature_alt_bn128")]
419            AltBn128DeserializationError { msg } => write!(f, "AltBn128 deserialization error: {}", msg),
420            #[cfg(feature = "protocol_feature_alt_bn128")]
421            AltBn128SerializationError { msg } => write!(f, "AltBn128 serialization error: {}", msg),
422            ECRecoverError { msg } => write!(f, "ECDSA recover error: {}", msg),
423        }
424    }
425}
426
427pub mod hex_format {
428    use hex::{decode, encode};
429
430    use serde::de;
431    use serde::{Deserialize, Deserializer, Serializer};
432
433    pub fn serialize<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error>
434    where
435        S: Serializer,
436        T: AsRef<[u8]>,
437    {
438        serializer.serialize_str(&encode(data))
439    }
440
441    pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
442    where
443        D: Deserializer<'de>,
444        T: From<Vec<u8>>,
445    {
446        let s = String::deserialize(deserializer)?;
447        decode(&s).map_err(|err| de::Error::custom(err.to_string())).map(Into::into)
448    }
449}
450
451#[cfg(test)]
452mod tests {
453    use crate::{CompilationError, FunctionCallError, MethodResolveError, PrepareError, VMError};
454
455    #[test]
456    fn test_display() {
457        // TODO: proper printing
458        assert_eq!(
459            VMError::FunctionCallError(FunctionCallError::MethodResolveError(
460                MethodResolveError::MethodInvalidSignature
461            ))
462            .to_string(),
463            "MethodInvalidSignature"
464        );
465        assert_eq!(
466            VMError::FunctionCallError(FunctionCallError::CompilationError(
467                CompilationError::PrepareError(PrepareError::StackHeightInstrumentation)
468            ))
469            .to_string(),
470            "PrepareError: Stack instrumentation failed."
471        );
472    }
473}