Anchor's defaults (Borsh accounts, fixed #[derive(Accounts)] lists, InitSpace) get most programs shipping. Production systems hit edges: large books, variable hop routes, schema growth, foreign callees, and hard CU or deploy-size ceilings.
This page is the conceptual map for Advanced Anchor - zero-copy, remaining_accounts, realloc, custom account types, feature flags and cfg, CU and binary size, and interop with native or Pinocchio programs on Agave 4.1.1.
Advanced Anchor keeps the same runtime contract (declared accounts, ownership, rent, CU) but opts out of conveniences that burn compute, freeze layout, or hide variable metas, replacing them with loaders, tails, resizes, and CPI bridges.
Insight: Wrong zero-copy layout, unvalidated remaining accounts, sloppy realloc, or feature-gated IDL drift turns framework speed into corruption, rent griefing, or client breakage under mainnet load.
Key Concepts:#[account(zero_copy)] / AccountLoader, ctx.remaining_accounts, realloc + realloc::payer / realloc::zero, manual account traits, Cargo features / #[cfg], compute units and .so size, native / Pinocchio CPI (invoke, declare_program!).
When to Use This Model: Large fixed state, routers and multi-oracle paths, growing schemas, mixed-framework DeFi, CU or deploy size pressure, or dev-only instructions that must never ship.
Limitations/Trade-offs: More power means more manual validation; zero-copy and realloc fight schema churn; remaining accounts leave the IDL; cfg can fork IDL and layout; interop trusts the callee's rules, not Anchor constraints on the other side.
Anchor sits on Solana's account model: programs are stateless executables; durable state lives in program-owned data accounts; every account an instruction needs must appear in the transaction with correct signer and writable flags.
The usual path is: name accounts in #[derive(Accounts)] with constraints; decode bodies with Borsh (Account<'info, T>); fix space at init with space = / InitSpace; expose a stable IDL for clients (@solana/kit 7.0.0, Codama, Anchor TS).
Advanced Anchor starts when that path is wrong for the workload:
Pressure
Default that breaks down
Advanced response
Large fixed tables (books, ticks, bitmaps)
Full Borsh deserialize/serialize each ix
Zero-copy + AccountLoader
Variable account count (routes, hooks, oracles)
Fixed named fields only
ctx.remaining_accounts
Schema or collection growth after init
Fixed SPACE forever
realloc (or migrate to a new account)
Foreign or exotic layouts
Account<T> + #[account] only
Custom types or UncheckedAccount + manual checks
Dev diagnostics / cluster-only ix
Same binary everywhere
Features + cfg (or separate crates)
CU ceiling or deploy size
Verbose logs, re-find PDAs, fat deps
Measure, then cut hot-path cost and binary weight
Compose with non-Anchor programs
Anchor-only CPI helpers
Raw invoke / declare_program! interop
Nothing here changes Sealevel locks or ownership: you still cannot invent accounts at runtime, write foreign data without CPI into the owner, or spend unbounded CU. You only change how Anchor maps those rules into types and macros.
For small configs, Account<'info, T> is fine: Anchor checks owner and discriminator, Borsh-decodes, and writes back on drop when mutable. When T is large and mostly fixed (order books, tick arrays, slabs), full parse/serialize burns CU with size. Zero-copy keeps bytes in place and mutates through a typed view.
Prefer #[repr(C)], fixed-size fields, and bytemuck/Pod-friendly layout (no naive String/Vec). Discriminators still apply. Use AccountLoader, not Account, and do not mix Borsh and zero-copy on the same live account. Plan capacity up front; mark mut for load_mut. Profile first: on tiny accounts zero-copy often costs more than it saves.
The typed Accounts struct is a prefix of the instruction's account list. Anything after those named fields is ctx.remaining_accounts: raw AccountInfo with no automatic owner, signer, or discriminator checks.
Typical uses: multi-hop routers, optional oracle sets or hooks, and forwarding open-ended metas into CPI.
pub fn route(ctx: Context<Route>, hops: u8) -> Result<()> { require!( ctx.remaining_accounts.len() == hops as usize * 2, RouterError::BadMetas ); require!(ctx.remaining_accounts.len() <= MAX_REMAINING, RouterError::TooMany); for (i, acc) in ctx.remaining_accounts.iter().enumerate() { if i % 2 == 0 { require_keys_eq!(*acc.owner, anchor_spl::token::ID, RouterError::BadOwner); } } Ok(())}
Remaining accounts are not named in the IDL; document the protocol for clients and indexers, or ship typed instructions per common route. Clients append metas in agreed order with correct is_signer / is_writable. Cap length. Validate every entry before read, write, or CPI.
Init freezes data_len and the rent-exempt minimum for that size. When schemas or collections grow, realloc changes the buffer under program ownership inside an instruction:
realloc = N: target data_len (include the 8-byte discriminator when using INIT_SPACE patterns). Config::INIT_SPACE only exists if Config carries #[derive(InitSpace)].
realloc::payer: funds the rent delta on growth; must be mutable.
realloc::zero: whether new bytes are zeroed. Zero when new bytes become new fields; stale bytes are a bug class if you leave them dirty.
System program must be present for rent transfers.
Prefer forward growth and migrate instructions for large redesigns. Do not realloc on every hot path: grow in chunks or preallocate when the ceiling is known. Zero-copy plus frequent realloc is a poor mix.
Most programs should stay on Account<'info, T>, InterfaceAccount / token wrappers, AccountLoader, and constrained UncheckedAccount / SystemAccount / Signer.
Custom account types implement Anchor traits by hand (AccountDeserialize, ownership checks) when foreign layouts or specialized validation do not map to derives. They raise audit surface: lifetimes, try_deserialize_unchecked misuse, missing owner checks. Prefer wrapping built-ins or a foreign IDL, with LiteSVM fixtures if you must go custom.
Good uses: optional diagnostics, heavier test asserts, cluster constants from build scripts. Dangerous uses: splitting #[program] so mainnet IDL differs from clients; gating account field layouts across builds of the same program ID; leaving devnet-only instructions in release. Prefer empty defaults, CI that forbids dangerous mainnet feature combos, runtime admin gates, or a separate dev crate when instructions must never exist on mainnet.
Compute units meter work inside a finite transaction budget (plan around a default on the order of ~1,400,000 CU unless Compute Budget instructions raise the limit within protocol caps). Deserialization, constraints, logs, PDA derivation, and CPI all draw from the same pool.
High-leverage habits: store canonical bumps; use zero-copy only where Borsh dominates on large state; trim msg! and prefer events; fewer accounts and shallower CPI; right-size client CU limits from simulation with headroom; release profiles and splitting rare admin surfaces can shrink the .so. Never drop validation to save CU. Measure first; when framework tax still dominates, consider a Pinocchio or native hot path via CPI.
Anchor constraints apply only to yourAccounts struct. Validate program IDs, account order, writable/signer flags, and post-CPI state. Clients with @solana/kit 7.0.0 must encode the same discriminators and metas the callee expects.
Treat these tools as a stack of escape hatches, not a checklist to enable all at once.
Tool
Wins
Costs
Fits
Zero-copy
Low CU on large fixed state
Rigid layout, harder migrations
Books, slabs, tick arrays
remaining_accounts
Variable composition
No IDL names; full manual validation
Routers, hooks, multi-oracle
realloc
Pay rent when needed
Payer, zeroing, CU, max caps
Additive growth, bounded lists
New account + migrate
Clean V2 layout
UX and dual-read period
Breaking redesigns
Custom account types
Foreign layout fit
Audit and trait burden
Last-resort interop
cfg features
Smaller or safer release binaries
IDL/layout fork risk
Debug logs, never-ship dev ix
CU/size tuning
Landing under budget, deploy fit
Complexity if unmeasured
Profiled hot paths
Native/Pinocchio CPI
Specialized performance or shared protocol
Byte-level contract risk
Mixed DeFi stacks
Migration: prefer append-only Borsh fields and a version byte when possible; realloc for bounded growth; dedicated migrate when layout breaks; dual-read during rollout.
Clients and indexers: every hatch that leaves the IDL (remaining accounts, manual discriminators, cfg-gated instructions) needs a published protocol: constants, route docs, Codama/kit codecs, and migration events.
Security review: remaining-account authentication, realloc rent griefing and max size, zero-copy alignment and type confusion, unchecked custom deserialize, feature-flag attack surface, CPI program spoofing.
On an Agave 4.1.1-aligned stack (CLI 3.0.10, Anchor 0.32.1, Rust 1.91.1, LiteSVM 0.6.x, Surfpool 0.12.0), exercise growth, adversarial remaining accounts, and CU profiles before mainnet traffic does.
What counts as "Advanced Anchor" versus everyday Anchor?
Everyday Anchor: Borsh Account<T>, fixed account structs, init with known space, standard CPI helpers. Advanced Anchor: zero-copy loaders, remaining account tails, realloc growth, hand-rolled account types, compile-time feature splits, aggressive CU/size work, and native/Pinocchio interop.
When should I use zero-copy accounts?
When account data is large and mostly fixed, and profiling shows Borsh deserialize/serialize dominating CU. Use #[account(zero_copy)] with #[repr(C)] and AccountLoader. See Zero-Copy Accounts.
Does zero-copy still use a discriminator?
Yes. Type tags still protect against reading one layout as another. Init and load paths must write and check the expected prefix.
What is ctx.remaining_accounts?
Accounts listed on the instruction after the fields of your #[derive(Accounts)] struct. They are not named in the IDL and receive no automatic constraint checks. See remaining_accounts.
When do I need realloc in Anchor?
When an existing program-owned account must grow for new fields or more entries (or carefully shrink). Use realloc, realloc::payer, realloc::zero, and the system program. See realloc in Anchor.
Is realloc a substitute for account versioning?
No. Realloc changes byte length; versioning decides how you parse old and new layouts. Combine a stable discriminator, a version field, dual-read, and migrate or realloc as needed.
Should I implement custom account types?
Only when built-ins cannot express the layout or validation. Prefer Account, InterfaceAccount, AccountLoader, and constraints. See Custom Account Types.
How do feature flags interact with the IDL?
Anything that changes instructions or account shapes between builds produces different IDLs. Keep a single mainnet release profile. See Feature Flags & cfg.
How do I reduce CU without weakening security?
Store bumps, cut verbose logs, prefer zero-copy only on large hot state, minimize accounts and CPI depth, and set client CU limits from simulation. Never remove ownership or signer checks for CU. See CU & Size Optimization.
How do I measure CU and binary size?
Use compute unit consumption in LiteSVM/Surfpool tests and RPC simulation on devnet; inspect release .so size after anchor build with production features.
Can an Anchor program CPI into a Pinocchio or native program?
Yes. Prefer declare_program! when an IDL exists; otherwise build instruction data and metas and invoke / invoke_signed. Validate the program ID and every account. See Interacting with Native/Pinocchio Programs.
What does declare_program! need?
A callee IDL JSON available at compile time so Anchor can generate typed CPI helpers. Without an IDL, use raw instruction construction.
How should @solana/kit clients treat these advanced patterns?
Generate or hand-write codecs that match discriminators, fixed accounts, remaining-account protocols, and any non-Anchor CPI bytes. Target @solana/kit 7.0.0 aligned with this stack.
When should I leave Anchor for Pinocchio on a hot path?
When measured CU or binary size still fails product goals after Anchor-side optimizations, and you can isolate a small surface with a clear CPI boundary.
Stack versions: This page was written for Agave 4.1.1, Solana CLI 3.0.10, Anchor 0.32.1, anchor-lang 0.32.1, Rust 1.91.1, @solana/kit 7.0.0, Surfpool 0.12.0, and LiteSVM 0.6.x.
Y29kZWd1aWRlcy5pb3xjZ2lvMjgyfDIwMjYwNw
Reviewed by Chris St. John·Last updated Jul 19, 2026