use std::cell::Ref;
use std::error::Error;
use solana_program::account_info::{Account, IntoAccountInfo};
use solana_program::account_info::AccountInfo;
use solana_program::pubkey::Pubkey;
use crate::accounts::account_header::AccountHeader;
use crate::dvl_error::DvlError;
use crate::errors::*;
pub trait DevolAccount {
fn expected_size() -> usize;
fn expected_tag() -> u8;
fn expected_version() -> u32;
#[inline(always)]
fn account_header<'a>(data: Ref<&mut [u8]>) -> &'a AccountHeader {
unsafe { &*(data.as_ptr() as *const AccountHeader) }
}
#[inline(always)]
fn check_basic(account_info: &AccountInfo, root_addr: &Pubkey, program_id: &Pubkey) -> Result<(), DvlError> {
let tag = AccountTag::from_u8(Self::expected_tag());
Self::check_size(tag, account_info.data.borrow())?;
let header = Self::account_header(account_info.data.borrow());
Self::check_tag_and_version(tag, header)?;
Self::check_root(tag, header, root_addr)?;
Self::check_program_id(tag, account_info, program_id)?;
Ok(())
}
#[inline(always)]
fn check_size(tag: AccountTag, account_data: Ref<&mut [u8]>) -> Result<(), DvlError> {
let actual_size= account_data.len();
if actual_size < Self::expected_size() {
Err(DvlError::new_with_account(tag, ContractError::AccountSize))
} else {
Ok(())
}
}
#[inline(always)]
fn check_tag_and_version(tag: AccountTag, header: &AccountHeader) -> Result<(), DvlError> {
if header.tag != Self::expected_tag() as u32 {
Err(DvlError::new_with_account(tag, ContractError::WrongAccountTag))
} else if header.version > Self::expected_version() {
Err(DvlError::new_with_account(tag, ContractError::AccountVersionTooHigh))
} else if header.version < Self::expected_version() {
Err(DvlError::new_with_account(tag, ContractError::AccountVersionTooLow))
} else {
Ok(())
}
}
#[inline(always)]
fn check_root(tag: AccountTag, header: &AccountHeader, root_addr: &Pubkey) -> Result<(), DvlError> {
if header.root != *root_addr {
Err(DvlError::new_with_account(tag, ContractError::RootAddress))
} else {
Ok(())
}
}
#[inline(always)]
fn check_program_id(tag: AccountTag, account_info: &AccountInfo, program_id: &Pubkey) -> Result<(), DvlError> {
if account_info.owner != program_id {
Err(DvlError::new_with_account(tag, ContractError::AccountOwner))
} else {
Ok(())
}
}
#[inline(always)]
fn from_account_info_basic<'a>(
account_info: &'a AccountInfo,
root_addr: &Pubkey,
program_id: &Pubkey,
) -> Result<&'a Self, DvlError>
where
Self: Sized,
{
Self::check_basic(account_info, root_addr, program_id)?;
let account = unsafe { &*(account_info.data.borrow().as_ptr() as *const Self) };
Ok(account)
}
#[inline(always)]
fn from_account_info_mut_basic<'a>(
account_info: &'a AccountInfo,
root_addr: &Pubkey,
program_id: &Pubkey,
) -> Result<&'a mut Self, DvlError>
where
Self: Sized,
{
Self::check_basic(account_info, root_addr, program_id)?;
if !account_info.is_writable {
return Err(DvlError::new_with_account(AccountTag::from_u8(Self::expected_tag()), ContractError::AccountWritableAttribute));
}
let account = unsafe { &mut *(account_info.data.borrow_mut().as_ptr() as *mut Self) };
Ok(account)
}
#[inline(always)]
fn from_account_basic(
key: &Pubkey,
account: &mut impl Account,
root_addr: &Pubkey,
program_id: &Pubkey,
) -> Result<Box<Self>, Box<dyn Error>>
where
Self: Sized + Copy
{
let account_info = (key, account).into_account_info();
let account_ref = Self::from_account_info_basic(&account_info, root_addr, program_id)?;
Ok(Box::new(*account_ref))
}
}
#[cfg(not(feature = "on-chain"))]
#[cfg(test)]
mod tests {
use std::str::FromStr;
use super::*;
use solana_sdk::pubkey::Pubkey;
use solana_client::rpc_client::RpcClient;
use crate::accounts::devol_regular_account::DevolRegularAccount;
use crate::accounts::root::root_account::{ROOT_ACCOUNT_TAG, RootAccount};
use crate::constants::test_constants::{PROGRAM_ID, ROOT_ADDRESS, RPC_URL};
#[test]
fn test_read_root_account() {
let root_addr = Pubkey::from_str(ROOT_ADDRESS).unwrap();
let client = RpcClient::new(String::from(RPC_URL));
let mut account_data = client.get_account(&root_addr).unwrap();
let program_id = Pubkey::from_str(PROGRAM_ID).unwrap();
assert_eq!(account_data.data.len(), RootAccount::expected_size());
match RootAccount::from_account(&root_addr, &mut account_data, &root_addr, &program_id) {
Ok(root_account) => {
assert!(true, "RootAccount success");
assert_eq!(root_account.header.tag, ROOT_ACCOUNT_TAG as u32);
}
Err(e) => panic!("Error building RootAccount: {:?}", e),
}
}
}