solana_account_decoder/
parse_sysvar.rs

1#[allow(deprecated)]
2use solana_sdk::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_sdk::{
11        clock::{Clock, Epoch, Slot, UnixTimestamp},
12        epoch_schedule::EpochSchedule,
13        pubkey::Pubkey,
14        rent::Rent,
15        slot_hashes::SlotHashes,
16        slot_history::{self, SlotHistory},
17        stake_history::{StakeHistory, StakeHistoryEntry},
18        sysvar::{
19            self, epoch_rewards::EpochRewards, last_restart_slot::LastRestartSlot, rewards::Rewards,
20        },
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            deserialize::<SlotHashes>(data).ok().map(|slot_hashes| {
60                let slot_hashes = slot_hashes
61                    .iter()
62                    .map(|slot_hash| UiSlotHashEntry {
63                        slot: slot_hash.0,
64                        hash: slot_hash.1.to_string(),
65                    })
66                    .collect();
67                SysvarAccountType::SlotHashes(slot_hashes)
68            })
69        } else if pubkey == &sysvar::slot_history::id() {
70            deserialize::<SlotHistory>(data).ok().map(|slot_history| {
71                SysvarAccountType::SlotHistory(UiSlotHistory {
72                    next_slot: slot_history.next_slot,
73                    bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
74                })
75            })
76        } else if pubkey == &sysvar::stake_history::id() {
77            deserialize::<StakeHistory>(data).ok().map(|stake_history| {
78                let stake_history = stake_history
79                    .iter()
80                    .map(|entry| UiStakeHistoryEntry {
81                        epoch: entry.0,
82                        stake_history: entry.1.clone(),
83                    })
84                    .collect();
85                SysvarAccountType::StakeHistory(stake_history)
86            })
87        } else if pubkey == &sysvar::last_restart_slot::id() {
88            deserialize::<LastRestartSlot>(data)
89                .ok()
90                .map(|last_restart_slot| {
91                    let last_restart_slot = last_restart_slot.last_restart_slot;
92                    SysvarAccountType::LastRestartSlot(UiLastRestartSlot { last_restart_slot })
93                })
94        } else if pubkey == &sysvar::epoch_rewards::id() {
95            deserialize::<EpochRewards>(data)
96                .ok()
97                .map(|epoch_rewards| SysvarAccountType::EpochRewards(epoch_rewards.into()))
98        } else {
99            None
100        }
101    };
102    parsed_account.ok_or(ParseAccountError::AccountNotParsable(
103        ParsableAccount::Sysvar,
104    ))
105}
106
107#[derive(Debug, Serialize, Deserialize, PartialEq)]
108#[serde(rename_all = "camelCase", tag = "type", content = "info")]
109pub enum SysvarAccountType {
110    Clock(UiClock),
111    EpochSchedule(EpochSchedule),
112    #[allow(deprecated)]
113    Fees(UiFees),
114    #[allow(deprecated)]
115    RecentBlockhashes(Vec<UiRecentBlockhashesEntry>),
116    Rent(UiRent),
117    Rewards(UiRewards),
118    SlotHashes(Vec<UiSlotHashEntry>),
119    SlotHistory(UiSlotHistory),
120    StakeHistory(Vec<UiStakeHistoryEntry>),
121    LastRestartSlot(UiLastRestartSlot),
122    EpochRewards(UiEpochRewards),
123}
124
125#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
126#[serde(rename_all = "camelCase")]
127pub struct UiClock {
128    pub slot: Slot,
129    pub epoch: Epoch,
130    pub epoch_start_timestamp: UnixTimestamp,
131    pub leader_schedule_epoch: Epoch,
132    pub unix_timestamp: UnixTimestamp,
133}
134
135impl From<Clock> for UiClock {
136    fn from(clock: Clock) -> Self {
137        Self {
138            slot: clock.slot,
139            epoch: clock.epoch,
140            epoch_start_timestamp: clock.epoch_start_timestamp,
141            leader_schedule_epoch: clock.leader_schedule_epoch,
142            unix_timestamp: clock.unix_timestamp,
143        }
144    }
145}
146
147#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Default)]
148#[serde(rename_all = "camelCase")]
149pub struct UiFees {
150    pub fee_calculator: UiFeeCalculator,
151}
152#[allow(deprecated)]
153impl From<Fees> for UiFees {
154    fn from(fees: Fees) -> Self {
155        Self {
156            fee_calculator: fees.fee_calculator.into(),
157        }
158    }
159}
160
161#[derive(Debug, Serialize, Deserialize, PartialEq, Default)]
162#[serde(rename_all = "camelCase")]
163pub struct UiRent {
164    pub lamports_per_byte_year: StringAmount,
165    pub exemption_threshold: f64,
166    pub burn_percent: u8,
167}
168
169impl From<Rent> for UiRent {
170    fn from(rent: Rent) -> Self {
171        Self {
172            lamports_per_byte_year: rent.lamports_per_byte_year.to_string(),
173            exemption_threshold: rent.exemption_threshold,
174            burn_percent: rent.burn_percent,
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_sdk::sysvar::recent_blockhashes::IterItem;
272    use {
273        super::*,
274        solana_sdk::{account::create_account_for_test, fee_calculator::FeeCalculator, hash::Hash},
275    };
276
277    #[test]
278    fn test_parse_sysvars() {
279        let hash = Hash::new(&[1; 32]);
280
281        let clock_sysvar = create_account_for_test(&Clock::default());
282        assert_eq!(
283            parse_sysvar(&clock_sysvar.data, &sysvar::clock::id()).unwrap(),
284            SysvarAccountType::Clock(UiClock::default()),
285        );
286
287        let epoch_schedule = EpochSchedule {
288            slots_per_epoch: 12,
289            leader_schedule_slot_offset: 0,
290            warmup: false,
291            first_normal_epoch: 1,
292            first_normal_slot: 12,
293        };
294        let epoch_schedule_sysvar = create_account_for_test(&epoch_schedule);
295        assert_eq!(
296            parse_sysvar(&epoch_schedule_sysvar.data, &sysvar::epoch_schedule::id()).unwrap(),
297            SysvarAccountType::EpochSchedule(epoch_schedule),
298        );
299
300        #[allow(deprecated)]
301        {
302            let fees_sysvar = create_account_for_test(&Fees::default());
303            assert_eq!(
304                parse_sysvar(&fees_sysvar.data, &sysvar::fees::id()).unwrap(),
305                SysvarAccountType::Fees(UiFees::default()),
306            );
307
308            let recent_blockhashes: RecentBlockhashes =
309                vec![IterItem(0, &hash, 10)].into_iter().collect();
310            let recent_blockhashes_sysvar = create_account_for_test(&recent_blockhashes);
311            assert_eq!(
312                parse_sysvar(
313                    &recent_blockhashes_sysvar.data,
314                    &sysvar::recent_blockhashes::id()
315                )
316                .unwrap(),
317                SysvarAccountType::RecentBlockhashes(vec![UiRecentBlockhashesEntry {
318                    blockhash: hash.to_string(),
319                    fee_calculator: FeeCalculator::new(10).into(),
320                }]),
321            );
322        }
323
324        let rent = Rent {
325            lamports_per_byte_year: 10,
326            exemption_threshold: 2.0,
327            burn_percent: 5,
328        };
329        let rent_sysvar = create_account_for_test(&rent);
330        assert_eq!(
331            parse_sysvar(&rent_sysvar.data, &sysvar::rent::id()).unwrap(),
332            SysvarAccountType::Rent(rent.into()),
333        );
334
335        let rewards_sysvar = create_account_for_test(&Rewards::default());
336        assert_eq!(
337            parse_sysvar(&rewards_sysvar.data, &sysvar::rewards::id()).unwrap(),
338            SysvarAccountType::Rewards(UiRewards::default()),
339        );
340
341        let mut slot_hashes = SlotHashes::default();
342        slot_hashes.add(1, hash);
343        let slot_hashes_sysvar = create_account_for_test(&slot_hashes);
344        assert_eq!(
345            parse_sysvar(&slot_hashes_sysvar.data, &sysvar::slot_hashes::id()).unwrap(),
346            SysvarAccountType::SlotHashes(vec![UiSlotHashEntry {
347                slot: 1,
348                hash: hash.to_string(),
349            }]),
350        );
351
352        let mut slot_history = SlotHistory::default();
353        slot_history.add(42);
354        let slot_history_sysvar = create_account_for_test(&slot_history);
355        assert_eq!(
356            parse_sysvar(&slot_history_sysvar.data, &sysvar::slot_history::id()).unwrap(),
357            SysvarAccountType::SlotHistory(UiSlotHistory {
358                next_slot: slot_history.next_slot,
359                bits: format!("{:?}", SlotHistoryBits(slot_history.bits)),
360            }),
361        );
362
363        let mut stake_history = StakeHistory::default();
364        let stake_history_entry = StakeHistoryEntry {
365            effective: 10,
366            activating: 2,
367            deactivating: 3,
368        };
369        stake_history.add(1, stake_history_entry.clone());
370        let stake_history_sysvar = create_account_for_test(&stake_history);
371        assert_eq!(
372            parse_sysvar(&stake_history_sysvar.data, &sysvar::stake_history::id()).unwrap(),
373            SysvarAccountType::StakeHistory(vec![UiStakeHistoryEntry {
374                epoch: 1,
375                stake_history: stake_history_entry,
376            }]),
377        );
378
379        let bad_pubkey = solana_sdk::pubkey::new_rand();
380        assert!(parse_sysvar(&stake_history_sysvar.data, &bad_pubkey).is_err());
381
382        let bad_data = vec![0; 4];
383        assert!(parse_sysvar(&bad_data, &sysvar::stake_history::id()).is_err());
384
385        let last_restart_slot = LastRestartSlot {
386            last_restart_slot: 1282,
387        };
388        let last_restart_slot_account = create_account_for_test(&last_restart_slot);
389        assert_eq!(
390            parse_sysvar(
391                &last_restart_slot_account.data,
392                &sysvar::last_restart_slot::id()
393            )
394            .unwrap(),
395            SysvarAccountType::LastRestartSlot(UiLastRestartSlot {
396                last_restart_slot: 1282
397            })
398        );
399
400        let epoch_rewards = EpochRewards {
401            distribution_starting_block_height: 42,
402            total_rewards: 100,
403            distributed_rewards: 20,
404            active: true,
405            ..EpochRewards::default()
406        };
407        let epoch_rewards_sysvar = create_account_for_test(&epoch_rewards);
408        assert_eq!(
409            parse_sysvar(&epoch_rewards_sysvar.data, &sysvar::epoch_rewards::id()).unwrap(),
410            SysvarAccountType::EpochRewards(epoch_rewards.into()),
411        );
412    }
413}