123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- ---
- title: Zero Copy
- description:
- 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.
- ```toml title="Cargo.toml"
- [dependencies]
- bytemuck = { version = "1.20.0", features = ["min_const_generics"] }
- anchor-lang = "0.31.0"
- ```
- ### Define a Zero Copy Account
- To define an account type that uses zero-copy, annotate the struct with
- [`#[account(zero_copy)]`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/attribute/account/src/lib.rs#L417).
- ```rust
- // [!code highlight]
- #[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:
- ```rust
- // [!code highlight:4]
- #[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>`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L95-L99),
- where `T` is the zero-copy account type defined with the `#[account(zero_copy)]`
- attribute.
- For example:
- ```rust
- #[derive(Accounts)]
- pub struct InstructionAccounts<'info> {
- // [!code word:AccountLoader]
- // [!code highlight]
- 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.
- ```rust
- #[derive(Accounts)]
- pub struct Initialize<'info> {
- #[account(
- // [!code word:init:1]
- // [!code highlight:4]
- 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>,
- }
- ```
- <Callout type="info">
- 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.
- </Callout>
- When initializing a zero-copy account for the first time, use
- [`load_init`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L199-L221)
- to get a mutable reference to the account data. The `load_init` method also sets
- the account discriminator.
- ```rust
- pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
- // [!code word:load_init]
- // [!code highlight]
- 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`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/syn/src/codegen/accounts/constraints.rs#L200-L264)
- constraint instead of `init`. The `zero` constraint verifies the account has not
- been initialized by checking that its discriminator has not been set.
- ```rust
- #[derive(Accounts)]
- pub struct Initialize<'info> {
- // [!code word:zero]
- // [!code highlight]
- #[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).
- ```rust
- pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
- // [!code word:load_init]
- // [!code highlight]
- let account = &mut ctx.accounts.data_account.load_init()?;
- account.data = [1; 10_485_752];
- Ok(())
- }
- ```
- #### Update a Zero Copy Account
- Use
- [`load_mut`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L172-L195)
- when you need mutable access to update an existing zero-copy account:
- ```rust
- #[derive(Accounts)]
- pub struct Update<'info> {
- // [!code highlight]
- #[account(mut)]
- pub data_account: AccountLoader<'info, Data>,
- }
- ```
- ```rust
- pub fn update(ctx: Context<Update>) -> Result<()> {
- // [!code word:load_mut]
- // [!code highlight]
- let account = &mut ctx.accounts.data_account.load_mut()?;
- account.data = [2; 10232];
- Ok(())
- }
- ```
- #### Read a Zero Copy Account
- Use
- [`load`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L154-L169)
- to only read the account data.
- ```rust
- #[derive(Accounts)]
- pub struct ReadOnly<'info> {
- pub data_account: AccountLoader<'info, Data>,
- }
- ```
- ```rust
- pub fn read_only(ctx: Context<ReadOnly>) -> Result<()> {
- // [!code word:load]
- // [!code highlight]
- 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
- <Tabs items={["Program", "Client"]}>
- <Tab value="Program">
- ```rust title="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],
- }
- ```
- </Tab>
- <Tab value="Client">
- ```ts title="test.ts"
- import * as anchor from "@coral-xyz/anchor";
- import { Program } from "@coral-xyz/anchor";
- import { ZeroCopy } from "../target/types/zero_copy";
- describe("zero-copy", () => {
- // Configure the client to use the local cluster.
- anchor.setProvider(anchor.AnchorProvider.env());
- const program = anchor.workspace.ZeroCopy as Program<ZeroCopy>;
- const dataAccount = anchor.web3.Keypair.generate();
- it("Is initialized!", async () => {
- const tx = await program.methods
- .initialize()
- .accounts({
- dataAccount: dataAccount.publicKey,
- })
- .signers([dataAccount])
- .rpc();
- console.log("Your transaction signature", tx);
- const account = await program.account.data.fetch(dataAccount.publicKey);
- console.log("Account", account);
- });
- it("Update!", async () => {
- const tx = await program.methods
- .update()
- .accounts({
- dataAccount: dataAccount.publicKey,
- })
- .rpc();
- console.log("Your transaction signature", tx);
- const account = await program.account.data.fetch(dataAccount.publicKey);
- console.log("Account", account);
- });
- });
- ```
- </Tab>
- </Tabs>
- ### 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.
- <Tabs items={["Program", "Client"]}>
- <Tab value="Program">
- ```rust title="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],
- }
- ```
- </Tab>
- <Tab value="Client">
- ```ts title="test.ts"
- import * as anchor from "@coral-xyz/anchor";
- import { Program } from "@coral-xyz/anchor";
- import { ZeroCopyTwo } from "../target/types/zero_copy_two";
- describe("zero-copy-two", () => {
- // Configure the client to use the local cluster.
- anchor.setProvider(anchor.AnchorProvider.env());
- const program = anchor.workspace.ZeroCopyTwo as Program<ZeroCopyTwo>;
- const dataAccount = anchor.web3.Keypair.generate();
- it("Is initialized!", async () => {
- const space = 10_485_760; // 10MB max account size
- const lamports =
- await program.provider.connection.getMinimumBalanceForRentExemption(
- space,
- );
- // [!code highlight:7]
- const createAccountInstruction = anchor.web3.SystemProgram.createAccount({
- fromPubkey: program.provider.publicKey,
- newAccountPubkey: dataAccount.publicKey,
- space,
- lamports,
- programId: program.programId,
- });
- // [!code highlight:6]
- const initializeInstruction = await program.methods
- .initialize()
- .accounts({
- dataAccount: dataAccount.publicKey,
- })
- .instruction();
- const transaction = new anchor.web3.Transaction().add(
- createAccountInstruction,
- initializeInstruction,
- );
- const tx = await program.provider.sendAndConfirm(transaction, [
- dataAccount,
- ]);
- console.log("Your transaction signature", tx);
- const account = await program.account.data.fetch(dataAccount.publicKey);
- console.log("Account", account);
- });
- });
- ```
- </Tab>
- </Tabs>
|