Σε Αναμονή Ενεργοποίησης Λειτουργίας

Account Data Direct Mapping

Validators will stop copying account data, and programs will lose a few unsafe shortcuts

Κοινοποίηση:

Οκτώβριος 2026, από Solana Foundation

4 → 0
Account data copy paths
5
Access patterns that will fail or change behavior
4 → 8
CPI nesting depth this unblocks

Account Data Direct Mapping

October 2026 • Solana Foundation

Before a program runs, the validator copies every account's data into the program's virtual machine. When the program finishes, it copies the data back. Cross-program invocations (CPIs) repeat this in both directions. Direct mapping will remove the copying: the program will read and write account data where it already lives.

today: copy in, copy outaccount datain validator memorycopy into the VMbefore the program runsprogram runson the copy, CPIs copy againcopy backand compare for changesdirect mapping: no copiesaccount datain validator memoryVM maps itsame bytes, no copyprogram runsreads and writes in placenothing to copy backeach access was checkedup to four copies per instruction become zero, and every memory access is checked instead
Today the validator copies account data into the virtual machine and back, and every CPI repeats that. With direct mapping the virtual machine points at the account data itself, so the runtime has to check each read and write as it happens.

For that to be safe, the runtime has to check every memory access. Today a program can read past the end of an account, write to a read-only account as long as it puts the old value back, or access two memory regions in one go, and nothing happens. After this upgrade, each of those will fail with an instruction error.

Two feature gates deliver the change:

  • virtual_address_space_adjustments adds the checks. This is the gate that can break programs.
  • account_data_direct_mapping removes the copies. It changes how data reaches the program, not which accesses are allowed.

A third gate from the same proposal, syscall_parameter_address_restrictions, covers the pointers a program passes to CPI syscalls. It has been live on mainnet since June 30, 2026 and is not part of this upgrade. If your CPIs work on mainnet today, you already comply.

Most programs are not affected. Anza replayed mainnet traffic and contacted the teams whose programs would fail (SIMD-0219, Impact), but the replay only covers traffic it has seen. Test your own program instead of waiting to be contacted.

A violation rarely breaks a whole program. Usually only some instructions fail, such as a close, resize, or particular CPI path, so test every instruction, not one workflow.

One change does not fail loudly. The stack layout change can alter your program's behavior without returning an error. A passing transaction does not prove a stack-dependent path is safe. See Stack Frame Gaps Are Removed.

  • Users: Nothing to do.
  • Validator operators: Nothing to do.
  • App and client developers: Nothing to do, unless a program you call breaks the rules below.
  • Program developers: Test on devnet. Your program is at risk if it reads account bytes directly instead of through the SDK's account types, uses Rust unsafe blocks or raw pointers, resizes accounts, or, on Anchor before v1.2.2, reassigns a typed account through a CPI.
Expected Mainnet Activation DateTo be determined. Both gates are listed as pending on Anza's Feature Gate Tracker
Devnet ActivationLive on devnet (epochs 1073 and 1074, May 2026) and testnet (epochs 957 and 958, May 2026)
Breaking Change?Yes, for programs that break the rules below
Indexing Changes Required?No
Feature Gate7VgiehxNxu53KdxgLspGQY8myE6f7UokaWa4jsGcaSz (virtual_address_space_adjustments), CR3dVN2Yoo95Y96kLSTaziWDAQT2MNEpiWh5cqVq2pNE (account_data_direct_mapping)
ProposalsSIMD-0460, split from SIMD-0219. account_data_direct_mapping has no SIMD of its own.

Current feature gate activation status:

virtual_address_space_adjustments (SIMD-0460):

ClusterΚατάσταση ενεργοποίησης
TestnetΕνεργή
DevnetΕνεργή
MainnetΜη ενεργοποιημένη

account_data_direct_mapping (no SIMD):

ClusterΚατάσταση ενεργοποίησης
TestnetΕνεργή
DevnetΕνεργή
MainnetΜη ενεργοποιημένη

Breaking Changes:

All of these come from virtual_address_space_adjustments and are already live on devnet and testnet.

If your programWhat you have to do
Reads past the end of an account's dataCheck data_len() first. Learn more
Writes to a read-only or foreign-owned accountRemove the write, even if it restores the old value. Learn more
Resizes an account past the limits or with reallocUse resize and stay within the growth limits. Learn more
Accesses memory across a region boundary in one goSplit it into one access per region. Learn more
Depends on stack layout or overruns a stack frameRemove absolute stack addresses and out-of-bounds accesses. This can fail silently. Learn more

Test It Before Mainnet

A program deployed to devnet or testnet already runs under the new rules. To check a gate from the CLI, run solana feature status 7VgiehxNxu53KdxgLspGQY8myE6f7UokaWa4jsGcaSz --url <cluster>.

solana-test-validator turns on every gate it knows about, so the new rules are on by default. To compare with today's mainnet, start a fresh ledger with both gates off:

# Both gates on (the default)
solana-test-validator

# Both gates off. --deactivate-feature only applies when the ledger is created,
# so use --reset if test-ledger already exists.
solana-test-validator --reset --deactivate-feature 7VgiehxNxu53KdxgLspGQY8myE6f7UokaWa4jsGcaSz CR3dVN2Yoo95Y96kLSTaziWDAQT2MNEpiWh5cqVq2pNE

If you see --deactivate-feature argument ignored, ledger already exists, the gates are still on. Rerun with --reset.

Other test frameworks have different defaults:

LiteSVMGates off by default (mainnet feature set). Turn them on in code.+

LiteSVM builds its runtime environment when builtins are added, so set the feature set first. See the LiteSVM API docs.

use agave_feature_set::{account_data_direct_mapping, virtual_address_space_adjustments};
use litesvm::LiteSVM;

let mut features = LiteSVM::mainnet_feature_set();
features.activate(&virtual_address_space_adjustments::id(), 0);
features.activate(&account_data_direct_mapping::id(), 0);

let svm = LiteSVM::default()
    .with_feature_set(features)
    .with_builtins()
    .with_sysvars()
    .with_feature_accounts()
    .with_default_programs()
    .with_lamports(1_000_000_000_000_000)
    .with_sigverify(true)
    .with_blockhash_check(true);
MolluskGates on by default (every gate). Turn them off to compare with mainnet.+

Mollusk builds its program cache when the instance is created, so rebuild it after changing the gates, then add your program. See the Mollusk API docs.

use mollusk_svm::{program::ProgramCache, Mollusk};

let mut mollusk = Mollusk::default();
mollusk.feature_set.virtual_address_space_adjustments = false;
mollusk.feature_set.account_data_direct_mapping = false;
mollusk.program_cache = ProgramCache::new(&mollusk.feature_set, &mollusk.compute_budget, false);
mollusk.add_program(&program_id, "my_program");
SurfpoolGates off by default (mainnet feature set). Turn them on with --feature.+

Pass both gate addresses. See the Surfpool docs.

surfpool start --feature 7VgiehxNxu53KdxgLspGQY8myE6f7UokaWa4jsGcaSz --feature CR3dVN2Yoo95Y96kLSTaziWDAQT2MNEpiWh5cqVq2pNE

To test a real mainnet transaction, open it in SVM Scope, a community tool that replays transactions from the last 30 days. Every transaction has its own link:

https://svmscope.vercel.app/tx/<signature>

Replay once, switch on the gate it labels "direct mapping" (7Vgieh…), and replay again. A transaction that passes with the gate off and fails with it on belongs to a program that needs a fix.

Migration Checklist

For UsersNo action needed.+

Wallets, balances, fees, and confirmation times are unaffected. If an app you use runs a program that still breaks the rules, its transactions can fail after activation until the app team fixes it. A failed transaction changes no account state but still pays the usual fee.

For Program DevelopersTest on devnet now, fix any access errors, and redeploy before mainnet activation.+

Every program:

Most programs pass without changes. To confirm yours does:

  • Run your tests against solana-test-validator, then on devnet. Cover every instruction, including the close, resize, and CPI paths.
  • Search your code for unsafe, raw pointer casts, and realloc. If none appear and you are not affected by the Anchor issue below, the risk is low.
  • Optional: replay recent mainnet transactions in SVM Scope to cover traffic your tests miss.
  • If something fails, redeploy the fix before mainnet activation. Immutable programs cannot be fixed, so they need a new program and a user migration.

If you read, write, or resize account data by hand (details):

  • Check data_len() before casting account bytes to a struct.
  • Remove writes to read-only accounts and to accounts you no longer own, even if the write restores the old value.
  • Replace realloc with resize, which always zero-fills. With Anchor's realloc constraint, upgrade to v0.32 or later, or set realloc::zero = true. With Pinocchio, upgrade to v0.9 or later, where realloc calls resize.

If you access memory in unsafe code or raw syscalls (details):

  • Split any load, store, or syscall that crosses a region boundary into one access per region.

If you rely on stack layout (details):

  • This can alter behavior without an error. Remove absolute stack addresses, address math across frames, and out-of-bounds stack accesses.

If you use Anchor:

  • Anchor writes every mutable typed account back after your handler returns, even if a CPI reassigned the account to another program. Under the new rules that write fails with ExternalAccountDataModified. Affected types: Account, InterfaceAccount, AccountLoader, LazyAccount, and Migration, including inside Box or Option.
  • The fix (#5011) ships in Anchor v1.2.2 (milestone). Upgrade to v1.2.2 and rebuild.
  • Until you can upgrade, declare an account you reassign through a CPI as UncheckedAccount or AccountInfo. These skip Anchor's owner and discriminator checks, so check the account's owner and type yourself.
For App and Client DevelopersNo code changes. Check that the programs you call are ready.+
  • Wallets, frontends, bots, indexers, and tools that build, decode, or simulate transactions need no changes. Transaction format, RPC methods, and account data are unchanged.
  • If your app calls another team's program, ask them whether they have tested on devnet. Report failures with the link to the SVM Scope replay (https://svmscope.vercel.app/tx/<signature>).
For Validator OperatorsNothing to do. Stay on a release at or above the mainnet minimum.+
  • Stay at or above the mainnet version floor for Agave and Firedancer. Every release at or above it contains both gates. The Feature Gate Tracker lists the floor and the mainnet epoch.
  • There is nothing to configure.

Breaking Changes in Detail

For Program DevelopersCheck lengths before reading, stop writing to accounts you do not own, split cross-region accesses, and drop stack layout assumptions.+

Account data and region violations fail loudly: the instruction returns an error, the transaction fails, and account state does not change. Stack layout changes are the exception (see below).

Breaking Change: Every Account Data Access Is Checked

Every read is checked against the account's current length. Every write is checked against the account's owner and writable flag, and, if it reaches past the end, against the resize limits. Before, a bad access went unnoticed if it stayed inside the reserved address space or if a write was undone before the program returned.

before: checked only when the program returnsaccount data (current length)10 KiB reserved paddingwrite to a read-only account, then restore itallowed, nothing noticedread past the end of the dataallowed, reads the paddingafter: every access is checkedaccount data (current length)10 KiB reserved paddingwrite to a read-only account, then restore itfails: ReadonlyDataModifiedread past the end of the datafails: AccountDataTooSmall or InvalidRealloca write past the end of a writable account your program owns still works: it grows the account, within the resize limits
The same account bytes, before and after. Today the runtime only compares the data when the program returns, so a write that is undone in time, or a read into the reserved padding after the data, goes unnoticed. Under the new rules each access is checked as it happens.
The accessThe errorIn the logs
Read past the end of a read-only or foreign-owned accountInstructionError::AccountDataTooSmallaccount data too small for instruction
Read past the end of a writable account your program ownsInstructionError::InvalidReallocFailed to reallocate account data
Write to an account not flagged writableInstructionError::ReadonlyDataModifiedinstruction modified data of a read-only account
Write to an account your program does not ownInstructionError::ExternalAccountDataModifiedinstruction modified data of an account it does not own
Write past the maximum account size or the growth limitsInstructionError::InvalidReallocFailed to reallocate account data

Simulation and getTransaction results carry the bare variant name, for example {"InstructionError": [0, "AccountDataTooSmall"]}. Logs, including solana logs and solana confirm -v, print the sentence, for example Program <id> failed: account data too small for instruction.

Writes inside a writable account your program owns work as before. A write past the end is treated as a resize: it succeeds, and the runtime zero-extends the account, as long as it stays within the growth limits: 10 KiB per instruction, 10 MiB per account, and the transaction's total growth cap. Beyond that padding, or outside any account, you get a generic access violation (Program <id> failed: Access violation ..., reported as ProgramFailedToComplete).

A common cause: a program casts account bytes to a fixed-size struct and the account is shorter than the struct.

Two fixes that do not work. Catching the error in a calling program fails because the error aborts the transaction. Restoring the old bytes after a write fails because the runtime checks the write, not the final value. Remove the write.

AccountInfo::realloc was deprecated in solana-account-info 2.3.0 and removed in 3.0.0. Called with zero_init = false, it grows the length without writing the new bytes, so the first read of them is a read past the real end and fails with InvalidRealloc. Use AccountInfo::resize, which always zero-fills.

Breaking Change: Accesses Cannot Span Memory Regions

The virtual machine gives a program separate address ranges, called regions: its read-only data, the stack, the heap, and, in the input area, each account's metadata, each account's data, and the instruction data. A load, store, or syscall that crosses from one region into the next is an access violation once the gate is active.

For example, the serialized input places each account's 8-byte length field directly before its data. A program that copies both in one go relied on that layout:

// Before: one copy that starts in the account's metadata and ends in its data.
// An access violation once the gate is active.
let data = account.try_borrow_data()?;
let mut header = [0u8; 40];
unsafe {
    core::ptr::copy_nonoverlapping(data.as_ptr().sub(8), header.as_mut_ptr(), 40);
}

Read each part from its own region instead:

// After: one access per region.
let data = account.try_borrow_data()?;
let body = data.get(..32).ok_or(ProgramError::AccountDataTooSmall)?;
let mut header = [0u8; 40];
header[..8].copy_from_slice(&(data.len() as u64).to_le_bytes());
header[8..].copy_from_slice(body);

Slices from the SDK stay inside one region, so this mostly affects unsafe code and hand-written syscalls.

Breaking Change: Stack Frame Gaps Are Removed

SIMD-0460 removes the unmapped gaps between stack frames for every program, including sBPFv0 programs, which still have them. With the gate active, the stack is one contiguous range at lower addresses. For sBPFv0 programs, each call or callx (indirect call) moves the stack pointer 4 KiB instead of 8 KiB, as sBPFv3 already does. The total stack size does not change.

This can break programs that use absolute stack addresses, do address math across frames, or rely on an out-of-bounds access stopping at a frame boundary.

  • An access outside the new, smaller range, such as an absolute address into the old upper half, fails with an access violation.
  • An access inside it does not fail. An overrun that would have hit a gap reads or writes the neighboring frame instead, and an absolute address that worked before may point at a different frame's bytes. The program keeps running with different data and no error.

Anza's replay found one program that used slightly fewer compute units with the gate on and no other difference. The suspected cause is an out-of-bounds stack access. If a program shows an unexplained compute unit change, check for a stack overrun, and test stack-heavy unsafe code on devnet.

Technical Details

Four Copies Become Zero

Today an instruction can copy account data four times:

CopyDirection
SerializationRuntime to virtual machine
CPI call edgeVirtual machine to runtime
CPI return edgeRuntime to virtual machine
DeserializationVirtual machine to runtime

The validator also compares the data afterward to find what changed. With account_data_direct_mapping active, the virtual machine's memory points at the account's data, so none of the copies or comparisons are needed. Instructions that make CPIs or touch large accounts save the most.

Why Two Gates

Anza and Firedancer split SIMD-0219 so each part could ship and be tested on its own. The access checks in virtual_address_space_adjustments went first, so any breakage could be traced to them and not to the mapping change. Agave separated account_data_direct_mapping from the checks in anza-xyz/agave#7113.

On devnet and testnet both gates are live, so a passing test there covers both. A replay with only virtual_address_space_adjustments on exercises the rules but not the mapping, so confirm on devnet or testnet.

SIMDTitleWhat it covers
SIMD-0219Stricter ABI and Runtime ConstraintsThe original proposal. Superseded by SIMD-0459 and SIMD-0460.
SIMD-0459Syscall Parameter Address RestrictionsCPI pointer rules (InvalidPointer), gate syscall_parameter_address_restrictions (EDGMC5kxFxGk4ixsNkGt8bW7QL5hDMXnbwaZvYMwNfzF). Live on mainnet since epoch 995.
SIMD-0460Virtual Address Space AdjustmentsThe access checks and stack gap removal, gate virtual_address_space_adjustments.
SIMD-0268Raise CPI Nesting LimitRaises CPI depth from 4 to 8, to activate after SIMD-0219. See Deeper CPI Nesting.
SIMD-0166SBPF Dynamic stack framesLive on mainnet since epoch 809 (June 2025). It removed stack gaps for sBPFv1 and later because they were "incompatible with direct mapping".

SIMD-0460 is still in review and does not list its feature key; the gate address comes from Agave's feature set.

Learn more: Solana Upgrades