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