@@ -5,6 +5,50 @@ description:
55 account data in Solana programs.
66---
77
8+ ## Overview
9+
10+ Zero-copy deserialization allows programs to read and write account data
11+ ** directly from memory** without copying or deserializing it. This is essential
12+ for handling large accounts efficiently on Solana.
13+
14+ ### Why Use Zero-Copy?
15+
16+ Traditional account deserialization (` Account<T> ` ) copies data from the account
17+ into a heap-allocated struct. This has limitations:
18+
19+ - ** Size Constraints** : Stack (4KB) and heap (32KB) limits restrict account sizes
20+ - ** Compute Cost** : Deserialization consumes significant compute units
21+ - ** Memory Overhead** : Data is duplicated in memory
22+
23+ Zero-copy (` AccountLoader<T> ` ) instead:
24+
25+ - ** Direct Access** : Casts raw account bytes to the struct type (no copying)
26+ - ** Larger Accounts** : Supports accounts up to 10MB (10,485,760 bytes)
27+ - ** Lower Compute** : ~ 90% reduction in CU usage for large accounts
28+ - ** In-Place Updates** : Modifies account data directly
29+
30+ ### Performance Comparison
31+
32+ | Account Size | Account\< T\> | AccountLoader\< T\> | Improvement |
33+ | ------------ | ------------ | ------------------ | ----------- |
34+ | 1 KB | ~ 8,000 CU | ~ 1,500 CU | 81% faster |
35+ | 10 KB | ~ 50,000 CU | ~ 5,000 CU | 90% faster |
36+ | 100 KB | Too large | ~ 12,000 CU | Possible |
37+ | 1 MB | Impossible | ~ 25,000 CU | Possible |
38+
39+ ### When to Use Zero-Copy
40+
41+ ** Use zero-copy for:**
42+ - Accounts larger than 1KB
43+ - Arrays with many elements (orderbooks, event queues)
44+ - High-frequency operations
45+ - Compute-sensitive programs
46+ ** Use regular Account\< T\> for:**
47+ - Small accounts (< 1KB)
48+ - Dynamic data structures (Vec, String, HashMap)
49+ - Frequently changing schemas
50+ - Simple state that doesn't need optimization
51+
852## Usage
953
1054Zero copy is a deserialization feature that allows programs to read account data
@@ -193,6 +237,230 @@ pub fn read_only(ctx: Context<ReadOnly>) -> Result<()> {
193237}
194238```
195239
240+ ## Common Patterns
241+
242+ ### Nested Zero-Copy Types
243+
244+ For types used within zero-copy accounts, use ` #[zero_copy] ` (without ` account ` ):
245+
246+ ``` rust
247+ #[account(zero_copy)]
248+ pub struct OrderBook {
249+ pub market : Pubkey ,
250+ pub bids : [Order ; 1000 ],
251+ pub asks : [Order ; 1000 ],
252+ }
253+
254+ // [!code word:#[zero_copy]]
255+ #[zero_copy]
256+ pub struct Order {
257+ pub trader : Pubkey ,
258+ pub price : u64 ,
259+ pub quantity : u64 ,
260+ }
261+ ```
262+
263+ ### Accessor Methods for Byte Arrays
264+
265+ Zero-copy uses ` #[repr(packed)] ` , making field references unsafe. Use the
266+ ` #[accessor] ` attribute for safe getter/setter methods:
267+
268+ ``` rust
269+ #[account(zero_copy)]
270+ pub struct Config {
271+ pub authority : Pubkey ,
272+ // [!code word:accessor]
273+ #[accessor(Pubkey )]
274+ pub secondary_authority : [u8 ; 32 ],
275+ }
276+
277+ // Usage:
278+ let config = & mut ctx . accounts. config. load_mut ()? ;
279+ let secondary = config . get_secondary_authority ();
280+ config . set_secondary_authority (& new_authority );
281+ ```
282+
283+ ### Zero-Copy with PDAs
284+
285+ Zero-copy accounts work seamlessly with program-derived addresses:
286+
287+ ``` rust
288+ #[derive(Accounts )]
289+ pub struct CreatePdaAccount <'info > {
290+ #[account(
291+ init,
292+ // [!code word:seeds]
293+ seeds = [b " data" , authority. key(). as_ref()],
294+ bump ,
295+ payer = authority ,
296+ space = 8 + std :: mem :: size_of :: <Data >(),
297+ )]
298+ pub data_account : AccountLoader <'info , Data >,
299+ #[account(mut )]
300+ pub authority : Signer <'info >,
301+ pub system_program : Program <'info , System >,
302+ }
303+ ```
304+
305+ ### Separate Types for RPC Parameters
306+
307+ Zero-copy types cannot derive ` AnchorSerialize ` /` AnchorDeserialize ` . Use
308+ separate types for instruction parameters:
309+
310+ ``` rust
311+ // For zero-copy account
312+ #[zero_copy]
313+ pub struct Event {
314+ pub from : Pubkey ,
315+ pub data : u64 ,
316+ }
317+
318+ // For RPC/instruction parameters
319+ // [!code word:AnchorSerialize]
320+ #[derive(AnchorSerialize , AnchorDeserialize )]
321+ pub struct EventParams {
322+ pub from : Pubkey ,
323+ pub data : u64 ,
324+ }
325+
326+ impl From <EventParams > for Event {
327+ fn from (params : EventParams ) -> Self {
328+ Event {
329+ from : params . from,
330+ data : params . data,
331+ }
332+ }
333+ }
334+ ```
335+
336+ ## Common Pitfalls
337+
338+ ### Forgetting the Account Discriminator
339+
340+ Always add 8 bytes for the account discriminator when calculating space:
341+
342+ ``` rust
343+ // Wrong - missing discriminator
344+ space = std :: mem :: size_of :: <Data >()
345+
346+ // Correct - includes discriminator
347+ // [!code highlight]
348+ space = 8 + std :: mem :: size_of :: <Data >()
349+ ```
350+
351+ ### Using Dynamic Types
352+
353+ Zero-copy requires all fields to be ` Copy ` types:
354+
355+ ``` rust
356+ #[account(zero_copy)]
357+ pub struct InvalidData {
358+ pub items : Vec <u64 >, // Vec is not Copy
359+ pub name : String , // String is not Copy
360+ }
361+
362+ #[account(zero_copy)]
363+ pub struct ValidData {
364+ pub items : [u64 ; 100 ], // Fixed-size array
365+ pub name : [u8 ; 32 ], // Fixed-size bytes
366+ }
367+ ```
368+
369+ ### Using load_init vs load_mut
370+
371+ Use ` load_init() ` for first-time initialization (sets discriminator):
372+
373+ ``` rust
374+ // First initialization
375+ pub fn initialize (ctx : Context <Initialize >) -> Result <()> {
376+ // [!code highlight]
377+ let account = & mut ctx . accounts. data_account. load_init ()? ;
378+ account . data = [1 ; 10232 ];
379+ Ok (())
380+ }
381+
382+ // Subsequent updates
383+ pub fn update (ctx : Context <Update >) -> Result <()> {
384+ // [!code highlight]
385+ let account = & mut ctx . accounts. data_account. load_mut ()? ;
386+ account . data = [2 ; 10232 ];
387+ Ok (())
388+ }
389+ ```
390+
391+ ### Not Validating Array Indices
392+
393+ Always validate array indices to prevent panics:
394+
395+ ``` rust
396+ pub fn update_item (
397+ ctx : Context <Update >,
398+ index : u32 ,
399+ value : u64
400+ ) -> Result <()> {
401+ let account = & mut ctx . accounts. data_account. load_mut ()? ;
402+
403+ // [!code highlight:4]
404+ require! (
405+ (index as usize ) < account . items. len (),
406+ ErrorCode :: IndexOutOfBounds
407+ );
408+
409+ account . items[index as usize ] = value ;
410+ Ok (())
411+ }
412+ ```
413+
414+ ## Real-World Use Cases
415+
416+ ### Event Queue Pattern
417+
418+ Store large sequences of events efficiently:
419+
420+ ``` rust
421+ #[account(zero_copy)]
422+ pub struct EventQueue {
423+ pub head : u64 ,
424+ pub count : u64 ,
425+ pub events : [Event ; 10000 ],
426+ }
427+
428+ #[zero_copy]
429+ pub struct Event {
430+ pub timestamp : i64 ,
431+ pub user : Pubkey ,
432+ pub event_type : u8 ,
433+ pub data : [u8 ; 32 ],
434+ }
435+ ```
436+
437+ ** Used by** : Trading protocols, audit logs, messaging systems
438+
439+ ### Order Book Pattern
440+
441+ Efficient storage for trading pairs:
442+
443+ ``` rust
444+ #[account(zero_copy)]
445+ pub struct OrderBook {
446+ pub market : Pubkey ,
447+ pub bid_count : u32 ,
448+ pub ask_count : u32 ,
449+ pub bids : [Order ; 1000 ],
450+ pub asks : [Order ; 1000 ],
451+ }
452+
453+ #[zero_copy]
454+ pub struct Order {
455+ pub trader : Pubkey ,
456+ pub price : u64 ,
457+ pub size : u64 ,
458+ pub timestamp : i64 ,
459+ }
460+ ```
461+
462+ ** Used by** : DEXs (Serum, Mango), NFT marketplaces
463+
196464## Examples
197465
198466The examples below demonstrate two approaches for initializing zero-copy
0 commit comments