use borsh::{BorshDeserialize, BorshSerialize};
use std::any::Any;
use std::fmt::{self, Error, Formatter};
use std::io;
#[derive(Debug, thiserror::Error)]
pub enum VMRunnerError {
#[error("{0}")]
InconsistentStateError(InconsistentStateError),
#[error("cache error: {0}")]
CacheError(#[from] CacheError),
#[error("loading error: {0}")]
LoadingError(String),
#[error("external error")]
ExternalError(AnyError),
#[error("non-deterministic error during contract execution: {0}")]
Nondeterministic(String),
#[error("unknown error during contract execution: {debug_message}")]
WasmUnknownError { debug_message: String },
}
#[derive(Debug, PartialEq, Eq, strum::IntoStaticStr)]
pub enum FunctionCallError {
CompilationError(CompilationError),
LinkError {
msg: String,
},
MethodResolveError(MethodResolveError),
WasmTrap(WasmTrap),
HostError(HostError),
}
#[derive(Debug, thiserror::Error, strum::IntoStaticStr)]
pub enum CacheError {
#[error("cache read error")]
ReadError(#[source] io::Error),
#[error("cache write error")]
WriteError(#[source] io::Error),
#[error("cache deserialization error")]
DeserializationError,
#[error("cache serialization error")]
SerializationError { hash: [u8; 32] },
}
#[derive(Debug, Clone, PartialEq, Eq, strum::IntoStaticStr)]
pub enum WasmTrap {
Unreachable,
IncorrectCallIndirectSignature,
MemoryOutOfBounds,
CallIndirectOOB,
IllegalArithmetic,
MisalignedAtomicAccess,
IndirectCallToNull,
StackOverflow,
GenericTrap,
}
#[derive(Debug, Clone, PartialEq, Eq, strum::IntoStaticStr)]
pub enum MethodResolveError {
MethodEmptyName,
MethodNotFound,
MethodInvalidSignature,
}
#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize, strum::IntoStaticStr)]
pub enum CompilationError {
CodeDoesNotExist {
account_id: Box<str>,
},
PrepareError(PrepareError),
WasmerCompileError {
msg: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq, BorshDeserialize, BorshSerialize)]
pub enum PrepareError {
Serialization,
Deserialization,
InternalMemoryDeclared,
GasInstrumentation,
StackHeightInstrumentation,
Instantiate,
Memory,
TooManyFunctions,
TooManyLocals,
}
#[derive(Debug, Clone, PartialEq, Eq, strum::IntoStaticStr)]
pub enum HostError {
BadUTF16,
BadUTF8,
GasExceeded,
GasLimitExceeded,
BalanceExceeded,
EmptyMethodName,
GuestPanic { panic_msg: String },
IntegerOverflow,
InvalidPromiseIndex { promise_idx: u64 },
CannotAppendActionToJointPromise,
CannotReturnJointPromise,
InvalidPromiseResultIndex { result_idx: u64 },
InvalidRegisterId { register_id: u64 },
MemoryAccessViolation,
InvalidReceiptIndex { receipt_index: u64 },
InvalidIteratorIndex { iterator_index: u64 },
InvalidAccountId,
InvalidMethodName,
InvalidPublicKey,
ProhibitedInView { method_name: String },
NumberOfLogsExceeded { limit: u64 },
KeyLengthExceeded { length: u64, limit: u64 },
ValueLengthExceeded { length: u64, limit: u64 },
TotalLogLengthExceeded { length: u64, limit: u64 },
NumberPromisesExceeded { number_of_promises: u64, limit: u64 },
NumberInputDataDependenciesExceeded { number_of_input_data_dependencies: u64, limit: u64 },
ReturnedValueLengthExceeded { length: u64, limit: u64 },
ContractSizeExceeded { size: u64, limit: u64 },
Deprecated { method_name: String },
ECRecoverError { msg: String },
AltBn128InvalidInput { msg: String },
Ed25519VerifyInvalidInput { msg: String },
}
#[derive(Debug, PartialEq, Eq)]
pub enum VMLogicError {
HostError(HostError),
ExternalError(AnyError),
InconsistentStateError(InconsistentStateError),
}
impl std::error::Error for VMLogicError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InconsistentStateError {
IntegerOverflow,
}
impl From<HostError> for VMLogicError {
fn from(err: HostError) -> Self {
VMLogicError::HostError(err)
}
}
impl From<InconsistentStateError> for VMLogicError {
fn from(err: InconsistentStateError) -> Self {
VMLogicError::InconsistentStateError(err)
}
}
impl From<PrepareError> for FunctionCallError {
fn from(err: PrepareError) -> Self {
FunctionCallError::CompilationError(CompilationError::PrepareError(err))
}
}
impl TryFrom<VMLogicError> for FunctionCallError {
type Error = VMRunnerError;
fn try_from(err: VMLogicError) -> Result<Self, Self::Error> {
match err {
VMLogicError::HostError(h) => Ok(FunctionCallError::HostError(h)),
VMLogicError::ExternalError(s) => Err(VMRunnerError::ExternalError(s)),
VMLogicError::InconsistentStateError(e) => {
Err(VMRunnerError::InconsistentStateError(e))
}
}
}
}
impl fmt::Display for VMLogicError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
write!(f, "{:?}", self)
}
}
impl fmt::Display for PrepareError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
use PrepareError::*;
f.write_str(match self {
Serialization => "Error happened while serializing the module.",
Deserialization => "Error happened while deserializing the module.",
InternalMemoryDeclared => "Internal memory declaration has been found in the module.",
GasInstrumentation => "Gas instrumentation failed.",
StackHeightInstrumentation => "Stack instrumentation failed.",
Instantiate => "Error happened during instantiation.",
Memory => "Error creating memory.",
TooManyFunctions => "Too many functions in contract.",
TooManyLocals => "Too many locals declared in the contract.",
})
}
}
impl fmt::Display for FunctionCallError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
FunctionCallError::CompilationError(e) => e.fmt(f),
FunctionCallError::MethodResolveError(e) => e.fmt(f),
FunctionCallError::HostError(e) => e.fmt(f),
FunctionCallError::LinkError { msg } => write!(f, "{}", msg),
FunctionCallError::WasmTrap(trap) => write!(f, "WebAssembly trap: {}", trap),
}
}
}
impl fmt::Display for WasmTrap {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
WasmTrap::Unreachable => write!(f, "An `unreachable` opcode was executed."),
WasmTrap::IncorrectCallIndirectSignature => {
write!(f, "Call indirect incorrect signature trap.")
}
WasmTrap::MemoryOutOfBounds => write!(f, "Memory out of bounds trap."),
WasmTrap::CallIndirectOOB => write!(f, "Call indirect out of bounds trap."),
WasmTrap::IllegalArithmetic => {
write!(f, "An arithmetic exception, e.g. divided by zero.")
}
WasmTrap::MisalignedAtomicAccess => write!(f, "Misaligned atomic access trap."),
WasmTrap::GenericTrap => write!(f, "Generic trap."),
WasmTrap::StackOverflow => write!(f, "Stack overflow."),
WasmTrap::IndirectCallToNull => write!(f, "Indirect call to null."),
}
}
}
impl fmt::Display for CompilationError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
CompilationError::CodeDoesNotExist { account_id } => {
write!(f, "cannot find contract code for account {}", account_id)
}
CompilationError::PrepareError(p) => write!(f, "PrepareError: {}", p),
CompilationError::WasmerCompileError { msg } => {
write!(f, "Wasmer compilation error: {}", msg)
}
}
}
}
impl fmt::Display for MethodResolveError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
fmt::Debug::fmt(self, f)
}
}
impl std::fmt::Display for InconsistentStateError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
InconsistentStateError::IntegerOverflow => write!(
f,
"Math operation with a value from the state resulted in a integer overflow.",
),
}
}
}
impl std::fmt::Display for HostError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
use HostError::*;
match self {
BadUTF8 => write!(f, "String encoding is bad UTF-8 sequence."),
BadUTF16 => write!(f, "String encoding is bad UTF-16 sequence."),
GasExceeded => write!(f, "Exceeded the prepaid gas."),
GasLimitExceeded => {
write!(f, "Exceeded the maximum amount of gas allowed to burn per contract.")
}
BalanceExceeded => write!(f, "Exceeded the account balance."),
EmptyMethodName => write!(f, "Tried to call an empty method name."),
GuestPanic { panic_msg } => write!(f, "Smart contract panicked: {}", panic_msg),
IntegerOverflow => write!(f, "Integer overflow."),
InvalidIteratorIndex { iterator_index } => {
write!(f, "Iterator index {:?} does not exist", iterator_index)
}
InvalidPromiseIndex { promise_idx } => {
write!(f, "{:?} does not correspond to existing promises", promise_idx)
}
CannotAppendActionToJointPromise => {
write!(f, "Actions can only be appended to non-joint promise.")
}
CannotReturnJointPromise => {
write!(f, "Returning joint promise is currently prohibited.")
}
InvalidPromiseResultIndex { result_idx } => {
write!(f, "Accessed invalid promise result index: {:?}", result_idx)
}
InvalidRegisterId { register_id } => {
write!(f, "Accessed invalid register id: {:?}", register_id)
}
MemoryAccessViolation => write!(f, "Accessed memory outside the bounds."),
InvalidReceiptIndex { receipt_index } => {
write!(f, "VM Logic returned an invalid receipt index: {:?}", receipt_index)
}
InvalidAccountId => write!(f, "VM Logic returned an invalid account id"),
InvalidMethodName => write!(f, "VM Logic returned an invalid method name"),
InvalidPublicKey => write!(f, "VM Logic provided an invalid public key"),
ProhibitedInView { method_name } => {
write!(f, "{} is not allowed in view calls", method_name)
}
NumberOfLogsExceeded { limit } => {
write!(f, "The number of logs will exceed the limit {}", limit)
}
KeyLengthExceeded { length, limit } => {
write!(f, "The length of a storage key {} exceeds the limit {}", length, limit)
}
ValueLengthExceeded { length, limit } => {
write!(f, "The length of a storage value {} exceeds the limit {}", length, limit)
}
TotalLogLengthExceeded { length, limit } => {
write!(f, "The length of a log message {} exceeds the limit {}", length, limit)
}
NumberPromisesExceeded { number_of_promises, limit } => write!(
f,
"The number of promises within a FunctionCall {} exceeds the limit {}",
number_of_promises, limit
),
NumberInputDataDependenciesExceeded { number_of_input_data_dependencies, limit } => {
write!(
f,
"The number of input data dependencies {} exceeds the limit {}",
number_of_input_data_dependencies, limit
)
}
ReturnedValueLengthExceeded { length, limit } => {
write!(f, "The length of a returned value {} exceeds the limit {}", length, limit)
}
ContractSizeExceeded { size, limit } => write!(
f,
"The size of a contract code in DeployContract action {} exceeds the limit {}",
size, limit
),
Deprecated { method_name } => {
write!(f, "Attempted to call deprecated host function {}", method_name)
}
AltBn128InvalidInput { msg } => write!(f, "AltBn128 invalid input: {}", msg),
ECRecoverError { msg } => write!(f, "ECDSA recover error: {}", msg),
Ed25519VerifyInvalidInput { msg } => {
write!(f, "ED25519 signature verification error: {}", msg)
}
}
}
}
pub struct AnyError {
any: Box<dyn AnyEq>,
}
#[derive(Debug, thiserror::Error)]
#[error("failed to downcast")]
pub struct DowncastFailedError;
impl AnyError {
pub fn new<E: Any + Eq + Send + Sync + 'static>(err: E) -> AnyError {
AnyError { any: Box::new(err) }
}
pub fn downcast<E: Any + Eq + Send + Sync + 'static>(self) -> Result<E, DowncastFailedError> {
match self.any.into_any().downcast::<E>() {
Ok(it) => Ok(*it),
Err(_) => Err(DowncastFailedError),
}
}
}
impl PartialEq for AnyError {
fn eq(&self, other: &Self) -> bool {
self.any.any_eq(&*other.any)
}
}
impl Eq for AnyError {}
impl fmt::Debug for AnyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(self.any.as_any(), f)
}
}
trait AnyEq: Any + Send + Sync {
fn any_eq(&self, rhs: &dyn AnyEq) -> bool;
fn as_any(&self) -> &dyn Any;
fn into_any(self: Box<Self>) -> Box<dyn Any>;
}
impl<T: Any + Eq + Sized + Send + Sync> AnyEq for T {
fn any_eq(&self, rhs: &dyn AnyEq) -> bool {
match rhs.as_any().downcast_ref::<Self>() {
Some(rhs) => self == rhs,
None => false,
}
}
fn as_any(&self) -> &dyn Any {
self
}
fn into_any(self: Box<Self>) -> Box<dyn Any> {
self
}
}
#[cfg(test)]
mod tests {
use crate::logic::errors::{
CompilationError, FunctionCallError, MethodResolveError, PrepareError,
};
#[test]
fn test_display() {
assert_eq!(
FunctionCallError::MethodResolveError(MethodResolveError::MethodInvalidSignature)
.to_string(),
"MethodInvalidSignature"
);
assert_eq!(
FunctionCallError::CompilationError(CompilationError::PrepareError(
PrepareError::StackHeightInstrumentation
))
.to_string(),
"PrepareError: Stack instrumentation failed."
);
}
}