atlas_sysvar/
slot_hashes.rs1#[cfg(feature = "bytemuck")]
48use bytemuck_derive::{Pod, Zeroable};
49use {crate::Sysvar, atlas_clock::Slot, atlas_hash::Hash};
50#[cfg(feature = "bincode")]
51use {crate::SysvarSerialize, atlas_account_info::AccountInfo};
52
53#[cfg(feature = "bytemuck")]
54const U64_SIZE: usize = std::mem::size_of::<u64>();
55
56#[cfg(any(feature = "bytemuck", feature = "bincode"))]
57const SYSVAR_LEN: usize = 20_488; pub use {
60 atlas_sdk_ids::sysvar::slot_hashes::{check_id, id, ID},
61 atlas_slot_hashes::SlotHashes,
62 atlas_sysvar_id::SysvarId,
63};
64
65impl Sysvar for SlotHashes {}
66#[cfg(feature = "bincode")]
67impl SysvarSerialize for SlotHashes {
68 fn size_of() -> usize {
70 SYSVAR_LEN
72 }
73 fn from_account_info(
74 _account_info: &AccountInfo,
75 ) -> Result<Self, atlas_program_error::ProgramError> {
76 Err(atlas_program_error::ProgramError::UnsupportedSysvar)
78 }
79}
80
81#[cfg_attr(feature = "bytemuck", derive(Pod, Zeroable))]
83#[derive(Copy, Clone, Default)]
84#[repr(C)]
85pub struct PodSlotHash {
86 pub slot: Slot,
87 pub hash: Hash,
88}
89
90#[cfg(feature = "bytemuck")]
91#[derive(Default)]
96pub struct PodSlotHashes {
97 data: Vec<u8>,
98 slot_hashes_start: usize,
99 slot_hashes_end: usize,
100}
101
102#[cfg(feature = "bytemuck")]
103impl PodSlotHashes {
104 pub fn fetch() -> Result<Self, atlas_program_error::ProgramError> {
106 let sysvar_len = SYSVAR_LEN;
108 let mut data = vec![0; sysvar_len];
109
110 if data.as_ptr().align_offset(8) != 0 {
112 return Err(atlas_program_error::ProgramError::InvalidAccountData);
113 }
114
115 crate::get_sysvar(
118 &mut data,
119 &SlotHashes::id(),
120 0,
121 sysvar_len as u64,
122 )?;
123
124 let length = data
130 .get(..U64_SIZE)
131 .and_then(|bytes| bytes.try_into().ok())
132 .map(u64::from_le_bytes)
133 .and_then(|length| length.checked_mul(std::mem::size_of::<PodSlotHash>() as u64))
134 .ok_or(atlas_program_error::ProgramError::InvalidAccountData)?;
135
136 let slot_hashes_start = U64_SIZE;
137 let slot_hashes_end = slot_hashes_start.saturating_add(length as usize);
138
139 Ok(Self {
140 data,
141 slot_hashes_start,
142 slot_hashes_end,
143 })
144 }
145
146 pub fn as_slice(&self) -> Result<&[PodSlotHash], atlas_program_error::ProgramError> {
149 self.data
150 .get(self.slot_hashes_start..self.slot_hashes_end)
151 .and_then(|data| bytemuck::try_cast_slice(data).ok())
152 .ok_or(atlas_program_error::ProgramError::InvalidAccountData)
153 }
154
155 pub fn get(&self, slot: &Slot) -> Result<Option<Hash>, atlas_program_error::ProgramError> {
158 self.as_slice().map(|pod_hashes| {
159 pod_hashes
160 .binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
161 .map(|idx| pod_hashes[idx].hash)
162 .ok()
163 })
164 }
165
166 pub fn position(
169 &self,
170 slot: &Slot,
171 ) -> Result<Option<usize>, atlas_program_error::ProgramError> {
172 self.as_slice().map(|pod_hashes| {
173 pod_hashes
174 .binary_search_by(|PodSlotHash { slot: this, .. }| slot.cmp(this))
175 .ok()
176 })
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use {
183 super::*, crate::tests::mock_get_sysvar_syscall, serial_test::serial, atlas_hash::Hash,
184 atlas_sha256_hasher::hash, atlas_slot_hashes::MAX_ENTRIES, test_case::test_case,
185 };
186
187 #[test]
188 fn test_size_of() {
189 assert_eq!(
190 SlotHashes::size_of(),
191 bincode::serialized_size(
192 &(0..MAX_ENTRIES)
193 .map(|slot| (slot as Slot, Hash::default()))
194 .collect::<SlotHashes>()
195 )
196 .unwrap() as usize
197 );
198 }
199
200 fn mock_slot_hashes(slot_hashes: &SlotHashes) {
201 let mut data = vec![0; SlotHashes::size_of()];
203 bincode::serialize_into(&mut data[..], slot_hashes).unwrap();
204 mock_get_sysvar_syscall(&data);
205 }
206
207 #[test_case(0)]
208 #[test_case(1)]
209 #[test_case(2)]
210 #[test_case(5)]
211 #[test_case(10)]
212 #[test_case(64)]
213 #[test_case(128)]
214 #[test_case(192)]
215 #[test_case(256)]
216 #[test_case(384)]
217 #[test_case(MAX_ENTRIES)]
218 #[serial]
219 fn test_pod_slot_hashes(num_entries: usize) {
220 let mut slot_hashes = vec![];
221 for i in 0..num_entries {
222 slot_hashes.push((
223 i as u64,
224 hash(&[(i >> 24) as u8, (i >> 16) as u8, (i >> 8) as u8, i as u8]),
225 ));
226 }
227
228 let check_slot_hashes = SlotHashes::new(&slot_hashes);
229 mock_slot_hashes(&check_slot_hashes);
230
231 let pod_slot_hashes = PodSlotHashes::fetch().unwrap();
232
233 let pod_slot_hashes_slice = pod_slot_hashes.as_slice().unwrap();
236 assert_eq!(pod_slot_hashes_slice.len(), slot_hashes.len());
237
238 for slot in slot_hashes.iter().map(|(slot, _hash)| slot) {
241 assert_eq!(
243 pod_slot_hashes.get(slot).unwrap().as_ref(),
244 check_slot_hashes.get(slot),
245 );
246 assert_eq!(
248 pod_slot_hashes.position(slot).unwrap(),
249 check_slot_hashes.position(slot),
250 );
251 }
252
253 let not_a_slot = num_entries.saturating_add(1) as u64;
255 assert_eq!(
256 pod_slot_hashes.get(¬_a_slot).unwrap().as_ref(),
257 check_slot_hashes.get(¬_a_slot),
258 );
259 assert_eq!(pod_slot_hashes.get(¬_a_slot).unwrap(), None);
260 assert_eq!(
261 pod_slot_hashes.position(¬_a_slot).unwrap(),
262 check_slot_hashes.position(¬_a_slot),
263 );
264 assert_eq!(pod_slot_hashes.position(¬_a_slot).unwrap(), None);
265
266 let not_a_slot = num_entries.saturating_add(2) as u64;
267 assert_eq!(
268 pod_slot_hashes.get(¬_a_slot).unwrap().as_ref(),
269 check_slot_hashes.get(¬_a_slot),
270 );
271 assert_eq!(pod_slot_hashes.get(¬_a_slot).unwrap(), None);
272 assert_eq!(
273 pod_slot_hashes.position(¬_a_slot).unwrap(),
274 check_slot_hashes.position(¬_a_slot),
275 );
276 assert_eq!(pod_slot_hashes.position(¬_a_slot).unwrap(), None);
277 }
278}