use {
crate::discriminator::AccountDiscriminator,
pinocchio::{
account_info::AccountInfo,
program_error::ProgramError,
pubkey::{find_program_address, Pubkey},
ProgramResult,
},
};
pub trait AccountSerialize: AccountDiscriminator {
const SERIALIZED_SIZE: usize;
fn to_bytes(&self) -> Result<Vec<u8>, ProgramError> {
let mut data = vec![0u8; Self::SERIALIZED_SIZE];
self.into_bytes(&mut data)?;
Ok(data)
}
fn into_bytes(&self, buffer: &mut [u8]) -> Result<(), ProgramError> {
if buffer.len() < Self::SERIALIZED_SIZE {
return Err(ProgramError::AccountDataTooSmall);
}
buffer[0] = Self::DISCRIMINATOR;
let len = buffer.len()-1;
buffer[1..len].copy_from_slice(&self.to_bytes_inner());
Ok(())
}
fn to_bytes_inner(&self) -> Vec<u8>;
}
pub trait AccountDeserialize: AccountDiscriminator + Sized {
fn try_from_bytes(data: &[u8]) -> Result<Self, ProgramError> {
if data[0] != Self::DISCRIMINATOR {
return Err(ProgramError::InvalidAccountData);
}
Ok(Self::from_bytes(&data[1..]))
}
fn from_bytes(data: &[u8]) -> Self;
}
pub trait AccountWrite: AccountSerialize + Sized {
fn account_write(self, account_info: &AccountInfo) -> ProgramResult {
let mut data = account_info.try_borrow_mut_data()?;
self.account_write_into(&mut data[..Self::SERIALIZED_SIZE])
}
fn account_write_into(self, buffer: &mut [u8]) -> Result<(), ProgramError> {
self.into_bytes(buffer)
}
}
pub trait AccountRead: AccountDeserialize + PdaDeriver + Sized {
fn account_read(account_info: &AccountInfo) -> Result<Self, ProgramError> {
if !account_info.is_owned_by(&Self::PROGRAM_ID) {
return Err(ProgramError::InvalidAccountOwner);
}
let account = Self::try_from_bytes(&account_info.try_borrow_data()?)?;
let expected_pda = account.create_pda();
if !account_info.key().eq(&expected_pda) {
return Err(ProgramError::InvalidSeeds);
}
Ok(account)
}
}
pub trait PdaDeriver: ProgramId {
fn pda_derive(seeds: &[&[u8]]) -> (Pubkey, u8) {
find_program_address(seeds, &Self::PROGRAM_ID)
}
fn create_pda(&self) -> Pubkey;
}
pub trait ProgramId {
const PROGRAM_ID: Pubkey;
}
#[cfg(test)]
mod test {
use {
super::*,
crate::{account::AccountDeserialize, uint::parse_u64},
pinocchio::pubkey::Pubkey,
};
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct FooBar {
pub key: Pubkey,
pub amount: u64,
}
impl AccountDiscriminator for FooBar {
const DISCRIMINATOR: u8 = 69;
}
impl AccountSerialize for FooBar {
const SERIALIZED_SIZE: usize = 1 + 32 + 8;
fn to_bytes_inner(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(40);
buf.extend_from_slice(&self.key);
buf.extend_from_slice(&self.amount.to_le_bytes());
buf
}
}
impl AccountDeserialize for FooBar {
fn from_bytes(data: &[u8]) -> Self {
let key: Pubkey = data[0..32].try_into().expect("insufficient bytes");
let amount = parse_u64(&data[32..]);
Self { key, amount }
}
}
impl AccountWrite for FooBar {}
#[test]
fn test_account_serialize() {
let foo_bar = FooBar {
key: pinocchio_pubkey::from_str("9yyz5BqahoXPivcGdBKpgqt5dbTTLELNW8LkPRwWagqs"),
amount: 420_69_1337_1234,
};
let foo_bar_bytes = foo_bar.to_bytes().unwrap();
let decoded_foobar = FooBar::try_from_bytes(&foo_bar_bytes).unwrap();
assert_eq!(foo_bar, decoded_foobar);
assert_eq!(
bs58::encode(&decoded_foobar.key).into_string().as_str(),
"9yyz5BqahoXPivcGdBKpgqt5dbTTLELNW8LkPRwWagqs"
);
assert_eq!(decoded_foobar.amount, 420_69_1337_1234);
}
#[test]
fn test_account_write() {
let foo_bar = FooBar {
key: pinocchio_pubkey::from_str("9yyz5BqahoXPivcGdBKpgqt5dbTTLELNW8LkPRwWagqs"),
amount: 420_69_1337_1234,
};
let mut buffer = [0u8; FooBar::SERIALIZED_SIZE];
foo_bar.clone().account_write_into(&mut buffer).unwrap();
let decoded_foobar = FooBar::try_from_bytes(&buffer).unwrap();
assert_eq!(foo_bar, decoded_foobar);
assert_eq!(
bs58::encode(&decoded_foobar.key).into_string().as_str(),
"9yyz5BqahoXPivcGdBKpgqt5dbTTLELNW8LkPRwWagqs"
);
assert_eq!(decoded_foobar.amount, 420_69_1337_1234);
}
#[test]
#[should_panic(expected = "InvalidAccountData")]
fn test_account_deserialize_invalid_discriminator() {
FooBar::try_from_bytes(&[4, 2, 0]).unwrap();
}
}