devol_accounts_kit/accounts/
devol_account.rs

1use std::cell::Ref;
2use std::error::Error;
3use solana_program::account_info::{Account, IntoAccountInfo};
4use solana_program::account_info::AccountInfo;
5use solana_program::pubkey::Pubkey;
6use crate::accounts::account_header::AccountHeader;
7use crate::dvl_error::DvlError;
8use crate::errors::*;
9
10pub trait DevolAccount {
11    fn expected_size() -> usize;
12
13    fn expected_tag() -> u8;
14
15    fn expected_version() -> u32;
16
17    #[inline(always)]
18    fn account_header<'a>(data: Ref<&mut [u8]>) -> &'a AccountHeader {
19        unsafe { &*(data.as_ptr() as *const AccountHeader) }
20    }
21
22    #[inline(always)]
23    fn check_basic(account_info: &AccountInfo, root_addr: &Pubkey, program_id: &Pubkey) -> Result<(), DvlError> {
24        let tag = AccountTag::from_u8(Self::expected_tag());
25        Self::check_size(tag, account_info.data.borrow())?;
26        let header = Self::account_header(account_info.data.borrow());
27        Self::check_tag_and_version(tag, header)?;
28        Self::check_root(tag, header, root_addr)?;
29        Self::check_program_id(tag, account_info, program_id)?;
30        Ok(())
31    }
32
33    #[inline(always)]
34    fn check_size(tag: AccountTag, account_data: Ref<&mut [u8]>) -> Result<(), DvlError> {
35        let actual_size= account_data.len();
36        if actual_size < Self::expected_size() {
37            Err(DvlError::new_with_account(tag, ContractError::AccountSize))
38        } else {
39            Ok(())
40        }
41    }
42
43    #[inline(always)]
44    fn check_tag_and_version(tag: AccountTag, header: &AccountHeader) -> Result<(), DvlError> {
45        if header.tag != Self::expected_tag() as u32 {
46            Err(DvlError::new_with_account(tag, ContractError::WrongAccountTag))
47        } else if header.version > Self::expected_version() {
48            Err(DvlError::new_with_account(tag, ContractError::AccountVersionTooHigh))
49        } else if header.version < Self::expected_version() {
50            Err(DvlError::new_with_account(tag, ContractError::AccountVersionTooLow))
51        } else {
52            Ok(())
53        }
54    }
55
56    #[inline(always)]
57    fn check_root(tag: AccountTag, header: &AccountHeader, root_addr: &Pubkey) -> Result<(), DvlError> {
58        if header.root != *root_addr {
59            Err(DvlError::new_with_account(tag, ContractError::RootAddress))
60        } else {
61            Ok(())
62        }
63    }
64
65    #[inline(always)]
66    fn check_program_id(tag: AccountTag, account_info: &AccountInfo, program_id: &Pubkey) -> Result<(), DvlError> {
67        if account_info.owner != program_id {
68            Err(DvlError::new_with_account(tag, ContractError::AccountOwner))
69        } else {
70            Ok(())
71        }
72    }
73
74    /// Transforms `AccountInfo` into a reference of `Self` for on-chain use without the intent to modify the data.
75    #[inline(always)]
76    fn from_account_info_basic<'a>(
77        account_info: &'a AccountInfo,
78        root_addr: &Pubkey,
79        program_id: &Pubkey,
80    ) -> Result<&'a Self, DvlError>
81        where
82            Self: Sized,
83    {
84        Self::check_basic(account_info, root_addr, program_id)?;
85        let account = unsafe { &*(account_info.data.borrow().as_ptr() as *const Self) };
86        Ok(account)
87    }
88
89    /// Transforms `AccountInfo` into a mutable reference of `Self` for on-chain use with the intent to modify the data.
90    /// Ensures the account is marked as writable.
91    #[inline(always)]
92    fn from_account_info_mut_basic<'a>(
93        account_info: &'a AccountInfo,
94        root_addr: &Pubkey,
95        program_id: &Pubkey,
96    ) -> Result<&'a mut Self, DvlError>
97        where
98            Self: Sized,
99    {
100        Self::check_basic(account_info, root_addr, program_id)?;
101        if !account_info.is_writable {
102            return Err(DvlError::new_with_account(AccountTag::from_u8(Self::expected_tag()), ContractError::AccountWritableAttribute));
103        }
104        let account = unsafe { &mut *(account_info.data.borrow_mut().as_ptr() as *mut Self) };
105        Ok(account)
106    }
107
108    /// Used off-chain to convert raw account data from RPC to a blockchain-utilized account structure.
109    #[inline(always)]
110    fn from_account_basic(
111        key: &Pubkey,
112        account: &mut impl Account,
113        root_addr: &Pubkey,
114        program_id: &Pubkey,
115    ) -> Result<Box<Self>, Box<dyn Error>>
116        where
117            Self: Sized + Copy
118    {
119        let account_info = (key, account).into_account_info();
120        let account_ref = Self::from_account_info_basic(&account_info, root_addr, program_id)?;
121        Ok(Box::new(*account_ref))
122    }
123}
124
125#[cfg(not(feature = "on-chain"))]
126#[cfg(test)]
127mod tests {
128    use std::str::FromStr;
129    use super::*;
130    use solana_sdk::pubkey::Pubkey;
131    use solana_client::rpc_client::RpcClient;
132    use crate::accounts::devol_regular_account::DevolRegularAccount;
133    use crate::accounts::root::root_account::{ROOT_ACCOUNT_TAG, RootAccount};
134    use crate::constants::test_constants::{PROGRAM_ID, ROOT_ADDRESS, RPC_URL};
135
136    #[test]
137    fn test_read_root_account() {
138        let root_addr = Pubkey::from_str(ROOT_ADDRESS).unwrap();
139        let client = RpcClient::new(String::from(RPC_URL));
140        let mut account_data = client.get_account(&root_addr).unwrap();
141        let program_id = Pubkey::from_str(PROGRAM_ID).unwrap();
142
143        assert_eq!(account_data.data.len(), RootAccount::expected_size());
144
145        match RootAccount::from_account(&root_addr, &mut account_data, &root_addr, &program_id) {
146            Ok(root_account) => {
147                assert!(true, "RootAccount success");
148                assert_eq!(root_account.header.tag, ROOT_ACCOUNT_TAG as u32);
149            }
150            Err(e) => panic!("Error building RootAccount: {:?}", e),
151        }
152    }
153}