Skip to main content

pyra_types/
lib.rs

1//! Shared account types for Pyra services.
2//!
3//! Provides type definitions for Drift protocol accounts, Pyra vault accounts,
4//! and Redis cache wrappers. These types are the superset of fields needed by
5//! api-v2, settlement-service, and notification-service for Redis deserialization.
6
7mod cache;
8mod drift;
9mod pyra;
10
11pub use cache::Cache;
12pub use drift::{
13    DriftUser, HistoricalOracleData, InsuranceFund, SpotBalanceType, SpotMarket, SpotPosition,
14};
15pub use pyra::{SpendLimitsOrderAccount, TimeLock, Vault, WithdrawOrderAccount};
16
17#[cfg(test)]
18mod tests {
19    use super::*;
20
21    #[test]
22    fn cache_deserialize_with_slot() {
23        let json = r#"{"account":{"spot_positions":[]},"last_updated_slot":285847350}"#;
24        let cache: Cache<DriftUser> = serde_json::from_str(json).unwrap();
25        assert_eq!(cache.last_updated_slot, 285847350);
26        assert!(cache.account.spot_positions.is_empty());
27    }
28
29    #[test]
30    fn cache_deserialize_without_slot() {
31        let json = r#"{"account":{"spot_positions":[]}}"#;
32        let cache: Cache<DriftUser> = serde_json::from_str(json).unwrap();
33        assert_eq!(cache.last_updated_slot, 0);
34    }
35
36    #[test]
37    fn spot_market_partial_fields() {
38        // Core fields (market_index, decimals, interest, margin weights) are required.
39        // Optional service-specific fields use serde(default).
40        let json = r#"{"market_index":1,"decimals":6,"cumulative_deposit_interest":100,"cumulative_borrow_interest":50,"initial_asset_weight":8000,"initial_liability_weight":12000,"imf_factor":0,"scale_initial_asset_weight_start":0}"#;
41        let market: SpotMarket = serde_json::from_str(json).unwrap();
42        assert_eq!(market.market_index, 1);
43        assert_eq!(market.decimals, 6);
44        assert_eq!(market.initial_asset_weight, 8000);
45        assert_eq!(market.initial_liability_weight, 12000);
46        // Optional fields default to zero
47        assert_eq!(market.deposit_balance, 0);
48    }
49
50    #[test]
51    fn spot_market_missing_core_field_fails() {
52        // market_index, decimals, cumulative_*_interest, margin weights are required
53        let json = r#"{"market_index":1}"#;
54        let result = serde_json::from_str::<SpotMarket>(json);
55        assert!(result.is_err());
56
57        // Missing margin weight fields also fails
58        let json = r#"{"market_index":1,"decimals":6,"cumulative_deposit_interest":100,"cumulative_borrow_interest":50}"#;
59        let result = serde_json::from_str::<SpotMarket>(json);
60        assert!(result.is_err());
61    }
62
63    #[test]
64    fn spot_position_with_balance_type() {
65        let json = r#"{"scaled_balance":1000000,"market_index":0,"balance_type":"Deposit"}"#;
66        let pos: SpotPosition = serde_json::from_str(json).unwrap();
67        assert_eq!(pos.scaled_balance, 1000000);
68        assert_eq!(pos.balance_type, SpotBalanceType::Deposit);
69    }
70
71    #[test]
72    fn spot_position_borrow() {
73        let json =
74            r#"{"scaled_balance":500,"market_index":1,"balance_type":"Borrow","open_orders":2}"#;
75        let pos: SpotPosition = serde_json::from_str(json).unwrap();
76        assert_eq!(pos.balance_type, SpotBalanceType::Borrow);
77        assert_eq!(pos.open_orders, 2);
78    }
79
80    #[test]
81    fn drift_user_with_positions() {
82        let json = r#"{
83            "spot_positions": [
84                {"scaled_balance":1000,"market_index":0,"balance_type":"Deposit"},
85                {"scaled_balance":500,"market_index":1,"balance_type":"Borrow"}
86            ]
87        }"#;
88        let user: DriftUser = serde_json::from_str(json).unwrap();
89        assert_eq!(user.spot_positions.len(), 2);
90        assert_eq!(user.spot_positions[0].market_index, 0);
91        assert_eq!(user.spot_positions[1].balance_type, SpotBalanceType::Borrow);
92    }
93
94    #[test]
95    fn vault_partial_fields() {
96        let json = r#"{"owner":[1,2,3]}"#;
97        let vault: Vault = serde_json::from_str(json).unwrap();
98        assert_eq!(vault.owner, vec![1, 2, 3]);
99        assert_eq!(vault.spend_limit_per_transaction, 0);
100    }
101
102    #[test]
103    fn spot_market_roundtrip() {
104        let market = SpotMarket {
105            pubkey: vec![],
106            market_index: 1,
107            initial_asset_weight: 8000,
108            initial_liability_weight: 0,
109            imf_factor: 0,
110            scale_initial_asset_weight_start: 0,
111            decimals: 9,
112            cumulative_deposit_interest: 1_050_000_000_000,
113            cumulative_borrow_interest: 0,
114            deposit_balance: 0,
115            borrow_balance: 0,
116            optimal_utilization: 0,
117            optimal_borrow_rate: 0,
118            max_borrow_rate: 0,
119            min_borrow_rate: 0,
120            insurance_fund: InsuranceFund::default(),
121            historical_oracle_data: HistoricalOracleData::default(),
122            oracle: None,
123        };
124        let json = serde_json::to_string(&market).unwrap();
125        let deserialized: SpotMarket = serde_json::from_str(&json).unwrap();
126        assert_eq!(deserialized.market_index, 1);
127        assert_eq!(deserialized.cumulative_deposit_interest, 1_050_000_000_000);
128    }
129
130    #[test]
131    fn cache_spot_market_roundtrip() {
132        let json = r#"{"account":{"market_index":0,"decimals":6,"cumulative_deposit_interest":100,"cumulative_borrow_interest":50,"initial_asset_weight":8000,"initial_liability_weight":12000,"imf_factor":0,"scale_initial_asset_weight_start":0},"last_updated_slot":12345}"#;
133        let deserialized: Cache<SpotMarket> = serde_json::from_str(json).unwrap();
134        assert_eq!(deserialized.account.market_index, 0);
135        assert_eq!(deserialized.last_updated_slot, 12345);
136    }
137
138    #[test]
139    fn spot_market_ignores_unknown_fields() {
140        let json = r#"{"market_index":1,"some_future_field":"value","decimals":6,"cumulative_deposit_interest":0,"cumulative_borrow_interest":0,"initial_asset_weight":8000,"initial_liability_weight":12000,"imf_factor":0,"scale_initial_asset_weight_start":0}"#;
141        // Default serde behavior: unknown fields are ignored for structs without deny_unknown_fields
142        let market: SpotMarket = serde_json::from_str(json).unwrap();
143        assert_eq!(market.market_index, 1);
144    }
145}