use crate::exn::{
CallingError, FaultInfo, FaultString, RawException,
client_library_throw as library_throw, syscall_exn,
};
use crate::sys::{SysErr, SyscallNo};
#[derive(Debug, PartialEq)]
pub enum RawCallError<X = RawException> {
Invalid,
Reentrance,
DepthExceeded,
InvokeRejected(SysErr),
Exception(X),
Fault(FaultInfo),
UnparseableReturn(FaultString),
UnparseableAbort(FaultString),
UnparseableAuthHandles(FaultString),
EncodingArgs(FaultString),
}
impl<X> RawCallError<X> {
pub fn is_rejected_before_call(&self) -> bool {
matches!(
self,
RawCallError::Invalid
| RawCallError::Reentrance
| RawCallError::DepthExceeded
| RawCallError::InvokeRejected(_)
)
}
}
pub(crate) fn classify_invoke_rejection<X>(syserr: SysErr) -> RawCallError<X> {
use crate::sys::SyscallErrorCode as Code;
if syserr.is_invalid_input() {
RawCallError::Invalid
} else if syserr.is_code(Code::kSyscallErrorCodeReentrance) {
RawCallError::Reentrance
} else if syserr.is_code(Code::kSyscallErrorCodeInvokeDepthExceeded) {
RawCallError::DepthExceeded
} else {
RawCallError::InvokeRejected(syserr)
}
}
pub type CallError = RawCallError;
pub type CallFault = RawCallError<core::convert::Infallible>;
#[cfg(not(target_arch = "riscv32"))]
impl<X: core::fmt::Debug> core::fmt::Display for RawCallError<X> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
RawCallError::Invalid => {
write!(f, "contract not found (or invalid auth tokens)")
}
RawCallError::Reentrance => {
write!(f, "call refused: target already has an open frame")
}
RawCallError::DepthExceeded => {
write!(f, "call refused: cross-invoke depth bound reached")
}
RawCallError::InvokeRejected(code) => {
write!(f, "call refused: {code:?}")
}
RawCallError::Exception(exn) => write!(f, "exception: {exn:?}"),
RawCallError::Fault(fault) => write!(f, "fault: {fault}"),
RawCallError::UnparseableReturn(error) => {
write!(f, "unparseable return: {error}")
}
RawCallError::UnparseableAbort(error) => {
write!(f, "unparseable abort: {error}")
}
RawCallError::UnparseableAuthHandles(error) => {
write!(f, "unparseable auth handles: {error}")
}
RawCallError::EncodingArgs(error) => {
write!(f, "encoding args: {error}")
}
}
}
}
impl<X: crate::re_throw::ReThrow> crate::re_throw::ReThrow
for RawCallError<X>
{
#[cfg_attr(not(target_arch = "riscv32"), track_caller)]
fn re_throw(self) -> ! {
match self {
RawCallError::Invalid => library_throw(CallingError::Invalid),
RawCallError::Reentrance => {
library_throw(CallingError::Reentrance)
}
RawCallError::DepthExceeded => {
library_throw(CallingError::DepthExceeded)
}
RawCallError::InvokeRejected(code) => {
library_throw(syscall_exn(SyscallNo::kInvoke, code))
}
RawCallError::Exception(exn) => {
crate::re_throw::ReThrow::re_throw(exn)
}
#[cfg(not(target_arch = "riscv32"))]
RawCallError::Fault(f) => panic!("{f}"),
#[cfg(target_arch = "riscv32")]
RawCallError::Fault(_) => panic!(),
RawCallError::UnparseableReturn(error) => {
library_throw(CallingError::DecodingSuccess(error))
}
RawCallError::UnparseableAbort(error) => {
library_throw(CallingError::DecodingException(error))
}
RawCallError::UnparseableAuthHandles(error) => {
library_throw(CallingError::DecodingSuccess(error))
}
RawCallError::EncodingArgs(error) => {
library_throw(CallingError::EncodingArgs(error))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::re_throw::OrReThrow;
#[test]
fn or_re_throw_ok_returns_value() {
let result: Result<i32, RawCallError> = Ok(42);
assert_eq!(result.or_re_throw(), 42);
}
#[test]
fn documented_rejections_map_to_their_own_variants() {
use crate::sys::SyscallErrorCode as Code;
let cases = [
(
Code::kSyscallErrorCodeInvalidSyscallInput,
RawCallError::Invalid,
),
(Code::kSyscallErrorCodeReentrance, RawCallError::Reentrance),
(
Code::kSyscallErrorCodeInvokeDepthExceeded,
RawCallError::DepthExceeded,
),
];
for (code, expected) in cases {
let got: RawCallError = classify_invoke_rejection(code.into());
assert_eq!(got, expected, "rejection code {code:?}");
}
}
#[test]
fn unmodelled_rejection_keeps_its_code() {
let unknown = SysErr(0x7fff_0000);
let got: RawCallError = classify_invoke_rejection(unknown);
assert_eq!(got, RawCallError::InvokeRejected(unknown));
}
#[test]
fn rejections_are_distinguished_from_a_ran_and_faulted_callee() {
let rejected: [RawCallError; 4] = [
RawCallError::Invalid,
RawCallError::Reentrance,
RawCallError::DepthExceeded,
RawCallError::InvokeRejected(SysErr(1234)),
];
for e in &rejected {
assert!(e.is_rejected_before_call(), "{e:?}");
assert_ne!(*e, RawCallError::Fault(FaultInfo::empty()));
}
let ran: RawCallError = RawCallError::Fault(FaultInfo::empty());
assert!(!ran.is_rejected_before_call());
}
}