solana_account_decoder/
parse_sysvar.rs

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}