use std::fmt::Display;
use derive_more::Display;
use fvm_shared::error::ErrorNumber;
pub type Result<T> = std::result::Result<T, ExecutionError>;
#[macro_export]
macro_rules! syscall_error {
( $code:ident; $msg:literal $(, $ex:expr)* ) => {
$crate::kernel::SyscallError::new(fvm_shared::error::ErrorNumber::$code, format_args!($msg, $($ex,)*))
};
( $code:ident; $msg:expr ) => { $crate::kernel::SyscallError::new(fvm_shared::error::ErrorNumber::$code, $msg) };
( $code:ident, $msg:expr ) => { $crate::syscall_error!($code; $msg) };
( $code:ident, $msg:literal $(, $ex:expr)+ ) => {
$crate::syscall_error!($code; $msg $(, $ex)*)
};
}
#[derive(Display, Debug)]
pub enum ExecutionError {
OutOfGas,
Syscall(SyscallError),
Fatal(anyhow::Error),
}
impl ExecutionError {
pub fn is_fatal(&self) -> bool {
use ExecutionError::*;
match self {
Fatal(_) => true,
OutOfGas | Syscall(_) => false,
}
}
}
impl From<SyscallError> for ExecutionError {
fn from(e: SyscallError) -> Self {
ExecutionError::Syscall(e)
}
}
pub trait ClassifyResult: Sized {
type Value;
type Error;
fn or_fatal(self) -> Result<Self::Value>
where
Self::Error: Into<anyhow::Error>;
fn or_error(self, code: ErrorNumber) -> Result<Self::Value>
where
Self::Error: Display;
fn or_illegal_argument(self) -> Result<Self::Value>
where
Self::Error: Display,
{
self.or_error(ErrorNumber::IllegalArgument)
}
}
impl<T, E> ClassifyResult for std::result::Result<T, E> {
type Value = T;
type Error = E;
fn or_fatal(self) -> Result<Self::Value>
where
Self::Error: Into<anyhow::Error>,
{
self.map_err(|e| ExecutionError::Fatal(e.into()))
}
fn or_error(self, code: ErrorNumber) -> Result<Self::Value>
where
Self::Error: Display,
{
self.map_err(|e| ExecutionError::Syscall(SyscallError(e.to_string(), code)))
}
}
pub trait Context {
type WithContext;
fn context<D>(self, context: D) -> Self::WithContext
where
D: Display;
fn with_context<D, F>(self, cfn: F) -> Self::WithContext
where
D: Display,
F: FnOnce() -> D;
}
impl<T> Context for Result<T> {
type WithContext = Result<T>;
fn context<D: Display>(self, context: D) -> Self::WithContext {
self.map_err(|e| e.context(context))
}
fn with_context<D, F>(self, cfn: F) -> Self::WithContext
where
D: Display,
F: FnOnce() -> D,
{
self.map_err(|e| e.with_context(cfn))
}
}
impl Context for ExecutionError {
type WithContext = Self;
fn context<D: Display>(self, context: D) -> Self {
use ExecutionError::*;
match self {
Syscall(e) => Syscall(SyscallError(format!("{}: {}", context, e.0), e.1)),
Fatal(e) => Fatal(e.context(context.to_string())),
OutOfGas => OutOfGas, }
}
fn with_context<D, F>(self, cfn: F) -> Self::WithContext
where
D: Display,
F: FnOnce() -> D,
{
self.context(cfn())
}
}
impl From<ExecutionError> for anyhow::Error {
fn from(e: ExecutionError) -> Self {
use ExecutionError::*;
match e {
OutOfGas => anyhow::anyhow!("out of gas"),
Syscall(err) => anyhow::anyhow!(err.0),
Fatal(err) => err,
}
}
}
#[derive(thiserror::Error, Debug, Clone)]
#[error("syscall error: {0} (exit_code={1:?})")]
pub struct SyscallError(pub String, pub ErrorNumber);
impl SyscallError {
pub fn new<D: Display>(c: ErrorNumber, d: D) -> Self {
SyscallError(d.to_string(), c)
}
}
#[test]
fn test_syscall_error_formatting() {
let test_value = 1;
assert_eq!(
syscall_error!(IllegalArgument; "msg: {test_value}").0,
"msg: 1"
);
assert_eq!(
syscall_error!(IllegalArgument; "msg: {}", test_value).0,
"msg: 1"
);
assert_eq!(syscall_error!(IllegalArgument; "msg").0, "msg");
assert_eq!(
syscall_error!(IllegalArgument; String::from("msg")).0,
"msg"
);
}