Skip to main content

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}