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