1mod 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 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 assert_eq!(market.deposit_balance, 0);
48 }
49
50 #[test]
51 fn spot_market_missing_core_field_fails() {
52 let json = r#"{"market_index":1}"#;
54 let result = serde_json::from_str::<SpotMarket>(json);
55 assert!(result.is_err());
56
57 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_all_fields_required() {
96 let json = r#"{"owner":[1,2,3],"bump":1,"spend_limit_per_transaction":100,"spend_limit_per_timeframe":1000,"remaining_spend_limit_per_timeframe":500,"next_timeframe_reset_timestamp":12345,"timeframe_in_seconds":86400}"#;
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, 100);
100 assert_eq!(vault.timeframe_in_seconds, 86400);
101 }
102
103 #[test]
104 fn vault_missing_field_fails() {
105 let json = r#"{"owner":[1,2,3]}"#;
107 let result = serde_json::from_str::<Vault>(json);
108 assert!(result.is_err());
109 }
110
111 #[test]
112 fn time_lock_deserializes_snake_case() {
113 let zero_pubkey = serde_json::to_string(&solana_pubkey::Pubkey::default()).unwrap();
115 let json = format!(
116 r#"{{"owner":{pk},"payer":{pk},"release_slot":42}}"#,
117 pk = zero_pubkey
118 );
119 let tl: TimeLock = serde_json::from_str(&json).unwrap();
120 assert_eq!(tl.release_slot, 42);
121 }
122
123 #[test]
124 fn time_lock_serializes_camel_case() {
125 let tl = TimeLock {
126 owner: solana_pubkey::Pubkey::default(),
127 payer: solana_pubkey::Pubkey::default(),
128 release_slot: 42,
129 };
130 let json = serde_json::to_string(&tl).unwrap();
131 assert!(json.contains("releaseSlot"));
132 assert!(!json.contains("release_slot"));
133 }
134
135 #[test]
136 fn withdraw_order_deserializes_snake_case() {
137 let pk = serde_json::to_string(&solana_pubkey::Pubkey::default()).unwrap();
138 let json = format!(
139 concat!(
140 r#"{{"time_lock":{{"owner":{pk},"payer":{pk},"release_slot":100}},"#,
141 r#""amount_base_units":5000,"drift_market_index":0,"reduce_only":false,"#,
142 r#""destination":{pk}}}"#,
143 ),
144 pk = pk
145 );
146 let order: WithdrawOrderAccount = serde_json::from_str(&json).unwrap();
147 assert_eq!(order.amount_base_units, 5000);
148 assert_eq!(order.drift_market_index, 0);
149 }
150
151 #[test]
152 fn spot_market_roundtrip() {
153 let market = SpotMarket {
154 pubkey: vec![],
155 market_index: 1,
156 initial_asset_weight: 8000,
157 initial_liability_weight: 0,
158 imf_factor: 0,
159 scale_initial_asset_weight_start: 0,
160 decimals: 9,
161 cumulative_deposit_interest: 1_050_000_000_000,
162 cumulative_borrow_interest: 0,
163 deposit_balance: 0,
164 borrow_balance: 0,
165 optimal_utilization: 0,
166 optimal_borrow_rate: 0,
167 max_borrow_rate: 0,
168 min_borrow_rate: 0,
169 insurance_fund: InsuranceFund::default(),
170 historical_oracle_data: HistoricalOracleData::default(),
171 oracle: None,
172 };
173 let json = serde_json::to_string(&market).unwrap();
174 let deserialized: SpotMarket = serde_json::from_str(&json).unwrap();
175 assert_eq!(deserialized.market_index, 1);
176 assert_eq!(deserialized.cumulative_deposit_interest, 1_050_000_000_000);
177 }
178
179 #[test]
180 fn cache_spot_market_roundtrip() {
181 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}"#;
182 let deserialized: Cache<SpotMarket> = serde_json::from_str(json).unwrap();
183 assert_eq!(deserialized.account.market_index, 0);
184 assert_eq!(deserialized.last_updated_slot, 12345);
185 }
186
187 #[test]
188 fn spot_market_ignores_unknown_fields() {
189 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}"#;
190 let market: SpotMarket = serde_json::from_str(json).unwrap();
192 assert_eq!(market.market_index, 1);
193 }
194}