diff --git a/Cargo.lock b/Cargo.lock index 47c72a7863b..84f058b5601 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7036,6 +7036,7 @@ name = "spl-pod" version = "0.1.0" dependencies = [ "base64 0.21.6", + "bincode", "borsh 0.10.3", "bytemuck", "serde", diff --git a/libraries/pod/Cargo.toml b/libraries/pod/Cargo.toml index 3a472f51da9..39fe7c61bef 100644 --- a/libraries/pod/Cargo.toml +++ b/libraries/pod/Cargo.toml @@ -21,6 +21,7 @@ solana-zk-token-sdk = "1.17.6" spl-program-error = { version = "0.3", path = "../program-error" } [dev-dependencies] +bincode = "1" serde_json = "1.0.111" [lib] diff --git a/libraries/pod/src/primitives.rs b/libraries/pod/src/primitives.rs index 2ea7c4bbc60..b0313105179 100644 --- a/libraries/pod/src/primitives.rs +++ b/libraries/pod/src/primitives.rs @@ -54,6 +54,11 @@ macro_rules! impl_int_conversion { Self::from_le_bytes(pod.0) } } + impl Into for $P { + fn into(self) -> usize { + usize::try_from(<$I>::from_le_bytes(self.0)).unwrap() + } + } }; } diff --git a/libraries/pod/src/slice.rs b/libraries/pod/src/slice.rs index ac912dc3005..f27ad3456ef 100644 --- a/libraries/pod/src/slice.rs +++ b/libraries/pod/src/slice.rs @@ -14,21 +14,21 @@ use { const LENGTH_SIZE: usize = std::mem::size_of::(); /// Special type for using a slice of `Pod`s in a zero-copy way -pub struct PodSlice<'data, T: Pod> { - length: &'data PodU32, +pub struct PodSlice<'data, T: Pod, S: Pod + Into> { + length: &'data S, data: &'data [T], } -impl<'data, T: Pod> PodSlice<'data, T> { +impl<'data, T: Pod, S: Pod + Into> PodSlice<'data, T, S> { /// Unpack the buffer into a slice pub fn unpack<'a>(data: &'a [u8]) -> Result where 'a: 'data, { - if data.len() < LENGTH_SIZE { + if data.len() < std::mem::size_of::() { return Err(PodSliceError::BufferTooSmall.into()); } - let (length, data) = data.split_at(LENGTH_SIZE); - let length = pod_from_bytes::(length)?; + let (length, data) = data.split_at(std::mem::size_of::()); + let length = pod_from_bytes::(length)?; let _max_length = max_len_for_type::(data.len())?; let data = pod_slice_from_bytes(data)?; Ok(Self { length, data }) @@ -36,7 +36,7 @@ impl<'data, T: Pod> PodSlice<'data, T> { /// Get the slice data pub fn data(&self) -> &[T] { - let length = u32::from(*self.length) as usize; + let length = (*self.length).into(); &self.data[..length] } @@ -44,7 +44,7 @@ impl<'data, T: Pod> PodSlice<'data, T> { pub fn size_of(num_items: usize) -> Result { std::mem::size_of::() .checked_mul(num_items) - .and_then(|len| len.checked_add(LENGTH_SIZE)) + .and_then(|len| len.checked_add(std::mem::size_of::())) .ok_or_else(|| PodSliceError::CalculationFailure.into()) } } @@ -129,7 +129,14 @@ fn max_len_for_type(data_len: usize) -> Result { #[cfg(test)] mod tests { - use {super::*, crate::bytemuck::pod_slice_to_bytes, bytemuck::Zeroable}; + use { + super::*, + crate::{bytemuck::pod_slice_to_bytes, primitives::PodU64}, + bincode::serialize, + bytemuck::Zeroable, + solana_program::stake_history::{StakeHistory, StakeHistoryEntry, MAX_ENTRIES}, + std::ops::Deref, + }; #[repr(C)] #[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)] @@ -155,14 +162,14 @@ mod tests { pod_slice_bytes[0..4].copy_from_slice(&len_bytes); pod_slice_bytes[4..70].copy_from_slice(&data_bytes); - let pod_slice = PodSlice::::unpack(&pod_slice_bytes).unwrap(); + let pod_slice = PodSlice::::unpack(&pod_slice_bytes).unwrap(); let pod_slice_data = pod_slice.data(); assert_eq!(*pod_slice.length, PodU32::from(2)); assert_eq!(pod_slice_to_bytes(pod_slice.data()), data_bytes); assert_eq!(pod_slice_data[0].test_field, test_field_bytes[0]); assert_eq!(pod_slice_data[0].test_pubkey, test_pubkey_bytes); - assert_eq!(PodSlice::::size_of(1).unwrap(), 37); + assert_eq!(PodSlice::::size_of(1).unwrap(), 37); } #[test] @@ -170,7 +177,7 @@ mod tests { // 1 `TestStruct` + length = 37 bytes // we pass 38 to trigger BufferTooLarge let pod_slice_bytes = [1; 38]; - let err = PodSlice::::unpack(&pod_slice_bytes) + let err = PodSlice::::unpack(&pod_slice_bytes) .err() .unwrap(); assert_eq!( @@ -185,7 +192,7 @@ mod tests { // 1 `TestStruct` + length = 37 bytes // we pass 36 to trigger BufferTooSmall let pod_slice_bytes = [1; 36]; - let err = PodSlice::::unpack(&pod_slice_bytes) + let err = PodSlice::::unpack(&pod_slice_bytes) .err() .unwrap(); assert_eq!( @@ -213,4 +220,58 @@ mod tests { .expect_err("Expected an `PodSliceError::BufferTooSmall` error"); assert_eq!(err, PodSliceError::BufferTooSmall.into()); } + + #[repr(C)] + #[derive(Debug, PartialEq, Default, Clone, Copy, Pod, Zeroable)] + pub struct EpochAndStakeHistoryEntry { + pub epoch: PodU64, + pub entry: PodStakeHistoryEntry, + } + + #[repr(C)] + #[derive(Debug, PartialEq, Default, Clone, Copy, Pod, Zeroable)] + pub struct PodStakeHistoryEntry { + pub effective: PodU64, // effective stake at this epoch + pub activating: PodU64, // sum of portion of stakes not fully warmed up + pub deactivating: PodU64, // requested to be cooled down, not fully deactivated yet + } + + impl From for StakeHistoryEntry { + fn from(item: PodStakeHistoryEntry) -> Self { + Self { + effective: item.effective.into(), + activating: item.activating.into(), + deactivating: item.deactivating.into(), + } + } + } + + fn test_stake_history() -> StakeHistory { + let mut stake_history = StakeHistory::default(); + for i in 0..MAX_ENTRIES as u64 + 1 { + stake_history.add( + i, + StakeHistoryEntry { + activating: i, + ..StakeHistoryEntry::default() + }, + ); + } + stake_history + } + + #[test] + fn test_serde() { + let stake_history = test_stake_history(); + let serialized = serialize(&stake_history).unwrap(); + let pod_slice = PodSlice::::unpack(&serialized).unwrap(); + assert_eq!( + &pod_slice + .data() + .iter() + .map(|x| (u64::from(x.epoch), x.entry.into())) + .collect::>(), + stake_history.deref() + ); + } }