Soldev

Solana development with Anchor

Last updated:

Anchor is the Ruby on Rails of Solana. It's a framework that helps you do some of the heavy lifting required:

Anchor uses Borsh serialization for encoding/decoding. This is a choice and other programs don't have to follow this convention.

Borsh serializes and deserializes Rust structs.

use borsh::BorshSerialize;

#[derive(BorshSerialize)]
struct MyBorshSerializableStruct {
  value: String,
}

let x = MyBorshSerializableStruct { value: "hello".to_owned() };
let mut buffer: Vec<u8> = Vec::new();
x.serialize(&mut buffer).unwrap();

Anchor program consists of 3 parts:

  1. The program module for business logic
  2. The Accounts structs to validate accounts
  3. The declare_id macro to create an ID field that stores the address of the program
// use this import to gain access to common anchor features
use anchor_lang::prelude::*;

// declare an id for your program
declare_id!("Fg6PaFpoGXkYsidMpWTK6W2BeZ7FEfcYkg476zPFsLnS");

// write your business logic here
#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
        Ok(())
    }
}

// validate incoming accounts here
#[derive(Accounts)]
pub struct Initialize {}

Each instruction you write will take as its argument a Context<YourAccountsStruct>.

This is what a context looks like:

pub struct Context<'a, 'b, 'c, 'info, T: Bumps> {
    pub program_id: &'a Pubkey,
    pub accounts: &'b mut T,
    pub remaining_accounts: &'c [AccountInfo<'info>],
    pub bumps: T::Bumps,
}

The type it holds is created by deriving it through a macro.

#[derive(Accounts)]
pub struct YourAccountsStruct {
  #[account(mut)]
  payer: Signer<'info>,
  system_program: Program<'info, System>
}

Using the accounts inside the context is as simple as accessing them through the accounts field holding your type:

pub fn set_data(ctx: Context<SetData>, age: u64, other_data: u32) -> Result<()> {
    // Set account data like this
    (*ctx.accounts.my_account).age = age;
    (*ctx.accounts.my_account).other_data = other_data;
    // or like this
    let my_account = &mut ctx.account.my_account;
    my_account.age = age;
    my_account.other_data = other_data;
    Ok(())
}

The #[derive(Accounts)] will turn all the public keys of your struct into their associated accounts.

This is much easier and less error prone then generating the instruction data ourselves:

let instr = Instruction::new_with_borsh(
    program_id,
    &instruction,
    vec![
        AccountMeta::new(from, true),
        AccountMeta::new(to, false),
    ],
);

The smaller #[account(mut)] is a check. Its going to ensure that the account is allowed to be borrowed mutably. There are many more checks, we use them to ensure the security and success of the transactions that get submitted.

#[account] generates trait implementations for the following traits:

#[derive(Accounts)]
pub struct DoStuff<'info> {
  #[account(
    init,
    space = 0,
    payer = payer,
    seeds = ["something".as_bytes().as_ref(), user.key().as_ref()],
    bump
  )]
  something: Account<'info, SomeType>
  #[account(mut)]
  payer: Signer<'info>,
  user: SystemAccount<'info>
}

This is going to create the something account according to the params laid out in the #[account] derive macro.

In addition, Anchor is going to generate an IDL that is going to make the client side code easier to write.

Invoking another program in anchor uses a CpiContext to handle anchors own context structs which do a lot of interesting magic behind the scenes.

pub struct CpiContext<'a, 'b, 'c, 'info, T>
where
    T: ToAccountMetas + ToAccountInfos<'info>,
{
    pub accounts: T,
    pub remaining_accounts: Vec<AccountInfo<'info>>,
    pub program: AccountInfo<'info>,
    pub signer_seeds: &'a [&'b [&'c [u8]]],
}

Any public key that is associated with an account that you need deserialized information about, or any AccountInfo structs, need to be passed into the account context. This will force it to go through the parsing process.

pub struct AccountInfo<'info> {
  pub key: &'info Pubkey,
  pub is_signer: bool,
  pub lamports: Rc<RefCell<&'info mut u64>>,
  pub data: Rc<RefCell<&'info mut [u8]>>,
  // ALWAYS CHECK THE OWNER
  pub owner: &'info Pubkey,
  pub executable: bool
}

Parameters to instructions are all the other pieces of data that are required for the instruction to execute, but not tied to an account.

The main macros found in an Anchor program include:

When an instruction in an Anchor program is invoked, the program first validates the accounts provided before executing the instruction's logic. After validation, these accounts can be accessed within the instruction using the ctx.accounts syntax.

use anchor_lang::prelude::*;

declare_id!("11111111111111111111111111111111");

#[program]
mod hello_anchor {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>, data: u64) -> Result<()> {
        ctx.accounts.new_account.data = data;
        msg!("Changed data to: {}!", data);
        Ok(())
    }
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = signer, space = 8 + 8)]
    pub new_account: Account<'info, NewAccount>,
    #[account(mut)]
    pub signer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

#[account]
pub struct NewAccount {
    data: u64,
}

Account macro

The account attribute is used to define the data structure for an account's data. The account derive macro is used to define all the necessary account inputs for a particular instruction.

#[account] macro gives you:

When you do something like:

#[account(
  init,
  payer = somebody,
  seeds = [...],
  bump = some_bump
)]

You're initializing an account that will live at the PDA specified by those seeds + bump.

It's necessary to specify the seeds + bump here because whenever you create an account in Solana, its address has to sign. For a PDA, you need to know its seeds + bump in order to sign.

#[account(
  init,
  seeds = [b"token-seed".as_ref()],
  bump = vault_account_bump,
  payer = initiator,
  token::mint = mint_a,
  token::authority = initiator
)]

This says you want to init a token account with the mint mint_a and authority (the "owner" of the tokens) equal to initiator.

The token account will live at one of our program's derived addresses with those seeds + bump. The initiator would also pay for the token account's rent.

Under the hood these are constructing some cross-program invocations for us.

Constraints

Most of the macros for anchor are a form of constraints on the type of accounts that get passed to our program.

Clients can pass any accounts into any slot. It is up to us as developers to make sure these accounts are what we expect.

This is where most of the security risk comes from. Just using accounts without checking their types and data can be dangerous when moving lamports from one account to another.

Custom errors on failure

Errors are supported by adding them to the constraint

#[account(
  mut @ MyErrors::NotMutable
  signer @ MyErrors::WrongSigner
)]
pub payer: AccountInfo<'info>

Signer

Checks the given account signed the transaction.

#[account(signer)]
pub authority: AccountInfo<'info>,

Consider using the Signer type if you would only have this constraint on the account.

#[account(mut)]
pub authority: Signer<'info>, // The creator of the pool

If you use the Signer type you should never try and access the underlying account data. It only ensures an account has signed the transaction, but it does not guarantee any additional properties about the account itself.

Mut

Checks the given account is mutable. Makes anchor persist any state changes. Custom errors are supported via @.

#[account(mut)]
pub data_account: Account<'info, MyData>,
#[account(mut @ MyError::MyErrorCode)]
pub data_account_two: Account<'info, MyData>

Init

init is how you can initialize a new account for the purpose of the instruction.

To add the init constraint we need some things:

  1. Requires the payer constraint to also be on the account, which will pay for the account creation
  2. Requires the system program to exist on the struct and be called system_program
  3. Requires that the space constraint is specified

There will always be at least 8 bytes for the discriminator then our data goes after.

#[account]
pub struct MyData {
    pub data: u64
}

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(init, payer = payer, space = 8 + 8)]
    pub data_account_two: Account<'info, MyData>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

Usually init would make the owner of the account the executing program. We can override this by explicitly setting the owner constraint.

If we want the new account to be a PDA then we can add the seeds constraint:

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init, payer = payer, space = 8 + 8
        seeds = [b"example_seed"], bump
    )]
    pub pda_data_account: Account<'info, MyData>,
    #[account(
        init, payer = payer,
        space = 8 + 8, owner = other_program.key()
    )]
    pub account_for_other_program: AccountInfo<'info>,
    #[account(
        init, payer = payer, space = 8 + 8,
        owner = other_program.key(),
        seeds = [b"other_seed"], bump
    )]
    pub pda_for_other_program: AccountInfo<'info>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
    pub other_program: Program<'info, OtherProgram>
}

When the bump is left without a value, anchor will figure it out using find_program_address to calculate the value.

If we wanted to, we could allow the bump to be passed in:

#[derive(Accounts)]
#[instruction(bump: u8)]
pub struct Initialize<'info> {
    #[account(
        init, payer = payer, space = 8 + 8
        seeds = [b"example_seed"], bump = bump
    )]
    pub pda_data_account: Account<'info, MyData>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
    pub other_program: Program<'info, OtherProgram>
}

Here we are setting the bump = bump which comes from the instruction argument.

Init if needed

This is the much scarier brother of init. It's been the subject of exploits in the past and therefore has lots of red tape around it.

Exact same functionality as the init constraint but only runs if the account does not exist yet. If the account does exist, it still checks whether the given init constraints are correct, e.g. that the account has the expected amount of space and, if it's a PDA, the correct seeds etc.

And then for how to handle:

You need to include checks in your code that check that the initialized account cannot be reset to its initial settings after the first time it was initialized (unless that it what you want).

Because of the possibility of re-initialization attacks and the general guideline that instructions should avoid having multiple execution flows (which is important so they remain easy to understand), consider breaking up your instruction into two instructions - one for initializing and one for using the account - unless you have a good reason not to do so.

Seeds

We can use the seeds to check that an account is a PDA derived directly by the executing program.

Anchor will use a canonical bump (the default). But we can override this by passing in a specific bump ourselves.

To change the program the seed is derived from, we can set it with the seeds::program = <expr>.

#[derive(Accounts)]
#[instruction(first_bump: u8, second_bump: u8)]
pub struct Example {
    #[account(seeds = [b"example_seed"], bump)]
    pub canonical_pda: AccountInfo<'info>,
    #[account(
        seeds = [b"example_seed"],
        bump,
        seeds::program = other_program.key()
    )]
    pub canonical_pda_two: AccountInfo<'info>,
    #[account(seeds = [b"other_seed"], bump = first_bump)]
    pub arbitrary_pda: AccountInfo<'info>
    #[account(
        seeds = [b"other_seed"],
        bump = second_bump,
        seeds::program = other_program.key()
    )]
    pub arbitrary_pda_two: AccountInfo<'info>,
    pub other_program: Program<'info, OtherProgram>
}

Has one

#[account(has_one = <target_account>)]

Checks the target_account field on the account matches the key of the target_account field in the Accounts struct.

#[account(mut, has_one = authority)]
pub data: Account<'info, MyData>,
pub authority: Signer<'info>

In this example has_one checks that data.authority = authority.key()

Account constraint

Checks the account key matches the pubkey.

#[account(address = crate::ID)]
pub data: Account<'info, MyData>,

Similarly we can check for a specific owner

#[account(owner = Token::ID @ MyError::MyErrorCode)]
pub data: Account<'info, MyData>,
#[account(owner = token_program.key())]
pub data_two: Account<'info, MyData>,
pub token_program: Program<'info, Token>

Executable

We can check that an account is executable:

#[account(executable)]

But we should use the Program type instead which will check:

pub token_program: Program<'info, Token>
#[account(mut, seeds = [b"admin"], bump)]
pub admin_settings: Account<'info, AdminSettings>,
#[account(constraint = program.programdata_address()? == Some(program_data.key()))]
pub program: Program<'info, MyProgram>,
#[account(constraint = program_data.upgrade_authority_address == Some(authority.key()))]
pub program_data: Account<'info, ProgramData>,

program is the account of the program itself. Its constraint checks that program_data is the account that contains the program’s upgrade authority. Implicitly, this checks that program is a BPFUpgradeable program (program.programdata_address()? will be None if it’s not).

There are 3 program types provided out of the box:

Rent

rent_exempt sets constraints for rent exemption

rent_exempt = skip skips the exemption check and rent_exempt = enforce checks it.

Close

Closes the account by:

Requires mut to exist on the account.

#[account(mut, close = receiver)]
pub data_account: Account<'info, MyData>,
#[account(mut)]
pub receiver: SystemAccount<'info>

Constraint

For arbitrary constraints we can use constraint = ...

#[account(constraint = one.keys[0].age == two.apple.age)]
pub one: Account<'info, MyData>,
pub two: Account<'info, OtherData>

UncheckedAccount

Explicit wrapper for AccountInfo types to emphasize that no checks are performed

Declare ID

Defines the program’s ID. This should be used at the root of all Anchor based programs.

declare_id! is there because although we technically do not need to know our own program's ID (its passed to the program at runtime) we often want to know some other program's ID statically.

So when you have some_account.state you need to make sure that some_account has the owner you expect, or else people could trick you by passing in the wrong accounts. So declare_id! is a way that other programs can validate you are the correct program.

Declare program

Declare an external program based on its IDL.

The IDL of the program must exist in a directory named idls. This directory can be at any depth, e.g. both inside the program’s directory (<PROGRAM_DIR>/idls) and inside Anchor workspace root directory (<PROGRAM_DIR>/../../idls) are valid.

A program should only be defined once. If you have multiple programs that depend on the same definition, you should consider creating a separate crate for the external program definition and reusing it in your programs.

Account and AccountInfo

#[repr(C)]
pub struct AccountInfo<'a> {
    pub key: &'a Pubkey,
    pub lamports: Rc<RefCell<&'a mut u64>>,
    pub data: Rc<RefCell<&'a mut [u8]>>,
    pub owner: &'a Pubkey,
    pub rent_epoch: u64,
    pub is_signer: bool,
    pub is_writable: bool,
    pub executable: bool,
}

When you enumerate account info structs you use a specific iterator that returns a ProgramError instead of an Option

use solana_program_error::ProgramResult;
use solana_account_info::{AccountInfo, next_account_info};
use solana_pubkey::Pubkey;

pub fn process_instruction(
    program_id: &Pubkey,
    accounts: &[AccountInfo],
    instruction_data: &[u8],
) -> ProgramResult {
    let accounts_iter = &mut accounts.iter();
    let signer = next_account_info(accounts_iter)?;
    let payer = next_account_info(accounts_iter)?;

    // do stuff ...

    Ok(())
}

Account<T> also stores the AccountInfo type, but if you clone it, changes won't be saved unless you manually write the data yourself.

Account is very similar to AccountInfo but anchor doesn't check if the account is owned by the program.

Account<T> is storing the T on the stack. If the size was too large then you could Box<Account<T>> to store the data on the heap instead.

If you need to modify an account you would use Account<T> but if you just need to read you can use the AccountInfo struct.

When you AccountInfo<'info, T> you are telling the program to deserialize that account's data into the T type.

Account<T> is a validation wrapper around AccountInfo. It asserts that:

  1. The account is owned by the program the account struct T is declared in
  2. That its account discriminator matches the struct

An account discriminator in an Anchor program refers to an 8 byte identifier unique to each account type.

Anchor assigns a unique 8 byte discriminator to each instruction and account type in a program. These discriminators serve as identifiers to distinguish between different instructions or account types.

The discriminator is generated using the first 8 bytes of the Sha256 hash of a prefix combined with the instruction or account name. As of Anchor v0.30, these discriminators are included in the IDL file.

The discriminator for an instruction is the first 8 bytes of the Sha256 hash of the prefix global plus the instruction name.

sha256("global:initialize")

Which gives you:

af af 6d 1f 0d 98 9b ed d4 6a 95 07 32 81 ad c2 1b b5 e0 e1 d7 73 b2 fb bd 7a b5 04 cd d4 aa 30

With the first 8 bytes:

af = 175
af = 175
6d = 109
1f = 31
0d = 13
98 = 152
9b = 155
ed = 237

Which would match the IDL:

{
  "instructions": [
    {
      "name": "initialize",


      "discriminator": [175, 175, 109, 31, 13, 152, 155, 237],
       ...
    }
  ]
}

The Anchor client can automatically resolve the PDA address using the IDL file.

In the example below, Anchor automatically resolves the PDA address using the provider wallet as the signer, and its public key as the dynamic seed for PDA derivation. This removes the need to explicitly derive the PDA when building the instruction.

Cross Program Invocations (CPIs) allow one program to invoke instructions on another program. The process of implementing a CPI is the same as that of creating a instruction where you must specify:

Typescript client

Using rpc

// Generate keypair for the new account
const newAccountKp = new Keypair();

const data = new BN(42);
const transactionSignature = await program.methods
  .initialize(data)
  .accounts({
    newAccount: newAccountKp.publicKey,
    signer: wallet.publicKey,
    systemProgram: SystemProgram.programId,
  })
  .signers([newAccountKp])
  .rpc();

Using .transaction

// Generate keypair for the new account
const newAccountKp = new Keypair();

const data = new BN(42);
const transaction = await program.methods
  .initialize(data)
  .accounts({
    newAccount: newAccountKp.publicKey,
    signer: wallet.publicKey,
    systemProgram: SystemProgram.programId,
  })
  .transaction();

const transactionSignature = await connection.sendTransaction(transaction, [
  wallet.payer,
  newAccountKp,
]);

Using .instruction

// Generate keypair for the new account
const newAccountKp = new Keypair();

const data = new BN(42);
const instruction = await program.methods
  .initialize(data)
  .accounts({
    newAccount: newAccountKp.publicKey,
    signer: wallet.publicKey,
    systemProgram: SystemProgram.programId,
  })
  .instruction();

const transaction = new Transaction().add(instruction);

const transactionSignature = await connection.sendTransaction(transaction, [
  wallet.payer,
  newAccountKp,
]);

We can declare outside dependencies through IDL's

declare_program!(example);
use example::{
    accounts::Counter,
    cpi::{
        self,
        accounts::{Increment, Initialize},
    },
    program::Example,
};

The declare program brings these modules into scope which you can then use to perform CPI.

declare_program!(example);  // Looks for /idls/example.json

To use the declare_program!() macro, you need the IDL file for the target program. The IDL file must be placed in a directory named /idls in your project. The /idls directory can be located at any level in your project structure.

This can be done on the reading side too:

use anchor_client::{
    solana_client::rpc_client::RpcClient,
    solana_sdk::{
        commitment_config::CommitmentConfig, native_token::LAMPORTS_PER_SOL, signature::Keypair,
        signer::Signer, system_program,
    },
    Client, Cluster,
};
use anchor_lang::prelude::*;
use std::rc::Rc;

declare_program!(example);
use example::{accounts::Counter, client::accounts, client::args};

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let connection = RpcClient::new_with_commitment(
        "http://127.0.0.1:8899", // Local validator URL
        CommitmentConfig::confirmed(),
    );

    // Generate Keypairs and request airdrop
    let payer = Keypair::new();
    let counter = Keypair::new();
    println!("Generated Keypairs:");
    println!("   Payer: {}", payer.pubkey());
    println!("   Counter: {}", counter.pubkey());

    println!("\nRequesting 1 SOL airdrop to payer");
    let airdrop_signature = connection.request_airdrop(&payer.pubkey(), LAMPORTS_PER_SOL)?;

    // Wait for airdrop confirmation
    while !connection.confirm_transaction(&airdrop_signature)? {
        std::thread::sleep(std::time::Duration::from_millis(100));
    }
    println!("   Airdrop confirmed!");

    // Create program client
    let provider = Client::new_with_options(
        Cluster::Localnet,
        Rc::new(payer),
        CommitmentConfig::confirmed(),
    );
    let program = provider.program(example::ID)?;

    // Build and send instructions
    println!("\nSend transaction with initialize and increment instructions");
    let initialize_ix = program
        .request()
        .accounts(accounts::Initialize {
            counter: counter.pubkey(),
            payer: program.payer(),
            system_program: system_program::ID,
        })
        .args(args::Initialize)
        .instructions()?
        .remove(0);

    let increment_ix = program
        .request()
        .accounts(accounts::Increment {
            counter: counter.pubkey(),
        })
        .args(args::Increment)
        .instructions()?
        .remove(0);

    let signature = program
        .request()
        .instruction(initialize_ix)
        .instruction(increment_ix)
        .signer(&counter)
        .send()
        .await?;
    println!("   Transaction confirmed: {}", signature);

    println!("\nFetch counter account data");
    let counter_account: Counter = program.account::<Counter>(counter.pubkey()).await?;
    println!("   Counter value: {}", counter_account.count);
    Ok(())
}

Mints/SPL

A mint account is an account type in Solana's Token Programs that uniquely represents a token on the network and stores global metadata about the token.

/// Mint data.
#[repr(C)]
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Mint {
  /// Optional authority used to mint new tokens. The mint authority may only
  /// be provided during mint creation. If no mint authority is present
  /// then the mint has a fixed supply and no further tokens may be
  /// minted.
  pub mint_authority: COption<Pubkey>,
  /// Total supply of tokens.
  pub supply: u64,
  /// Number of base 10 digits to the right of the decimal place.
  pub decimals: u8,
  /// Is `true` if this structure has been initialized
  pub is_initialized: bool,
  /// Optional authority to freeze token accounts.
  pub freeze_authority: COption<Pubkey>,
}

Anchor provides two sets of constraints for working with token accounts:

associated_token::mint = mint and associated_token::authority = signer enforces that the token account generated uses the specified mint and signer as seeds alongside enforcing that the token account corresponds to the specified mint and the token account authority corresponds to the signer.

While token::mint = mint and token::authority = signer only enforce that the token account corresponds to the specified mint and the token account authority corresponds to the specified signer.

Hence, using associated_token:: would not work for token accounts that are PDAs, token:: should be used instead. Consequently, this means that multiple token accounts for a publickey that has a private key can be generated but only one of them can be an ATA.

Relationships

has_one requires the account that's using the constraint to be resolved first

Context

The difference between new and new_with_signer.

Lets say you want to transfer lamports from your program account to user account via solana program invocation.

You reduce the lamports of your program account so you need to "sign" this CPI call with that exact amount. So you need to create a CpiContext with a signer and provide the account seeds.

You use new without a signer when all signers required by a CPI have already signed the transaction (or ther eis no signers). For example, if you want to transfer funds from a user to your program. The user already signed this transaction when you invoked the solana program and passed the user as elligible to transfer lamports.

SPL Constraints

In addition to the base constraints there are a set of SPL constraints:

#[account(
  token::mint = <target_account>,
  token::authority = <target_account>,
  token::token_program = <target_account>
)]
#[account(
  mint::authority = <target_account>,
  mint::decimals = <expr>,
  mint::freeze_authority = <target_account>
)]

We can override the token program:

#[account(*::token_program = <target_account>)]

Here's it all put together

use anchor_spl::token_interface::{TokenInterface, TokenAccount, Mint};


#[derive(Accounts)]
pub struct DoSomething<'info> {
  #[account(
      mint::token_program = token_a_token_program,
  )]
  pub token_a_mint: InterfaceAccount<'info, Mint>,
  #[account(
      mint::token_program = token_b_token_program,
  )]
  pub token_b_mint: InterfaceAccount<'info, Mint>,
  #[account(
      init,
      payer = payer,
      token::mint = token_a_mint,
      token::authority = payer,
      token::token_program = token_a_token_program,
  )]
  pub token_a_account: InterfaceAccount<'info, TokenAccount>,
  #[account(
      init,
      payer = payer,
      token::mint = token_b_mint,
      token::authority = payer,
      token::token_program = token_b_token_program,
  )]
  pub token_b_account: InterfaceAccount<'info, TokenAccount>,
  pub token_a_token_program: Interface<'info, TokenInterface>,
  pub token_b_token_program: Interface<'info, TokenInterface>,
#[account(mut)]
  pub payer: Signer<'info>,
  pub system_program: Program<'info, System>
}

Errors

We can use the require! family of macros to raise custom errors unless conditions.

// Instruction function
pub fn set_data(ctx: Context<SetData>, data: u64) -> Result<()> {
    require!(ctx.accounts.data.mutation_allowed, MyError::MutationForbidden);
    ctx.accounts.data.data = data;
    Ok(())
}

// An enum for custom error codes
#[error_code]
pub enum MyError {
    MutationForbidden
}

// An account definition
#[account]
#[derive(Default)]
pub struct MyData {
    mutation_allowed: bool,
    data: u64
}

// An account validation struct
#[derive(Accounts)]
pub struct SetData<'info> {
    #[account(mut)]
    pub data: Account<'info, MyData>
}

Access control

This example demonstrates a useful pattern. Not only can you use #[access_control] to ensure any invariants or preconditions hold prior to executing an instruction, but also it can be used to finish any validation on the Accounts struct, particularly when instruction arguments are needed. Here, we use the given bump_seed to verify it creates a valid program-derived address.

use anchor_lang::prelude::*;

#[program]
mod errors {
    use super::*;

    #[access_control(Create::accounts(&ctx, bump_seed))]
    pub fn create(ctx: Context<Create>, bump_seed: u8) -> Result<()> {
      let my_account = &mut ctx.accounts.my_account;
      my_account.bump_seed = bump_seed;
    }
}

#[derive(Accounts)]
pub struct Create {
  #[account(init)]
  my_account: Account<'info, MyAccount>,
}

impl Create {
  pub fn accounts(ctx: &Context<Create>, bump_seed: u8) -> Result<()> {
    let seeds = &[ctx.accounts.my_account.to_account_info().key.as_ref(), &[bump_seed]];
    Pubkey::create_program_address(seeds, ctx.program_id)
      .map_err(|_| ErrorCode::InvalidNonce)?;
    Ok(())
  }
}

Transfer hooks

use anchor_lang::prelude::*;

// SPL Transfer Hook Interface: `Execute` instruction.
//
// This instruction is invoked by Token-2022 when a transfer occurs,
// if a mint has specified this program as its transfer hook.
#[interface(spl_transfer_hook_interface::execute)]
pub fn execute_transfer(ctx: Context<Execute>, amount: u64) -> Result<()> {
    // Check that all extra accounts were provided
    let data = ctx.accounts.extra_metas_account.try_borrow_data()?;
    ExtraAccountMetaList::check_account_infos::<ExecuteInstruction>(
        &ctx.accounts.to_account_infos(),
        &TransferHookInstruction::Execute { amount }.pack(),
        &ctx.program_id,
        &data,
    )?;

    // Or maybe perform some custom logic
    if ctx.accounts.token_metadata.mint != ctx.accounts.token_account.mint {
        return Err(ProgramError::IncorrectAccount);
    }

    Ok(())
}