use super::{impl_from_err, BT};
use std::fmt::{Debug, Display};
use thiserror::Error;
use cosmwasm_crypto::CryptoError;
use super::communication_error::CommunicationError;
use crate::backend::BackendError;
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum VmError {
#[error("Aborted: {}", msg)]
Aborted { msg: String, backtrace: BT },
#[error("Error calling into the VM's backend: {}", source)]
BackendErr { source: BackendError, backtrace: BT },
#[error("Cache error: {msg}")]
CacheErr { msg: String, backtrace: BT },
#[error("Error in guest/host communication: {source}")]
CommunicationErr {
source: CommunicationError,
backtrace: BT,
},
#[error("Error compiling Wasm: {msg}")]
CompileErr { msg: String, backtrace: BT },
#[error("Couldn't convert from {} to {}. Input: {}", from_type, to_type, input)]
ConversionErr {
from_type: String,
to_type: String,
input: String,
backtrace: BT,
},
#[error("Crypto error: {}", source)]
CryptoErr { source: CryptoError, backtrace: BT },
#[error("Ran out of gas during contract execution")]
GasDepletion { backtrace: BT },
#[error("Generic error: {msg}")]
GenericErr { msg: String, backtrace: BT },
#[error("Error instantiating a Wasm module: {msg}")]
InstantiationErr { msg: String, backtrace: BT },
#[error("Hash doesn't match stored data")]
IntegrityErr { backtrace: BT },
#[error("Error parsing into type {target_type}: {msg}")]
ParseErr {
target_type: String,
msg: String,
backtrace: BT,
},
#[error("Data too long for deserialization. Got: {length} bytes; limit: {max_length} bytes")]
DeserializationLimitExceeded {
length: usize,
max_length: usize,
backtrace: BT,
},
#[error("Error serializing type {source_type}: {msg}")]
SerializeErr {
source_type: String,
msg: String,
backtrace: BT,
},
#[error("Error resolving Wasm function: {}", msg)]
ResolveErr { msg: String, backtrace: BT },
#[error(
"Unexpected number of result values when calling '{}'. Expected: {}, actual: {}.",
function_name,
expected,
actual
)]
ResultMismatch {
function_name: String,
expected: usize,
actual: usize,
backtrace: BT,
},
#[error("Error executing Wasm: {}", msg)]
RuntimeErr { msg: String, backtrace: BT },
#[error("Error during static Wasm validation: {}", msg)]
StaticValidationErr { msg: String, backtrace: BT },
#[error("Uninitialized Context Data: {}", kind)]
UninitializedContextData { kind: String, backtrace: BT },
#[error("Must not call a writing storage function in this context.")]
WriteAccessDenied { backtrace: BT },
#[error("Maximum call depth exceeded.")]
MaxCallDepthExceeded { backtrace: BT },
#[error(
"The called function args arity does not match. The contract's method arity: {}",
contract_method_arity
)]
FunctionArityMismatch {
contract_method_arity: usize,
backtrace: BT,
},
}
impl VmError {
pub(crate) fn aborted(msg: impl Into<String>) -> Self {
VmError::Aborted {
msg: msg.into(),
backtrace: BT::capture(),
}
}
pub(crate) fn backend_err(original: BackendError) -> Self {
VmError::BackendErr {
source: original,
backtrace: BT::capture(),
}
}
pub(crate) fn cache_err(msg: impl Into<String>) -> Self {
VmError::CacheErr {
msg: msg.into(),
backtrace: BT::capture(),
}
}
pub(crate) fn compile_err(msg: impl Into<String>) -> Self {
VmError::CompileErr {
msg: msg.into(),
backtrace: BT::capture(),
}
}
pub(crate) fn conversion_err(
from_type: impl Into<String>,
to_type: impl Into<String>,
input: impl Into<String>,
) -> Self {
VmError::ConversionErr {
from_type: from_type.into(),
to_type: to_type.into(),
input: input.into(),
backtrace: BT::capture(),
}
}
pub(crate) fn crypto_err(original: CryptoError) -> Self {
VmError::CryptoErr {
source: original,
backtrace: BT::capture(),
}
}
pub(crate) fn gas_depletion() -> Self {
VmError::GasDepletion {
backtrace: BT::capture(),
}
}
pub(crate) fn generic_err(msg: impl Into<String>) -> Self {
VmError::GenericErr {
msg: msg.into(),
backtrace: BT::capture(),
}
}
pub(crate) fn instantiation_err(msg: impl Into<String>) -> Self {
VmError::InstantiationErr {
msg: msg.into(),
backtrace: BT::capture(),
}
}
pub(crate) fn integrity_err() -> Self {
VmError::IntegrityErr {
backtrace: BT::capture(),
}
}
pub(crate) fn parse_err(target: impl Into<String>, msg: impl Display) -> Self {
VmError::ParseErr {
target_type: target.into(),
msg: msg.to_string(),
backtrace: BT::capture(),
}
}
pub(crate) fn deserialization_limit_exceeded(length: usize, max_length: usize) -> Self {
VmError::DeserializationLimitExceeded {
length,
max_length,
backtrace: BT::capture(),
}
}
pub(crate) fn serialize_err(source: impl Into<String>, msg: impl Display) -> Self {
VmError::SerializeErr {
source_type: source.into(),
msg: msg.to_string(),
backtrace: BT::capture(),
}
}
pub(crate) fn resolve_err(msg: impl Into<String>) -> Self {
VmError::ResolveErr {
msg: msg.into(),
backtrace: BT::capture(),
}
}
pub(crate) fn result_mismatch(
function_name: impl Into<String>,
expected: usize,
actual: usize,
) -> Self {
VmError::ResultMismatch {
function_name: function_name.into(),
expected,
actual,
backtrace: BT::capture(),
}
}
fn runtime_err(msg: impl Into<String>) -> Self {
VmError::RuntimeErr {
msg: msg.into(),
backtrace: BT::capture(),
}
}
pub(crate) fn static_validation_err(msg: impl Into<String>) -> Self {
VmError::StaticValidationErr {
msg: msg.into(),
backtrace: BT::capture(),
}
}
pub(crate) fn uninitialized_context_data(kind: impl Into<String>) -> Self {
VmError::UninitializedContextData {
kind: kind.into(),
backtrace: BT::capture(),
}
}
pub(crate) fn write_access_denied() -> Self {
VmError::WriteAccessDenied {
backtrace: BT::capture(),
}
}
pub(crate) fn max_call_depth_exceeded() -> Self {
VmError::MaxCallDepthExceeded {
backtrace: BT::capture(),
}
}
pub(crate) fn function_arity_mismatch(contract_method_arity: usize) -> Self {
VmError::FunctionArityMismatch {
contract_method_arity,
backtrace: BT::capture(),
}
}
}
impl_from_err!(CommunicationError, VmError, VmError::CommunicationErr);
impl From<BackendError> for VmError {
fn from(original: BackendError) -> Self {
match original {
BackendError::OutOfGas {} => VmError::gas_depletion(),
_ => VmError::backend_err(original),
}
}
}
impl From<CryptoError> for VmError {
fn from(original: CryptoError) -> Self {
VmError::crypto_err(original)
}
}
impl From<wasmer::wasmparser::BinaryReaderError> for VmError {
fn from(original: wasmer::wasmparser::BinaryReaderError) -> Self {
VmError::static_validation_err(format!(
"Wasm bytecode could not be deserialized. Deserialization error: \"{original}\""
))
}
}
impl From<wasmer::ExportError> for VmError {
fn from(original: wasmer::ExportError) -> Self {
VmError::resolve_err(format!("Could not get export: {original}"))
}
}
impl From<wasmer::SerializeError> for VmError {
fn from(original: wasmer::SerializeError) -> Self {
VmError::cache_err(format!("Could not serialize module: {original}"))
}
}
impl From<wasmer::DeserializeError> for VmError {
fn from(original: wasmer::DeserializeError) -> Self {
VmError::cache_err(format!("Could not deserialize module: {original}"))
}
}
impl From<wasmer::RuntimeError> for VmError {
fn from(original: wasmer::RuntimeError) -> Self {
let message = format!("RuntimeError: {}", original.message());
debug_assert!(
original.to_string().starts_with(&message),
"The error message we created is not a prefix of the error message from Wasmer. Our message: '{}'. Wasmer message: '{}'",
&message,
original
);
VmError::runtime_err(format!("Wasmer runtime error: {}", &message))
}
}
impl From<wasmer::CompileError> for VmError {
fn from(original: wasmer::CompileError) -> Self {
VmError::compile_err(format!("Could not compile: {original}"))
}
}
impl From<std::convert::Infallible> for VmError {
fn from(_original: std::convert::Infallible) -> Self {
unreachable!();
}
}
impl From<VmError> for wasmer::RuntimeError {
fn from(original: VmError) -> wasmer::RuntimeError {
let msg: String = original.to_string();
wasmer::RuntimeError::new(msg)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn backend_err_works() {
let error = VmError::backend_err(BackendError::unknown("something went wrong"));
match error {
VmError::BackendErr {
source: BackendError::Unknown { msg },
..
} => assert_eq!(msg, "something went wrong"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn cache_err_works() {
let error = VmError::cache_err("something went wrong");
match error {
VmError::CacheErr { msg, .. } => assert_eq!(msg, "something went wrong"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn compile_err_works() {
let error = VmError::compile_err("something went wrong");
match error {
VmError::CompileErr { msg, .. } => assert_eq!(msg, "something went wrong"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn conversion_err_works() {
let error = VmError::conversion_err("i32", "u32", "-9");
match error {
VmError::ConversionErr {
from_type,
to_type,
input,
..
} => {
assert_eq!(from_type, "i32");
assert_eq!(to_type, "u32");
assert_eq!(input, "-9");
}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn crypto_err_works() {
let error = VmError::crypto_err(CryptoError::generic_err("something went wrong"));
match error {
VmError::CryptoErr {
source: CryptoError::GenericErr { msg, .. },
..
} => assert_eq!(msg, "something went wrong"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn gas_depletion_works() {
let error = VmError::gas_depletion();
match error {
VmError::GasDepletion { .. } => {}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn generic_err_works() {
let guess = 7;
let error = VmError::generic_err(format!("{guess} is too low"));
match error {
VmError::GenericErr { msg, .. } => {
assert_eq!(msg, String::from("7 is too low"));
}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn instantiation_err_works() {
let error = VmError::instantiation_err("something went wrong");
match error {
VmError::InstantiationErr { msg, .. } => assert_eq!(msg, "something went wrong"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn integrity_err_works() {
let error = VmError::integrity_err();
match error {
VmError::IntegrityErr { .. } => {}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn parse_err_works() {
let error = VmError::parse_err("Book", "Missing field: title");
match error {
VmError::ParseErr {
target_type, msg, ..
} => {
assert_eq!(target_type, "Book");
assert_eq!(msg, "Missing field: title");
}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn serialize_err_works() {
let error = VmError::serialize_err("Book", "Content too long");
match error {
VmError::SerializeErr {
source_type, msg, ..
} => {
assert_eq!(source_type, "Book");
assert_eq!(msg, "Content too long");
}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn resolve_err_works() {
let error = VmError::resolve_err("function has different signature");
match error {
VmError::ResolveErr { msg, .. } => assert_eq!(msg, "function has different signature"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn result_mismatch_works() {
let error = VmError::result_mismatch("action", 0, 1);
match error {
VmError::ResultMismatch {
function_name,
expected,
actual,
..
} => {
assert_eq!(function_name, "action");
assert_eq!(expected, 0);
assert_eq!(actual, 1);
}
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn runtime_err_works() {
let error = VmError::runtime_err("something went wrong");
match error {
VmError::RuntimeErr { msg, .. } => assert_eq!(msg, "something went wrong"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn static_validation_err_works() {
let error = VmError::static_validation_err("export xy missing");
match error {
VmError::StaticValidationErr { msg, .. } => assert_eq!(msg, "export xy missing"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn uninitialized_context_data_works() {
let error = VmError::uninitialized_context_data("foo");
match error {
VmError::UninitializedContextData { kind, .. } => assert_eq!(kind, "foo"),
e => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn write_access_denied() {
let error = VmError::write_access_denied();
match error {
VmError::WriteAccessDenied { .. } => {}
e => panic!("Unexpected error: {e:?}"),
}
}
}