|
| 1 | +use pinocchio::{ |
| 2 | + account_info::AccountInfo, |
| 3 | + instruction::{AccountMeta, Instruction, Signer}, |
| 4 | + program::invoke_signed, |
| 5 | + pubkey::{self, Pubkey, MAX_SEED_LEN}, |
| 6 | + sysvars::{rent::Rent, Sysvar}, |
| 7 | + ProgramResult, |
| 8 | + program_error::ProgramError, |
| 9 | +}; |
| 10 | + |
| 11 | +/// Create a new account at an address derived from a base pubkey and a seed. |
| 12 | +/// |
| 13 | +/// ### Accounts: |
| 14 | +/// 0. `[WRITE, SIGNER]` Funding account |
| 15 | +/// 1. `[WRITE]` Created account |
| 16 | +/// 2. `[SIGNER]` (optional) Base account; the account matching the base Pubkey below must be |
| 17 | +/// provided as a signer, but may be the same as the funding account |
| 18 | +pub struct CreateAccountWithSeedChecked<'a, 'b, 'c> { |
| 19 | + /// Funding account. |
| 20 | + pub from: &'a AccountInfo, |
| 21 | + |
| 22 | + /// New account. |
| 23 | + pub to: &'a AccountInfo, |
| 24 | + |
| 25 | + /// Base account. |
| 26 | + /// |
| 27 | + /// The account matching the base Pubkey below must be provided as |
| 28 | + /// a signer, but may be the same as the funding account and provided |
| 29 | + /// as account 0. |
| 30 | + pub base: Option<&'a AccountInfo>, |
| 31 | + |
| 32 | + /// String of ASCII chars, no longer than `Pubkey::MAX_SEED_LEN`. |
| 33 | + pub seed: &'b str, |
| 34 | + |
| 35 | + /// Number of bytes of memory to allocate. |
| 36 | + pub space: u64, |
| 37 | + |
| 38 | + /// Address of program that will own the new account. |
| 39 | + pub owner: &'c Pubkey, |
| 40 | + |
| 41 | + /// Sysvar rent account. |
| 42 | + pub sysvar_rent_account: &'a AccountInfo, |
| 43 | +} |
| 44 | + |
| 45 | +impl CreateAccountWithSeed<'_, '_, '_> { |
| 46 | + #[inline(always)] |
| 47 | + pub fn invoke(&self) -> ProgramResult { |
| 48 | + self.invoke_signed_checked(&[]) |
| 49 | + } |
| 50 | + |
| 51 | + #[inline(always)] |
| 52 | + pub fn invoke_signed_checked(&self, signers: &[Signer]) -> ProgramResult { |
| 53 | + // Get lamports from rent |
| 54 | + let rent = Rent::from_account_info(self.sysvar_rent_account)?; |
| 55 | + let lamports = rent.minimum_balance(self.space as usize); |
| 56 | + |
| 57 | + // Check if the seed is valid |
| 58 | + if self.seed.len() > MAX_SEED_LEN { |
| 59 | + return Err(ProgramError::InvalidInstructionData); |
| 60 | + } |
| 61 | + |
| 62 | + // Check if the funding account has enough lamports |
| 63 | + if self.from.lamports() < lamports { |
| 64 | + return Err(ProgramError::InsufficientFunds); |
| 65 | + } |
| 66 | + |
| 67 | + // Check if the new account is already initialized |
| 68 | + if !self.to.data_is_empty() { |
| 69 | + return Err(ProgramError::InvalidAccountData); |
| 70 | + } |
| 71 | + |
| 72 | + self.invoke_signed(signers, lamports) |
| 73 | + } |
| 74 | + |
| 75 | + #[inline(always)] |
| 76 | + fn invoke_signed(&self, signers: &[Signer], lamports: u64) -> ProgramResult { |
| 77 | + // account metadata |
| 78 | + let account_metas: [AccountMeta; 3] = [ |
| 79 | + AccountMeta::writable_signer(self.from.key()), |
| 80 | + AccountMeta::writable(self.to.key()), |
| 81 | + AccountMeta::readonly_signer(self.base.unwrap_or(self.from).key()), |
| 82 | + ]; |
| 83 | + |
| 84 | + // instruction data |
| 85 | + // - [0..4 ]: instruction discriminator |
| 86 | + // - [4..36 ]: base pubkey |
| 87 | + // - [36..44]: seed length |
| 88 | + // - [44.. ]: seed (max 32) |
| 89 | + // - [.. +8]: lamports |
| 90 | + // - [.. +8]: account space |
| 91 | + // - [.. +32]: owner pubkey |
| 92 | + let mut instruction_data = [0; 120]; |
| 93 | + instruction_data[0] = 3; |
| 94 | + instruction_data[4..36].copy_from_slice(self.base.unwrap_or(self.from).key()); |
| 95 | + instruction_data[36..44].copy_from_slice(&u64::to_le_bytes(self.seed.len() as u64)); |
| 96 | + |
| 97 | + let offset = 44 + self.seed.len(); |
| 98 | + instruction_data[44..offset].copy_from_slice(self.seed.as_bytes()); |
| 99 | + instruction_data[offset..offset + 8].copy_from_slice(&lamports.to_le_bytes()); |
| 100 | + instruction_data[offset + 8..offset + 16].copy_from_slice(&self.space.to_le_bytes()); |
| 101 | + instruction_data[offset + 16..offset + 48].copy_from_slice(self.owner.as_ref()); |
| 102 | + |
| 103 | + let instruction = Instruction { |
| 104 | + program_id: &crate::ID, |
| 105 | + accounts: &account_metas, |
| 106 | + data: &instruction_data[..offset + 48], |
| 107 | + }; |
| 108 | + |
| 109 | + invoke_signed( |
| 110 | + &instruction, |
| 111 | + &[self.from, self.to, self.base.unwrap_or(self.from)], |
| 112 | + signers, |
| 113 | + ) |
| 114 | + } |
| 115 | +} |
0 commit comments