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