gpl_governance_tools/
account.rs

1//! General purpose account utility functions
2
3use borsh::{BorshDeserialize, BorshSerialize};
4use gemachain_program::{
5    account_info::AccountInfo, borsh::try_from_slice_unchecked, msg, program::invoke,
6    program::invoke_signed, program_error::ProgramError, program_pack::IsInitialized,
7    pubkey::Pubkey, rent::Rent, system_instruction::create_account, system_program, sysvar::Sysvar,
8};
9
10use crate::error::GovernanceToolsError;
11
12/// Trait for accounts to return their max size
13pub trait AccountMaxSize {
14    /// Returns max account size or None if max size is not known and actual instance size should be used
15    fn get_max_size(&self) -> Option<usize> {
16        None
17    }
18}
19
20/// Creates a new account and serializes data into it using AccountMaxSize to determine the account's size
21pub fn create_and_serialize_account<'a, T: BorshSerialize + AccountMaxSize>(
22    payer_info: &AccountInfo<'a>,
23    account_info: &AccountInfo<'a>,
24    account_data: &T,
25    program_id: &Pubkey,
26    system_info: &AccountInfo<'a>,
27) -> Result<(), ProgramError> {
28    // Assert the account is not initialized yet
29    if !(account_info.data_is_empty() && *account_info.owner == system_program::id()) {
30        return Err(GovernanceToolsError::AccountAlreadyInitialized.into());
31    }
32
33    let (serialized_data, account_size) = if let Some(max_size) = account_data.get_max_size() {
34        (None, max_size)
35    } else {
36        let serialized_data = account_data.try_to_vec()?;
37        let account_size = serialized_data.len();
38        (Some(serialized_data), account_size)
39    };
40
41    let rent = Rent::get()?;
42
43    let create_account_instruction = create_account(
44        payer_info.key,
45        account_info.key,
46        rent.minimum_balance(account_size),
47        account_size as u64,
48        program_id,
49    );
50
51    invoke(
52        &create_account_instruction,
53        &[
54            payer_info.clone(),
55            account_info.clone(),
56            system_info.clone(),
57        ],
58    )?;
59
60    if let Some(serialized_data) = serialized_data {
61        account_info
62            .data
63            .borrow_mut()
64            .copy_from_slice(&serialized_data);
65    } else {
66        account_data.serialize(&mut *account_info.data.borrow_mut())?;
67    }
68
69    Ok(())
70}
71
72/// Creates a new account and serializes data into it using the provided seeds to invoke signed CPI call
73/// Note: This functions also checks the provided account PDA matches the supplied seeds
74pub fn create_and_serialize_account_signed<'a, T: BorshSerialize + AccountMaxSize>(
75    payer_info: &AccountInfo<'a>,
76    account_info: &AccountInfo<'a>,
77    account_data: &T,
78    account_address_seeds: &[&[u8]],
79    program_id: &Pubkey,
80    system_info: &AccountInfo<'a>,
81    rent: &Rent,
82) -> Result<(), ProgramError> {
83    // Get PDA and assert it's the same as the requested account address
84    let (account_address, bump_seed) =
85        Pubkey::find_program_address(account_address_seeds, program_id);
86
87    if account_address != *account_info.key {
88        msg!(
89            "Create account with PDA: {:?} was requested while PDA: {:?} was expected",
90            account_info.key,
91            account_address
92        );
93        return Err(ProgramError::InvalidSeeds);
94    }
95
96    let (serialized_data, account_size) = if let Some(max_size) = account_data.get_max_size() {
97        (None, max_size)
98    } else {
99        let serialized_data = account_data.try_to_vec()?;
100        let account_size = serialized_data.len();
101        (Some(serialized_data), account_size)
102    };
103
104    let create_account_instruction = create_account(
105        payer_info.key,
106        account_info.key,
107        rent.minimum_balance(account_size),
108        account_size as u64,
109        program_id,
110    );
111
112    let mut signers_seeds = account_address_seeds.to_vec();
113    let bump = &[bump_seed];
114    signers_seeds.push(bump);
115
116    invoke_signed(
117        &create_account_instruction,
118        &[
119            payer_info.clone(),
120            account_info.clone(),
121            system_info.clone(),
122        ],
123        &[&signers_seeds[..]],
124    )?;
125
126    if let Some(serialized_data) = serialized_data {
127        account_info
128            .data
129            .borrow_mut()
130            .copy_from_slice(&serialized_data);
131    } else {
132        account_data.serialize(&mut *account_info.data.borrow_mut())?;
133    }
134
135    Ok(())
136}
137
138/// Deserializes account and checks it's initialized and owned by the specified program
139pub fn get_account_data<T: BorshDeserialize + IsInitialized>(
140    owner_program_id: &Pubkey,
141    account_info: &AccountInfo,
142) -> Result<T, ProgramError> {
143    if account_info.data_is_empty() {
144        return Err(GovernanceToolsError::AccountDoesNotExist.into());
145    }
146    if account_info.owner != owner_program_id {
147        return Err(GovernanceToolsError::InvalidAccountOwner.into());
148    }
149
150    let account: T = try_from_slice_unchecked(&account_info.data.borrow())?;
151    if !account.is_initialized() {
152        Err(ProgramError::UninitializedAccount)
153    } else {
154        Ok(account)
155    }
156}
157
158/// Asserts the given account is not empty, owned by the given program and of the expected type
159/// Note: The function assumes the account type T is stored as the first element in the account data
160pub fn assert_is_valid_account<T: BorshDeserialize + PartialEq>(
161    account_info: &AccountInfo,
162    expected_account_type: T,
163    owner_program_id: &Pubkey,
164) -> Result<(), ProgramError> {
165    if account_info.owner != owner_program_id {
166        return Err(GovernanceToolsError::InvalidAccountOwner.into());
167    }
168
169    if account_info.data_is_empty() {
170        return Err(GovernanceToolsError::AccountDoesNotExist.into());
171    }
172
173    let account_type: T = try_from_slice_unchecked(&account_info.data.borrow())?;
174
175    if account_type != expected_account_type {
176        return Err(GovernanceToolsError::InvalidAccountType.into());
177    };
178
179    Ok(())
180}
181
182/// Disposes account by transferring its carats to the beneficiary account and zeros its data
183// After transaction completes the runtime would remove the account with no carats
184pub fn dispose_account(account_info: &AccountInfo, beneficiary_info: &AccountInfo) {
185    let account_carats = account_info.carats();
186    **account_info.carats.borrow_mut() = 0;
187
188    **beneficiary_info.carats.borrow_mut() = beneficiary_info
189        .carats()
190        .checked_add(account_carats)
191        .unwrap();
192
193    let mut account_data = account_info.data.borrow_mut();
194
195    account_data.fill(0);
196}