Skip to content

6. Programs, SVM, and development

A Solana program is not a server waiting for HTTP requests. It is deterministic logic invoked by instructions and given explicit accounts. This chapter turns the architecture into a development workflow.

Conceptually, a handler receives:

program id
ordered account inputs
instruction data bytes

The program parses the instruction, validates every account, applies business rules, updates permitted state, and may invoke other programs. Execution is metered and deterministic.

Programs compile to Solana bytecode and run under the Solana runtime. The term SVM describes this execution environment and programming model. Performance depends on code, compute, account access, scheduler behavior, and validator implementation, not on a label alone.

Before writing Rust, specify the state transition:

Instruction: UpdateProfile(name)
Signer: authority
Writable: profile PDA
Read-only: system/clock inputs only if required
Checks:
profile PDA matches ["profile", authority]
profile owner is this program
stored authority equals signer
name byte length <= 64
Effect:
profile.name becomes name
Errors:
wrong authority, wrong PDA, invalid data, name too long

This document becomes the basis for implementation, tests, client generation, and review.

A native program commonly follows this shape:

pub fn process_instruction(
program_id: &Pubkey,
accounts: &[AccountInfo],
data: &[u8],
) -> ProgramResult {
let instruction = MyInstruction::unpack(data)?;
match instruction {
MyInstruction::UpdateProfile { name } => {
process_update_profile(program_id, accounts, name)
}
}
}

The real work is not the match. The real work is validating account addresses, owners, signers, writable privileges, data shape, authority, bounds, and arithmetic.

Anchor provides account serialization, instruction dispatch, IDL generation, error conventions, and declarative account constraints.

#[derive(Accounts)]
pub struct UpdateProfile<'info> {
#[account(
mut,
seeds = [b"profile", authority.key().as_ref()],
bump,
has_one = authority
)]
pub profile: Account<'info, Profile>,
pub authority: Signer<'info>,
}

Read each constraint as a security claim. mut permits change, seeds checks the PDA namespace, and has_one connects stored authority to the signer account.

Choose Anchor for common application development, typed clients, and a productive constraint system. Choose lower-level Rust when you need explicit control, specialized interfaces, or deeper runtime work. A strong developer understands the underlying account model regardless of framework.

programs/profile/src/
├── lib.rs
├── state.rs
├── errors.rs
└── instructions/
├── initialize.rs
├── update.rs
└── close.rs

Keep state definitions and instruction-specific validation readable. As the program grows, a single large handler hides security boundaries.

A useful testing ladder is:

Test parsing, serialization, arithmetic, rounding, and bounded input rules.

Test complete instructions against an isolated validator or test environment.

Attempt:

  • wrong owner program;
  • correct signer but wrong authority;
  • fake PDA or bump;
  • duplicate account aliasing;
  • repeated initialization;
  • overflow or underflow;
  • fake token mint;
  • wrong CPI program;
  • unauthorized close;
  • stale oracle data.

Test wallet approval, RPC behavior, transaction confirmation, and deployment configuration with disposable assets.

An upgradeable program has an upgrade authority under the applicable loader model. If one laptop key controls that authority, users are trusting one laptop.

Production questions include:

  • Who can upgrade?
  • Is authority held by a multisig or governance process?
  • Are upgrades delayed or announced?
  • Can users verify deployed code against source?
  • Is there an incident and rollback process?
  • Can authority eventually be revoked if immutability is intended?

Use separate identities for development, deployment, treasury, minting, and emergency operations.

Before production:

  • validate every account and exact external program ID;
  • verify PDA seeds, owners, types, and authorities;
  • use checked arithmetic and document rounding;
  • bound variable-length data;
  • minimize writable accounts;
  • secure initialization, migration, and close paths;
  • define duplicate and replay behavior;
  • verify oracle freshness and confidence;
  • pin dependencies and review changes;
  • measure compute and contention;
  • obtain independent review for valuable assets.

A development path that teaches the architecture

Section titled “A development path that teaches the architecture”
  1. Build a counter on a local validator.
  2. Replace one global counter with one PDA per user.
  3. Add an authority and close instruction.
  4. Write tests for wrong signer, wrong PDA, and repeated initialization.
  5. Add a TypeScript client that separates read, build, sign, submit, and confirm stages.
  6. Deploy to Devnet and inspect every account in an explorer.
  7. Only then add token CPI or external data.

Explain why this statement is dangerous:

“The account signed, so the account is allowed to update the profile.”

The signer may be valid but unrelated to the stored profile authority. Authorization requires the relationship, not merely a signature.

Next, apply the model to the most common on-chain assets in Tokens, NFTs, and DeFi.