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 {
285 use super::*;
286
287 fn check_sysvar_account<S: Sysvar>(
288 transaction_context: &TransactionContext,
289 instruction_context: &InstructionContext,
290 instruction_account_index: IndexOfAccount,
291 ) -> Result<(), InstructionError> {
292 let index_in_transaction = instruction_context
293 .get_index_of_instruction_account_in_transaction(instruction_account_index)?;
294 if !S::check_id(transaction_context.get_key_of_account_at_index(index_in_transaction)?) {
295 return Err(InstructionError::InvalidArgument);
296 }
297 Ok(())
298 }
299
300 pub fn clock(
301 invoke_context: &InvokeContext,
302 instruction_context: &InstructionContext,
303 instruction_account_index: IndexOfAccount,
304 ) -> Result<Arc<Clock>, InstructionError> {
305 check_sysvar_account::<Clock>(
306 invoke_context.transaction_context,
307 instruction_context,
308 instruction_account_index,
309 )?;
310 invoke_context.get_sysvar_cache().get_clock()
311 }
312
313 pub fn rent(
314 invoke_context: &InvokeContext,
315 instruction_context: &InstructionContext,
316 instruction_account_index: IndexOfAccount,
317 ) -> Result<Arc<Rent>, InstructionError> {
318 check_sysvar_account::<Rent>(
319 invoke_context.transaction_context,
320 instruction_context,
321 instruction_account_index,
322 )?;
323 invoke_context.get_sysvar_cache().get_rent()
324 }
325
326 pub fn slot_hashes(
327 invoke_context: &InvokeContext,
328 instruction_context: &InstructionContext,
329 instruction_account_index: IndexOfAccount,
330 ) -> Result<Arc<SlotHashes>, InstructionError> {
331 check_sysvar_account::<SlotHashes>(
332 invoke_context.transaction_context,
333 instruction_context,
334 instruction_account_index,
335 )?;
336 invoke_context.get_sysvar_cache().get_slot_hashes()
337 }
338
339 #[allow(deprecated)]
340 pub fn recent_blockhashes(
341 invoke_context: &InvokeContext,
342 instruction_context: &InstructionContext,
343 instruction_account_index: IndexOfAccount,
344 ) -> Result<Arc<RecentBlockhashes>, InstructionError> {
345 check_sysvar_account::<RecentBlockhashes>(
346 invoke_context.transaction_context,
347 instruction_context,
348 instruction_account_index,
349 )?;
350 invoke_context.get_sysvar_cache().get_recent_blockhashes()
351 }
352
353 pub fn stake_history(
354 invoke_context: &InvokeContext,
355 instruction_context: &InstructionContext,
356 instruction_account_index: IndexOfAccount,
357 ) -> Result<Arc<StakeHistory>, InstructionError> {
358 check_sysvar_account::<StakeHistory>(
359 invoke_context.transaction_context,
360 instruction_context,
361 instruction_account_index,
362 )?;
363 invoke_context.get_sysvar_cache().get_stake_history()
364 }
365
366 pub fn last_restart_slot(
367 invoke_context: &InvokeContext,
368 instruction_context: &InstructionContext,
369 instruction_account_index: IndexOfAccount,
370 ) -> Result<Arc<LastRestartSlot>, InstructionError> {
371 check_sysvar_account::<LastRestartSlot>(
372 invoke_context.transaction_context,
373 instruction_context,
374 instruction_account_index,
375 )?;
376 invoke_context.get_sysvar_cache().get_last_restart_slot()
377 }
378}
379
380#[cfg(test)]
381mod tests {
382 use {super::*, test_case::test_case};
383
384 #[test_case(Clock::default(); "clock")]
392 #[test_case(EpochSchedule::default(); "epoch_schedule")]
393 #[test_case(EpochRewards::default(); "epoch_rewards")]
394 #[test_case(Rent::default(); "rent")]
395 #[test_case(SlotHashes::default(); "slot_hashes")]
396 #[test_case(StakeHistory::default(); "stake_history")]
397 #[test_case(LastRestartSlot::default(); "last_restart_slot")]
398 fn test_sysvar_cache_preserves_bytes<T: Sysvar>(_: T) {
399 let id = T::id();
400 let size = T::size_of().saturating_mul(2);
401 let in_buf = vec![0; size];
402
403 let mut sysvar_cache = SysvarCache::default();
404 sysvar_cache.fill_missing_entries(|pubkey, callback| {
405 if *pubkey == id {
406 callback(&in_buf)
407 }
408 });
409 let sysvar_cache = sysvar_cache;
410
411 let out_buf = sysvar_cache.sysvar_id_to_buffer(&id).clone().unwrap();
412
413 assert_eq!(out_buf, in_buf);
414 }
415}