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 ZeroOutAmount,
26 AlmostZeroOutAmount,
27 ZeroInAmount,
28 MaxInAmount,
29 InsufficientLiquidity,
30
31 UninitializedPool,
33 ZeroReserves,
34 InvalidReserves,
35 PoolNotFound,
36
37 AbiEncodingFailed,
39 AbiDecodingFailed { method: &'static str, reason: AbiError },
40 ContractCallFailed { method: &'static str, revert_reason: Option<Vec<u8>> },
41
42 OverflowInCalculation,
44 UnderflowInCalculation,
45 InvalidK,
46 InvalidSqrtPrice,
47
48 InvalidTokenOrder,
50 TokenNotInPool,
51 SameTokenSwap,
52
53 InvalidTickRange,
55 NoLiquidityInRange,
56 PriceSlippageExceeded,
57
58 InvalidAmplificationParameter,
60 ConvergenceFailure,
61 InvalidPoolType,
62
63 UnsupportedPoolType,
65 InvalidSwapPath,
66
67 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 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 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 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 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 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 PoolError::EvmReverted { reason, .. } => SwapErrorKind::CalculationError(reason.clone()),
135
136 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 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 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 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 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 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 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 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 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 {}