kabu_types_swap/
swap_error.rs

1use alloy_primitives::{Address, U256, hex};
2use eyre::{Report, eyre};
3use kabu_types_market::SwapPath;
4use kabu_types_market::{PoolError, PoolId};
5use std::fmt;
6use std::hash::{Hash, Hasher};
7
8#[derive(Clone, Debug)]
9pub struct EstimationError {
10    pub msg: String,
11    pub swap_path: SwapPath,
12}
13
14impl PartialEq<Self> for EstimationError {
15    fn eq(&self, other: &Self) -> bool {
16        self.swap_path == other.swap_path
17    }
18}
19
20impl Eq for EstimationError {}
21
22#[derive(Clone, Debug, PartialEq, Eq, Hash)]
23pub enum SwapErrorKind {
24    // Amount-related errors
25    ZeroOutAmount,
26    AlmostZeroOutAmount,
27    ZeroInAmount,
28    MaxInAmount,
29    InsufficientLiquidity,
30
31    // Pool state errors
32    UninitializedPool,
33    ZeroReserves,
34    InvalidReserves,
35    PoolNotFound,
36
37    // ABI/Contract errors
38    AbiEncodingFailed,
39    AbiDecodingFailed { method: &'static str, reason: AbiError },
40    ContractCallFailed { method: &'static str, revert_reason: Option<Vec<u8>> },
41
42    // Calculation errors
43    OverflowInCalculation,
44    UnderflowInCalculation,
45    InvalidK,
46    InvalidSqrtPrice,
47
48    // Token-related errors
49    InvalidTokenOrder,
50    TokenNotInPool,
51    SameTokenSwap,
52
53    // UniswapV3 specific
54    InvalidTickRange,
55    NoLiquidityInRange,
56    PriceSlippageExceeded,
57
58    // Curve specific
59    InvalidAmplificationParameter,
60    ConvergenceFailure,
61    InvalidPoolType,
62
63    // Generic pool errors
64    UnsupportedPoolType,
65    InvalidSwapPath,
66
67    // Catch-all for other errors
68    CalculationError(String),
69}
70
71impl From<PoolError> for SwapErrorKind {
72    fn from(err: PoolError) -> Self {
73        use kabu_types_market::{CurveError, MaverickError, UniswapV2Error, UniswapV3Error};
74
75        match err {
76            // UniswapV2 errors
77            PoolError::UniswapV2(e) => match e {
78                UniswapV2Error::OutAmountIsZero => SwapErrorKind::ZeroOutAmount,
79                UniswapV2Error::InAmountIsZero => SwapErrorKind::ZeroInAmount,
80                UniswapV2Error::ReserveExceeded | UniswapV2Error::ReserveOutExceeded => SwapErrorKind::InsufficientLiquidity,
81                UniswapV2Error::AmountInWithFeeOverflow
82                | UniswapV2Error::NumeratorOverflow
83                | UniswapV2Error::NumeratorOverflowFee
84                | UniswapV2Error::DenominatorOverflow
85                | UniswapV2Error::DenominatorOverflowFee
86                | UniswapV2Error::InAmountOverflow => SwapErrorKind::OverflowInCalculation,
87                UniswapV2Error::DenominatorUnderflow => SwapErrorKind::UnderflowInCalculation,
88                UniswapV2Error::CannotCalculateZeroReserve => SwapErrorKind::ZeroReserves,
89            },
90
91            // UniswapV3 errors
92            PoolError::UniswapV3(e) => match e {
93                UniswapV3Error::OutAmountIsZero => SwapErrorKind::ZeroOutAmount,
94                UniswapV3Error::InAmountIsZero => SwapErrorKind::ZeroInAmount,
95                UniswapV3Error::InvalidTick => SwapErrorKind::InvalidTickRange,
96                UniswapV3Error::NoLiquidityInRange => SwapErrorKind::NoLiquidityInRange,
97                UniswapV3Error::PriceOutOfBounds => SwapErrorKind::PriceSlippageExceeded,
98                UniswapV3Error::InvalidSqrtPrice | UniswapV3Error::BadPriceStep => SwapErrorKind::InvalidSqrtPrice,
99                UniswapV3Error::OverflowInCalculation => SwapErrorKind::OverflowInCalculation,
100                UniswapV3Error::UnderflowInCalculation => SwapErrorKind::UnderflowInCalculation,
101            },
102
103            // Curve errors
104            PoolError::Curve(e) => match e {
105                CurveError::OutAmountIsZero => SwapErrorKind::ZeroOutAmount,
106                CurveError::InAmountIsZero => SwapErrorKind::ZeroInAmount,
107                CurveError::TokenNotInPool { .. } => SwapErrorKind::TokenNotInPool,
108                CurveError::InvalidPoolState | CurveError::CannotGetBalance | CurveError::UnderlyingCoinNotFound => {
109                    SwapErrorKind::InvalidReserves
110                }
111            },
112
113            // Maverick errors
114            PoolError::Maverick(e) => match e {
115                MaverickError::MaxInAmount => SwapErrorKind::MaxInAmount,
116                MaverickError::OutAmountIsZero => SwapErrorKind::ZeroOutAmount,
117                MaverickError::InAmountIsZero => SwapErrorKind::ZeroInAmount,
118                MaverickError::ReserveOutExceeded => SwapErrorKind::InsufficientLiquidity,
119            },
120
121            // ABI errors
122            PoolError::AbiDecodingError { method, source } => SwapErrorKind::AbiDecodingFailed {
123                method,
124                reason: match source {
125                    alloy_sol_types::Error::Overrun => AbiError::BufferOverrun,
126                    alloy_sol_types::Error::BufferNotEmpty => AbiError::BufferOverrun,
127                    alloy_sol_types::Error::UnknownSelector { .. } => AbiError::InvalidSelector,
128                    alloy_sol_types::Error::TypeCheckFail { .. } => AbiError::Other,
129                    _ => AbiError::Other,
130                },
131            },
132
133            // EVM revert errors
134            PoolError::EvmReverted { reason, .. } => SwapErrorKind::CalculationError(reason.clone()),
135
136            // Generic errors
137            PoolError::NotImplemented => SwapErrorKind::UnsupportedPoolType,
138            PoolError::InvalidInput { reason } => SwapErrorKind::CalculationError(reason.to_string()),
139            PoolError::DatabaseError(e) => SwapErrorKind::CalculationError(format!("Database error: {e}")),
140            PoolError::GeneralError(e) => SwapErrorKind::CalculationError(e.clone()),
141        }
142    }
143}
144
145#[derive(Clone, Debug, PartialEq, Eq, Hash)]
146pub enum AbiError {
147    BufferOverrun,
148    InvalidReturnDataSize { expected: usize, actual: usize },
149    InvalidSelector,
150    MissingReturnData,
151    InvalidUtf8,
152    Other,
153}
154
155impl fmt::Display for AbiError {
156    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
157        match self {
158            AbiError::BufferOverrun => write!(f, "buffer overrun while deserializing"),
159            AbiError::InvalidReturnDataSize { expected, actual } => {
160                write!(f, "invalid return data size: expected {expected}, got {actual}")
161            }
162            AbiError::InvalidSelector => write!(f, "invalid function selector"),
163            AbiError::MissingReturnData => write!(f, "missing return data"),
164            AbiError::InvalidUtf8 => write!(f, "invalid UTF-8 in return data"),
165            AbiError::Other => write!(f, "unknown ABI error"),
166        }
167    }
168}
169
170impl fmt::Display for SwapErrorKind {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        match self {
173            // Amount errors
174            SwapErrorKind::ZeroOutAmount => write!(f, "output amount is zero"),
175            SwapErrorKind::AlmostZeroOutAmount => write!(f, "output amount below minimum threshold"),
176            SwapErrorKind::ZeroInAmount => write!(f, "input amount is zero"),
177            SwapErrorKind::MaxInAmount => write!(f, "input amount is MAX_UINT256"),
178            SwapErrorKind::InsufficientLiquidity => write!(f, "insufficient liquidity for swap"),
179
180            // Pool state errors
181            SwapErrorKind::UninitializedPool => write!(f, "pool is not initialized"),
182            SwapErrorKind::ZeroReserves => write!(f, "pool has zero reserves"),
183            SwapErrorKind::InvalidReserves => write!(f, "pool reserves are invalid"),
184            SwapErrorKind::PoolNotFound => write!(f, "pool not found"),
185
186            // ABI/Contract errors
187            SwapErrorKind::AbiEncodingFailed => write!(f, "ABI encoding failed"),
188            SwapErrorKind::AbiDecodingFailed { method, reason } => write!(f, "ABI decoding failed for {method}: {reason}"),
189            SwapErrorKind::ContractCallFailed { method, revert_reason } => {
190                if let Some(data) = revert_reason {
191                    write!(f, "contract call failed for {}: 0x{}", method, hex::encode(data))
192                } else {
193                    write!(f, "contract call failed for {method}")
194                }
195            }
196
197            // Calculation errors
198            SwapErrorKind::OverflowInCalculation => write!(f, "overflow in calculation"),
199            SwapErrorKind::UnderflowInCalculation => write!(f, "underflow in calculation"),
200            SwapErrorKind::InvalidK => write!(f, "invalid constant product K"),
201            SwapErrorKind::InvalidSqrtPrice => write!(f, "invalid sqrt price"),
202
203            // Token errors
204            SwapErrorKind::InvalidTokenOrder => write!(f, "invalid token order"),
205            SwapErrorKind::TokenNotInPool => write!(f, "token not in pool"),
206            SwapErrorKind::SameTokenSwap => write!(f, "cannot swap same token"),
207
208            // UniswapV3 specific
209            SwapErrorKind::InvalidTickRange => write!(f, "invalid tick range"),
210            SwapErrorKind::NoLiquidityInRange => write!(f, "no liquidity in tick range"),
211            SwapErrorKind::PriceSlippageExceeded => write!(f, "price slippage exceeded"),
212
213            // Curve specific
214            SwapErrorKind::InvalidAmplificationParameter => write!(f, "invalid amplification parameter"),
215            SwapErrorKind::ConvergenceFailure => write!(f, "numerical convergence failure"),
216            SwapErrorKind::InvalidPoolType => write!(f, "invalid Curve pool type"),
217
218            // Generic
219            SwapErrorKind::UnsupportedPoolType => write!(f, "unsupported pool type"),
220            SwapErrorKind::InvalidSwapPath => write!(f, "invalid swap path"),
221            SwapErrorKind::CalculationError(msg) => write!(f, "{msg}"),
222        }
223    }
224}
225
226#[derive(Clone, Debug)]
227pub struct SwapError {
228    pub kind: SwapErrorKind,
229    pub pool: PoolId,
230    pub token_from: Address,
231    pub token_to: Address,
232    pub is_in_amount: bool,
233    pub amount: U256,
234}
235
236impl From<SwapError> for Report {
237    fn from(value: SwapError) -> Self {
238        eyre!("SwapError::{:?} at pool {:?}: {}", value.kind, value.pool, value.kind)
239    }
240}
241
242impl fmt::Display for SwapError {
243    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244        write!(
245            f,
246            "SwapError {{ kind: {}, pool: {:?}, {} -> {}, amount: {} }}",
247            self.kind, self.pool, self.token_from, self.token_to, self.amount
248        )
249    }
250}
251
252impl Hash for SwapError {
253    fn hash<H: Hasher>(&self, state: &mut H) {
254        self.pool.hash(state);
255        self.token_from.hash(state);
256        self.token_to.hash(state);
257    }
258}
259
260impl PartialEq<Self> for SwapError {
261    fn eq(&self, other: &Self) -> bool {
262        self.pool == other.pool && self.token_to == other.token_to && self.token_from == other.token_from
263    }
264}
265
266impl Eq for SwapError {}