#[cfg(feature = "pyo3")]
use pyo3::{exceptions::PyValueError, PyErr};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum TickMathError {
#[error("Invalid tick value: {0}. Must be in range [-887272, 887272]")]
InvalidTick(i32),
#[error("Sqrt ratio {actual} out of bounds [{min}, {max})")]
SqrtRatioOutOfBounds {
actual: alloy::primitives::aliases::U160,
min: alloy::primitives::aliases::U160,
max: alloy::primitives::aliases::U160,
},
}
#[derive(Debug, thiserror::Error, PartialEq)]
#[non_exhaustive]
pub enum ClMathError {
#[error("DIVISION BY ZERO")]
DivisionByZero,
#[error("product > MAX_UINT256")]
Uint256Overflow,
#[error("Zero input")]
ZeroInput,
#[error("InvalidPrice")]
InvalidPrice,
#[error("InvalidPriceOrLiquidity")]
InvalidPriceOrLiquidity,
#[error("PriceOverflow")]
PriceOverflow,
#[error("Result overflowed MAX_UINT160")]
ResultOverflowedUint160,
#[error("NotEnoughLiquidity")]
InsufficientLiquidity,
#[error("InvalidLiquidity")]
InvalidLiquidity,
#[error("SafeCastOverflow")]
SafeCastOverflow,
}
#[cfg(feature = "pyo3")]
impl From<ClMathError> for PyErr {
fn from(err: ClMathError) -> Self {
Self::new::<PyValueError, _>(err.to_string())
}
}
impl From<TickMathError> for ClMathError {
fn from(err: TickMathError) -> Self {
match err {
TickMathError::InvalidTick(_) | TickMathError::SqrtRatioOutOfBounds { .. } => {
Self::InvalidPrice
}
}
}
}
#[cfg(feature = "pyo3")]
impl From<TickMathError> for PyErr {
fn from(err: TickMathError) -> Self {
Self::new::<PyValueError, _>(err.to_string())
}
}
#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum AbiDecodeError {
#[error("Types list cannot be empty")]
EmptyTypesList,
#[error("Data cannot be empty")]
EmptyData,
#[error("Invalid array size in type: {0}")]
InvalidArraySize(String),
#[error("Insufficient data: need {needed} bytes at offset {offset}, have {have} bytes")]
InsufficientData {
needed: usize,
have: usize,
offset: usize,
},
#[error("Invalid offset: {0}")]
InvalidOffset(String),
#[error("Invalid length: {0}")]
InvalidLength(String),
#[error("Unsupported type: {0}")]
UnsupportedType(String),
#[error("Fixed-point types are not yet implemented")]
FixedPointNotImplemented,
#[error("Non-strict decoding mode is not yet implemented")]
NonStrictNotImplemented,
}
#[cfg(feature = "pyo3")]
impl From<AbiDecodeError> for PyErr {
fn from(err: AbiDecodeError) -> Self {
Self::new::<PyValueError, _>(err.to_string())
}
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum AddressError {
#[error("Invalid address string: {0}")]
InvalidAddress(String),
#[error("Address must be exactly 20 bytes, got {0} bytes")]
InvalidByteLength(usize),
}
#[cfg(feature = "pyo3")]
impl From<AddressError> for PyErr {
fn from(err: AddressError) -> Self {
Self::new::<PyValueError, _>(err.to_string())
}
}
#[derive(Debug, thiserror::Error, Clone)]
#[non_exhaustive]
pub enum ProviderError {
#[error("Request timeout: {message}")]
Timeout { message: String },
#[error("Rate limited: {message}")]
RateLimited { message: String },
#[error("Connection failed: {message}")]
ConnectionFailed { message: String },
#[error("Invalid response: {message}")]
InvalidResponse { message: String },
#[error("RPC error: {code} - {message}")]
RpcError { code: i64, message: String },
#[error("execution reverted: {message}")]
ExecutionReverted { code: i64, message: String },
#[error("Invalid block range: {from} to {to}")]
InvalidBlockRange { from: u64, to: u64 },
#[error("Invalid address format: {address} - {reason}")]
InvalidAddress { address: String, reason: String },
#[error("Invalid topic format: {topic} - {reason}")]
InvalidTopic { topic: String, reason: String },
#[error("Invalid parameters: {message}")]
InvalidParams { message: String },
#[error("Anvil error: {message}")]
AnvilError { message: String },
#[error("Invalid ABI: {message}")]
InvalidAbi { message: String },
#[error("Encoding error: {message}")]
EncodingError { message: String },
#[error("Decoding error: {message}")]
DecodingError { message: String },
#[error("Serialization error: {message}")]
SerializationError { message: String },
#[error("Subscription not supported: {message}")]
SubscriptionNotSupported { message: String },
#[error("endpoint {endpoint} reports chain id {actual}, not the expected {expected}")]
ChainMismatch {
expected: u64,
actual: u64,
endpoint: String,
},
#[error("{message}")]
Other { message: String },
}
impl ProviderError {
#[must_use]
pub const fn is_retryable(&self) -> bool {
matches!(
self,
Self::RateLimited { .. } | Self::Timeout { .. } | Self::ConnectionFailed { .. }
)
}
}
#[cfg(feature = "pyo3")]
impl From<ProviderError> for PyErr {
fn from(err: ProviderError) -> Self {
let msg = format!("Provider error: {err}");
match err {
ProviderError::Timeout { .. } => Self::new::<pyo3::exceptions::PyTimeoutError, _>(msg),
ProviderError::ConnectionFailed { .. } => {
Self::new::<pyo3::exceptions::PyConnectionError, _>(msg)
}
ProviderError::ChainMismatch { .. } => {
Self::new::<pyo3::exceptions::PyValueError, _>(msg)
}
ProviderError::SubscriptionNotSupported { .. }
| ProviderError::RateLimited { .. }
| ProviderError::RpcError { .. }
| ProviderError::ExecutionReverted { .. }
| ProviderError::SerializationError { .. }
| ProviderError::InvalidResponse { .. }
| ProviderError::AnvilError { .. }
| ProviderError::Other { .. }
| ProviderError::InvalidBlockRange { .. }
| ProviderError::InvalidAddress { .. }
| ProviderError::InvalidTopic { .. }
| ProviderError::InvalidParams { .. }
| ProviderError::InvalidAbi { .. }
| ProviderError::EncodingError { .. }
| ProviderError::DecodingError { .. } => {
Self::new::<pyo3::exceptions::PyRuntimeError, _>(msg)
}
}
}
}
pub type ProviderResult<T> = Result<T, ProviderError>;
#[derive(Debug, thiserror::Error, Clone)]
#[non_exhaustive]
pub enum ContractError {
#[error("Invalid ABI: {message}")]
InvalidAbi {
message: String,
},
#[error("Invalid address: {address} - {reason}")]
InvalidAddress {
address: String,
reason: String,
},
#[error("Encoding error: {message}")]
EncodingError {
message: String,
},
#[error("Decoding error: {message}")]
DecodingError {
message: String,
},
}
impl From<ContractError> for ProviderError {
fn from(err: ContractError) -> Self {
match err {
ContractError::InvalidAbi { message } => Self::InvalidAbi { message },
ContractError::InvalidAddress { address, reason } => {
Self::InvalidAddress { address, reason }
}
ContractError::EncodingError { message } => Self::EncodingError { message },
ContractError::DecodingError { message } => Self::DecodingError { message },
}
}
}
#[cfg(feature = "pyo3")]
impl From<ContractError> for PyErr {
fn from(err: ContractError) -> Self {
Self::new::<PyValueError, _>(format!("{err}"))
}
}
impl From<crate::errors::AbiDecodeError> for ContractError {
fn from(err: crate::errors::AbiDecodeError) -> Self {
match err {
crate::errors::AbiDecodeError::UnsupportedType(msg) => {
Self::InvalidAbi { message: msg }
}
crate::errors::AbiDecodeError::InvalidLength(msg)
| crate::errors::AbiDecodeError::InvalidOffset(msg) => {
Self::DecodingError { message: msg }
}
crate::errors::AbiDecodeError::InsufficientData { .. } => Self::DecodingError {
message: err.to_string(),
},
other => Self::DecodingError {
message: other.to_string(),
},
}
}
}
pub type ContractResult<T> = Result<T, ContractError>;