use super::library::LibraryExn;
use crate::internal::MUBuf;
use crate::xdr::WriteXDR;
use xdrs::cxs::unchecked_buf_writer::UncheckedBufWriter;
pub(crate) const ABORT_MAX: usize = {
let payload = match <LibraryExn as xdrs::cxs::ConstMaxSize>::MAX_SIZE {
Some(n) => n,
None => panic!("LibraryExn wire encoding must be bounded"),
};
xdrs::cxs::const_writer::size_u64(u64::MAX) + payload
};
const _: () = assert!(
ABORT_MAX < 64,
"ABORT_MAX grew: a LibraryExn variant is carrying a payload it should be \
keeping host-side"
);
pub(crate) fn encode_library_abort(
exn: &LibraryExn,
buf: &mut MUBuf<ABORT_MAX>,
) {
let ce = crate::exn::cross_exn::ContractExn::new(exn);
buf.clear();
let mut w = UncheckedBufWriter::new(buf.spare_capacity_mut());
unsafe {
ce.write_xdr_unchecked(&mut w);
}
let n = w.written();
unsafe { buf.set_len(n) };
}
#[inline(never)]
#[cfg_attr(not(target_arch = "riscv32"), track_caller)]
pub(crate) fn library_abort(exn: &LibraryExn) -> ! {
#[cfg(all(not(target_arch = "riscv32"), feature = "std"))]
crate::contract_op::abort_breadcrumb("Throwing", "Location", exn);
let mut buf = MUBuf::<ABORT_MAX>::new();
encode_library_abort(exn, &mut buf);
crate::contract_op::contract_abort(buf.as_ref())
}
#[inline(never)]
#[cfg_attr(not(target_arch = "riscv32"), track_caller)]
pub(crate) fn client_library_abort(exn: &LibraryExn) -> ! {
#[cfg(all(not(target_arch = "riscv32"), feature = "std"))]
crate::contract_op::abort_breadcrumb(
"Throwing",
"Interface location",
exn,
);
let mut buf = MUBuf::<ABORT_MAX>::new();
encode_library_abort(exn, &mut buf);
crate::contract_op::contract_abort(buf.as_ref())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::exn::cross_exn::ContractExn;
use crate::exn::{
AuthTokenError, CallingError, FaultString, HandlingError, SyscallExn,
};
use crate::internal::encode_xdr;
use crate::sys::{SysErr, SyscallNo};
fn assert_wire_compat(exn: LibraryExn) {
let mut buf = MUBuf::<ABORT_MAX>::new();
encode_library_abort(&exn, &mut buf);
let oracle = encode_xdr(&ContractExn::new(exn)).unwrap();
assert_eq!(buf.as_ref(), &oracle[..]);
}
fn fault_strings() -> [FaultString; 3] {
let long = core::str::from_utf8(&[b'x'; 500]).unwrap();
[
FaultString::empty(),
FaultString::from_display("boom"),
FaultString::from_display(long),
]
}
const U64_EDGES: &[u64] = &[
0,
1,
30,
31,
61,
62,
63,
64,
127,
128,
255,
256,
65535,
65536,
u32::MAX as u64 - 1,
u32::MAX as u64,
u32::MAX as u64 + 1,
u64::MAX,
];
const U32_EDGES: &[u32] = &[
0,
1,
30,
31,
62,
63,
127,
128,
255,
256,
65535,
65536,
u32::MAX,
];
const I32_EDGES: &[i32] = &[
i32::MIN,
-65537,
-65536,
-32769,
-32768,
-129,
-128,
-33,
-32,
-31,
-1,
0,
1,
30,
31,
62,
63,
127,
128,
32767,
32768,
i32::MAX,
];
#[test]
fn no_payload_variants_wire_compat() {
assert_wire_compat(LibraryExn::NonNegativeIntUnderflow);
assert_wire_compat(LibraryExn::NonNegativeIntOverflow);
assert_wire_compat(LibraryExn::StorageSerialization);
assert_wire_compat(LibraryExn::StorageKeypathOverflow);
assert_wire_compat(LibraryExn::CapacityConstraintAlreadyWritten);
assert_wire_compat(LibraryExn::UnknownMethod);
}
#[test]
fn syscall_variant_wire_compat_across_int_widths() {
for &callno in I32_EDGES {
for &errno in I32_EDGES {
assert_wire_compat(LibraryExn::Syscall(SyscallExn::new(
SyscallNo::from(callno),
SysErr(errno),
)));
}
}
}
#[test]
fn faultstring_variants_wire_compat() {
for fs in fault_strings() {
assert_wire_compat(LibraryExn::Handling(
HandlingError::DecodingArgs(fs.clone()),
));
assert_wire_compat(LibraryExn::Handling(
HandlingError::EncodingSuccess(fs.clone()),
));
assert_wire_compat(LibraryExn::Handling(
HandlingError::EncodingException(fs.clone()),
));
assert_wire_compat(LibraryExn::Handling(
HandlingError::EncodingEvent(fs.clone()),
));
assert_wire_compat(LibraryExn::Calling(
CallingError::DecodingSuccess(fs.clone()),
));
assert_wire_compat(LibraryExn::Calling(
CallingError::DecodingException(fs.clone()),
));
assert_wire_compat(LibraryExn::Calling(
CallingError::EncodingArgs(fs.clone()),
));
assert_wire_compat(LibraryExn::Called(fs.clone()));
assert_wire_compat(LibraryExn::AuthToken(
AuthTokenError::MintEncodeFailure(fs.clone()),
));
assert_wire_compat(LibraryExn::AuthToken(
AuthTokenError::ConsumeDecodeFailure(fs.clone()),
));
assert_wire_compat(LibraryExn::AuthToken(
AuthTokenError::ReceiveAuthHandlesFailure(fs),
));
}
assert_wire_compat(LibraryExn::Calling(CallingError::Invalid));
assert_wire_compat(LibraryExn::Calling(CallingError::Reentrance));
assert_wire_compat(LibraryExn::Calling(CallingError::DepthExceeded));
}
#[test]
fn sized_variants_wire_compat_across_int_widths() {
for &expected in U32_EDGES {
for &found in U32_EDGES {
assert_wire_compat(LibraryExn::BytestringWrongSize {
expected,
found,
});
assert_wire_compat(
LibraryExn::RetrocausalQueueEntryWrongSize {
expected,
found,
},
);
}
}
for &len in U64_EDGES {
for &access in U64_EDGES {
assert_wire_compat(LibraryExn::StorageIndexOutOfBounds {
len,
access,
});
}
}
}
#[test]
fn max_width_variants_fit() {
let long = core::str::from_utf8(&[b'x'; 500]).unwrap();
let cases = [
LibraryExn::Syscall(SyscallExn::new(
SyscallNo::from(i32::MIN),
SysErr(i32::MIN),
)),
LibraryExn::Handling(HandlingError::EncodingException(
FaultString::from_display(long),
)),
LibraryExn::Called(FaultString::from_display(long)),
LibraryExn::StorageIndexOutOfBounds {
len: u64::MAX,
access: u64::MAX,
},
LibraryExn::BytestringWrongSize {
expected: u32::MAX,
found: u32::MAX,
},
];
for exn in cases {
let mut buf = MUBuf::<ABORT_MAX>::new();
encode_library_abort(&exn, &mut buf);
assert!(buf.as_ref().len() <= ABORT_MAX);
}
}
}