Skip to content

Commit a791928

Browse files
author
Akash Thota
committed
feat: add more docs to zero copy
1 parent d5d7eb9 commit a791928

File tree

1 file changed

+268
-0
lines changed

1 file changed

+268
-0
lines changed

docs/content/docs/features/zero-copy.mdx

Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

1054
Zero 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

198466
The examples below demonstrate two approaches for initializing zero-copy

0 commit comments

Comments
 (0)