Sol.College

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.

restacked-dev/valhalla · 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(),
  1. Line 20 pub creator: Signer<'info>,: Requires this account to have signed the transaction.
  2. 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.
  3. 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.
  4. Line 31 #[account(seeds = [constants::CONFIG_SEED], bump, has_one = dev_treasury)]: Checks that a stored pubkey field matches the passed account.
  5. Line 35 init,: Creates and pays for a new account. Space and payer must be specified.
ymbozkurt/Predix · programs/predix/src/lib.rs L62–87 @ 6f57791 · Apache-2.0 · original
pub 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>,
  1. Line 64 pub signer: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 66 #[account(init, payer = signer, space = 1000)]: Creates and pays for a new account. Space and payer must be specified.
  3. Line 83 #[account(mut, has_one = analyst)]: Checks that a stored pubkey field matches the passed account.
e-mk/peer-pay · escrow/programs/mm_escrow/src/instructions/accept.rs L12–37 @ ca496be · Apache-2.0 · original
pub 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>,
  1. Line 17 buyer: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 19 seeds = ["escrow".as_bytes(),: Declares the PDA seeds. Anchor re-derives the address and rejects the account if it doesn't match.
  3. Line 22 bump,: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
supermetalmons/mons-dot-rehab · 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>,}
  1. 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.
  2. 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.
  3. 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.
  4. Line 83 pub host: Signer<'info>,: Requires this account to have signed the transaction.
zklambda/zk-lokomotive-core · 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>,
  1. Line 92 pub wormhole_message_key: Signer<'info>,: Requires this account to have signed the transaction.
ProofOfEstimate/poe-solana · programs/poe/src/contexts/register_user.rs L7–32 @ 29a7c77 · MIT · original
use 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    )]
  1. Line 12 pub payer: Signer<'info>,: Requires this account to have signed the transaction.
  2. Line 14 init,: Creates and pays for a new account. Space and payer must be specified.
  3. 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.
  4. Line 18 bump: Uses the bump seed, the extra byte that pushes the derived address off the ed25519 curve.
  5. Line 28 init_if_needed,: Creates the account only if it doesn't exist. Watch for re-initialisation bugs.

Go further