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