Skip to main content

perpcity_sdk/
errors.rs

1//! Error types for the PerpCity SDK.
2//!
3//! Follows the axiomtrade-rs pattern: a single top-level enum with
4//! domain-specific variants plus `#[from]` conversions for library errors.
5
6use alloy::primitives::U256;
7use thiserror::Error;
8
9/// Central error type for the PerpCity SDK.
10#[derive(Error, Debug)]
11#[non_exhaustive]
12pub enum PerpCityError {
13    // ── Validation errors ────────────────────────────────────────────
14    /// Price must be positive and within protocol bounds.
15    #[error("invalid price: {reason}")]
16    InvalidPrice { reason: String },
17
18    /// Margin does not meet the minimum opening requirement.
19    #[error("invalid margin: {reason}")]
20    InvalidMargin { reason: String },
21
22    /// Leverage is outside the allowed range for this perp.
23    #[error("invalid leverage: {reason}")]
24    InvalidLeverage { reason: String },
25
26    /// Tick range violates protocol bounds or spacing.
27    #[error("invalid tick range: lower={lower}, upper={upper}")]
28    InvalidTickRange { lower: i32, upper: i32 },
29
30    /// Margin ratio is outside the allowed [min, max] window.
31    #[error("invalid margin ratio: {value} (must be in [{min}, {max}])")]
32    InvalidMarginRatio { value: u32, min: u32, max: u32 },
33
34    /// A configuration value is invalid or missing.
35    #[error("invalid configuration: {reason}")]
36    InvalidConfig { reason: String },
37
38    // ── Arithmetic errors ────────────────────────────────────────────
39    /// An arithmetic operation overflowed.
40    #[error("arithmetic overflow: {context}")]
41    Overflow { context: String },
42
43    // ── Transaction / on-chain errors ────────────────────────────────
44    /// A sent transaction reverted on-chain.
45    #[error("transaction reverted: {reason}")]
46    TxReverted { reason: String },
47
48    /// An expected event was not found in the transaction receipt.
49    #[error("event not found in receipt: {event_name}")]
50    EventNotFound { event_name: String },
51
52    /// Could not estimate or fetch gas price from the network.
53    #[error("gas price unavailable: {reason}")]
54    GasPriceUnavailable { reason: String },
55
56    /// Too many unconfirmed transactions in flight.
57    #[error("too many in-flight transactions: {count} (max {max})")]
58    TooManyInFlight { count: usize, max: usize },
59
60    // ── Contract / protocol errors ───────────────────────────────────
61    /// The perp does not exist on-chain.
62    #[error("perp does not exist: {perp_id}")]
63    PerpNotFound { perp_id: U256 },
64
65    /// The position does not exist on-chain.
66    #[error("position does not exist: id={pos_id}")]
67    PositionNotFound { pos_id: U256 },
68
69    /// A required module is not registered.
70    #[error("module not registered: {module}")]
71    ModuleNotRegistered { module: String },
72
73    // ── Transparent library error conversions ────────────────────────
74    /// Alloy contract call or ABI error.
75    #[error(transparent)]
76    AlloyContract(#[from] alloy::contract::Error),
77
78    /// Alloy transport (RPC) error.
79    #[error(transparent)]
80    AlloyTransport(#[from] alloy::transports::TransportError),
81
82    /// JSON serialization / deserialization error.
83    #[error(transparent)]
84    Serde(#[from] serde_json::Error),
85}
86
87/// Convenience alias used throughout the SDK.
88pub type Result<T> = std::result::Result<T, PerpCityError>;
89
90#[cfg(test)]
91mod tests {
92    use super::*;
93
94    #[test]
95    fn serde_error_converts() {
96        let json_err = serde_json::from_str::<String>("not valid json").unwrap_err();
97        let err: PerpCityError = json_err.into();
98        assert!(matches!(err, PerpCityError::Serde(_)));
99    }
100
101    #[test]
102    fn error_is_send_sync() {
103        fn assert_send_sync<T: Send + Sync>() {}
104        assert_send_sync::<PerpCityError>();
105    }
106}