1+ use pinocchio:: {
2+ account_info:: AccountInfo ,
3+ instruction:: { AccountMeta , Instruction , Seed , Signer } ,
4+ program:: invoke_signed,
5+ program_error:: ProgramError ,
6+ pubkey:: Pubkey ,
7+ ProgramResult ,
8+ } ;
9+
110/// Wrapper around the `sol_get_stack_height` syscall
211pub fn get_stack_height ( ) -> u64 {
312 #[ cfg( target_os = "solana" ) ]
@@ -7,3 +16,123 @@ pub fn get_stack_height() -> u64 {
716 #[ cfg( not( target_os = "solana" ) ) ]
817 0
918}
19+
20+ /// Safely initializes a PDA account using transfer-allocate-assign pattern.
21+ ///
22+ /// This prevents DoS attacks where malicious actors pre-fund target accounts
23+ /// with small amounts of lamports, causing the System Program's `create_account`
24+ /// instruction to fail (since it rejects accounts with non-zero balances).
25+ ///
26+ /// The transfer-allocate-assign pattern works in three steps:
27+ /// 1. **Transfer**: Add lamports to reach rent-exemption (if needed)
28+ /// 2. **Allocate**: Set the account's data size
29+ /// 3. **Assign**: Transfer ownership to the target program
30+ ///
31+ /// # Security
32+ /// - Prevents Issue #4: Create Account DoS vulnerability
33+ /// - Still enforces rent-exemption requirements
34+ /// - Properly assigns ownership to prevent unauthorized access
35+ /// - Works even if account is pre-funded by attacker
36+ ///
37+ /// # Arguments
38+ /// * `payer` - Account paying for initialization (must be signer & writable)
39+ /// * `target_pda` - PDA being initialized (will be writable)
40+ /// * `system_program` - System Program account
41+ /// * `space` - Number of bytes to allocate for account data
42+ /// * `rent_lamports` - Minimum lamports for rent-exemption
43+ /// * `owner` - Program that will own this account
44+ /// * `pda_seeds` - Seeds for PDA signing (for allocate & assign)
45+ ///
46+ /// # Errors
47+ /// Returns ProgramError if:
48+ /// - Payer has insufficient funds
49+ /// - Any CPI call fails
50+ /// - Account is already owned by another program
51+ pub fn initialize_pda_account (
52+ payer : & AccountInfo ,
53+ target_pda : & AccountInfo ,
54+ system_program : & AccountInfo ,
55+ space : usize ,
56+ rent_lamports : u64 ,
57+ owner : & Pubkey ,
58+ pda_seeds : & [ Seed ] ,
59+ ) -> ProgramResult {
60+ let current_balance = target_pda. lamports ( ) ;
61+
62+ // Step 1: Transfer lamports if needed to reach rent-exemption
63+ if current_balance < rent_lamports {
64+ let transfer_amount = rent_lamports
65+ . checked_sub ( current_balance)
66+ . ok_or ( ProgramError :: ArithmeticOverflow ) ?;
67+
68+ // System Program Transfer instruction (discriminator: 2)
69+ let mut transfer_data = Vec :: with_capacity ( 12 ) ;
70+ transfer_data. extend_from_slice ( & 2u32 . to_le_bytes ( ) ) ;
71+ transfer_data. extend_from_slice ( & transfer_amount. to_le_bytes ( ) ) ;
72+
73+ let transfer_accounts = [
74+ AccountMeta {
75+ pubkey : payer. key ( ) ,
76+ is_signer : true ,
77+ is_writable : true ,
78+ } ,
79+ AccountMeta {
80+ pubkey : target_pda. key ( ) ,
81+ is_signer : false ,
82+ is_writable : true ,
83+ } ,
84+ ] ;
85+
86+ let transfer_ix = Instruction {
87+ program_id : system_program. key ( ) ,
88+ accounts : & transfer_accounts,
89+ data : & transfer_data,
90+ } ;
91+
92+ pinocchio:: program:: invoke ( & transfer_ix, & [ & payer, & target_pda, & system_program] ) ?;
93+ }
94+
95+ // Step 2: Allocate space
96+ // System Program Allocate instruction (discriminator: 8)
97+ let mut allocate_data = Vec :: with_capacity ( 12 ) ;
98+ allocate_data. extend_from_slice ( & 8u32 . to_le_bytes ( ) ) ;
99+ allocate_data. extend_from_slice ( & ( space as u64 ) . to_le_bytes ( ) ) ;
100+
101+ let allocate_accounts = [ AccountMeta {
102+ pubkey : target_pda. key ( ) ,
103+ is_signer : true ,
104+ is_writable : true ,
105+ } ] ;
106+
107+ let allocate_ix = Instruction {
108+ program_id : system_program. key ( ) ,
109+ accounts : & allocate_accounts,
110+ data : & allocate_data,
111+ } ;
112+
113+ let signer: Signer = pda_seeds. into ( ) ;
114+ invoke_signed ( & allocate_ix, & [ & target_pda, & system_program] , & [ signer] ) ?;
115+
116+ // Step 3: Assign ownership to target program
117+ // System Program Assign instruction (discriminator: 1)
118+ let mut assign_data = Vec :: with_capacity ( 36 ) ;
119+ assign_data. extend_from_slice ( & 1u32 . to_le_bytes ( ) ) ;
120+ assign_data. extend_from_slice ( owner. as_ref ( ) ) ;
121+
122+ let assign_accounts = [ AccountMeta {
123+ pubkey : target_pda. key ( ) ,
124+ is_signer : true ,
125+ is_writable : true ,
126+ } ] ;
127+
128+ let assign_ix = Instruction {
129+ program_id : system_program. key ( ) ,
130+ accounts : & assign_accounts,
131+ data : & assign_data,
132+ } ;
133+
134+ let signer: Signer = pda_seeds. into ( ) ;
135+ invoke_signed ( & assign_ix, & [ & target_pda, & system_program] , & [ signer] ) ?;
136+
137+ Ok ( ( ) )
138+ }
0 commit comments