gemachain_account_decoder/
parse_sysvar.rs1use 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}