Skip to content

Instantly share code, notes, and snippets.

@mvines
Created August 27, 2021 17:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mvines/b7b9a01251ab53a106b70f2047434019 to your computer and use it in GitHub Desktop.
Save mvines/b7b9a01251ab53a106b70f2047434019 to your computer and use it in GitHub Desktop.
Solana program utilities for Plain Old Data types

Example account data as a Pod

#[derive(Clone, Copy, Pod, Zeroable)]
#[repr(C)]
pub struct ImmaPod{
    pub pubkey: PodPubkey,
    pub boolean: PodBool,
    pub number: PodU64,
    pub somebytes: [u8;256],
}
impl PodAccountInfo<'_, '_> for ImmaPod {}

then in your program access it from an AccountInfo with:

let mut immapod = TransferAuditor::from_account_info(immapod_info, &id())?;
//! Solana program utilities for Plain Old Data types
use {
bytemuck::Pod,
solana_program::{account_info::AccountInfo, program_error::ProgramError, pubkey::Pubkey},
std::{
cell::RefMut,
marker::PhantomData,
ops::{Deref, DerefMut},
},
};
/// Placeholder until/if `Pod` is derived for `Pubkey`
#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
#[repr(transparent)]
pub struct PodPubkey([u8; 32]);
impl From<Pubkey> for PodPubkey {
fn from(pubkey: Pubkey) -> Self {
Self(pubkey.to_bytes())
}
}
impl PodPubkey {
/// Check for equality with a normal `Pubkey`
pub fn equals(&self, pubkey: &Pubkey) -> bool {
self.0 == pubkey.as_ref()
}
}
/// The standard `bool` is not a `Pod`, define a replacement that is
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[repr(transparent)]
pub struct PodBool(u8);
impl From<bool> for PodBool {
fn from(b: bool) -> Self {
Self(if b { 1 } else { 0 })
}
}
impl From<&PodBool> for bool {
fn from(b: &PodBool) -> Self {
b.0 != 0
}
}
/// The standard `u64` can cause alignment issues when placed in a `Pod`, define a replacement that
/// is usable in all `Pod`s
#[derive(Clone, Copy, Debug, Default, PartialEq, Pod, Zeroable)]
#[repr(transparent)]
pub struct PodU64([u8; 8]);
impl From<u64> for PodU64 {
fn from(n: u64) -> Self {
Self(n.to_le_bytes())
}
}
impl From<PodU64> for u64 {
fn from(pod: PodU64) -> Self {
Self::from_le_bytes(pod.0)
}
}
/// On-chain size of a `Pod` type
pub fn pod_get_packed_len<T: Pod>() -> usize {
std::mem::size_of::<T>()
}
/// Convert a slice into a `Pod` (zero copy)
pub fn pod_from_bytes<T: Pod>(bytes: &[u8]) -> Result<&T, ProgramError> {
bytemuck::try_from_bytes(bytes).map_err(|_| ProgramError::InvalidArgument)
}
/// Maybe convert a slice into a `Pod` (zero copy)
///
/// Returns `None` if the slice is empty, but `Err` if all other lengths but `get_packed_len()`
/// This function exists primary because `Option<T>` is not a `Pod`.
pub fn pod_maybe_from_bytes<T: Pod>(bytes: &[u8]) -> Result<Option<&T>, ProgramError> {
if bytes.is_empty() {
Ok(None)
} else {
bytemuck::try_from_bytes(bytes)
.map(Some)
.map_err(|_| ProgramError::InvalidArgument)
}
}
/// Convert a slice into a mutable `Pod` (zero copy)
pub fn pod_from_bytes_mut<T: Pod>(bytes: &mut [u8]) -> Result<&mut T, ProgramError> {
bytemuck::try_from_bytes_mut(bytes).map_err(|_| ProgramError::InvalidArgument)
}
/// Convert a `Pod` into a slice (zero copy)
pub fn pod_bytes_of<T: Pod>(t: &T) -> &[u8] {
bytemuck::bytes_of(t)
}
/// Represents a `Pod` within `AccountInfo::data`
pub struct PodAccountInfoData<'a, 'b, T: Pod> {
account_data: RefMut<'a, &'b mut [u8]>,
phantom: PhantomData<&'a T>,
}
impl<'a, 'b, T: Pod> Deref for PodAccountInfoData<'a, 'b, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
pod_from_bytes(&self.account_data).unwrap()
}
}
impl<'a, 'b, T: Pod> DerefMut for PodAccountInfoData<'a, 'b, T> {
fn deref_mut(&mut self) -> &mut Self::Target {
pod_from_bytes_mut(&mut self.account_data).unwrap()
}
}
/// Utility trait to add a `from_account_info()` function to any `Pod` struct
pub trait PodAccountInfo<'a, 'b>: bytemuck::Pod {
fn from_account_info(
account_info: &'a AccountInfo<'b>,
owner: &Pubkey,
) -> Result<PodAccountInfoData<'a, 'b, Self>, ProgramError> {
if account_info.owner != owner {
return Err(ProgramError::InvalidArgument);
}
let account_data = account_info.data.borrow_mut();
let _ = pod_from_bytes::<Self>(&account_data)?;
Ok(PodAccountInfoData {
account_data,
phantom: PhantomData::default(),
})
}
/// Get the packed length
fn get_packed_len() -> usize {
pod_get_packed_len::<Self>()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pod_bool() {
assert!(pod_from_bytes::<PodBool>(&[]).is_err());
assert!(pod_from_bytes::<PodBool>(&[0, 0]).is_err());
for i in 0..=u8::MAX {
assert_eq!(i != 0, pod_from_bytes::<PodBool>(&[i]).unwrap().into());
}
}
#[test]
fn test_pod_u64() {
assert!(pod_from_bytes::<PodU64>(&[]).is_err());
assert_eq!(
1u64,
(*pod_from_bytes::<PodU64>(&[1, 0, 0, 0, 0, 0, 0, 0]).unwrap()).into()
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment