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