Account Initialization Patterns
Initialization is the highest-risk moment: reinit attacks, account substitution, and discriminator omission permanently compromise custody. Separate create from init, write discriminators once, and reject already-initialized accounts.
Recipe
if account.lamports() > 0 && is_initialized(&account)? { return Err(...); }When to reach for this:
- First user onboarding instruction.
- Anchor
initvsinit_if_neededchoice. - Auditing vault creation.
Working Example
pub fn initialize(state: &AccountInfo, payer: &AccountInfo, system: &AccountInfo, program_id: &Pubkey, bump: u8) -> ProgramResult {
if state.lamports() > 0 {
let data = state.try_borrow_data()?;
if data.len() >= 8 && data[..8] == STATE_DISC { return Err(ProgramError::AccountAlreadyInitialized); }
}
// create_account CPI if empty, then write disc + State::default()
Ok(())
}What this demonstrates:
- Detect existing discriminator.
- Return AccountAlreadyInitialized equivalent.
- Create only when empty.
Deep Dive
Patterns
| Pattern | Risk |
|---|---|
| init | Low if disc checked |
| init_if_needed | Reinit if guard weak |
| client create + program init | Split trust |
Anchor 0.32.1
init, init_if_needed, realloc constraints.
Rust Notes
// Always set discriminator before accepting deposits.Gotchas
- init_if_needed without disc check - Reinit drain.. Fix: Strong initialized guard.
- Init after token transfer - Anyone can front-run shape.. Fix: Init before value.
- Wrong account type initialized - Type confusion.. Fix: Unique disc per type.
- Payer not signer - Create fails or wrong payer.. Fix: Validate signer.
- Skip rent check - Account deallocated.. Fix: Fund minimum_balance.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| Two-step create/init txs | Safer UX cost | One-shot convenience |
| Client-only create | Simpler program | PDA flows harder |
| Anchor init macro | Faster | Must understand expansion |
FAQs
AccountAlreadyInitialized?
Custom or InvalidAccountData.
Anchor init payer?
Signer constraint.
PDA init?
invoke_signed create.
Disc first?
Before funds accepted.
Realloc at init?
If size unknown upfront - rare.
Close then reinit?
Allowed if fully zeroed.
Token acct init?
Separate SPL flow.
Tests?
Attempt double init in LiteSVM.
Front-run init?
Use idempotent create or user-signed payer only.
Immutable program?
Init logic frozen - test heavily.
Surfpool?
Sim create+init.
Audit #1?
Reinitialization class.
Related
- Reinitialization Attacks - Exploits
- Creating Accounts via CPI - CPI create
- Account Discriminators - Tags
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.