zero-copy.mdx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. ---
  2. title: Zero Copy
  3. description:
  4. Learn how to use Anchor's zero-copy deserialization feature to handle large
  5. account data in Solana programs.
  6. ---
  7. ## Usage
  8. Zero copy is a deserialization feature that allows programs to read account data
  9. directly from memory without copying it. This is particularly useful when
  10. working with large accounts.
  11. To use zero-copy add the `bytemuck` crate to your dependencies. Add the
  12. `min_const_generics` feature to allow working with arrays of any size in your
  13. zero-copy types.
  14. ```toml title="Cargo.toml"
  15. [dependencies]
  16. bytemuck = { version = "1.20.0", features = ["min_const_generics"] }
  17. anchor-lang = "0.31.0"
  18. ```
  19. ### Define a Zero Copy Account
  20. To define an account type that uses zero-copy, annotate the struct with
  21. [`#[account(zero_copy)]`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/attribute/account/src/lib.rs#L417).
  22. ```rust
  23. // [!code highlight]
  24. #[account(zero_copy)]
  25. pub struct Data {
  26. // 10240 bytes - 8 bytes account discriminator
  27. pub data: [u8; 10232],
  28. }
  29. ```
  30. The `#[account(zero_copy)]` attribute automatically implements several traits
  31. required for zero-copy deserialization:
  32. ```rust
  33. // [!code highlight:4]
  34. #[derive(Copy, Clone)]
  35. #[derive(bytemuck::Zeroable)]
  36. #[derive(bytemuck::Pod)]
  37. #[repr(C)]
  38. struct Data {
  39. // --snip--
  40. }
  41. ```
  42. ### Use AccountLoader for Zero Copy Accounts
  43. To deserialize a zero-copy account, use
  44. [`AccountLoader<'info, T>`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L95-L99),
  45. where `T` is the zero-copy account type defined with the `#[account(zero_copy)]`
  46. attribute.
  47. For example:
  48. ```rust
  49. #[derive(Accounts)]
  50. pub struct InstructionAccounts<'info> {
  51. // [!code word:AccountLoader]
  52. // [!code highlight]
  53. pub zero_copy_account: AccountLoader<'info, Data>,
  54. }
  55. ```
  56. #### Initialize a Zero Copy Account
  57. The `init` constraint can be used with the `AccountLoader` type to create a
  58. zero-copy account.
  59. ```rust
  60. #[derive(Accounts)]
  61. pub struct Initialize<'info> {
  62. #[account(
  63. // [!code word:init:1]
  64. // [!code highlight:4]
  65. init,
  66. // 10240 bytes is max space to allocate with init constraint
  67. space = 8 + 10232,
  68. payer = payer,
  69. )]
  70. pub data_account: AccountLoader<'info, Data>,
  71. #[account(mut)]
  72. pub payer: Signer<'info>,
  73. pub system_program: Program<'info, System>,
  74. }
  75. ```
  76. <Callout type="info">
  77. The `init` constraint is limited to allocating a maximum of 10240 bytes due to
  78. CPI limitations. Under the hood, the `init` constraint makes a CPI call to the
  79. SystemProgram to create the account.
  80. </Callout>
  81. When initializing a zero-copy account for the first time, use
  82. [`load_init`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L199-L221)
  83. to get a mutable reference to the account data. The `load_init` method also sets
  84. the account discriminator.
  85. ```rust
  86. pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
  87. // [!code word:load_init]
  88. // [!code highlight]
  89. let account = &mut ctx.accounts.data_account.load_init()?;
  90. account.data = [1; 10232];
  91. Ok(())
  92. }
  93. ```
  94. For accounts that require more than 10240 bytes, use the
  95. [`zero`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/syn/src/codegen/accounts/constraints.rs#L200-L264)
  96. constraint instead of `init`. The `zero` constraint verifies the account has not
  97. been initialized by checking that its discriminator has not been set.
  98. ```rust
  99. #[derive(Accounts)]
  100. pub struct Initialize<'info> {
  101. // [!code word:zero]
  102. // [!code highlight]
  103. #[account(zero)]
  104. pub data_account: AccountLoader<'info, Data>,
  105. }
  106. ```
  107. With the `zero` constraint, you'll need to first create the account in a
  108. separate instruction by directly calling the System Program. This allows you to
  109. create accounts up to Solana's maximum account size of 10MB (10_485_760 bytes),
  110. bypassing the CPI limitation.
  111. Just as before, use `load_init` to get a mutable reference to the account data
  112. and set the account discriminator. Since 8 bytes are reserved for the account
  113. discriminator, the maximum data size is 10_485_752 bytes (10MB - 8 bytes).
  114. ```rust
  115. pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
  116. // [!code word:load_init]
  117. // [!code highlight]
  118. let account = &mut ctx.accounts.data_account.load_init()?;
  119. account.data = [1; 10_485_752];
  120. Ok(())
  121. }
  122. ```
  123. #### Update a Zero Copy Account
  124. Use
  125. [`load_mut`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L172-L195)
  126. when you need mutable access to update an existing zero-copy account:
  127. ```rust
  128. #[derive(Accounts)]
  129. pub struct Update<'info> {
  130. // [!code highlight]
  131. #[account(mut)]
  132. pub data_account: AccountLoader<'info, Data>,
  133. }
  134. ```
  135. ```rust
  136. pub fn update(ctx: Context<Update>) -> Result<()> {
  137. // [!code word:load_mut]
  138. // [!code highlight]
  139. let account = &mut ctx.accounts.data_account.load_mut()?;
  140. account.data = [2; 10232];
  141. Ok(())
  142. }
  143. ```
  144. #### Read a Zero Copy Account
  145. Use
  146. [`load`](https://github.com/coral-xyz/anchor/blob/0e5285aecdf410fa0779b7cd09a47f235882c156/lang/src/accounts/account_loader.rs#L154-L169)
  147. to only read the account data.
  148. ```rust
  149. #[derive(Accounts)]
  150. pub struct ReadOnly<'info> {
  151. pub data_account: AccountLoader<'info, Data>,
  152. }
  153. ```
  154. ```rust
  155. pub fn read_only(ctx: Context<ReadOnly>) -> Result<()> {
  156. // [!code word:load]
  157. // [!code highlight]
  158. let account = &ctx.accounts.data_account.load()?;
  159. msg!("First 10 bytes: {:?}", &account.data[..10]);
  160. Ok(())
  161. }
  162. ```
  163. ## Examples
  164. The examples below demonstrate two approaches for initializing zero-copy
  165. accounts in Anchor:
  166. 1. Using the `init` constraint to initialize the account in a single instruction
  167. 2. Using the `zero` constraint to initialize an account with data greater than
  168. 10240 bytes
  169. ### Zero Copy
  170. <Tabs items={["Program", "Client"]}>
  171. <Tab value="Program">
  172. ```rust title="lib.rs"
  173. use anchor_lang::prelude::*;
  174. declare_id!("8B7XpDXjPWodpDUWDSzv4q9k73jB5WdNQXZxNBj1hqw1");
  175. #[program]
  176. pub mod zero_copy {
  177. use super::*;
  178. pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
  179. let account = &mut ctx.accounts.data_account.load_init()?;
  180. account.data = [1; 10232];
  181. Ok(())
  182. }
  183. pub fn update(ctx: Context<Update>) -> Result<()> {
  184. let account = &mut ctx.accounts.data_account.load_mut()?;
  185. account.data = [2; 10232];
  186. Ok(())
  187. }
  188. }
  189. #[derive(Accounts)]
  190. pub struct Initialize<'info> {
  191. #[account(
  192. init,
  193. // 10240 bytes is max space to allocate with init constraint
  194. space = 8 + 10232,
  195. payer = payer,
  196. )]
  197. pub data_account: AccountLoader<'info, Data>,
  198. #[account(mut)]
  199. pub payer: Signer<'info>,
  200. pub system_program: Program<'info, System>,
  201. }
  202. #[derive(Accounts)]
  203. pub struct Update<'info> {
  204. #[account(mut)]
  205. pub data_account: AccountLoader<'info, Data>,
  206. }
  207. #[account(zero_copy)]
  208. pub struct Data {
  209. // 10240 bytes - 8 bytes account discriminator
  210. pub data: [u8; 10232],
  211. }
  212. ```
  213. </Tab>
  214. <Tab value="Client">
  215. ```ts title="test.ts"
  216. import * as anchor from "@coral-xyz/anchor";
  217. import { Program } from "@coral-xyz/anchor";
  218. import { ZeroCopy } from "../target/types/zero_copy";
  219. describe("zero-copy", () => {
  220. // Configure the client to use the local cluster.
  221. anchor.setProvider(anchor.AnchorProvider.env());
  222. const program = anchor.workspace.ZeroCopy as Program<ZeroCopy>;
  223. const dataAccount = anchor.web3.Keypair.generate();
  224. it("Is initialized!", async () => {
  225. const tx = await program.methods
  226. .initialize()
  227. .accounts({
  228. dataAccount: dataAccount.publicKey,
  229. })
  230. .signers([dataAccount])
  231. .rpc();
  232. console.log("Your transaction signature", tx);
  233. const account = await program.account.data.fetch(dataAccount.publicKey);
  234. console.log("Account", account);
  235. });
  236. it("Update!", async () => {
  237. const tx = await program.methods
  238. .update()
  239. .accounts({
  240. dataAccount: dataAccount.publicKey,
  241. })
  242. .rpc();
  243. console.log("Your transaction signature", tx);
  244. const account = await program.account.data.fetch(dataAccount.publicKey);
  245. console.log("Account", account);
  246. });
  247. });
  248. ```
  249. </Tab>
  250. </Tabs>
  251. ### Initialize Large Account
  252. When initializing an account that requires more than 10,240 bytes of space, you
  253. must split the initialization into two steps:
  254. 1. Create the account in a separate instruction invoking the System Program
  255. 2. Initialize the account data in your program instruction
  256. Note that the maximum Solana account size is 10MB (10_485_760 bytes), 8 bytes
  257. are reserved for the account discriminator.
  258. <Tabs items={["Program", "Client"]}>
  259. <Tab value="Program">
  260. ```rust title="lib.rs"
  261. use anchor_lang::prelude::*;
  262. declare_id!("CZgWhy3FYPFgKE5v9atSGaiQzbSB7cM38ofwX1XxeCFH");
  263. #[program]
  264. pub mod zero_copy_two {
  265. use super::*;
  266. pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
  267. let account = &mut ctx.accounts.data_account.load_init()?;
  268. account.data = [1; 10_485_752];
  269. Ok(())
  270. }
  271. }
  272. #[derive(Accounts)]
  273. pub struct Initialize<'info> {
  274. #[account(zero)]
  275. pub data_account: AccountLoader<'info, Data>,
  276. }
  277. #[account(zero_copy)]
  278. pub struct Data {
  279. // 10240 bytes - 8 bytes account discriminator
  280. pub data: [u8; 10_485_752],
  281. }
  282. ```
  283. </Tab>
  284. <Tab value="Client">
  285. ```ts title="test.ts"
  286. import * as anchor from "@coral-xyz/anchor";
  287. import { Program } from "@coral-xyz/anchor";
  288. import { ZeroCopyTwo } from "../target/types/zero_copy_two";
  289. describe("zero-copy-two", () => {
  290. // Configure the client to use the local cluster.
  291. anchor.setProvider(anchor.AnchorProvider.env());
  292. const program = anchor.workspace.ZeroCopyTwo as Program<ZeroCopyTwo>;
  293. const dataAccount = anchor.web3.Keypair.generate();
  294. it("Is initialized!", async () => {
  295. const space = 10_485_760; // 10MB max account size
  296. const lamports =
  297. await program.provider.connection.getMinimumBalanceForRentExemption(
  298. space,
  299. );
  300. // [!code highlight:7]
  301. const createAccountInstruction = anchor.web3.SystemProgram.createAccount({
  302. fromPubkey: program.provider.publicKey,
  303. newAccountPubkey: dataAccount.publicKey,
  304. space,
  305. lamports,
  306. programId: program.programId,
  307. });
  308. // [!code highlight:6]
  309. const initializeInstruction = await program.methods
  310. .initialize()
  311. .accounts({
  312. dataAccount: dataAccount.publicKey,
  313. })
  314. .instruction();
  315. const transaction = new anchor.web3.Transaction().add(
  316. createAccountInstruction,
  317. initializeInstruction,
  318. );
  319. const tx = await program.provider.sendAndConfirm(transaction, [
  320. dataAccount,
  321. ]);
  322. console.log("Your transaction signature", tx);
  323. const account = await program.account.data.fetch(dataAccount.publicKey);
  324. console.log("Account", account);
  325. });
  326. });
  327. ```
  328. </Tab>
  329. </Tabs>