1#[allow(deprecated)]
2use solana_sysvar::{fees::Fees, recent_blockhashes::RecentBlockhashes};
3use {
4 crate::invoke_context::InvokeContext,
5 serde::de::DeserializeOwned,
6 solana_clock::Clock,
7 solana_epoch_rewards::EpochRewards,
8 solana_epoch_schedule::EpochSchedule,
9 solana_instruction::error::InstructionError,
10 solana_last_restart_slot::LastRestartSlot,
11 solana_pubkey::Pubkey,
12 solana_rent::Rent,
13 solana_sdk_ids::sysvar,
14 solana_slot_hashes::SlotHashes,
15 solana_sysvar::{stake_history::StakeHistory, Sysvar},
16 solana_sysvar_id::SysvarId,
17 solana_transaction_context::{IndexOfAccount, InstructionContext, TransactionContext},
18 solana_type_overrides::sync::Arc,
19};
20
21#[cfg(feature = "frozen-abi")]
22impl ::solana_frozen_abi::abi_example::AbiExample for SysvarCache {
23 fn example() -> Self {
24 SysvarCache::default()
26 }
27}
28
29#[derive(Default, Clone, Debug)]
30pub struct SysvarCache {
31 clock: Option<Vec<u8>>,
33 epoch_schedule: Option<Vec<u8>>,
34 epoch_rewards: Option<Vec<u8>>,
35 rent: Option<Vec<u8>>,
36 slot_hashes: Option<Vec<u8>>,
37 stake_history: Option<Vec<u8>>,
38 last_restart_slot: Option<Vec<u8>>,
39
40 slot_hashes_obj: Option<Arc<SlotHashes>>,
44 stake_history_obj: Option<Arc<StakeHistory>>,
45
46 #[allow(deprecated)]
48 fees: Option<Fees>,
49 #[allow(deprecated)]
50 recent_blockhashes: Option<RecentBlockhashes>,
51}
52
53const FEES_ID: Pubkey = Pubkey::from_str_const("SysvarFees111111111111111111111111111111111");
56const RECENT_BLOCKHASHES_ID: Pubkey =
57 Pubkey::from_str_const("SysvarRecentB1ockHashes11111111111111111111");
58
59impl SysvarCache {
60 #[allow(deprecated)]
62 pub fn set_sysvar_for_tests<T: Sysvar + SysvarId>(&mut self, sysvar: &T) {
63 let data = bincode::serialize(sysvar).expect("Failed to serialize sysvar.");
64 let sysvar_id = T::id();
65 match sysvar_id {
66 sysvar::clock::ID => {
67 self.clock = Some(data);
68 }
69 sysvar::epoch_rewards::ID => {
70 self.epoch_rewards = Some(data);
71 }
72 sysvar::epoch_schedule::ID => {
73 self.epoch_schedule = Some(data);
74 }
75 FEES_ID => {
76 let fees: Fees =
77 bincode::deserialize(&data).expect("Failed to deserialize Fees sysvar.");
78 self.fees = Some(fees);
79 }
80 sysvar::last_restart_slot::ID => {
81 self.last_restart_slot = Some(data);
82 }
83 RECENT_BLOCKHASHES_ID => {
84 let recent_blockhashes: RecentBlockhashes = bincode::deserialize(&data)
85 .expect("Failed to deserialize RecentBlockhashes sysvar.");
86 self.recent_blockhashes = Some(recent_blockhashes);
87 }
88 sysvar::rent::ID => {
89 self.rent = Some(data);
90 }
91 sysvar::slot_hashes::ID => {
92 let slot_hashes: SlotHashes =
93 bincode::deserialize(&data).expect("Failed to deserialize SlotHashes sysvar.");
94 self.slot_hashes = Some(data);
95 self.slot_hashes_obj = Some(Arc::new(slot_hashes));
96 }
97 sysvar::stake_history::ID => {
98 let stake_history: StakeHistory = bincode::deserialize(&data)
99 .expect("Failed to deserialize StakeHistory sysvar.");
100 self.stake_history = Some(data);
101 self.stake_history_obj = Some(Arc::new(stake_history));
102 }
103 _ => panic!("Unrecognized Sysvar ID: {sysvar_id}"),
104 }
105 }
106
107 pub fn sysvar_id_to_buffer(&self, sysvar_id: &Pubkey) -> &Option<Vec<u8>> {
109 if Clock::check_id(sysvar_id) {
110 &self.clock
111 } else if EpochSchedule::check_id(sysvar_id) {
112 &self.epoch_schedule
113 } else if EpochRewards::check_id(sysvar_id) {
114 &self.epoch_rewards
115 } else if Rent::check_id(sysvar_id) {
116 &self.rent
117 } else if SlotHashes::check_id(sysvar_id) {
118 &self.slot_hashes
119 } else if StakeHistory::check_id(sysvar_id) {
120 &self.stake_history
121 } else if LastRestartSlot::check_id(sysvar_id) {
122 &self.last_restart_slot
123 } else {
124 &None
125 }
126 }
127
128 fn get_sysvar_obj<T: DeserializeOwned>(
131 &self,
132 sysvar_id: &Pubkey,
133 ) -> Result<Arc<T>, InstructionError> {
134 if let Some(ref sysvar_buf) = self.sysvar_id_to_buffer(sysvar_id) {
135 bincode::deserialize(sysvar_buf)
136 .map(Arc::new)
137 .map_err(|_| InstructionError::UnsupportedSysvar)
138 } else {
139 Err(InstructionError::UnsupportedSysvar)
140 }
141 }
142
143 pub fn get_clock(&self) -> Result<Arc<Clock>, InstructionError> {
144 self.get_sysvar_obj(&Clock::id())
145 }
146
147 pub fn get_epoch_schedule(&self) -> Result<Arc<EpochSchedule>, InstructionError> {
148 self.get_sysvar_obj(&EpochSchedule::id())
149 }
150
151 pub fn get_epoch_rewards(&self) -> Result<Arc<EpochRewards>, InstructionError> {
152 self.get_sysvar_obj(&EpochRewards::id())
153 }
154
155 pub fn get_rent(&self) -> Result<Arc<Rent>, InstructionError> {
156 self.get_sysvar_obj(&Rent::id())
157 }
158
159 pub fn get_last_restart_slot(&self) -> Result<Arc<LastRestartSlot>, InstructionError> {
160 self.get_sysvar_obj(&LastRestartSlot::id())
161 }
162
163 pub fn get_stake_history(&self) -> Result<Arc<StakeHistory>, InstructionError> {
164 self.stake_history_obj
165 .clone()
166 .ok_or(InstructionError::UnsupportedSysvar)
167 }
168
169 pub fn get_slot_hashes(&self) -> Result<Arc<SlotHashes>, InstructionError> {
170 self.slot_hashes_obj
171 .clone()
172 .ok_or(InstructionError::UnsupportedSysvar)
173 }
174
175 #[deprecated]
176 #[allow(deprecated)]
177 pub fn get_fees(&self) -> Result<Arc<Fees>, InstructionError> {
178 self.fees
179 .clone()
180 .ok_or(InstructionError::UnsupportedSysvar)
181 .map(Arc::new)
182 }
183
184 #[deprecated]
185 #[allow(deprecated)]
186 pub fn get_recent_blockhashes(&self) -> Result<Arc<RecentBlockhashes>, InstructionError> {
187 self.recent_blockhashes
188 .clone()
189 .ok_or(InstructionError::UnsupportedSysvar)
190 .map(Arc::new)
191 }
192
193 pub fn fill_missing_entries<F: FnMut(&Pubkey, &mut dyn FnMut(&[u8]))>(
194 &mut self,
195 mut get_account_data: F,
196 ) {
197 if self.clock.is_none() {
198 get_account_data(&Clock::id(), &mut |data: &[u8]| {
199 if bincode::deserialize::<Clock>(data).is_ok() {
200 self.clock = Some(data.to_vec());
201 }
202 });
203 }
204
205 if self.epoch_schedule.is_none() {
206 get_account_data(&EpochSchedule::id(), &mut |data: &[u8]| {
207 if bincode::deserialize::<EpochSchedule>(data).is_ok() {
208 self.epoch_schedule = Some(data.to_vec());
209 }
210 });
211 }
212
213 if self.epoch_rewards.is_none() {
214 get_account_data(&EpochRewards::id(), &mut |data: &[u8]| {
215 if bincode::deserialize::<EpochRewards>(data).is_ok() {
216 self.epoch_rewards = Some(data.to_vec());
217 }
218 });
219 }
220
221 if self.rent.is_none() {
222 get_account_data(&Rent::id(), &mut |data: &[u8]| {
223 if bincode::deserialize::<Rent>(data).is_ok() {
224 self.rent = Some(data.to_vec());
225 }
226 });
227 }
228
229 if self.slot_hashes.is_none() {
230 get_account_data(&SlotHashes::id(), &mut |data: &[u8]| {
231 if let Ok(obj) = bincode::deserialize::<SlotHashes>(data) {
232 self.slot_hashes = Some(data.to_vec());
233 self.slot_hashes_obj = Some(Arc::new(obj));
234 }
235 });
236 }
237
238 if self.stake_history.is_none() {
239 get_account_data(&StakeHistory::id(), &mut |data: &[u8]| {
240 if let Ok(obj) = bincode::deserialize::<StakeHistory>(data) {
241 self.stake_history = Some(data.to_vec());
242 self.stake_history_obj = Some(Arc::new(obj));
243 }
244 });
245 }
246
247 if self.last_restart_slot.is_none() {
248 get_account_data(&LastRestartSlot::id(), &mut |data: &[u8]| {
249 if bincode::deserialize::<LastRestartSlot>(data).is_ok() {
250 self.last_restart_slot = Some(data.to_vec());
251 }
252 });
253 }
254
255 #[allow(deprecated)]
256 if self.fees.is_none() {
257 get_account_data(&Fees::id(), &mut |data: &[u8]| {
258 if let Ok(fees) = bincode::deserialize(data) {
259 self.fees = Some(fees);
260 }
261 });
262 }
263
264 #[allow(deprecated)]
265 if self.recent_blockhashes.is_none() {
266 get_account_data(&RecentBlockhashes::id(), &mut |data: &[u8]| {
267 if let Ok(recent_blockhashes) = bincode::deserialize(data) {
268 self.recent_blockhashes = Some(recent_blockhashes);
269 }
270 });
271 }
272 }
273
274 pub fn reset(&mut self) {
275 *self = Self::default();
276 }
277}
278
279pub mod get_sysvar_with_account_check {
284 use super::*;
285
286 fn check_sysvar_account<S: Sysvar>(
287 transaction_context: &TransactionContext,
288 instruction_context: &InstructionContext,
289 instruction_account_index: IndexOfAccount,
290 ) -> Result<(), InstructionError> {
291 let index_in_transaction = instruction_context
292 .get_index_of_instruction_account_in_transaction(instruction_account_index)?;
293 if !S::check_id(transaction_context.get_key_of_account_at_index(index_in_transaction)?) {
294 return Err(InstructionError::InvalidArgument);
295 }
296 Ok(())
297 }
298
299 pub fn clock(
300 invoke_context: &InvokeContext,
301 instruction_context: &InstructionContext,
302 instruction_account_index: IndexOfAccount,
303 ) -> Result<Arc<Clock>, InstructionError> {
304 check_sysvar_account::<Clock>(
305 invoke_context.transaction_context,
306 instruction_context,
307 instruction_account_index,
308 )?;
309 invoke_context.get_sysvar_cache().get_clock()
310 }
311
312 pub fn rent(
313 invoke_context: &InvokeContext,
314 instruction_context: &InstructionContext,
315 instruction_account_index: IndexOfAccount,
316 ) -> Result<Arc<Rent>, InstructionError> {
317 check_sysvar_account::<Rent>(
318 invoke_context.transaction_context,
319 instruction_context,
320 instruction_account_index,
321 )?;
322 invoke_context.get_sysvar_cache().get_rent()
323 }
324
325 pub fn slot_hashes(
326 invoke_context: &InvokeContext,
327 instruction_context: &InstructionContext,
328 instruction_account_index: IndexOfAccount,
329 ) -> Result<Arc<SlotHashes>, InstructionError> {
330 check_sysvar_account::<SlotHashes>(
331 invoke_context.transaction_context,
332 instruction_context,
333 instruction_account_index,
334 )?;
335 invoke_context.get_sysvar_cache().get_slot_hashes()
336 }
337
338 #[allow(deprecated)]
339 pub fn recent_blockhashes(
340 invoke_context: &InvokeContext,
341 instruction_context: &InstructionContext,
342 instruction_account_index: IndexOfAccount,
343 ) -> Result<Arc<RecentBlockhashes>, InstructionError> {
344 check_sysvar_account::<RecentBlockhashes>(
345 invoke_context.transaction_context,
346 instruction_context,
347 instruction_account_index,
348 )?;
349 invoke_context.get_sysvar_cache().get_recent_blockhashes()
350 }
351
352 pub fn stake_history(
353 invoke_context: &InvokeContext,
354 instruction_context: &InstructionContext,
355 instruction_account_index: IndexOfAccount,
356 ) -> Result<Arc<StakeHistory>, InstructionError> {
357 check_sysvar_account::<StakeHistory>(
358 invoke_context.transaction_context,
359 instruction_context,
360 instruction_account_index,
361 )?;
362 invoke_context.get_sysvar_cache().get_stake_history()
363 }
364
365 pub fn last_restart_slot(
366 invoke_context: &InvokeContext,
367 instruction_context: &InstructionContext,
368 instruction_account_index: IndexOfAccount,
369 ) -> Result<Arc<LastRestartSlot>, InstructionError> {
370 check_sysvar_account::<LastRestartSlot>(
371 invoke_context.transaction_context,
372 instruction_context,
373 instruction_account_index,
374 )?;
375 invoke_context.get_sysvar_cache().get_last_restart_slot()
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use {super::*, test_case::test_case};
382
383 #[test_case(Clock::default(); "clock")]
391 #[test_case(EpochSchedule::default(); "epoch_schedule")]
392 #[test_case(EpochRewards::default(); "epoch_rewards")]
393 #[test_case(Rent::default(); "rent")]
394 #[test_case(SlotHashes::default(); "slot_hashes")]
395 #[test_case(StakeHistory::default(); "stake_history")]
396 #[test_case(LastRestartSlot::default(); "last_restart_slot")]
397 fn test_sysvar_cache_preserves_bytes<T: Sysvar>(_: T) {
398 let id = T::id();
399 let size = T::size_of().saturating_mul(2);
400 let in_buf = vec![0; size];
401
402 let mut sysvar_cache = SysvarCache::default();
403 sysvar_cache.fill_missing_entries(|pubkey, callback| {
404 if *pubkey == id {
405 callback(&in_buf)
406 }
407 });
408 let sysvar_cache = sysvar_cache;
409
410 let out_buf = sysvar_cache.sysvar_id_to_buffer(&id).clone().unwrap();
411
412 assert_eq!(out_buf, in_buf);
413 }
414}