use alloc::{borrow::Cow, boxed::Box, collections::TryReserveError, string::String};
use alloy_primitives::{B256, LogData, hex};
use core::{cmp, fmt};
const MAX_TYPE_CHECK_FAIL_DATA_LEN: usize = 128;
pub type Result<T, E = Error> = core::result::Result<T, E>;
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
TypeCheckFail {
expected_type: Cow<'static, str>,
data: String,
},
Overrun,
Reserve(TryReserveError),
BufferNotEmpty,
ReserMismatch,
RecursionLimitExceeded(usize),
MemoryLimitExceeded(usize),
InvalidEnumValue {
name: &'static str,
value: u8,
max: u8,
},
InvalidEventSignatureHash {
name: &'static str,
got: B256,
expected: B256,
},
InvalidLog {
name: &'static str,
log: Box<LogData>,
},
UnknownSelector {
name: &'static str,
selector: alloy_primitives::FixedBytes<4>,
},
FromHexError(hex::FromHexError),
Other(Cow<'static, str>),
}
impl core::error::Error for Error {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
match self {
Self::Reserve(e) => Some(e),
Self::FromHexError(e) => Some(e),
_ => None,
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::TypeCheckFail { expected_type, data } => {
write!(f, "type check failed for {expected_type:?}")?;
if !data.is_empty() {
write!(f, " with data: {data}")?;
}
Ok(())
}
Self::Overrun
| Self::BufferNotEmpty
| Self::ReserMismatch
| Self::RecursionLimitExceeded(_)
| Self::MemoryLimitExceeded(_) => {
f.write_str("ABI decoding failed: ")?;
match *self {
Self::Overrun => f.write_str("buffer overrun while deserializing"),
Self::BufferNotEmpty => f.write_str("buffer not empty after deserialization"),
Self::ReserMismatch => f.write_str("reserialization did not match original"),
Self::RecursionLimitExceeded(limit) => {
write!(f, "recursion limit of {limit} exceeded during decoding")
}
Self::MemoryLimitExceeded(limit) => {
write!(f, "memory limit of {limit} bytes exceeded during decoding")
}
_ => unreachable!(),
}
}
Self::Reserve(e) => e.fmt(f),
Self::InvalidEnumValue { name, value, max } => {
write!(f, "`{value}` is not a valid {name} enum value (max: `{max}`)")
}
Self::InvalidEventSignatureHash { name, got, expected } => {
write!(
f,
"invalid signature hash for event {name:?}: got {got}, expected {expected}"
)
}
Self::InvalidLog { name, log } => {
write!(f, "could not decode {name} from log: {log:?}")
}
Self::UnknownSelector { name, selector } => {
write!(f, "unknown selector `{selector}` for {name}")
}
Self::FromHexError(e) => e.fmt(f),
Self::Other(e) => f.write_str(e),
}
}
}
impl Error {
#[cold]
pub fn custom(s: impl Into<Cow<'static, str>>) -> Self {
Self::Other(s.into())
}
#[cold]
pub fn type_check_fail_sig(mut data: &[u8], signature: &'static str) -> Self {
if data.len() > 4 {
data = &data[..4];
}
let expected_type = signature.split('(').next().unwrap();
Self::type_check_fail(data, expected_type)
}
#[cold]
pub fn type_check_fail_token<T: crate::SolType>(_token: &T::Token<'_>) -> Self {
Self::type_check_fail(&[], T::SOL_NAME)
}
#[cold]
pub fn type_check_fail(data: &[u8], expected_type: impl Into<Cow<'static, str>>) -> Self {
let data = &data[..cmp::min(data.len(), MAX_TYPE_CHECK_FAIL_DATA_LEN)];
Self::TypeCheckFail { expected_type: expected_type.into(), data: hex::encode(data) }
}
#[cold]
pub fn unknown_selector(name: &'static str, selector: [u8; 4]) -> Self {
Self::UnknownSelector { name, selector: selector.into() }
}
#[doc(hidden)] #[cold]
pub const fn invalid_event_signature_hash(
name: &'static str,
got: B256,
expected: B256,
) -> Self {
Self::InvalidEventSignatureHash { name, got, expected }
}
}
impl From<hex::FromHexError> for Error {
#[inline]
fn from(value: hex::FromHexError) -> Self {
Self::FromHexError(value)
}
}
impl From<TryReserveError> for Error {
#[inline]
fn from(value: TryReserveError) -> Self {
Self::Reserve(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn type_check_fail_data_is_bounded() {
let error = Error::type_check_fail(&[0; MAX_TYPE_CHECK_FAIL_DATA_LEN + 1], "test");
let Error::TypeCheckFail { data, .. } = error else { unreachable!() };
assert_eq!(data.len(), MAX_TYPE_CHECK_FAIL_DATA_LEN * 2);
}
#[test]
fn type_check_fail_token_omits_token_data() {
let token = crate::abi::token::WordToken(crate::Word::ZERO);
let error = Error::type_check_fail_token::<crate::sol_data::Bool>(&token);
let Error::TypeCheckFail { data, .. } = error else { unreachable!() };
assert!(data.is_empty());
}
#[test]
fn empty_type_check_fail_data_is_not_displayed() {
assert_eq!(
Error::type_check_fail(&[], "test").to_string(),
"type check failed for \"test\""
);
}
}