use std::fmt;
use crate::client::TransportError;
use crate::client::evm::abi::{self, 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 => "string",
PANIC => "uint256",
_ => return custom(),
};
let decoded = abi::params(shape).and_then(|params| abi::decode(¶ms, rest));
match decoded.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 },
}
#[cfg(test)]
#[path = "tests/error.rs"]
mod tests;