near_vm_errors/
lib.rs

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