gemachain_account_decoder/
parse_sysvar.rs

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