solana_account_decoder_wasm/
parse_sysvar.rs

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