1use bincode::deserialize;
2use bv::BitVec;
3use serde::Deserialize;
4use serde::Serialize;
5use solana_clock::Clock;
6use solana_clock::Epoch;
7use solana_clock::Slot;
8use solana_clock::UnixTimestamp;
9use solana_epoch_schedule::EpochSchedule;
10use solana_pubkey::Pubkey;
11use solana_rent::Rent;
12use solana_sdk_ids::sysvar;
13use solana_slot_hashes::SlotHashes;
14use solana_slot_history::SlotHistory;
15use solana_slot_history::{self as slot_history};
16use solana_sysvar::epoch_rewards::EpochRewards;
17#[allow(deprecated)]
18use solana_sysvar::fees::Fees;
19use solana_sysvar::last_restart_slot::LastRestartSlot;
20#[allow(deprecated)]
21use solana_sysvar::recent_blockhashes::RecentBlockhashes;
22use solana_sysvar::rewards::Rewards;
23use solana_sysvar::stake_history::StakeHistory;
24use solana_sysvar::stake_history::StakeHistoryEntry;
25
26use crate::StringAmount;
27use crate::UiFeeCalculator;
28use crate::parse_account_data::ParsableAccount;
29use crate::parse_account_data::ParseAccountError;
30
31pub fn parse_sysvar(data: &[u8], pubkey: &Pubkey) -> Result<SysvarAccountType, ParseAccountError> {
32 #[allow(deprecated)]
33 let parsed_account = {
34 if pubkey == &sysvar::clock::id() {
35 deserialize::<Clock>(data)
36 .ok()
37 .map(|clock| SysvarAccountType::Clock(clock.into()))
38 } else if pubkey == &sysvar::epoch_schedule::id() {
39 deserialize(data).ok().map(SysvarAccountType::EpochSchedule)
40 } else if pubkey == &sysvar::fees::id() {
41 deserialize::<Fees>(data)
42 .ok()
43 .map(|fees| SysvarAccountType::Fees(fees.into()))
44 } else if pubkey == &sysvar::recent_blockhashes::id() {
45 deserialize::<RecentBlockhashes>(data)
46 .ok()
47 .map(|recent_blockhashes| {
48 let recent_blockhashes = recent_blockhashes
49 .iter()
50 .map(|entry| {
51 UiRecentBlockhashesEntry {
52 blockhash: entry.blockhash.to_string(),
53 fee_calculator: entry.fee_calculator.into(),
54 }
55 })
56 .collect();
57 SysvarAccountType::RecentBlockhashes(recent_blockhashes)
58 })
59 } else if pubkey == &sysvar::rent::id() {
60 deserialize::<Rent>(data)
61 .ok()
62 .map(|rent| SysvarAccountType::Rent(rent.into()))
63 } else if pubkey == &sysvar::rewards::id() {
64 deserialize::<Rewards>(data)
65 .ok()
66 .map(|rewards| SysvarAccountType::Rewards(rewards.into()))
67 } else if pubkey == &sysvar::slot_hashes::id() {
68 deserialize::<SlotHashes>(data).ok().map(|slot_hashes| {
69 let slot_hashes = slot_hashes
70 .iter()
71 .map(|slot_hash| {
72 UiSlotHashEntry {
73 slot: slot_hash.0,
74 hash: slot_hash.1.to_string(),
75 }
76 })
77 .collect();
78 SysvarAccountType::SlotHashes(slot_hashes)
79 })
80 } else if pubkey == &sysvar::slot_history::id() {
81 deserialize::<SlotHistory>(data).ok().map(|slot_history| {
82 SysvarAccountType::SlotHistory(UiSlotHistory {
83 next_slot: slot_history.next_slot,
84 bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
85 })
86 })
87 } else if pubkey == &sysvar::stake_history::id() {
88 deserialize::<StakeHistory>(data).ok().map(|stake_history| {
89 let stake_history = stake_history
90 .iter()
91 .map(|entry| {
92 UiStakeHistoryEntry {
93 epoch: entry.0,
94 stake_history: entry.1.clone(),
95 }
96 })
97 .collect();
98 SysvarAccountType::StakeHistory(stake_history)
99 })
100 } else if pubkey == &sysvar::last_restart_slot::id() {
101 deserialize::<LastRestartSlot>(data)
102 .ok()
103 .map(|last_restart_slot| {
104 let last_restart_slot = last_restart_slot.last_restart_slot;
105 SysvarAccountType::LastRestartSlot(UiLastRestartSlot { last_restart_slot })
106 })
107 } else if pubkey == &sysvar::epoch_rewards::id() {
108 deserialize::<EpochRewards>(data)
109 .ok()
110 .map(|epoch_rewards| SysvarAccountType::EpochRewards(epoch_rewards.into()))
111 } else {
112 None
113 }
114 };
115 parsed_account.ok_or(ParseAccountError::AccountNotParsable(
116 ParsableAccount::Sysvar,
117 ))
118}
119
120#[derive(Debug, Serialize, Deserialize, PartialEq)]
121#[serde(rename_all = "camelCase", tag = "type", content = "info")]
122pub enum SysvarAccountType {
123 Clock(UiClock),
124 EpochSchedule(EpochSchedule),
125 #[allow(deprecated)]
126 Fees(UiFees),
127 #[allow(deprecated)]
128 RecentBlockhashes(Vec<UiRecentBlockhashesEntry>),
129 Rent(UiRent),
130 Rewards(UiRewards),
131 SlotHashes(Vec<UiSlotHashEntry>),
132 SlotHistory(UiSlotHistory),
133 StakeHistory(Vec<UiStakeHistoryEntry>),
134 LastRestartSlot(UiLastRestartSlot),
135 EpochRewards(UiEpochRewards),
136}
137
138#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
139#[serde(rename_all = "camelCase")]
140pub struct UiClock {
141 pub slot: Slot,
142 pub epoch: Epoch,
143 pub epoch_start_timestamp: UnixTimestamp,
144 pub leader_schedule_epoch: Epoch,
145 pub unix_timestamp: UnixTimestamp,
146}
147
148impl From<Clock> for UiClock {
149 fn from(clock: Clock) -> Self {
150 Self {
151 slot: clock.slot,
152 epoch: clock.epoch,
153 epoch_start_timestamp: clock.epoch_start_timestamp,
154 leader_schedule_epoch: clock.leader_schedule_epoch,
155 unix_timestamp: clock.unix_timestamp,
156 }
157 }
158}
159
160#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
161#[serde(rename_all = "camelCase")]
162pub struct UiFees {
163 pub fee_calculator: UiFeeCalculator,
164}
165#[allow(deprecated)]
166impl From<Fees> for UiFees {
167 fn from(fees: Fees) -> Self {
168 Self {
169 fee_calculator: fees.fee_calculator.into(),
170 }
171 }
172}
173
174#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
175#[serde(rename_all = "camelCase")]
176pub struct UiRent {
177 pub lamports_per_byte_year: StringAmount,
178 pub exemption_threshold: f64,
179 pub burn_percent: u8,
180}
181
182impl From<Rent> for UiRent {
183 fn from(rent: Rent) -> Self {
184 Self {
185 lamports_per_byte_year: rent.lamports_per_byte_year.to_string(),
186 exemption_threshold: rent.exemption_threshold,
187 burn_percent: rent.burn_percent,
188 }
189 }
190}
191
192#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
193#[serde(rename_all = "camelCase")]
194pub struct UiRewards {
195 pub validator_point_value: f64,
196}
197
198impl From<Rewards> for UiRewards {
199 fn from(rewards: Rewards) -> Self {
200 Self {
201 validator_point_value: rewards.validator_point_value,
202 }
203 }
204}
205
206#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
207#[serde(rename_all = "camelCase")]
208pub struct UiRecentBlockhashesEntry {
209 pub blockhash: String,
210 pub fee_calculator: UiFeeCalculator,
211}
212
213#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
214#[serde(rename_all = "camelCase")]
215pub struct UiSlotHashEntry {
216 pub slot: Slot,
217 pub hash: String,
218}
219
220#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
221#[serde(rename_all = "camelCase")]
222pub struct UiSlotHistory {
223 pub next_slot: Slot,
224 pub bits: String,
225}
226
227struct SlotHistoryBits(BitVec<u64>);
228
229impl std::fmt::Debug for SlotHistoryBits {
230 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231 for i in 0..slot_history::MAX_ENTRIES {
232 if self.0.get(i) {
233 write!(f, "1")?;
234 } else {
235 write!(f, "0")?;
236 }
237 }
238 Ok(())
239 }
240}
241
242#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
243#[serde(rename_all = "camelCase")]
244pub struct UiStakeHistoryEntry {
245 pub epoch: Epoch,
246 pub stake_history: StakeHistoryEntry,
247}
248
249#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
250#[serde(rename_all = "camelCase")]
251pub struct UiLastRestartSlot {
252 pub last_restart_slot: Slot,
253}
254
255#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
256#[serde(rename_all = "camelCase")]
257pub struct UiEpochRewards {
258 pub distribution_starting_block_height: u64,
259 pub num_partitions: u64,
260 pub parent_blockhash: String,
261 pub total_points: String,
262 pub total_rewards: String,
263 pub distributed_rewards: String,
264 pub active: bool,
265}
266
267impl From<EpochRewards> for UiEpochRewards {
268 fn from(epoch_rewards: EpochRewards) -> Self {
269 Self {
270 distribution_starting_block_height: epoch_rewards.distribution_starting_block_height,
271 num_partitions: epoch_rewards.num_partitions,
272 parent_blockhash: epoch_rewards.parent_blockhash.to_string(),
273 total_points: epoch_rewards.total_points.to_string(),
274 total_rewards: epoch_rewards.total_rewards.to_string(),
275 distributed_rewards: epoch_rewards.distributed_rewards.to_string(),
276 active: epoch_rewards.active,
277 }
278 }
279}
280
281#[cfg(test)]
282mod test {
283 use solana_account::create_account_for_test;
284 use solana_fee_calculator::FeeCalculator;
285 use solana_hash::Hash;
286 #[allow(deprecated)]
287 use solana_sysvar::recent_blockhashes::IterItem;
288
289 use super::*;
290
291 #[test]
292 fn test_parse_sysvars() {
293 let hash = Hash::new_from_array([1; 32]);
294
295 let clock_sysvar = create_account_for_test(&Clock::default());
296 assert_eq!(
297 parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(),
298 SysvarAccountType::Clock(UiClock::default()),
299 );
300
301 let epoch_schedule = EpochSchedule {
302 slots_per_epoch: 12,
303 leader_schedule_slot_offset: 0,
304 warmup: false,
305 first_normal_epoch: 1,
306 first_normal_slot: 12,
307 };
308 let epoch_schedule_sysvar = create_account_for_test(&epoch_schedule);
309 assert_eq!(
310 parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(),
311 SysvarAccountType::EpochSchedule(epoch_schedule),
312 );
313
314 #[allow(deprecated)]
315 {
316 let fees_sysvar = create_account_for_test(&Fees::default());
317 assert_eq!(
318 parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(),
319 SysvarAccountType::Fees(UiFees::default()),
320 );
321
322 let recent_blockhashes: RecentBlockhashes =
323 vec![IterItem(0, &hash, 10)].into_iter().collect();
324 let recent_blockhashes_sysvar = create_account_for_test(&recent_blockhashes);
325 assert_eq!(
326 parse_sysvar(
327 &recent_blockhashes_sysvar.data,
328 &sysvar::recent_blockhashes::id()
329 )
330 .unwrap(),
331 SysvarAccountType::RecentBlockhashes(vec![UiRecentBlockhashesEntry {
332 blockhash: hash.to_string(),
333 fee_calculator: FeeCalculator::new(10).into(),
334 }]),
335 );
336 }
337
338 let rent = Rent {
339 lamports_per_byte_year: 10,
340 exemption_threshold: 2.0,
341 burn_percent: 5,
342 };
343 let rent_sysvar = create_account_for_test(&rent);
344 assert_eq!(
345 parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(),
346 SysvarAccountType::Rent(rent.into()),
347 );
348
349 let rewards_sysvar = create_account_for_test(&Rewards::default());
350 assert_eq!(
351 parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(),
352 SysvarAccountType::Rewards(UiRewards::default()),
353 );
354
355 let mut slot_hashes = SlotHashes::default();
356 slot_hashes.add(1, hash);
357 let slot_hashes_sysvar = create_account_for_test(&slot_hashes);
358 assert_eq!(
359 parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(),
360 SysvarAccountType::SlotHashes(vec![UiSlotHashEntry {
361 slot: 1,
362 hash: hash.to_string(),
363 }]),
364 );
365
366 let mut slot_history = SlotHistory::default();
367 slot_history.add(42);
368 let slot_history_sysvar = create_account_for_test(&slot_history);
369 assert_eq!(
370 parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(),
371 SysvarAccountType::SlotHistory(UiSlotHistory {
372 next_slot: slot_history.next_slot,
373 bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
374 }),
375 );
376
377 let mut stake_history = StakeHistory::default();
378 let stake_history_entry = StakeHistoryEntry {
379 effective: 10,
380 activating: 2,
381 deactivating: 3,
382 };
383 stake_history.add(1, stake_history_entry.clone());
384 let stake_history_sysvar = create_account_for_test(&stake_history);
385 assert_eq!(
386 parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(),
387 SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry {
388 epoch: 1,
389 stake_history: stake_history_entry,
390 }]),
391 );
392
393 let bad_pubkey = solana_pubkey::new_rand();
394 assert!(parse_sysvar(&stake_history_sysvar.data, &bad_pubkey).is_err());
395
396 let bad_data = vec![0; 4];
397 assert!(parse_sysvar(&bad_data, &sysvar::stake_history::id()).is_err());
398
399 let last_restart_slot = LastRestartSlot {
400 last_restart_slot: 1282,
401 };
402 let last_restart_slot_account = create_account_for_test(&last_restart_slot);
403 assert_eq!(
404 parse_sysvar(
405 &last_restart_slot_account.data,
406 &sysvar::last_restart_slot::id()
407 )
408 .unwrap(),
409 SysvarAccountType::LastRestartSlot(UiLastRestartSlot {
410 last_restart_slot: 1282
411 })
412 );
413
414 let epoch_rewards = EpochRewards {
415 distribution_starting_block_height: 42,
416 total_rewards: 100,
417 distributed_rewards: 20,
418 active: true,
419 ..EpochRewards::default()
420 };
421 let epoch_rewards_sysvar = create_account_for_test(&epoch_rewards);
422 assert_eq!(
423 parse_sysvar(&epoch_rewards_sysvar.data, &sysvar::epoch_rewards::id()).unwrap(),
424 SysvarAccountType::EpochRewards(epoch_rewards.into()),
425 );
426 }
427}