1use {
4 crate::error::GovernanceToolsError,
5 borsh::{BorshDeserialize, BorshSerialize},
6 solana_program::{
7 account_info::AccountInfo,
8 borsh0_10::try_from_slice_unchecked,
9 msg,
10 program::{invoke, invoke_signed},
11 program_error::ProgramError,
12 program_pack::IsInitialized,
13 pubkey::Pubkey,
14 rent::Rent,
15 system_instruction::{self, create_account},
16 system_program,
17 sysvar::Sysvar,
18 },
19};
20
21pub trait AccountMaxSize {
23 fn get_max_size(&self) -> Option<usize> {
26 None
27 }
28}
29
30pub fn create_and_serialize_account<'a, T: BorshSerialize + AccountMaxSize>(
33 payer_info: &AccountInfo<'a>,
34 account_info: &AccountInfo<'a>,
35 account_data: &T,
36 program_id: &Pubkey,
37 system_info: &AccountInfo<'a>,
38) -> Result<(), ProgramError> {
39 if !(account_info.data_is_empty() && *account_info.owner == system_program::id()) {
41 return Err(GovernanceToolsError::AccountAlreadyInitialized.into());
42 }
43
44 let (serialized_data, account_size) = if let Some(max_size) = account_data.get_max_size() {
45 (None, max_size)
46 } else {
47 let serialized_data = account_data.try_to_vec()?;
48 let account_size = serialized_data.len();
49 (Some(serialized_data), account_size)
50 };
51
52 let rent = Rent::get()?;
53
54 let create_account_instruction = create_account(
55 payer_info.key,
56 account_info.key,
57 rent.minimum_balance(account_size),
58 account_size as u64,
59 program_id,
60 );
61
62 invoke(
63 &create_account_instruction,
64 &[
65 payer_info.clone(),
66 account_info.clone(),
67 system_info.clone(),
68 ],
69 )?;
70
71 if let Some(serialized_data) = serialized_data {
72 account_info
73 .data
74 .borrow_mut()
75 .copy_from_slice(&serialized_data);
76 } else {
77 borsh::to_writer(&mut account_info.data.borrow_mut()[..], account_data)?;
78 }
79
80 Ok(())
81}
82
83#[allow(clippy::too_many_arguments)]
88pub fn create_and_serialize_account_signed<'a, T: BorshSerialize + AccountMaxSize>(
89 payer_info: &AccountInfo<'a>,
90 account_info: &AccountInfo<'a>,
91 account_data: &T,
92 account_address_seeds: &[&[u8]],
93 program_id: &Pubkey,
94 system_info: &AccountInfo<'a>,
95 rent: &Rent,
96 extra_lamports: u64, ) -> Result<(), ProgramError> {
98 create_and_serialize_account_with_owner_signed(
99 payer_info,
100 account_info,
101 account_data,
102 account_address_seeds,
103 program_id,
104 program_id, system_info,
106 rent,
107 extra_lamports,
108 )
109}
110
111#[allow(clippy::too_many_arguments)]
115pub fn create_and_serialize_account_with_owner_signed<'a, T: BorshSerialize + AccountMaxSize>(
116 payer_info: &AccountInfo<'a>,
117 account_info: &AccountInfo<'a>,
118 account_data: &T,
119 account_address_seeds: &[&[u8]],
120 program_id: &Pubkey,
121 owner_program_id: &Pubkey,
122 system_info: &AccountInfo<'a>,
123 rent: &Rent,
124 extra_lamports: u64, ) -> Result<(), ProgramError> {
126 let (account_address, bump_seed) =
128 Pubkey::find_program_address(account_address_seeds, program_id);
129
130 if account_address != *account_info.key {
131 msg!(
132 "Create account with PDA: {:?} was requested while PDA: {:?} was expected",
133 account_info.key,
134 account_address
135 );
136 return Err(ProgramError::InvalidSeeds);
137 }
138
139 let (serialized_data, account_size) = if let Some(max_size) = account_data.get_max_size() {
140 (None, max_size)
141 } else {
142 let serialized_data = account_data.try_to_vec()?;
143 let account_size = serialized_data.len();
144 (Some(serialized_data), account_size)
145 };
146
147 let mut signers_seeds = account_address_seeds.to_vec();
148 let bump = &[bump_seed];
149 signers_seeds.push(bump);
150
151 let rent_exempt_lamports = rent.minimum_balance(account_size);
152 let total_lamports = rent_exempt_lamports.checked_add(extra_lamports).unwrap();
153
154 if account_info.lamports() > 0 {
159 let top_up_lamports = total_lamports.saturating_sub(account_info.lamports());
160
161 if top_up_lamports > 0 {
162 invoke(
163 &system_instruction::transfer(payer_info.key, account_info.key, top_up_lamports),
164 &[
165 payer_info.clone(),
166 account_info.clone(),
167 system_info.clone(),
168 ],
169 )?;
170 }
171
172 invoke_signed(
173 &system_instruction::allocate(account_info.key, account_size as u64),
174 &[account_info.clone(), system_info.clone()],
175 &[&signers_seeds[..]],
176 )?;
177
178 invoke_signed(
179 &system_instruction::assign(account_info.key, owner_program_id),
180 &[account_info.clone(), system_info.clone()],
181 &[&signers_seeds[..]],
182 )?;
183 } else {
184 let create_account_instruction = create_account(
186 payer_info.key,
187 account_info.key,
188 total_lamports,
189 account_size as u64,
190 owner_program_id,
191 );
192
193 invoke_signed(
194 &create_account_instruction,
195 &[
196 payer_info.clone(),
197 account_info.clone(),
198 system_info.clone(),
199 ],
200 &[&signers_seeds[..]],
201 )?;
202 }
203
204 if let Some(serialized_data) = serialized_data {
205 account_info
206 .data
207 .borrow_mut()
208 .copy_from_slice(&serialized_data);
209 } else if account_size > 0 {
210 borsh::to_writer(&mut account_info.data.borrow_mut()[..], account_data)?;
211 }
212
213 Ok(())
214}
215
216pub fn get_account_data<T: BorshDeserialize + IsInitialized>(
219 owner_program_id: &Pubkey,
220 account_info: &AccountInfo,
221) -> Result<T, ProgramError> {
222 if account_info.data_is_empty() {
223 return Err(GovernanceToolsError::AccountDoesNotExist.into());
224 }
225 if account_info.owner != owner_program_id {
226 return Err(GovernanceToolsError::InvalidAccountOwner.into());
227 }
228
229 let account: T = try_from_slice_unchecked(&account_info.data.borrow())?;
230 if !account.is_initialized() {
231 Err(ProgramError::UninitializedAccount)
232 } else {
233 Ok(account)
234 }
235}
236
237pub fn get_account_type<T: BorshDeserialize>(
240 owner_program_id: &Pubkey,
241 account_info: &AccountInfo,
242) -> Result<T, ProgramError> {
243 if account_info.data_is_empty() {
244 return Err(GovernanceToolsError::AccountDoesNotExist.into());
245 }
246
247 if account_info.owner != owner_program_id {
248 return Err(GovernanceToolsError::InvalidAccountOwner.into());
249 }
250
251 let account_type: T = try_from_slice_unchecked(&account_info.data.borrow())?;
252
253 Ok(account_type)
254}
255
256pub fn assert_is_valid_account_of_type<T: BorshDeserialize + PartialEq>(
260 owner_program_id: &Pubkey,
261 account_info: &AccountInfo,
262 account_type: T,
263) -> Result<(), ProgramError> {
264 assert_is_valid_account_of_types(owner_program_id, account_info, |at: &T| *at == account_type)
265}
266
267pub fn assert_is_valid_account_of_types<T: BorshDeserialize + PartialEq, F: Fn(&T) -> bool>(
272 owner_program_id: &Pubkey,
273 account_info: &AccountInfo,
274 is_account_type: F,
275) -> Result<(), ProgramError> {
276 if account_info.data_is_empty() {
277 return Err(GovernanceToolsError::AccountDoesNotExist.into());
278 }
279 if account_info.owner != owner_program_id {
280 return Err(GovernanceToolsError::InvalidAccountOwner.into());
281 }
282
283 let account_type: T = try_from_slice_unchecked(&account_info.data.borrow())?;
284
285 if !is_account_type(&account_type) {
286 return Err(GovernanceToolsError::InvalidAccountType.into());
287 };
288
289 Ok(())
290}
291
292pub fn dispose_account(
297 account_info: &AccountInfo,
298 beneficiary_info: &AccountInfo,
299) -> Result<(), ProgramError> {
300 let account_lamports = account_info.lamports();
301 **account_info.lamports.borrow_mut() = 0;
302
303 **beneficiary_info.lamports.borrow_mut() = beneficiary_info
304 .lamports()
305 .checked_add(account_lamports)
306 .unwrap();
307
308 account_info.assign(&system_program::id());
309 account_info.realloc(0, false)
310}
311
312pub fn extend_account_size<'a>(
314 account_info: &AccountInfo<'a>,
315 payer_info: &AccountInfo<'a>,
316 new_account_size: usize,
317 rent: &Rent,
318 system_info: &AccountInfo<'a>,
319) -> Result<(), ProgramError> {
320 if new_account_size <= account_info.data_len() {
321 return Err(GovernanceToolsError::InvalidNewAccountSize.into());
322 }
323
324 let rent_exempt_lamports = rent.minimum_balance(new_account_size);
325 let top_up_lamports = rent_exempt_lamports.saturating_sub(account_info.lamports());
326
327 if top_up_lamports > 0 {
328 invoke(
329 &system_instruction::transfer(payer_info.key, account_info.key, top_up_lamports),
330 &[
331 payer_info.clone(),
332 account_info.clone(),
333 system_info.clone(),
334 ],
335 )?;
336 }
337
338 account_info.realloc(new_account_size, false)
339}