Skip to main content

perpl_sdk/
error.rs

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/// Call/transaction revert reason decoded by
18/// the provided known ABI or in a generic raw form
19/// if can not be decoded.
20#[derive(Debug)]
21pub enum RevertReason<R> {
22    Known(R),
23    Generic(String),
24    Unknown,
25}
26
27/// Error returned by the RPC provider as a result of call or
28/// transaction execution.
29#[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                // Heuristic to determine if eth_call failed due to OutOfGas or
131                // if transaction was reverted during the gas estimation
132                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()) // Monad RPC can return empty response in case of unavailable block
149            },
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}