What Is a Solana Program?
Learn what Solana programs are, how they work, why code is separate from state, and how accounts, PDAs, CPI, and compute limits shape execution.

Introduction
Solana programs are Solana’s form of on-chain executable logic, but the important thing to notice is not that they are “smart contracts.” It is that they are smart contracts built around a different execution model. On Solana, a program is an account containing executable sBPF bytecode with its executable flag set to true, while mutable state lives elsewhere in separate data accounts. That separation sounds like an implementation detail at first. In practice, it changes almost everything: how transactions are constructed, how programs store data, how multiple transactions can run in parallel, and what kinds of bottlenecks developers hit.
If you come from Ethereum, the puzzle is this: why would a blockchain deliberately make contracts stateless? If you come from traditional systems, the puzzle is slightly different: how can useful applications exist if the code itself cannot simply hold persistent mutable memory? Solana’s answer is that explicit state management makes execution more predictable. The runtime can see which accounts an instruction will read and write, and that visibility is a big part of how Solana pushes toward high throughput.
So the right way to understand Solana programs is not as isolated code blobs, but as functions over accounts inside a tightly constrained runtime. Once that clicks, the rest of the model (program-derived addresses, cross-program invocation, upgrade authorities, compute budgets, and runtime limits) starts to fit together.
What exactly is a Solana program and how does it differ from an Ethereum contract?
At the most literal level, a Solana program is an on-chain account that stores executable sBPF bytecode and is marked executable. Solana uses LLVM to compile programs into ELF binaries containing Solana Bytecode Format, or sBPF, and the runtime executes that code inside a sandboxed virtual machine. sBPF is related to eBPF, but it is not standard eBPF; it is Solana’s own variant tailored to its runtime.
That definition is correct, but it is still incomplete unless you pair it with the other half of the model: programs are stateless. A program account contains code, not application state. If a decentralized exchange needs an order book, if a token system needs balances, or if a game needs player inventory, that data is stored in separate accounts. When a user sends a transaction, the instruction includes the program to invoke and the accounts the program is allowed to inspect or modify.
This is the central contrast with execution models where a contract address directly owns both its code and its persistent storage. On Solana, the code is deployed once and can manage many distinct data accounts. The benefit is that state becomes explicit at the transaction boundary. The cost is that developers must think much more carefully about account layout, ownership, initialization, and authority.
A useful way to say it is this: a Solana program is less like an object carrying its own fields, and more like a reusable state transition rule that is applied to a set of external state containers. That analogy explains why Solana can reason about access patterns ahead of execution. It fails if taken too far, because programs still have identities, authorities, upgrade lifecycles, and call relationships of their own.
Why does Solana store program code separately from persistent state?
The design goal behind Solana programs is not merely modularity. It is predictable execution under high throughput. Solana’s broader architecture uses a leader-based ordering model tied to Proof of History, where a leader sequences transactions and validators replicate execution. In that environment, it is valuable for the runtime to know up front which pieces of state a transaction intends to touch.
When state is externalized into accounts passed to instructions, the scheduler can reason about conflicts. Two transactions that touch disjoint writable accounts may be processed without contending on the same state. Two transactions that both want to write the same account must be ordered against each other. This is part of the logic behind Sealevel, Solana’s parallel runtime target: not “parallelism by magic,” but parallelism because state dependencies are surfaced explicitly.
That is why Solana’s account model is not just a storage convention. It is an execution strategy. If a contract could mutate arbitrary hidden internal storage, the runtime would know less about what conflicts with what. By forcing state to travel as accounts in the instruction interface, Solana makes dependency information visible to the runtime.
This also explains some of the discomfort developers initially feel. The model is more explicit because it pushes bookkeeping outward. You do not casually write to some global program state. You create, own, initialize, and pass a state account. That added friction is not accidental; it is the price of making state access machine-visible.
How do accounts function as the primary surface for Solana programs?
To understand Solana programs mechanically, you need to think in terms of account types and ownership. The executable field separates program accounts from ordinary data accounts. Program accounts have executable = true; data accounts have executable = false. A deployed program is owned by a loader program, and modern upgradeable deployments using loader-v3 separate the visible program account from the ProgramData account that actually stores the executable code.
For application logic, the important accounts are usually the data accounts. A program can define the structure of those accounts and enforce invariants over them, but it does not get to mutate arbitrary accounts on the chain. In practice, a transaction must present the accounts the program will use, and the runtime checks permissions, ownership, and mutability constraints.
Suppose a lending protocol wants a “position” for each user. On Solana, the protocol typically does not store all positions in hidden contract storage. Instead, each position is represented by an account. The program owns the account, defines its binary layout, and updates it when instructions are processed. The account may hold fields such as collateral amount, debt amount, and authority. A user transaction passes that position account into the instruction, and the program validates that the account really is one of its state accounts before modifying it.
This account-centric structure is why Solana documentation spends so much time on system accounts, sysvars, program accounts, data accounts, and ownership rules. Those are not side concepts. They are the real substrate of execution.
How are Solana programs invoked within transactions?
A Solana transaction contains one or more instructions, the needed signatures, and a recent blockhash. The network processes all instructions in the transaction atomically: if any instruction fails, the entire transaction fails and all state changes are reverted, though fees are still charged.
Inside that model, a program invocation works roughly like this. A transaction names a target program and provides a list of accounts plus instruction data. The runtime looks up the program’s compiled executable, often from a global program cache that stores verified and compiled programs. It then serializes the relevant account data into a flat parameter buffer, creates the sBPF virtual machine with stack, heap, and memory regions, executes the program while metering compute units, and finally deserializes any modified account data back into account state.
That serialization step is worth noticing. Programs do not run “inside” accounts. The runtime constructs a temporary execution environment from account inputs, runs the code, and then commits the results if execution succeeds. This helps explain both Solana’s safety model and some of its performance constraints. Memory inside execution is transient. Persistence comes only from writing back to the supplied accounts.
There is also an operational subtlety around deployment and upgrades: newly deployed or upgraded programs are not immediately visible in the same slot. Solana’s runtime delays visibility by one slot. If a program is deployed in slot N, it becomes callable in slot N+1. That avoids a class of ambiguity around code visibility during deployment, but it matters for tooling and release workflows.
How do you create and use state accounts on Solana (guestbook example)?
Imagine a simple on-chain guestbook. A user wants to leave a message that remains on-chain for later readers. In Solidity-style mental models, you might picture a contract appending to its own internal storage. On Solana, the flow is different.
First, someone creates an account to hold the message data. That creation usually goes through the System Program, which allocates the account and transfers ownership to the guestbook program. At this point, nothing meaningful has happened at the application layer yet; the chain has merely created a byte container owned by the program. Then the guestbook program is invoked with that new account plus instruction data containing the message text. The program checks that the account is owned by it, verifies that the account has enough space for the intended layout, and writes the message bytes into the account’s data.
Later, when another instruction wants to edit or read the message, the transaction must pass that message account again. The program does not “look up its internal state” in some opaque place. The state is the account. If the instruction forgets to include the account, the program cannot operate on it. If two transactions both try to write the same message account, they conflict on the same writable state. If they touch different message accounts, they are much easier for the runtime to schedule independently.
That is the mechanism in miniature. Solana programs are powerful not because they hide state, but because they make state addressable, explicit, and governable through ownership rules.
What are Program Derived Addresses (PDAs) and how do programs control accounts?
| Type | Who controls | Can sign? | Deterministic? | Limits / cost |
|---|---|---|---|---|
| PDA | Owning program | Runtime-signed via invokesigned | Yes; seeds + program ID | Max 16 seeds; bump retries cost CUs |
| Keypair | Private key holder | Yes (Ed25519 signature) | No (unless derived) | No seed limits; signature gas only |
Once state lives in accounts, a natural question appears: who controls those accounts? Sometimes the answer is an ordinary keypair. But many useful program patterns need an address that is controlled by the program itself rather than by a human-held private key. That is what Program Derived Addresses, or PDAs, are for.
A PDA is a 32-byte address deterministically derived from a program ID and a set of seeds. The important security property is that a PDA is guaranteed to be off the Ed25519 curve, so no private key exists for it. That means no outside user can sign as the PDA in the ordinary cryptographic sense. Instead, only the program whose ID participated in the derivation can cause the runtime to treat the PDA as a signer, using invoke_signed during a cross-program invocation.
This is one of the clever pieces of the Solana model. It lets a program create stable, predictable addresses for state and authority without managing secret keys. A token vault can be owned by a PDA. A user profile can live at a PDA derived from seeds like a namespace and the user’s public key. Because the same seeds and program ID always derive the same address, both the program and off-chain clients can find that account deterministically.
There are limits and tradeoffs. PDAs allow at most 16 seeds, each seed can be at most 32 bytes, and deriving an off-curve address may require trying a bump seed because some candidate hashes land on the Ed25519 curve. That retry process consumes compute units. So PDAs are not free; they are a controlled addressing mechanism with explicit runtime costs.
How does cross-program invocation (CPI) enable composition on Solana and what are its limits?
Useful blockchains do not consist of isolated programs. They compose. On Solana, composition happens through cross-program invocation, or CPI, where one program invokes another program during the same transaction.
Mechanically, CPI means the currently executing program asks the runtime to invoke a second program with a chosen set of accounts and instruction data. This is how an application program can call the token program, the system program, or another application program. The composition story is powerful because it lets specialized programs do one job well and lets others reuse them.
But Solana places strict limits on this composability. CPI depth is constrained. Current documentation describes a maximum instruction stack depth of 5 including the top-level instruction, with deeper limits referenced under newer changes, and the limitations page also frames CPI depth as constrained to 4 nested invocations. The important practical point is not the exact historical phrasing but the invariant: you cannot build arbitrarily deep call chains. Solana’s runtime is optimized for fast, bounded execution, so deep on-chain orchestration runs into hard limits quickly.
There is also a reentrancy rule. A program may only re-enter itself if the immediate caller is that same program. Deep self-recursion such as A -> A -> A is allowed subject to depth limits, but patterns like A -> B -> A are rejected with ReentrancyNotAllowed. This differs from ecosystems where reentrancy is a more open-ended source of bugs. Solana narrows the allowed space, but it does not remove the need for careful reasoning about account state and CPI behavior.
How do compute budgets constrain Solana program design?
| Instruction | Purpose | Default / range | One-per-tx? |
|---|---|---|---|
| SetComputeUnitLimit | Request compute-unit cap | Up to 1,400,000 CUs | Only one allowed |
| SetComputeUnitPrice | Set priority fee per CU | User-specified price | Only one allowed |
| RequestHeapFrame | Request larger heap frame | 32 KiB; 256 KiB (1 KiB step) | Only one allowed |
| SetLoadedAccountsDataSizeLimit | Increase loaded accounts data size | Non-zero, clamped by runtime | Only one allowed |
Many explanations of smart contracts talk about fees and cost only after explaining functionality. On Solana, the runtime budget is part of the functionality. Programs execute under a compute-unit model, and each transaction has a bounded compute budget. Developer documentation states a maximum per-transaction limit of 1.4 million compute units, with default budgeting behavior and instructions from the Compute Budget Program that let a transaction request a specific compute-unit limit, heap frame size, compute-unit price, and loaded-accounts data size limit.
This matters because a Solana program is not just asked, “Is the logic correct?” It is asked, “Is the logic correct within a very tight metered environment?” If execution exceeds the compute budget, the transaction fails. Since transactions are atomic, all state changes revert. Fees are still paid.
The consequences are practical. Operations that look harmless in ordinary Rust can be too expensive on-chain. Solana explicitly warns that bincode and string formatting are extremely computationally expensive. Floating-point operations are only partially supported, are software-emulated, and consume substantially more compute than integer math, so fixed-point arithmetic is generally preferred. Logging uses msg! , not println! . Standard library access is heavily restricted because programs must be deterministic and run in a single-threaded, resource-constrained environment.
In other words, Solana program development is not “normal Rust deployed to a blockchain.” It is closer to writing for a deterministic embedded runtime with explicit persistence and strict gas-like metering.
What memory, stack, and runtime limits should Solana developers plan for?
| Resource | Default | Max / limit | Failure symptom |
|---|---|---|---|
| Heap size | 32 KiB | 256 KiB (requestable) | Memory allocation failures |
| Stack frame | 4,096 bytes | 4,096 bytes | Stack overflow / panic |
| sBPF call depth | ; | 64 frames | CallDepthExceeded error |
| Instruction (CPI) depth | Top-level + CPIs ≤ 5 | 5 (9 with SIMD-0268) | CallDepth / Reentrancy errors |
| Program cache | ; | 512 loaded entries | Eviction and reload latency |
Beyond compute units, Solana enforces narrow runtime limits that shape program architecture. The default heap size is 32 KiB, though transactions can request a larger heap frame up to 256 KiB under the documented compute-budget rules. Stack frame size is limited to 4,096 bytes. Maximum sBPF call depth is 64. Program cache capacity is limited, with up to 512 compiled entries retained before least-used programs are evicted to an unloaded state.
These numbers matter because they turn certain software patterns from awkward into impossible. Large recursive structures, deeply layered abstractions, oversized stack allocations, or broad dynamic dispatch can all break down under these conditions. Even if code is logically valid, it may fail operationally because the runtime cannot support its memory shape.
This is why many Solana programs use compact account layouts, careful zero-copy patterns, and deliberately simple control flow. Anchor, the dominant framework for Solana program development, exists partly to reduce boilerplate around these patterns, but it does not change the underlying machine constraints. It makes the model easier to work with; it does not abolish the model.
How do deployment and upgradeability work on Solana, and what trust assumptions do they create?
A deployed program has a Program ID, which is the address users interact with, but under loader-v3 the actual executable code lives in a separate ProgramData account. Programs deployed under the upgradeable loader can be updated if an upgrade authority exists. If that authority is revoked, the program becomes immutable.
This is not just a deployment detail. It is a trust assumption. An upgradeable program is not the same thing as immutable code, even if the current version is audited. Whoever controls the upgrade authority can replace the logic. That may be desirable during active development or emergency response, but it means users are trusting governance and operational security as well as code.
The mechanics also have economic consequences. Deployment cost scales with program size, and if an updated binary needs more bytes than currently allocated, the tooling can extend the program account and charge additional SOL accordingly. Programs can also be made permanently immutable with deployment options that finalize them, and that step is irreversible.
The broad principle is simple: on Solana, “what is this program?” includes not just its source code, but also its loader model, upgrade authority, and deployment status.
What applications and use cases are Solana programs best suited for?
What developers actually build with Solana programs is not exotic relative to other chains: token systems, exchanges, lending markets, staking logic, governance modules, games, identity systems, and application-specific state machines. The difference is in how those systems are represented.
The Solana Program Library historically provided canonical examples of this style, including token-related programs, associated token account logic, governance modules, stake-pool components, and more. Even though that original monorepo has been archived and split into separate repositories, it remains useful evidence of the design pattern: each protocol is expressed as programs operating over explicit accounts within the Sealevel runtime.
That model is especially attractive when applications want many small pieces of state, predictable derived addresses, and composability with shared infrastructure programs such as the System Program or token programs. It is less comfortable when developers want rich hidden object graphs, unconstrained dynamic execution, or very deep chains of inter-contract logic.
What common pitfalls and operational risks do Solana programs introduce?
The strengths of Solana programs are inseparable from their constraints. Explicit accounts help the runtime schedule work, but they also push complexity into transaction construction and account management. Stateless code simplifies reasoning about persistence, but it means developers must design state schemas, initialization flows, and authority models very carefully.
The performance-oriented design also makes resource ceilings part of normal development. A feature can fail not because the business logic is wrong, but because it uses too much compute, too much heap, too much stack, too much account data, or too many nested invocations. Complex applications often need to split work across multiple instructions or transactions. That preserves runtime efficiency, but it complicates developer and user experience.
There is also a broader systems caveat. Solana’s execution environment sits inside a network architecture optimized for very high throughput. That creates operational pressure around congestion and hot spots. The 2022 mainnet outage report, for example, described how extreme transaction floods contributed to validator memory exhaustion and stalled consensus, leading to mitigations such as QUIC, stake-weighted quality of service, and fee-based execution priority. That incident was not “about programs” in the narrow sense, but it does show that program design exists inside a larger execution system with real capacity limits.
So the cleanest summary is not that Solana programs are fast or cheap or parallel. It is that they are designed for an execution environment where explicit state access and hard resource bounds are the price of throughput.
Conclusion
A Solana program is an executable on-chain account containing sBPF bytecode, but the deeper idea is that it is stateless code acting on explicit accounts. That one architectural choice explains most of the rest: why code is separate from state, why transactions must declare accounts up front, why PDAs matter, why CPI is constrained, and why compute and memory limits dominate design.
If you remember one thing tomorrow, remember this: Solana programs are not objects with hidden storage; they are bounded state-transition engines over accounts. Once you see them that way, Solana’s execution model stops looking unusual and starts looking internally consistent.
How does this part of the crypto stack affect real-world usage?
Solana Programs affects how a network or execution environment behaves in practice, so it should inform your setup before you trade or move funds on Cube Exchange. The safe workflow is to verify network assumptions first and only then fund the related market.
- Identify the chain or asset whose behavior depends on this execution or scaling concept.
- Check the compatibility, throughput, or tooling constraint Solana Programs implies for wallets, transfers, or market access.
- Confirm you are using the correct asset, network, and settlement path before funding the account.
- After those checks, open the relevant market on Cube and place the trade with the network constraint in mind.
Frequently Asked Questions
- Why does Solana separate program code from persistent state, and what trade-offs does that create? +
- Solana externalizes mutable state into explicit accounts so the runtime can know up front which pieces of state a transaction will read or write; that visibility lets the scheduler safely run transactions touching disjoint writable accounts in parallel and makes execution more predictable under high throughput. The trade-off is extra developer bookkeeping: you must design account layouts, initialize and pass accounts explicitly, and manage ownership/authority instead of relying on hidden contract storage.
- How can a Solana program “own” an account without holding a private key (what are PDAs and their limits)? +
- A Program Derived Address (PDA) is deterministically computed from a program ID and seeds and is guaranteed to be off the Ed25519 curve so no private key exists for it; only the associated program can cause the runtime to treat that PDA as a signer via invoke_signed, letting programs own accounts without managing secret keys. PDAs have limits (e.g., max seeds and seed length) and may require trying bump seeds if a candidate lands on-curve, which consumes compute units.
- What happens when a transaction runs out of compute units on Solana, and how should I think about expensive operations? +
- If execution exceeds the transaction’s compute-unit budget the transaction fails and all state changes are reverted, but fees are still charged; the compute-budget model is a hard operational constraint and some operations (bincode, heavy string formatting, floating-point emulation) are explicitly expensive and can cause failures. The docs note further details about error reporting and budgeting are delegated to reference pages, so exact error signatures and optimal budgeting strategies are not fully specified in the high-level overview.
- How deep can program-to-program calls go on Solana and are there reentrancy restrictions I need to worry about? +
- Cross-program invocation is intentionally bounded: Solana enforces a limited instruction/call depth (documentation references an instruction stack depth of 5 and historical references to 4 nested invocations) and disallows certain reentrancy patterns — a program may re-enter itself only if the immediate caller is the same program, while patterns like A -> B -> A are rejected with ReentrancyNotAllowed. These limits are part of the runtime’s design for fast, bounded execution, and exact numeric limits have evolved in documentation so you should check current references when designing complex call graphs.
- Can Solana programs be upgraded after deployment, and what trust implications does that have? +
- Programs deployed with the upgradeable loader (loader‑v3) use a separate ProgramData account to store executable bytes and may have an upgrade authority that can replace the code; revoking the authority or deploying with finalization options can make the program immutable, so upgradeability is a meaningful trust assumption — whoever controls the upgrade authority can change program logic unless the authority is removed.
- Why do Solana’s heap, stack, and call-depth limits matter for program design — which patterns commonly break? +
- Runtime limits like the default 32 KiB heap (expandable up to 256 KiB via compute-budget), 4,096‑byte stack frame, and limited sBPF call depth mean large recursive data structures, big stack allocations, extensive dynamic dispatch, or heavy library use can fail at runtime; frameworks like Anchor reduce boilerplate but do not remove these underlying memory and compute constraints.
- How does Solana prevent concurrent transactions from corrupting the same on-chain state? +
- Because transactions must list the accounts they will touch, the runtime can detect and schedule conflicts: two transactions that write disjoint accounts can run in parallel, while two that write the same account must be ordered against one another. That explicit account-passing model is the mechanism Solana uses to prevent concurrent corruption and enable Sealevel’s parallel execution strategy.
- Are deployed or upgraded programs immediately callable, and does deployment timing affect tooling? +
- There is a deliberate one-slot visibility delay for newly deployed or upgraded programs: a program deployed in slot N becomes callable in slot N+1, which avoids ambiguity during deployment but means release scripts, tests, and tooling must account for the delay. This delay and program-cache behavior (e.g., a limited program cache that can evict entries) also affect operational timing and performance.
- Do PDAs have runtime costs or limitations I should budget for? +
- Deriving and using PDAs is not free: there are hard limits (max seeds, seed length), bump-seed retries when a hash falls on-curve, and compute-unit costs associated with PDA derivation and signed CPI invocations, so frequent dynamic PDA derivation or poorly chosen seeds can increase transaction cost and compute consumption. The docs also do not prescribe a single canonical bump-storage pattern, so programs must decide how to expose or persist bump seeds for client derivation.
- What operational risks (outside of bugs in program code) have real-world impact on Solana programs? +
- Operational failures can come from beyond program logic: extreme transaction floods and hot‑spot workloads have previously caused validator memory exhaustion and consensus stalls on Mainnet (e.g., bot-driven NFT mint congestion), prompting mitigations like QUIC, stake‑weighted QoS, and fee‑based prioritization. Program design must therefore consider network-wide capacity and contention, not just local correctness.