use std::fmt;
use crate::account::keys::WalletError;
use crate::client::TransportError;
use crate::client::evm::abi::{self, Kind, Token};
use crate::client::evm::u256::U256;
use crate::utils::hex;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NodeError {
pub code: i64,
pub message: String,
pub data: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ErrorKind {
Parse,
InvalidRequest,
MethodNotFound,
InvalidParams,
Internal,
Reverted,
Server,
Other,
}
impl NodeError {
#[must_use]
pub fn kind(&self) -> ErrorKind {
match self.code {
-32700 => ErrorKind::Parse,
-32600 => ErrorKind::InvalidRequest,
-32601 => ErrorKind::MethodNotFound,
-32602 => ErrorKind::InvalidParams,
-32603 => ErrorKind::Internal,
3 => ErrorKind::Reverted,
-32099..=-32000 if self.message.starts_with("execution reverted") => ErrorKind::Reverted,
-32099..=-32000 => ErrorKind::Server,
_ => ErrorKind::Other,
}
}
#[must_use]
pub fn revert(&self) -> Option<Revert> {
let data = self.data.as_deref()?.strip_prefix("0x")?;
hex::decode(data).ok().map(|bytes| read_revert(&bytes))
}
}
impl fmt::Display for NodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (code {})", self.message, self.code)?;
match &self.data {
Some(data) => write!(f, ": {data}"),
None => Ok(()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Revert {
Message(String),
Panic(U256),
Custom(Vec<u8>),
}
const ERROR: [u8; 4] = [0x08, 0xc3, 0x79, 0xa0];
const PANIC: [u8; 4] = [0x4e, 0x48, 0x7b, 0x71];
#[must_use]
pub fn read_revert(data: &[u8]) -> Revert {
let custom = || Revert::Custom(data.to_vec());
let Some((selector, rest)) = data.split_first_chunk::<4>() else {
return custom();
};
let shape = match *selector {
ERROR => Kind::String,
PANIC => Kind::Uint(256),
_ => return custom(),
};
match abi::decode(&[shape], rest).as_deref() {
Ok([Token::String(text)]) => Revert::Message(text.clone()),
Ok([Token::Uint(code)]) => Revert::Panic(*code),
_ => custom(),
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum RpcError {
#[error(transparent)]
Transport(#[from] TransportError),
#[error("{method}: the node refused: {error}")]
Node { method: &'static str, error: NodeError },
#[error("{method}: the node's answer could not be read: {detail}")]
Unreadable { method: &'static str, detail: String },
}
impl RpcError {
#[must_use]
pub const fn may_have_landed(&self) -> bool {
matches!(self, RpcError::Transport(TransportError::Unreachable(_)) | RpcError::Unreadable { .. })
|| matches!(self, RpcError::Transport(TransportError::Status { status, .. }) if *status >= 500)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum L1Error {
#[error(transparent)]
Rpc(#[from] RpcError),
#[error(transparent)]
Wallet(#[from] WalletError),
#[error("the node is on chain {found}, not {expected}; nothing was sent")]
WrongChain { expected: u64, found: u64 },
}