levana_perpswap_cosmos/
error.rs

1//! Error handling helpers for within the perps protocol
2pub(crate) mod market;
3
4use crate::event::CosmwasmEventExt;
5use cosmwasm_std::Event;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use std::fmt;
9
10/// An error message for the perps protocol
11#[derive(Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
12pub struct PerpError<T = ()> {
13    /// Unique identifier for this error
14    pub id: ErrorId,
15    /// Where in the protocol the error came from
16    pub domain: ErrorDomain,
17    /// User friendly description
18    pub description: String,
19    /// Optional additional information
20    pub data: Option<T>,
21}
22
23/// Unique identifier for an error within perps
24#[allow(missing_docs)]
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
26#[serde(rename_all = "snake_case")]
27pub enum ErrorId {
28    InvalidStakeLp,
29    InvalidAmount,
30    SlippageAssert,
31    PriceAlreadyExists,
32    PriceNotFound,
33    PriceTooOld,
34    PositionUpdate,
35    NativeFunds,
36    Cw20Funds,
37    Auth,
38    Expired,
39    MsgValidation,
40    Conversion,
41    Config,
42    InternalReply,
43    Exceeded,
44    Any,
45    Stale,
46    InsufficientMargin,
47    InvalidLiquidityTokenMsg,
48    AddressAlreadyExists,
49    DeltaNeutralityFeeAlreadyLong,
50    DeltaNeutralityFeeAlreadyShort,
51    DeltaNeutralityFeeNewlyLong,
52    DeltaNeutralityFeeNewlyShort,
53    DeltaNeutralityFeeLongToShort,
54    DeltaNeutralityFeeShortToLong,
55    DirectionToBaseFlipped,
56    MissingFunds,
57    UnnecessaryFunds,
58    NoYieldToClaim,
59    InsufficientForReinvest,
60    TimestampSubtractUnderflow,
61
62    // Errors that come from MarketError
63    InvalidInfiniteMaxGains,
64    InvalidInfiniteTakeProfitPrice,
65    MaxGainsTooLarge,
66    WithdrawTooMuch,
67    InsufficientLiquidityForWithdrawal,
68    MissingPosition,
69    TraderLeverageOutOfRange,
70    MinimumDeposit,
71    Congestion,
72    MaxLiquidity,
73    InvalidTriggerPrice,
74    LiquidityCooldown,
75    PendingDeferredExec,
76    VolatilePriceFeedTimeDelta,
77    LimitOrderAlreadyCanceling,
78    PositionAlreadyClosing,
79    NoPricePublishTimeFound,
80    PositionAlreadyClosed,
81    MissingTakeProfit,
82    InsufficientLiquidityForUnlock,
83    Liquidity,
84}
85
86/// Source within the protocol for the error
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
88#[serde(rename_all = "snake_case")]
89#[allow(missing_docs)]
90pub enum ErrorDomain {
91    Market,
92    SpotPrice,
93    PositionToken,
94    LiquidityToken,
95    Cw20,
96    Wallet,
97    Factory,
98    Default,
99    Faucet,
100    Pyth,
101    Farming,
102    Stride,
103    SimpleOracle,
104}
105
106impl<T: Serialize> fmt::Display for PerpError<T> {
107    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
108        write!(
109            f,
110            "{}",
111            serde_json::to_string_pretty(&self).map_err(|_| fmt::Error)?
112        )
113    }
114}
115
116impl<T: Serialize> fmt::Debug for PerpError<T> {
117    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
118        write!(
119            f,
120            "{}",
121            serde_json::to_string_pretty(&self).map_err(|_| fmt::Error)?
122        )
123    }
124}
125
126impl PerpError {
127    /// Create a new [Self] but with empty data.
128    pub fn new(id: ErrorId, domain: ErrorDomain, desc: impl Into<String>) -> Self {
129        PerpError {
130            id,
131            domain,
132            description: desc.into(),
133            data: None,
134        }
135    }
136
137    /// Create a new auth error but with empty data.
138    pub fn auth(domain: ErrorDomain, desc: impl Into<String>) -> Self {
139        PerpError {
140            id: ErrorId::Auth,
141            domain,
142            description: desc.into(),
143            data: None,
144        }
145    }
146
147    /// Create a new [Self] for market contract, but with no data.
148    pub fn market(id: ErrorId, desc: impl Into<String>) -> Self {
149        PerpError {
150            id,
151            domain: ErrorDomain::Market,
152            description: desc.into(),
153            data: None,
154        }
155    }
156
157    /// Create a new [Self] for Cw20 contract with no data
158    pub fn cw20(id: ErrorId, desc: impl Into<String>) -> Self {
159        PerpError {
160            id,
161            domain: ErrorDomain::Cw20,
162            description: desc.into(),
163            data: None,
164        }
165    }
166    /// Include error information into an event
167    pub fn mixin_event(&self, evt: Event) -> Event {
168        // these unwraps are okay, just a shorthand helper to get the enum variants as a string
169        let evt = evt.add_attributes([
170            ("error-id", serde_json::to_string(&self.id).unwrap()),
171            ("error-domain", serde_json::to_string(&self.domain).unwrap()),
172            ("error-description", self.description.to_string()),
173        ]);
174
175        match &self.data {
176            None => evt,
177            // this should only fail if the inner to_json_vec of serde fails. that's a (very unlikely) genuine panic situation
178            Some(data) => evt.add_attribute("error-data", serde_json::to_string(data).unwrap()),
179        }
180    }
181
182    /// Generate an error saying something is unimplemented
183    pub fn unimplemented() -> Self {
184        Self {
185            id: ErrorId::Any,
186            domain: ErrorDomain::Default,
187            description: "unimplemented".to_string(),
188            data: None,
189        }
190    }
191}
192
193impl TryFrom<Event> for PerpError {
194    type Error = anyhow::Error;
195
196    fn try_from(evt: Event) -> anyhow::Result<Self> {
197        Ok(Self {
198            id: evt.json_attr("error-id")?,
199            domain: evt.json_attr("error-domain")?,
200            description: evt.string_attr("error-description")?,
201            data: evt.try_json_attr("error-data")?,
202        })
203    }
204}
205
206impl<T: Serialize> std::error::Error for PerpError<T> {}