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