1use std::fmt::Display;
2
3use alloy::{
4 contract,
5 primitives::Bytes,
6 providers::{MulticallError, PendingTransactionError},
7 sol_types::{self, SolInterface},
8 transports,
9};
10
11use crate::{
12 abi::errors::Exchange::ExchangeErrors,
13 state::{ContractFeatures, FeeScheduleKey, OrderBookError, OrderParseError},
14 types,
15};
16
17#[derive(Debug)]
21pub enum RevertReason<R> {
22 Known(R),
23 Generic(String),
24 Unknown,
25}
26
27#[derive(Debug, thiserror::Error)]
30pub enum ProviderError<R> {
31 #[error("fatal error: {0}")]
32 Fatal(String),
33
34 #[error("invalid request: {0}")]
35 InvalidRequest(String),
36
37 #[error("unexpected empty RPC response")]
38 NullResp,
39
40 #[error("transaction ran out of gas")]
41 OutOfGas,
42
43 #[error("transaction reverted: {0:?}")]
44 Reverted(Box<RevertReason<R>>),
45
46 #[error("transport error: {0}")]
47 Transport(String),
48
49 #[error("transaction timed out")]
50 Timeout,
51}
52
53#[derive(Debug, thiserror::Error)]
54pub enum DexError {
55 #[error("block out of order, expected: {0}, got: {1}")]
56 BlockOutOfOrder(u64, u64),
57
58 #[error("fee schedule not found: {0}")]
59 FeeScheduleNotFound(FeeScheduleKey),
60
61 #[error("invalid argument: {0}")]
62 InvalidArgument(String),
63
64 #[error("perp {0} order book error: {1}")]
65 OrderBook(types::PerpetualId, OrderBookError),
66
67 #[error("order context expected, tx: {0}, log: {1}")]
68 OrderContextExpected(u64, u64),
69
70 #[error("order extension error: {0}")]
71 OrderExtension(#[from] types::OrderExtensionError),
72
73 #[error("order not found: {0}")]
74 OrderNotFound(types::PerpetualId, types::OrderId),
75
76 #[error("perp {0} order parse error: {1}")]
77 OrderParse(types::PerpetualId, OrderParseError),
78
79 #[error("perpetual {0} is not tracked")]
80 PerpetualNotTracked(types::PerpetualId),
81
82 #[error("position not found, acc: {0}, perp: {1}")]
83 PositionNotFound(types::AccountId, types::PerpetualId),
84
85 #[error("provider error: {0}")]
86 Provider(#[from] ProviderError<ExchangeErrors>),
87
88 #[error("deployed exchange contract ({1}) does not support {0}")]
89 UnsupportedByContract(&'static str, ContractFeatures),
90}
91
92impl<R: SolInterface> From<contract::Error> for ProviderError<R> {
93 fn from(value: contract::Error) -> Self {
94 match value {
95 contract::Error::UnknownFunction(_) => Self::Fatal(value.to_string()),
96 contract::Error::UnknownSelector(_) => Self::Fatal(value.to_string()),
97 contract::Error::NotADeploymentTransaction => Self::Fatal(value.to_string()),
98 contract::Error::ContractNotDeployed => Self::Fatal(value.to_string()),
99 contract::Error::ZeroData(_, _) => Self::Fatal(value.to_string()),
100 contract::Error::AbiError(_) => Self::Fatal(value.to_string()),
101 contract::Error::TransportError(rpc_err) => Self::from(rpc_err),
102 contract::Error::PendingTransactionError(err) => err.into(),
103 }
104 }
105}
106
107impl<R: SolInterface> From<PendingTransactionError> for ProviderError<R> {
108 fn from(value: PendingTransactionError) -> Self {
109 match value {
110 alloy::providers::PendingTransactionError::FailedToRegister => {
111 Self::Fatal(value.to_string())
112 },
113 alloy::providers::PendingTransactionError::TransportError(rpc_err) => {
114 Self::from(rpc_err)
115 },
116 alloy::providers::PendingTransactionError::Recv(_) => {
117 Self::Transport(value.to_string())
118 },
119 alloy::providers::PendingTransactionError::TxWatcher(err) => match err {
120 alloy::providers::WatchTxError::Timeout => Self::Timeout,
121 },
122 }
123 }
124}
125
126impl<E: Display, R: SolInterface> From<transports::RpcError<E>> for ProviderError<R> {
127 fn from(value: transports::RpcError<E>) -> Self {
128 match value {
129 transports::RpcError::ErrorResp(ref resp) => {
130 let msg = resp.message.to_ascii_lowercase();
133 if (resp.code == -32603) && (msg.contains("gas") || msg.contains("oog")) {
134 Self::OutOfGas
135 } else if ((resp.code == -32600 || resp.code == -32601 || resp.code == -32602)
136 && (msg.contains("invalid") || msg.contains("not found")))
137 || (resp.code == -32603
138 && (msg.contains("block by number") || msg.contains("getting block")))
139 {
140 Self::InvalidRequest(msg)
141 } else if (resp.code == 3 || resp.code == -32603) && msg.contains("reverted") {
142 Self::Reverted(Box::new(RevertReason::from(value)))
143 } else {
144 Self::Transport(value.to_string())
145 }
146 },
147 transports::RpcError::DeserError { err, text } if text.is_empty() => {
148 Self::InvalidRequest(err.to_string()) },
150 transports::RpcError::NullResp => Self::NullResp,
151 _ => Self::Transport(value.to_string()),
152 }
153 }
154}
155
156impl<R: SolInterface> From<sol_types::Error> for ProviderError<R> {
157 fn from(value: sol_types::Error) -> Self { Self::Fatal(value.to_string()) }
158}
159
160impl<R: SolInterface> From<MulticallError> for ProviderError<R> {
161 fn from(value: MulticallError) -> Self {
162 match value {
163 MulticallError::ValueTx => Self::InvalidRequest(value.to_string()),
164 MulticallError::DecodeError(_) => Self::Fatal(value.to_string()),
165 MulticallError::NoReturnData => Self::NullResp,
166 MulticallError::CallFailed(bytes) => {
167 Self::Reverted(Box::new(RevertReason::from(bytes)))
168 },
169 MulticallError::TransportError(rpc_err) => Self::from(rpc_err),
170 }
171 }
172}
173
174impl<E: Display, R: SolInterface> From<transports::RpcError<E>> for RevertReason<R> {
175 fn from(value: transports::RpcError<E>) -> Self {
176 match value.as_error_resp() {
177 Some(payload) => match payload.as_decoded_interface_error::<R>() {
178 Some(known) => Self::Known(known),
179 None => Self::Generic(value.to_string()),
180 },
181 None => Self::Generic(value.to_string()),
182 }
183 }
184}
185
186impl<R: SolInterface> From<Bytes> for RevertReason<R> {
187 fn from(value: Bytes) -> Self {
188 match R::abi_decode(&value) {
189 Ok(known) => Self::Known(known),
190 Err(_) => Self::Generic(value.to_string()),
191 }
192 }
193}