
Back to Blog

Migrating Solana Programs to SBPFv3
Written By
Joe Caulfield
TL;DR
SBPFv3 is the new bytecode format for Solana programs.
Once SIMD-0500 activates, the network will reject new deployments, upgrades, and finalizations of programs built for older bytecode versions. Already-deployed programs will keep executing.
This guide covers what SBPFv3 is, how to build for it, and pitfalls that can break your program.
What Is SBPFv3?
SBPFv3 is a new bytecode format comprised of three separate SIMDs:
It has been available since early 2026 and the network recently gained support for it on mainnet-beta with the activation of feature 5cC3foj77CWun58pC51ebHFUWavHWKarWyR5UUik7dnC in June.
Some highlights from what’s changed between SBPFv0 - the most popular bytecode format currently - and SBPFv3:
SBPFv0 | SBPFv3 | |
|---|---|---|
Syscalls | Resolved at load time via relocations against the symbol table | Resolved at compile time; |
Runtime Relocations | Required | Not supported |
ELF layout | Permissive | Fixed segment layout and order, strict header validation |
| Target register named by the immediate field | Target register named by the destination field |
|
|
|
JMP32 class | Not supported | Supported |
Stack frame gaps | Enabled | Disabled |
SBPFv3 is not a superset of SBPFv2. Instead it reverts SIMD-0173 (encoding changes) and SIMD-0174 (arithmetic changes):
The PQR class (
udiv,urem,sdiv,srem,lmul) is removed.lddw,neg, andleare restored.The encoding of memory instructions reverts back to the SBPFv0 standard.
sub reg, immreturns to its original operand order, and results are no longer explicitly sign-extended.callxnames its register in the destination field rather than the source field.
Toolchains
We always recommend upgrading to the latest version for any toolchain or component. However, the suggested minimums for building SBPFv3 programs are as follows:
Component | Minimum |
|---|---|
platform-tools | v1.56 |
cargo-build-sbf | v4.2.0 |
solana-define-syscall | v3.0.0 |
platform-tools v1.56 ships with Rust 1.89.0 and LLVM 20. You can view other versions of platform-tools and their corresponding Rust and LLVM versions in their release notes.
cargo-build-sbf can be installed and updated separately from the Solana CLI:
You can specify a platform-tools version and a target architecture (bytecode format) like so:
Warning: do not pass --arch v3 to any platform-tools older than v1.53. This could produce an outdated bytecode format not compatible with SBPFv3. See the cargo-build-sbf README for more information.
solana-define-syscall v3.0.0 is the first version to use the new syntax for static syscalls compatible with SBPFv3.
Warning: using an older version of solana-define-syscall, or declaring syscall bindings with extern “C”, can brick your program. See Static Syscalls and Unresolved Symbols below for more information.
Building for SBPFv3
A hello-world repository demonstrating every step below is available at buffalojoec/hello-sbpfv3.
Your program will declare its bytecode version in the ELF header's e_flags field, where 0x3 means SBPFv3:
Static Syscalls
Declare a syscall using solana-define-syscall:
Under target_feature = "static-syscalls", the macro no longer emits an extern "C" import to be relocated at load time. It transmutes the syscall's murmur32 hash straight into a function pointer:
In the bytecode, this becomes an ordinary call (opcode 0x85) with source register 0 and the hashed function name in the immediate:
If you’re registering a custom syscall, you should also use define_syscall! to define its static pointer.
Unresolved Symbols
It’s imperative that you do not declare syscalls in your program with extern “C”. Doing so will result in an unresolved symbol remaining in your final bytecode, represented by call -1, which can brick your program.
The presence of this call -1 does not violate the runtime’s verification ruleset, therefore deployments with unresolved symbols will succeed. When your program is invoked, if execution encounters that unresolved symbol, your program will loop over itself until you reach the maximum number of instruction frames, finally returning error CallDepthExceeded.
Luckily, this issue is trivial to detect. We strongly recommend testing your programs before deploying them to any network, and most testing frameworks like Mollusk, LiteSVM, Surfpool, and Anchor will surface this issue.
cargo-build-sbf v4.2.0 is actually outfitted to pass the -z defs rust flag to emit an error at build time when your program contains an unresolved symbol. You can also pass this flag in RUSTFLAGS to any build tool you use.
callx Register Field Moved Again
The fields of the callx opcode have moved again.
Version | Register Field |
SBPFv0/SBPFv1 | immediate |
SBPFv2 | source |
SBPFv3 | destination |
This is most important for assembly developers, toolchain maintainers, or anyone working with ELFs by hand. For developers programming in Rust, the platform-tools compiler will emit the correct form for whichever --arch you pass.
Null Reads Can Silently Succeed
This is another important caveat in the new bytecode format that affects assembly developers and toolchain maintainers.
SBPFv3 moves read-only data to VM address 0. The linker script pads .rodata with a leading zero byte, 8-aligned, so nothing is placed at address 0. However, the region is still mapped and readable, so a load that aborted under SBPFv0 now succeeds and execution continues.
In SBPFv0, VM address 0 sat in the bytecode region, and the loader never mapped a readable data page there, so a read always produced an access violation.
Safe Rust will not construct such a dereference. However, in unsafe Rust, C, or assembly, a null pointer that would fail loudly in testing will now return a value and likely fail somewhere else.
Stack Behavior Changed
SBPFv3 has removed stack frame gaps, meaning all stack frames are now contiguous. If your program was previously running close to the 4096-byte limit, you should re-measure.
The platform-tools compiler will emit a warning about stack overflow, but it is not a rustc diagnostic, so it will not fail the build. Instead it just emits an Error: in your build logs.
Why Migrate Now: Old Bytecode Deployments Will Be Rejected
Once SIMD-0500 is activated, the network will reject deployments, upgrades, and finalizations of any program built for a bytecode version older than SBPFv3.
You'll need to rebuild your program for SBPFv3 and upgrade it before then. Already-deployed programs running SBPFv0, v1, or v2 will continue to execute without restriction.
The feature gate for SIMD-0500 is planned for activation in Agave v4.4.
Next Steps
Now that you know what SBPFv3 is and how to build your program for it, you should have everything you need to build or migrate your program.
To migrate your program:
Update your toolchain: platform-tools v1.56+, cargo-build-sbf v4.2.0+, solana-define-syscall v3.0.0+.
Rebuild with
cargo build-sbf --arch v3.Build with
-z defsto catch unresolved symbols.Verify the bytecode version:
readelf -h program.soshould showCPU Version: 3.Test before deploying to any network. Mollusk, LiteSVM, Surfpool, and Anchor all surface the issues covered in this guide.
Deploy and upgrade your program.
For a working end-to-end example, see buffalojoec/hello-sbpfv3.
We strongly recommend testing your programs before deploying them to any cluster.
Don't wait for an activation date. Migrate now, and follow @anza_xyz or the Solana Tech Discord for SIMD-0500 activation announcements.