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