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#[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 #[expect(deprecated)]
48 fees: Option<Fees>,
49 #[expect(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 #[expect(deprecated)]
62 pub fn set_sysvar_for_tests<T: Serialize + 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 wincode::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(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 #[expect(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 #[expect(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) = wincode::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 #[expect(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 #[expect(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: SysvarId>(
287 instruction_context: &InstructionContext,
288 instruction_account_index: IndexOfAccount,
289 ) -> Result<(), InstructionError> {
290 if !S::check_id(
291 instruction_context.get_key_of_instruction_account(instruction_account_index)?,
292 ) {
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>(instruction_context, instruction_account_index)?;
304 invoke_context.environment_config.sysvar_cache().get_clock()
305 }
306
307 pub fn rent(
308 invoke_context: &InvokeContext,
309 instruction_context: &InstructionContext,
310 instruction_account_index: IndexOfAccount,
311 ) -> Result<Arc<Rent>, InstructionError> {
312 check_sysvar_account::<Rent>(instruction_context, instruction_account_index)?;
313 invoke_context.environment_config.sysvar_cache().get_rent()
314 }
315
316 pub fn slot_hashes(
317 invoke_context: &InvokeContext,
318 instruction_context: &InstructionContext,
319 instruction_account_index: IndexOfAccount,
320 ) -> Result<Arc<SlotHashes>, InstructionError> {
321 check_sysvar_account::<SlotHashes>(instruction_context, instruction_account_index)?;
322 invoke_context
323 .environment_config
324 .sysvar_cache()
325 .get_slot_hashes()
326 }
327
328 #[expect(deprecated)]
329 pub fn recent_blockhashes(
330 invoke_context: &InvokeContext,
331 instruction_context: &InstructionContext,
332 instruction_account_index: IndexOfAccount,
333 ) -> Result<Arc<RecentBlockhashes>, InstructionError> {
334 check_sysvar_account::<RecentBlockhashes>(instruction_context, instruction_account_index)?;
335 invoke_context
336 .environment_config
337 .sysvar_cache()
338 .get_recent_blockhashes()
339 }
340
341 pub fn stake_history(
342 invoke_context: &InvokeContext,
343 instruction_context: &InstructionContext,
344 instruction_account_index: IndexOfAccount,
345 ) -> Result<Arc<StakeHistory>, InstructionError> {
346 check_sysvar_account::<StakeHistory>(instruction_context, instruction_account_index)?;
347 invoke_context
348 .environment_config
349 .sysvar_cache()
350 .get_stake_history()
351 }
352
353 pub fn last_restart_slot(
354 invoke_context: &InvokeContext,
355 instruction_context: &InstructionContext,
356 instruction_account_index: IndexOfAccount,
357 ) -> Result<Arc<LastRestartSlot>, InstructionError> {
358 check_sysvar_account::<LastRestartSlot>(instruction_context, instruction_account_index)?;
359 invoke_context
360 .environment_config
361 .sysvar_cache()
362 .get_last_restart_slot()
363 }
364}
365
366#[cfg(test)]
367mod tests {
368 use {
369 super::*, solana_stake_history::SIZE as STAKE_HISTORY_ACCOUNT_SIZE, test_case::test_case,
370 };
371
372 #[test_case(Clock::default(), 40; "clock")]
380 #[test_case(EpochSchedule::default(), 33; "epoch_schedule")]
381 #[test_case(EpochRewards::default(), 81; "epoch_rewards")]
382 #[test_case(Rent::default(), 17; "rent")]
383 #[test_case(SlotHashes::default(), 20_488; "slot_hashes")]
384 #[test_case(StakeHistory::default(), STAKE_HISTORY_ACCOUNT_SIZE; "stake_history")]
385 #[test_case(LastRestartSlot::default(), 8; "last_restart_slot")]
386 fn test_sysvar_cache_preserves_bytes<T: SysvarId>(_: T, account_size: usize) {
387 let id = T::id();
388 let account_size = account_size.saturating_mul(2);
389 let in_buf = vec![0; account_size];
390
391 let mut sysvar_cache = SysvarCache::default();
392 sysvar_cache.fill_missing_entries(|pubkey, callback| {
393 if *pubkey == id {
394 callback(&in_buf)
395 }
396 });
397 let sysvar_cache = sysvar_cache;
398
399 let out_buf = sysvar_cache.sysvar_id_to_buffer(&id).clone().unwrap();
400
401 assert_eq!(out_buf, in_buf);
402 }
403}