sealevel_tools/cpi/system_program/create_account.rs
1use core::mem::size_of;
2
3use crate::{
4 account::{system::ID, AccountSerde},
5 account_info::Account,
6 cpi::{CpiAuthority, CpiInstruction},
7 program_error::ProgramError,
8 pubkey::Pubkey,
9};
10
11/// Arguments to create an account reliably. If the account already has lamports, it will be topped
12/// up to the required rent, allocated with the specified amount of space and assigned to the
13/// specified program.
14///
15/// ### Examples
16///
17/// ```
18/// use sealevel_tools::{
19/// account_info::{
20/// try_next_enumerated_account, AccountInfoConstraints, Payer, Program,
21/// WritableAccount,
22/// },
23/// cpi::system_program::CreateAccount,
24/// entrypoint::{NoStdAccountInfo, ProgramResult},
25/// pubkey::Pubkey,
26/// };
27///
28/// fn process_instruction(
29/// program_id: &Pubkey,
30/// accounts: &[NoStdAccountInfo],
31/// instruction_data: &[u8],
32/// ) -> ProgramResult {
33/// let mut accounts_iter = accounts.iter().enumerate();
34///
35/// // Next account must writable signer (A.K.A. our payer).
36/// let (_, payer) =
37/// try_next_enumerated_account::<Payer>(&mut accounts_iter, Default::default())?;
38///
39/// let (new_thing_addr, new_thing_bump) =
40/// Pubkey::find_program_address(&[b"thing"], program_id);
41///
42/// // Next account must be writable data account matching PDA address.
43/// let (_, new_account) = try_next_enumerated_account::<WritableAccount>(
44/// &mut accounts_iter,
45/// AccountInfoConstraints {
46/// key: Some(&new_thing_addr),
47/// ..Default::default()
48/// },
49/// )?;
50///
51/// CreateAccount {
52/// payer: payer.as_cpi_authority(),
53/// to: new_account.as_cpi_authority(Some(&[b"thing", &[new_thing_bump]])),
54/// program_id,
55/// space: Some(16),
56/// lamports: None,
57/// }
58/// .try_into_invoke()?;
59///
60/// Ok(())
61/// }
62/// ```
63///
64/// Use [Self::try_invoke_and_serialize] to create a new data account and serialize data to it.
65/// ```
66/// use sealevel_tools::{
67/// borsh::{BorshDeserialize, BorshSerialize},
68/// account::{AccountSerde, BorshAccountSchema},
69/// account_info::{
70/// try_next_enumerated_account, AccountInfoConstraints, Payer, Program,
71/// WritableAccount,
72/// },
73/// cpi::system_program::CreateAccount,
74/// discriminator::{Discriminate, Discriminator},
75/// entrypoint::{NoStdAccountInfo, ProgramResult},
76/// pubkey::Pubkey,
77/// };
78///
79/// #[derive(Debug, BorshDeserialize, BorshSerialize)]
80/// pub struct Thing {
81/// pub data: u64,
82/// }
83///
84///
85/// impl Discriminate<4> for Thing {
86/// const DISCRIMINATOR: [u8; 4] = Discriminator::Sha2(b"Thing").to_bytes();
87/// }
88///
89/// fn process_instruction(
90/// program_id: &Pubkey,
91/// accounts: &[NoStdAccountInfo],
92/// instruction_data: &[u8],
93/// ) -> ProgramResult {
94/// let mut accounts_iter = accounts.iter().enumerate();
95///
96/// // Next account must writable signer (A.K.A. our payer).
97/// let (_, payer) =
98/// try_next_enumerated_account::<Payer>(&mut accounts_iter, Default::default())?;
99///
100/// let (new_thing_addr, new_thing_bump) =
101/// Pubkey::find_program_address(&[b"thing"], program_id);
102///
103/// // Next account must be writable data account matching PDA address.
104/// let (_, new_account) = try_next_enumerated_account::<WritableAccount>(
105/// &mut accounts_iter,
106/// AccountInfoConstraints {
107/// key: Some(&new_thing_addr),
108/// ..Default::default()
109/// },
110/// )?;
111///
112/// let thing = BorshAccountSchema(Thing { data: 420 });
113///
114/// CreateAccount {
115/// payer: payer.as_cpi_authority(),
116/// to: new_account.as_cpi_authority(Some(&[b"thing", &[new_thing_bump]])),
117/// program_id,
118/// space: None,
119/// lamports: None,
120/// }
121/// .try_invoke_and_serialize(&thing)?;
122///
123/// Ok(())
124/// }
125/// ```
126#[derive(Clone, PartialEq, Eq)]
127pub struct CreateAccount<'a, 'b: 'a> {
128 /// The account that will pay for the rent.
129 ///
130 /// ### Notes
131 ///
132 /// Pass in [None] for [CpiAuthority::signer_seeds] if the payer is passed in as a signer.
133 pub payer: CpiAuthority<'a, 'b>,
134
135 /// The account to be created.
136 ///
137 /// ### Notes
138 ///
139 /// Pass in [None] for [CpiAuthority::signer_seeds] if the account is passed in as a random
140 /// keypair.
141 pub to: CpiAuthority<'a, 'b>,
142
143 /// The program to assign the account to.
144 pub program_id: &'a Pubkey,
145
146 /// The space to allocate for the account. If [None], defaults to zero for
147 /// [Self::try_into_invoke] and will be determined by [AccountSerde::try_account_space] for
148 /// [Self::try_invoke_and_serialize].
149 pub space: Option<usize>,
150
151 pub lamports: Option<u64>,
152}
153
154impl<'a, 'b: 'a> CreateAccount<'a, 'b> {
155 /// Try to consume arguments to perform CPI calls.
156 #[inline(always)]
157 pub fn try_into_invoke(self) -> Result<Account<'b, true>, ProgramError> {
158 let Self {
159 payer,
160 to,
161 program_id,
162 space,
163 lamports,
164 } = self;
165
166 let space = space.unwrap_or_default();
167 let lamports = lamports.unwrap_or(crate::sysvar::get_rent_minimum_balance(space));
168
169 let current_lamports = *to.try_borrow_lamports()?;
170
171 if current_lamports == 0 {
172 _invoke_create_account(&payer, &to, lamports, space as u64, program_id);
173 } else {
174 let lamport_diff = lamports.saturating_sub(current_lamports);
175
176 if lamport_diff != 0 {
177 // Transfer remaining lamports.
178 super::_invoke_transfer(&payer, &to, lamport_diff);
179 }
180
181 if space != 0 {
182 // Allocate space.
183 super::_invoke_allocate(&to, space as u64);
184 }
185
186 // Assign to specified program.
187 crate::cpi::system_program::_invoke_assign(&to, program_id);
188 }
189
190 // We know that this account was writable, so we are safe to instantiate it like this.
191 Ok(Account(to.account))
192 }
193
194 /// Try to consume arguments to create a new data account and serialize data to it using the
195 /// account's implemented [AccountSerde], which includes its discriminator. This method uses
196 /// [Self::try_into_invoke] to create the account and then serializes the data to this account.
197 ///
198 /// The space to allocate for the account. If not specified, the space will be determined by
199 /// [AccountSerde::try_account_space].
200 #[inline(always)]
201 pub fn try_invoke_and_serialize<const DISC_LEN: usize, T: AccountSerde<DISC_LEN>>(
202 mut self,
203 account_data: &T,
204 ) -> Result<Account<'a, true>, ProgramError> {
205 let space = &mut self.space;
206
207 if space.is_none() {
208 space.replace(account_data.try_account_space()?);
209 }
210
211 let account = self.try_into_invoke()?;
212
213 {
214 let mut data = account.try_borrow_mut_data()?;
215 account_data.try_serialize_data(&mut data)?;
216 }
217
218 Ok(account)
219 }
220}
221
222#[inline(always)]
223fn _invoke_create_account(
224 from: &CpiAuthority,
225 to: &CpiAuthority,
226 lamports: u64,
227 space: u64,
228 owner: &Pubkey,
229) {
230 // Create account selector == 0.
231 let instruction_data = _serialize_instruction_data(lamports, space, owner);
232
233 CpiInstruction {
234 program_id: &ID,
235 accounts: &[from.to_meta_c(), to.to_meta_c_signer()],
236 data: &instruction_data,
237 }
238 .invoke_possibly_signed(
239 &[from.to_info_c(), to.to_info_c()],
240 &[from.signer_seeds, to.signer_seeds],
241 );
242}
243
244const IX_DATA_LEN: usize = {
245 4 // selector
246 + size_of::<u64>() // lamports
247 + size_of::<u64>() // space
248 + size_of::<Pubkey>() // owner
249};
250
251#[inline(always)]
252fn _serialize_instruction_data(lamports: u64, space: u64, owner: &Pubkey) -> [u8; IX_DATA_LEN] {
253 let mut instruction_data = [0; IX_DATA_LEN];
254 instruction_data[4..12].copy_from_slice(&lamports.to_le_bytes());
255 instruction_data[12..20].copy_from_slice(&space.to_le_bytes());
256 instruction_data[20..52].copy_from_slice(&owner.to_bytes());
257
258 instruction_data
259}
260
261#[cfg(test)]
262mod test {
263 use solana_sdk::system_instruction::SystemInstruction;
264
265 use super::*;
266
267 #[test]
268 fn test_serialize_instruction_data() {
269 let lamports = 420;
270 let space = 69;
271 let owner = Pubkey::new_unique();
272
273 let instruction_data = _serialize_instruction_data(lamports, space, &owner);
274
275 assert_eq!(
276 bincode::deserialize::<SystemInstruction>(&instruction_data).unwrap(),
277 SystemInstruction::CreateAccount {
278 lamports,
279 space,
280 owner,
281 }
282 );
283 }
284}