LogoAnchor Docs

Zero Copy

Learn how to use Anchor's zero-copy deserialization feature to handle large account data in Solana programs.

Usage

Zero copy is a deserialization feature that allows programs to read account data directly from memory without copying it. This is particularly useful when working with large accounts.

To use zero-copy add the bytemuck crate to your dependencies. Add the min_const_generics feature to allow working with arrays of any size in your zero-copy types.

Cargo.toml
[dependencies]
bytemuck = { version = "1.20.0", features = ["min_const_generics"] }
anchor-lang = "0.30.1"

Define a Zero Copy Account

To define an account type that uses zero-copy, annotate the struct with #[account(zero_copy)].


#[account(zero_copy)]
pub struct Data {
    // 10240 bytes - 8 bytes account discriminator
    pub data: [u8; 10232],
}

The #[account(zero_copy)] attribute automatically implements several traits required for zero-copy deserialization:


#[derive(Copy, Clone)]
#[derive(bytemuck::Zeroable)]
#[derive(bytemuck::Pod)]
#[repr(C)]
struct Data {
  // --snip--
}

Use AccountLoader for Zero Copy Accounts

To deserialize a zero-copy account, use AccountLoader<'info, T>, where T is the zero-copy account type defined with the #[account(zero_copy)] attribute.

For example:

#[derive(Accounts)]
pub struct InstructionAccounts<'info> {


    pub zero_copy_account: AccountLoader<'info, Data>,
}

Initialize a Zero Copy Account

The init constraint can be used with the AccountLoader type to create a zero-copy account.

#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(


        init,
        // 10240 bytes is max space to allocate with init constraint
        space = 8 + 10232,
        payer = payer,
    )]
    pub data_account: AccountLoader<'info, Data>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}

The init constraint is limited to allocating a maximum of 10240 bytes due to CPI limitations. Under the hood, the init constraint makes a CPI call to the SystemProgram to create the account.

When initializing a zero-copy account for the first time, use load_init to get a mutable reference to the account data. The load_init method also sets the account discriminator.

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {


    let account = &mut ctx.accounts.data_account.load_init()?;
    account.data = [1; 10232];
    Ok(())
}

For accounts that require more than 10240 bytes, use the zero constraint instead of init. The zero constraint verifies the account has not been initialized by checking that its discriminator has not been set.

#[derive(Accounts)]
pub struct Initialize<'info> {


    #[account(zero)]
    pub data_account: AccountLoader<'info, Data>,
}

With the zero constraint, you'll need to first create the account in a separate instruction by directly calling the System Program. This allows you to create accounts up to Solana's maximum account size of 10MB (10_485_760 bytes), bypassing the CPI limitation.

Just as before, use load_init to get a mutable reference to the account data and set the account discriminator. Since 8 bytes are reserved for the account discriminator, the maximum data size is 10_485_752 bytes (10MB - 8 bytes).

pub fn initialize(ctx: Context<Initialize>) -> Result<()> {


    let account = &mut ctx.accounts.data_account.load_init()?;
    account.data = [1; 10_485_752];
    Ok(())
}

Update a Zero Copy Account

Use load_mut when you need mutable access to update an existing zero-copy account:

#[derive(Accounts)]
pub struct Update<'info> {

    #[account(mut)]
    pub data_account: AccountLoader<'info, Data>,
}
pub fn update(ctx: Context<Update>) -> Result<()> {


    let account = &mut ctx.accounts.data_account.load_mut()?;
    account.data = [2; 10232];
    Ok(())
}

Read a Zero Copy Account

Use load to only read the account data.

#[derive(Accounts)]
pub struct ReadOnly<'info> {
    pub data_account: AccountLoader<'info, Data>,
}
pub fn read_only(ctx: Context<ReadOnly>) -> Result<()> {


    let account = &ctx.accounts.data_account.load()?;
    msg!("First 10 bytes: {:?}", &account.data[..10]);
    Ok(())
}

Examples

The examples below demonstrate two approaches for initializing zero-copy accounts in Anchor:

  1. Using the init constraint to initialize the account in a single instruction
  2. Using the zero constraint to initialize an account with data greater than 10240 bytes

Zero Copy

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("8B7XpDXjPWodpDUWDSzv4q9k73jB5WdNQXZxNBj1hqw1");
 
#[program]
pub mod zero_copy {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let account = &mut ctx.accounts.data_account.load_init()?;
        account.data = [1; 10232];
        Ok(())
    }
 
    pub fn update(ctx: Context<Update>) -> Result<()> {
        let account = &mut ctx.accounts.data_account.load_mut()?;
        account.data = [2; 10232];
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(
        init,
        // 10240 bytes is max space to allocate with init constraint
        space = 8 + 10232,
        payer = payer,
    )]
    pub data_account: AccountLoader<'info, Data>,
    #[account(mut)]
    pub payer: Signer<'info>,
    pub system_program: Program<'info, System>,
}
 
#[derive(Accounts)]
pub struct Update<'info> {
    #[account(mut)]
    pub data_account: AccountLoader<'info, Data>,
}
 
#[account(zero_copy)]
pub struct Data {
    // 10240 bytes - 8 bytes account discriminator
    pub data: [u8; 10232],
}

Initialize Large Account

When initializing an account that requires more than 10,240 bytes of space, you must split the initialization into two steps:

  1. Create the account in a separate instruction invoking the System Program
  2. Initialize the account data in your program instruction

Note that the maximum Solana account size is 10MB (10_485_760 bytes), 8 bytes are reserved for the account discriminator.

lib.rs
use anchor_lang::prelude::*;
 
declare_id!("CZgWhy3FYPFgKE5v9atSGaiQzbSB7cM38ofwX1XxeCFH");
 
#[program]
pub mod zero_copy_two {
    use super::*;
    pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
        let account = &mut ctx.accounts.data_account.load_init()?;
        account.data = [1; 10_485_752];
        Ok(())
    }
}
 
#[derive(Accounts)]
pub struct Initialize<'info> {
    #[account(zero)]
    pub data_account: AccountLoader<'info, Data>,
}
 
#[account(zero_copy)]
pub struct Data {
    // 10240 bytes - 8 bytes account discriminator
    pub data: [u8; 10_485_752],
}

On this page

Edit on GitHub