Anchor account constraints
In Anchor, the #[derive(Accounts)] struct is where most security lives. Constraints such as init, mut, has_one, seeds/bump, constraint = ... and close are checked before your handler runs. Reading a program's accounts structs tells you exactly which relationships it enforces.
Reference: Anchor docs: Account constraints
Real code, line by line
6 excerpts from different projects. Each caption links the exact lines at the fork's pinned commit. The notes below each excerpt explain the lines that matter.
anchor/programs/valhalla/src/instructions/create.rs L15–40 @ 7aa9ddf · MIT · original #[derive(Accounts)]#[instruction(identifier: u64)]pub struct CreateVault<'info> { #[account(mut)] pub creator: Signer<'info>, #[account(mut)] pub recipient: SystemAccount<'info>, #[account(mut)] pub dev_treasury: SystemAccount<'info>, #[account(mut)] pub dao_treasury: SystemAccount<'info>, #[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)] pub config: Box<Account<'info, Config>>, #[account( init, payer = creator, seeds = [ identifier.to_le_bytes().as_ref(), creator.key().as_ref(), mint.key().as_ref(),- Line 20
pub creator: Signer<'info>,: Requires this account to have signed the transaction. - Line 31
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 31
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 31
#[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Checks that a stored pubkey field matches the passed account. - Line 35
init,: Creates and pays for a new account. Space and payer must be specified.
programs/predix/src/lib.rs L62–87 @ 6f57791 · Apache-2.0 · originalpub struct InitAnalyst<'info> { #[account(mut)] pub signer: Signer<'info>, #[account(init, payer = signer, space = 1000)] analyst: Account<'info, Analyst>, pub system_program: AccountInfo<'info>,} #[derive(Accounts)]pub struct CreateInsight<'info> { #[account(mut)] pub signer: Signer<'info>, #[account(init, payer = signer, space = 1000)] pub insight: Account<'info, Insight>, pub system_program: AccountInfo<'info>,} #[derive(Accounts)]pub struct ValidateInsight<'info> { #[account(mut, has_one = analyst)] pub insight: Account<'info, Insight>, #[account(mut)] pub analyst: Account<'info, Analyst>,- Line 64
pub signer: Signer<'info>,: Requires this account to have signed the transaction. - Line 66
#[account(init, payer = signer, space = 1000)]: Creates and pays for a new account. Space and payer must be specified. - Line 83
#[account(mut, has_one = analyst)]: Checks that a stored pubkey field matches the passed account.
escrow/programs/mm_escrow/src/instructions/accept.rs L12–37 @ ca496be · Apache-2.0 · originalpub use crate::state::*; #[derive(Accounts)]pub struct Accept<'info> { #[account(mut)] buyer: Signer<'info>, #[account( seeds = ["escrow".as_bytes(), escrow.authority.key().as_ref(), escrow.product_seed.as_str().as_bytes()], bump, )] pub escrow: Account<'info, Escrow>, #[account(mut, constraint = escrowed_x_tokens.key() == escrow.escrowed_x_tokens)] pub escrowed_x_tokens: Account<'info, TokenAccount>, #[account(mut, constraint = sellers_y_tokens.mint == escrow.y_mint)] pub sellers_y_tokens: Account<'info, TokenAccount>, #[account(mut, constraint = buyers_x_tokens.mint == escrowed_x_tokens.mint)] pub buyers_x_tokens: Account<'info, TokenAccount>, #[account(mut, associated_token::mint = escrow.y_mint, associated_token::authority = buyer.key())] pub buyers_y_tokens: Account<'info, TokenAccount>,- Line 17
buyer: Signer<'info>,: Requires this account to have signed the transaction. - Line 19
seeds = ["escrow".as_bytes(),: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 22
bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
anchor/programs/mons-dot-rehab/src/lib.rs L76–101 @ 0359070 · CC0-1.0 · original #[derive(Accounts)]#[instruction(game_id: u64)]pub struct CreateGame<'info> { #[account(init, payer = host, space = 1 + 8 + 8 + 32 + 32, seeds = [b"game", &game_id.to_le_bytes()[..]], bump)] pub game: Account<'info, Game>, #[account(mut)] pub host: Signer<'info>, pub system_program: Program<'info, System>,} #[account]pub struct Game { pub game_id: u64, pub host_id: Pubkey, pub guest_id: Pubkey, pub bump: u8,} #[derive(Accounts)]pub struct JoinGame<'info> { #[account(mut, seeds = [b"game", &game.game_id.to_le_bytes()[..]], bump)] pub game: Account<'info, Game>, pub guest: Signer<'info>, pub system_program: Program<'info, System>,}- Line 80
#[account(init, payer = host, space = 1 + 8 + 8 + 32 + 32, seeds = [b"game", &game_id.to_l: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 80
#[account(init, payer = host, space = 1 + 8 + 8 + 32 + 32, seeds = [b"game", &game_id.to_l: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 80
#[account(init, payer = host, space = 1 + 8 + 8 + 32 + 32, seeds = [b"game", &game_id.to_l: Creates and pays for a new account. Space and payer must be specified. - Line 83
pub host: Signer<'info>,: Requires this account to have signed the transaction.
zkl-messenger/chains/solana/programs/solana/src/context.rs L89–114 @ 3da6216 · MIT · original /// CHECK: If someone passes in the wrong account, Guardians won't read the message pub wormhole_sequence: AccountInfo<'info>, #[account(mut)] pub wormhole_message_key: Signer<'info>, #[account(mut)] pub payer: Signer<'info>, pub system_program: Program<'info, System>, #[account( constraint = clock.key() == clock::id() )] /// CHECK: The account constraint will make sure it's the right clock var pub clock: AccountInfo<'info>, #[account( constraint = rent.key() == rent::id() )] /// CHECK: The account constraint will make sure it's the right rent var pub rent: AccountInfo<'info>, #[account(mut)] pub config: Account<'info, Config>,} #[derive(Accounts)]#[instruction()]pub struct ConfirmMsg<'info>{ #[account(mut)] pub payer: Signer<'info>,- Line 92
pub wormhole_message_key: Signer<'info>,: Requires this account to have signed the transaction.
programs/poe/src/contexts/register_user.rs L7–32 @ 29a7c77 · MIT · originaluse crate::states::*; #[derive(Accounts)]pub struct RegisterUser<'info> { #[account(mut)] pub payer: Signer<'info>, #[account( init, payer = payer, seeds=[User::SEED_PREFIX.as_bytes(), payer.key().as_ref()], space=User::LEN, bump )] pub user: Box<Account<'info, User>>, #[account( seeds = ["poeken_mint".as_bytes()], bump, mut )] pub mint: Box<Account<'info, Mint>>, #[account( init_if_needed, payer = payer, associated_token::mint = mint, associated_token::authority = payer )]- Line 12
pub payer: Signer<'info>,: Requires this account to have signed the transaction. - Line 14
init,: Creates and pays for a new account. Space and payer must be specified. - Line 16
seeds=[User::SEED_PREFIX.as_bytes(), payer.key().as_ref()],: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match. - Line 18
bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve. - Line 28
init_if_needed,: Creates the account only if it doesn't exist. Watch for re-initialisation bugs.
Go further
- valhalla: explore the full project
- Predix: explore the full project
- peer-pay: explore the full project
- mons-dot-rehab: explore the full project
- zk-lokomotive-core: explore the full project
- poe-solana: explore the full project
- Next lesson: Program Derived Addresses (PDAs)