unc_vm_runner/logic/
errors.rs

1use borsh::{BorshDeserialize, BorshSerialize};
2use std::any::Any;
3use std::fmt::{self, Error, Formatter};
4use std::io;
5
6/// For bugs in the runtime itself, crash and die is the usual response.
7///
8/// See the doc comment on `VMResult` for an explanation what the difference
9/// between this and a `FunctionCallError` is.
10#[derive(Debug, thiserror::Error)]
11pub enum VMRunnerError {
12    /// An error that is caused by an operation on an inconsistent state.
13    /// E.g. an integer overflow by using a value from the given context.
14    #[error("{0}")]
15    InconsistentStateError(InconsistentStateError),
16    /// Error caused by caching.
17    #[error("cache error: {0}")]
18    CacheError(#[from] CacheError),
19    /// Error (eg, resource exhausting) when loading a successfully compiled
20    /// contract into executable memory.
21    #[error("loading error: {0}")]
22    LoadingError(String),
23    /// Type erased error from `External` trait implementation.
24    #[error("external error")]
25    ExternalError(AnyError),
26    /// Non-deterministic error.
27    #[error("non-deterministic error during contract execution: {0}")]
28    Nondeterministic(String),
29    #[error("unknown error during contract execution: {debug_message}")]
30    WasmUnknownError { debug_message: String },
31}
32
33/// Permitted errors that cause a function call to fail gracefully.
34///
35/// Occurrence of such errors will be included in the merklize state on chain
36/// using a single bit to signal failure vs Success.
37///
38/// See the doc comment on `VMResult` for an explanation what the difference
39/// between this and a `VMRunnerError` is. And see `PartialExecutionStatus`
40/// for what gets stored on chain.
41#[derive(Debug, PartialEq, Eq, strum::IntoStaticStr)]
42pub enum FunctionCallError {
43    /// Wasm compilation error
44    CompilationError(CompilationError),
45    /// Wasm binary env link error
46    LinkError {
47        msg: String,
48    },
49    /// Import/export resolve error
50    MethodResolveError(MethodResolveError),
51    /// A trap happened during execution of a binary
52    WasmTrap(WasmTrap),
53    HostError(HostError),
54}
55
56#[derive(Debug, thiserror::Error, strum::IntoStaticStr)]
57pub enum CacheError {
58    #[error("cache read error")]
59    ReadError(#[source] io::Error),
60    #[error("cache write error")]
61    WriteError(#[source] io::Error),
62    #[error("cache deserialization error")]
63    DeserializationError,
64    #[error("cache serialization error")]
65    SerializationError { hash: [u8; 32] },
66}
67/// A kind of a trap happened during execution of a binary
68#[derive(Debug, Clone, PartialEq, Eq, strum::IntoStaticStr)]
69pub enum WasmTrap {
70    /// An `unreachable` opcode was executed.
71    Unreachable,
72    /// Call indirect incorrect signature trap.
73    IncorrectCallIndirectSignature,
74    /// Memory out of bounds trap.
75    MemoryOutOfBounds,
76    /// Call indirect out of bounds trap.
77    CallIndirectOOB,
78    /// An arithmetic exception, e.g. divided by zero.
79    IllegalArithmetic,
80    /// Misaligned atomic access trap.
81    MisalignedAtomicAccess,
82    /// Indirect call to null.
83    IndirectCallToNull,
84    /// Stack overflow.
85    StackOverflow,
86    /// Generic trap.
87    GenericTrap,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, strum::IntoStaticStr)]
91pub enum MethodResolveError {
92    MethodEmptyName,
93    MethodNotFound,
94    MethodInvalidSignature,
95}
96
97#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, strum::IntoStaticStr)]
98pub enum CompilationError {
99    CodeDoesNotExist {
100        account_id: Box<str>,
101    },
102    PrepareError(PrepareError),
103    /// This is for defense in depth.
104    /// We expect our runtime-independent preparation code to fully catch all invalid wasms,
105    /// but, if it ever misses something we’ll emit this error
106    WasmerCompileError {
107        msg: String,
108    },
109}
110
111#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
112/// Error that can occur while preparing or executing Wasm smart-contract.
113pub enum PrepareError {
114    /// Error happened while serializing the module.
115    Serialization,
116    /// Error happened while deserializing the module.
117    Deserialization,
118    /// Internal memory declaration has been found in the module.
119    InternalMemoryDeclared,
120    /// Gas instrumentation failed.
121    ///
122    /// This most likely indicates the module isn't valid.
123    GasInstrumentation,
124    /// Stack instrumentation failed.
125    ///
126    /// This  most likely indicates the module isn't valid.
127    StackHeightInstrumentation,
128    /// Error happened during instantiation.
129    ///
130    /// This might indicate that `start` function trapped, or module isn't
131    /// instantiable and/or unlinkable.
132    Instantiate,
133    /// Error creating memory.
134    Memory,
135    /// Contract contains too many functions.
136    TooManyFunctions,
137    /// Contract contains too many locals.
138    TooManyLocals,
139}
140
141#[derive(Debug, Clone, PartialEq, Eq, strum::IntoStaticStr)]
142pub enum HostError {
143    /// String encoding is bad UTF-16 sequence
144    BadUTF16,
145    /// String encoding is bad UTF-8 sequence
146    BadUTF8,
147    /// Exceeded the prepaid gas
148    GasExceeded,
149    /// Exceeded the maximum amount of gas allowed to burn per contract
150    GasLimitExceeded,
151    /// Exceeded the account balance
152    BalanceExceeded,
153    /// Tried to call an empty method name
154    EmptyMethodName,
155    /// Smart contract panicked
156    GuestPanic { panic_msg: String },
157    /// IntegerOverflow happened during a contract execution
158    IntegerOverflow,
159    /// `promise_idx` does not correspond to existing promises
160    InvalidPromiseIndex { promise_idx: u64 },
161    /// Actions can only be appended to non-joint promise.
162    CannotAppendActionToJointPromise,
163    /// Returning joint promise is currently prohibited
164    CannotReturnJointPromise,
165    /// Accessed invalid promise result index
166    InvalidPromiseResultIndex { result_idx: u64 },
167    /// Accessed invalid register id
168    InvalidRegisterId { register_id: u64 },
169    /// Accessed memory outside the bounds
170    MemoryAccessViolation,
171    /// VM Logic returned an invalid receipt index
172    InvalidReceiptIndex { receipt_index: u64 },
173    /// Iterator index `iterator_index` does not exist
174    InvalidIteratorIndex { iterator_index: u64 },
175    /// VM Logic returned an invalid account id
176    InvalidAccountId,
177    /// VM Logic returned an invalid method name
178    InvalidMethodName,
179    /// VM Logic provided an invalid public key
180    InvalidPublicKey,
181    /// `method_name` is not allowed in view calls
182    ProhibitedInView { method_name: String },
183    /// The total number of logs will exceed the limit.
184    NumberOfLogsExceeded { limit: u64 },
185    /// The storage key length exceeded the limit.
186    KeyLengthExceeded { length: u64, limit: u64 },
187    /// The storage value length exceeded the limit.
188    ValueLengthExceeded { length: u64, limit: u64 },
189    /// The total log length exceeded the limit.
190    TotalLogLengthExceeded { length: u64, limit: u64 },
191    /// The maximum number of promises within a FunctionCall exceeded the limit.
192    NumberPromisesExceeded { number_of_promises: u64, limit: u64 },
193    /// The maximum number of input data dependencies exceeded the limit.
194    NumberInputDataDependenciesExceeded { number_of_input_data_dependencies: u64, limit: u64 },
195    /// The returned value length exceeded the limit.
196    ReturnedValueLengthExceeded { length: u64, limit: u64 },
197    /// The contract size for DeployContract action exceeded the limit.
198    ContractSizeExceeded { size: u64, limit: u64 },
199    /// The host function was deprecated.
200    Deprecated { method_name: String },
201    /// General errors for ECDSA recover.
202    ECRecoverError { msg: String },
203    /// Invalid input to alt_bn128 familiy of functions (e.g., point which isn't
204    /// on the curve).
205    AltBn128InvalidInput { msg: String },
206    /// Invalid input to ed25519 signature verification function (e.g. signature cannot be
207    /// derived from bytes).
208    Ed25519VerifyInvalidInput { msg: String },
209}
210
211#[derive(Debug, PartialEq, Eq)]
212pub enum VMLogicError {
213    /// Errors coming from native Wasm VM.
214    HostError(HostError),
215    /// Type erased error from `External` trait implementation.
216    ExternalError(AnyError),
217    /// An error that is caused by an operation on an inconsistent state.
218    InconsistentStateError(InconsistentStateError),
219}
220
221impl std::error::Error for VMLogicError {}
222
223/// An error that is caused by an operation on an inconsistent state, such as
224/// integer overflow.
225#[derive(Debug, Clone, PartialEq, Eq)]
226pub enum InconsistentStateError {
227    /// Math operation with a value from the state resulted in a integer overflow.
228    IntegerOverflow,
229}
230
231impl From<HostError> for VMLogicError {
232    fn from(err: HostError) -> Self {
233        VMLogicError::HostError(err)
234    }
235}
236
237impl From<InconsistentStateError> for VMLogicError {
238    fn from(err: InconsistentStateError) -> Self {
239        VMLogicError::InconsistentStateError(err)
240    }
241}
242
243impl From<PrepareError> for FunctionCallError {
244    fn from(err: PrepareError) -> Self {
245        FunctionCallError::CompilationError(CompilationError::PrepareError(err))
246    }
247}
248
249/// Try to convert a general error that happened at the `VMLogic` level to a
250/// `FunctionCallError` that can be included in a `VMOutcome`.
251///
252/// `VMLogicError` have two very different types of errors. Some are just
253/// a result from the guest code doing incorrect things, other are bugs in
254/// unc-infra.
255/// The first type can be converted to a `FunctionCallError`, the other becomes
256/// a `VMRunnerError` instead and must be treated differently.
257impl TryFrom<VMLogicError> for FunctionCallError {
258    type Error = VMRunnerError;
259    fn try_from(err: VMLogicError) -> Result<Self, Self::Error> {
260        match err {
261            VMLogicError::HostError(h) => Ok(FunctionCallError::HostError(h)),
262            VMLogicError::ExternalError(s) => Err(VMRunnerError::ExternalError(s)),
263            VMLogicError::InconsistentStateError(e) => {
264                Err(VMRunnerError::InconsistentStateError(e))
265            }
266        }
267    }
268}
269
270impl fmt::Display for VMLogicError {
271    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
272        write!(f, "{:?}", self)
273    }
274}
275
276impl fmt::Display for PrepareError {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
278        use PrepareError::*;
279        f.write_str(match self {
280            Serialization => "Error happened while serializing the module.",
281            Deserialization => "Error happened while deserializing the module.",
282            InternalMemoryDeclared => "Internal memory declaration has been found in the module.",
283            GasInstrumentation => "Gas instrumentation failed.",
284            StackHeightInstrumentation => "Stack instrumentation failed.",
285            Instantiate => "Error happened during instantiation.",
286            Memory => "Error creating memory.",
287            TooManyFunctions => "Too many functions in contract.",
288            TooManyLocals => "Too many locals declared in the contract.",
289        })
290    }
291}
292
293impl fmt::Display for FunctionCallError {
294    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
295        match self {
296            FunctionCallError::CompilationError(e) => e.fmt(f),
297            FunctionCallError::MethodResolveError(e) => e.fmt(f),
298            FunctionCallError::HostError(e) => e.fmt(f),
299            FunctionCallError::LinkError { msg } => write!(f, "{}", msg),
300            FunctionCallError::WasmTrap(trap) => write!(f, "WebAssembly trap: {}", trap),
301        }
302    }
303}
304
305impl fmt::Display for WasmTrap {
306    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
307        match self {
308            WasmTrap::Unreachable => write!(f, "An `unreachable` opcode was executed."),
309            WasmTrap::IncorrectCallIndirectSignature => {
310                write!(f, "Call indirect incorrect signature trap.")
311            }
312            WasmTrap::MemoryOutOfBounds => write!(f, "Memory out of bounds trap."),
313            WasmTrap::CallIndirectOOB => write!(f, "Call indirect out of bounds trap."),
314            WasmTrap::IllegalArithmetic => {
315                write!(f, "An arithmetic exception, e.g. divided by zero.")
316            }
317            WasmTrap::MisalignedAtomicAccess => write!(f, "Misaligned atomic access trap."),
318            WasmTrap::GenericTrap => write!(f, "Generic trap."),
319            WasmTrap::StackOverflow => write!(f, "Stack overflow."),
320            WasmTrap::IndirectCallToNull => write!(f, "Indirect call to null."),
321        }
322    }
323}
324
325impl fmt::Display for CompilationError {
326    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
327        match self {
328            CompilationError::CodeDoesNotExist { account_id } => {
329                write!(f, "cannot find contract code for account {}", account_id)
330            }
331            CompilationError::PrepareError(p) => write!(f, "PrepareError: {}", p),
332            CompilationError::WasmerCompileError { msg } => {
333                write!(f, "Wasmer compilation error: {}", msg)
334            }
335        }
336    }
337}
338
339impl fmt::Display for MethodResolveError {
340    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
341        fmt::Debug::fmt(self, f)
342    }
343}
344
345impl std::fmt::Display for InconsistentStateError {
346    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
347        match self {
348            InconsistentStateError::IntegerOverflow => write!(
349                f,
350                "Math operation with a value from the state resulted in a integer overflow.",
351            ),
352        }
353    }
354}
355
356impl std::fmt::Display for HostError {
357    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
358        use HostError::*;
359        match self {
360            BadUTF8 => write!(f, "String encoding is bad UTF-8 sequence."),
361            BadUTF16 => write!(f, "String encoding is bad UTF-16 sequence."),
362            GasExceeded => write!(f, "Exceeded the prepaid gas."),
363            GasLimitExceeded => {
364                write!(f, "Exceeded the maximum amount of gas allowed to burn per contract.")
365            }
366            BalanceExceeded => write!(f, "Exceeded the account balance."),
367            EmptyMethodName => write!(f, "Tried to call an empty method name."),
368            GuestPanic { panic_msg } => write!(f, "Smart contract panicked: {}", panic_msg),
369            IntegerOverflow => write!(f, "Integer overflow."),
370            InvalidIteratorIndex { iterator_index } => {
371                write!(f, "Iterator index {:?} does not exist", iterator_index)
372            }
373            InvalidPromiseIndex { promise_idx } => {
374                write!(f, "{:?} does not correspond to existing promises", promise_idx)
375            }
376            CannotAppendActionToJointPromise => {
377                write!(f, "Actions can only be appended to non-joint promise.")
378            }
379            CannotReturnJointPromise => {
380                write!(f, "Returning joint promise is currently prohibited.")
381            }
382            InvalidPromiseResultIndex { result_idx } => {
383                write!(f, "Accessed invalid promise result index: {:?}", result_idx)
384            }
385            InvalidRegisterId { register_id } => {
386                write!(f, "Accessed invalid register id: {:?}", register_id)
387            }
388            MemoryAccessViolation => write!(f, "Accessed memory outside the bounds."),
389            InvalidReceiptIndex { receipt_index } => {
390                write!(f, "VM Logic returned an invalid receipt index: {:?}", receipt_index)
391            }
392            InvalidAccountId => write!(f, "VM Logic returned an invalid account id"),
393            InvalidMethodName => write!(f, "VM Logic returned an invalid method name"),
394            InvalidPublicKey => write!(f, "VM Logic provided an invalid public key"),
395            ProhibitedInView { method_name } => {
396                write!(f, "{} is not allowed in view calls", method_name)
397            }
398            NumberOfLogsExceeded { limit } => {
399                write!(f, "The number of logs will exceed the limit {}", limit)
400            }
401            KeyLengthExceeded { length, limit } => {
402                write!(f, "The length of a storage key {} exceeds the limit {}", length, limit)
403            }
404            ValueLengthExceeded { length, limit } => {
405                write!(f, "The length of a storage value {} exceeds the limit {}", length, limit)
406            }
407            TotalLogLengthExceeded { length, limit } => {
408                write!(f, "The length of a log message {} exceeds the limit {}", length, limit)
409            }
410            NumberPromisesExceeded { number_of_promises, limit } => write!(
411                f,
412                "The number of promises within a FunctionCall {} exceeds the limit {}",
413                number_of_promises, limit
414            ),
415            NumberInputDataDependenciesExceeded { number_of_input_data_dependencies, limit } => {
416                write!(
417                    f,
418                    "The number of input data dependencies {} exceeds the limit {}",
419                    number_of_input_data_dependencies, limit
420                )
421            }
422            ReturnedValueLengthExceeded { length, limit } => {
423                write!(f, "The length of a returned value {} exceeds the limit {}", length, limit)
424            }
425            ContractSizeExceeded { size, limit } => write!(
426                f,
427                "The size of a contract code in DeployContract action {} exceeds the limit {}",
428                size, limit
429            ),
430            Deprecated { method_name } => {
431                write!(f, "Attempted to call deprecated host function {}", method_name)
432            }
433            AltBn128InvalidInput { msg } => write!(f, "AltBn128 invalid input: {}", msg),
434            ECRecoverError { msg } => write!(f, "ECDSA recover error: {}", msg),
435            Ed25519VerifyInvalidInput { msg } => {
436                write!(f, "ED25519 signature verification error: {}", msg)
437            }
438        }
439    }
440}
441
442/// Type-erased error used to shuttle some concrete error coming from `External`
443/// through vm-logic.
444///
445/// The caller is supposed to downcast this to a concrete error type they should
446/// know. This would be just `Box<dyn Any + Eq>` if the latter actually worked.
447pub struct AnyError {
448    any: Box<dyn AnyEq>,
449}
450
451#[derive(Debug, thiserror::Error)]
452#[error("failed to downcast")]
453pub struct DowncastFailedError;
454
455impl AnyError {
456    pub fn new<E: Any + Eq + Send + Sync + 'static>(err: E) -> AnyError {
457        AnyError { any: Box::new(err) }
458    }
459    pub fn downcast<E: Any + Eq + Send + Sync + 'static>(self) -> Result<E, DowncastFailedError> {
460        match self.any.into_any().downcast::<E>() {
461            Ok(it) => Ok(*it),
462            Err(_) => Err(DowncastFailedError),
463        }
464    }
465}
466
467impl PartialEq for AnyError {
468    fn eq(&self, other: &Self) -> bool {
469        self.any.any_eq(&*other.any)
470    }
471}
472
473impl Eq for AnyError {}
474
475impl fmt::Debug for AnyError {
476    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
477        fmt::Debug::fmt(self.any.as_any(), f)
478    }
479}
480
481trait AnyEq: Any + Send + Sync {
482    fn any_eq(&self, rhs: &dyn AnyEq) -> bool;
483    fn as_any(&self) -> &dyn Any;
484    fn into_any(self: Box<Self>) -> Box<dyn Any>;
485}
486
487impl<T: Any + Eq + Sized + Send + Sync> AnyEq for T {
488    fn any_eq(&self, rhs: &dyn AnyEq) -> bool {
489        match rhs.as_any().downcast_ref::<Self>() {
490            Some(rhs) => self == rhs,
491            None => false,
492        }
493    }
494    fn as_any(&self) -> &dyn Any {
495        self
496    }
497    fn into_any(self: Box<Self>) -> Box<dyn Any> {
498        self
499    }
500}
501
502#[cfg(test)]
503mod tests {
504    use crate::logic::errors::{
505        CompilationError, FunctionCallError, MethodResolveError, PrepareError,
506    };
507
508    #[test]
509    fn test_display() {
510        // TODO: proper printing
511        assert_eq!(
512            FunctionCallError::MethodResolveError(MethodResolveError::MethodInvalidSignature)
513                .to_string(),
514            "MethodInvalidSignature"
515        );
516        assert_eq!(
517            FunctionCallError::CompilationError(CompilationError::PrepareError(
518                PrepareError::StackHeightInstrumentation
519            ))
520            .to_string(),
521            "PrepareError: Stack instrumentation failed."
522        );
523    }
524}