Crypto OS
Technical Crypto OSStage 1 · Blockchain Developer Mental Model

T6 · The Solana Account Model

Why don't Solana programs store their own data?

Capabilities practiced
BuilderProtocol Literacy
Hands on
Create an account on Devnet and write data to it; use the CLI to read its owner, lamports, and data.
AI Lab
Have AI compare EVM and Solana account models, then explain why Solana can execute in parallel.

A Real-World Problem

You are building a cross-chain asset dashboard. The EVM side is done. T5 taught you to find a token contract, calculate the slot for an address in its balanceOf mapping, and read the balance. The model is tidy: data lives in the contract, indexed by address.

Now you turn to Solana. You apply the same idea: find the token program's address and look for its table of all users' balances.

**There is no such table.**The program address contains code but not a byte of balance data.

You inspect a user's wallet address instead. Stranger still, it has seven or eight unfamiliar addresses associated with it, each holding one kind of token. The user never deliberately created or recognized them.

The product requirement adds trouble: a token transfer to a new user fails because no place exists at the destination to hold that token. You have to pay to create one first.

Where is the balance stored? Who created those apparently new addresses? Why must you "build" something before transferring to an address?

Thought Experiment

Take apart the global computer from T5.

T5's model is one big table keyed by address, with code and data together in each value. Code and its data stay bound to each other.

Now split them:

Move code to a read-only place. A deployed program is something you can read but not write. It has no storage space of its own. Think of it as a pure function.

**Scatter data into independent files.**Each "file" has its own address and contains:

lamports      how much money is in the file
owner         which program may modify its contents
data          raw bytes interpreted by the owner program
executable    whether the account holds executable code

Notice owner. It does not mean "who owns the money." It means which program may modify these bytes. Your wallet can sign, but your signature cannot directly change a token account's balance; it authorizes the token program to do so.

Continue the reasoning:

Step one: if the program stores nothing, how does it know which file to change? The caller must tell it. A transaction must therefore list the addresses of every file it will touch.

Step two: if every transaction declares ahead of time which files it will read and write, two transactions whose write sets do not conflict can run concurrently.

That is the secret. Parallelism comes from mandatory advance declarations, not a faster machine.

Step three: where do file addresses come from? If every user's points account had a random address, how would the program find it next time? "This program + this user" must deterministically yield the same address. And that address must have no private key, or a key holder could bypass the program and make unauthorized changes.

Together, these steps answer the opening questions. Balances live in independent files; their addresses are derived deterministically; and the thing created before a transfer is the file that holds the token. Storage costs resources, so someone must pay to create it.

Your Decision

A program must remember each user's points. Where should the data live?

What Happens

All four choices answer one question: how do you address data?

EVM (T5)Solana
Data locationContract storageIndependent accounts
Finding one user's dataMapping: hash a key and slot number togetherDerived address: hash seeds and a program address together
Who can change the data?The contract's codeProgram named in the account's owner field
Who pays for storage?Caller pays gas onceCreator deposits enough in the account; it can be returned on closure
Parallel execution?No; access is known after executionYes; transactions declare read/write sets in advance

The hash-based addressing on both sides looks remarkably similar: combine a key with a namespace and hash it.

The real divergence is in the last two rows:

**1. Different payment models.**EVM charges once when state is written; it is then stored permanently with no refund. Solana requires a deposit based on account size, returned when the account is closed. That explicitly prices state growth.

**2. Parallelism depends on whether the scheduler knows conflicts in advance.**EVM does not know, as T5 explained, so execution is serial. Solana requires advance declarations, enabling parallel groups.

That explains the whole chapter: listing accounts, creating them, and deriving addresses are all costs paid to get parallel execution.

Build a Model

The complete fields of an account:

address       32-byte address (the public key itself, unlike EVM's hashed form)
lamports      balance; 1 SOL = 10^9 lamports
owner         program authorized to modify data
data          raw bytes whose format the owner defines
executable    true if this is a program

Three deductions, each tied to real bugs:

  1. **A wallet address and a token account are different accounts.**The first is owned by the system program and holds native coins; the second is owned by the token program and holds one token type. A UI combines them, but code must keep them distinct.
  2. Data is raw bytes, interpreted entirely by the owner. A program must verify that a supplied account has the expected owner. Without that check, an attacker can supply an account they control whose bytes imitate valid data. This is a common vulnerability on Solana.
  3. **Executable accounts are read-only.**A program cannot modify itself and has no private writable space.

What a transaction contains:

Transaction
 ├─ signatures[]              list of signatures
 └─ message
      ├─ accounts[]           every account touched, with signer and writable flags
      ├─ recent_blockhash     expiry token; an expired transaction is invalid (see T3 versus nonce)
      └─ instructions[]       one or more instructions
           ├─ program_id      program to call
           ├─ accounts[]      accounts from the transaction used by this instruction
           └─ data            argument bytes

The accounts array is central to this chain. It is not an optimization hint. It is mandatory and part of consensus. A program cannot touch an account omitted from the list.

How parallelism happens:

  1. Each transaction declares its read/write set
  2. Scheduler groups by overlapping write sets
  3. Nonconflicting groups execute concurrently
  4. Conflicting transactions queue
Reads can run beside reads; reads conflict with writes, and writes conflict with writes. Hot accounts reduce parallelism to serial execution.

The derived address with no private key:

Derivation hashes seed byte strings, the program address, and a fixed constant string together.

Roughly half the results lie on the elliptic curve—meaning a private key could correspond to them. That is unsafe. So the rule includes one more byte: try values downward from 255 until the result lies off the curve.

Off-curve means no private key mathematically corresponds to the address. Only the program that derived it can "sign" on its behalf during execution.

That is the technical meaning of "code enforces control" in the third choice.

About storage deposits:

An account must hold a minimum amount according to its byte size or risk reclamation. Query the amount before creation. On closure, send the deposit to a designated recipient.

This is separate from gas or transaction fees. Computation fees pay for work; the deposit covers occupied storage. Transaction fees do not rise directly with bytes written, but larger accounts require a larger fixed deposit.

What It Is Called

AccountAccount

The chain's unit of storage: an address with lamports, owner, data, and executable fields.

Everything is an account: a wallet, token balance, program, or program configuration. It is broader than the EVM account of T5. A Solana account behaves more like a file.

ProgramProgram

An account with executable set to true and code in its data. It has no writable storage of its own. Think of it as a pure function: it takes accounts and arguments, then changes the accounts it is authorized to write.

That answers the title question. A program does not store data because its design gives it nowhere to store mutable state.

OwnerOwner

An account field naming the program authorized to modify that account's data.

It does not mean "the owner of the money." A token account is owned by the token program; "who may spend the tokens" is a different field inside its data. Few Solana concepts are confused as often as these two.

PDAProgram-derived address

An address deterministically derived from seeds and a program address, off the elliptic curve and therefore without a private key.

It lets a program locate its own data without an index and hold assets without a human-controlled private key.

It resembles mapping-based addressing from T5, but a mapping position is only a storage slot. A PDA is a first-class address that other programs can reference directly.

InstructionInstruction

One call to a program: which program, which accounts, and what arguments.

A transaction may include several instructions. They all succeed or all roll back. Account creation, initialization, and transfer can therefore form one atomic operation, while EVM needs a contract wrapper for a comparable sequence.

RentRent / storage deposit

The minimum lamports an account must hold for its size. With enough, it remains active; on closure the deposit can be recovered.

Despite the name "rent," this behaves more like a deposit. It explicitly prices occupied state, whereas T5's EVM model pays once to write state and does not refund it on deletion.

Hands-on

Hands onCreate an account and write data on Devnet, then read it with the CLI and RPCsolana CLI + spl-token CLI + curlFree Devnet coins from a faucet

**Use Devnet throughout. Its test coins have no real value and no real assets are involved.**Create a dedicated key file rather than using a mainnet key.

Point to Devnet, create a wallet, and request coins.

solana config set --url https://api.devnet.solana.com
solana-keygen new --outfile ~/solana-lab.json
solana config set --keypair ~/solana-lab.json
solana address
solana airdrop 2
solana balance

Faucets have rate limits; if a request fails, try later or use another faucet.

Inspect your own account.

solana account $(solana address)

The four fields appear: lamports, owner, data, and executable.

Notice that you are not its owner field; the system program is. This corrects a common misconception: owner means who can modify the bytes, not whose money it is.

Inspect a program account and compare.

solana account <any-program-address>

executable is true, data is long compiled code, and lamports cover its storage deposit.

Place its output beside the previous account. The same structure serves two different roles. That is the literal meaning of "everything is an account."

Check the required deposit.

solana rent 0
solana rent 165
solana rent 10000

The amount grows with size. Remember its order of magnitude: it determines the cost of creating an account for a user and makes "who pays?" a real product decision.

Create an account that really stores data.

spl-token create-token
spl-token create-account <MINT-address-from-the-previous-command>
spl-token mint <MINT-address> 100
spl-token accounts

The first command creates a mint account defining the token. The second creates your token account: the apparently new address from the opening problem, derived from your wallet and the mint.

The third writes a quantity into it. You do not directly change that value; the token program does, with your signature as authorization.

Read that token account's raw bytes.

solana account <account-address-from-create-account>

Notice three things:

  • owner is the token program, not you. You can authorize it with a signature, but cannot edit the bytes directly.
  • data is a fixed-size byte string encoding the mint, holder, amount, and other fields.
  • lamports match the required deposit for its size.

**This is the heart of the Lab.**You can see that the balance is not inside the token program. It is in an independent account whose owner points to that program.

Read it again through JSON-RPC, as your backend must.

curl -s https://api.devnet.solana.com -X POST \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"getAccountInfo",
       "params":["<that-token-account-address>",{"encoding":"base64"}]}'

curl -s https://api.devnet.solana.com -X POST \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"getBalance","params":["<your-wallet-address>"]}'

getAccountInfo returns base64 data that decodes to raw bytes. Interpreting them as a balance requires knowing the token program's layout.

getBalance returns lamports, not SOL. The common unit error is a factor of a billion.

Attempt a transfer that fails, as in the opening problem.

solana-keygen new --outfile ~/solana-lab-2.json --no-bip39-passphrase
spl-token transfer <MINT-address> 1 <second-wallet-address>

It reports that the recipient lacks a token account. Find the recipient-account-creation option in spl-token transfer --help and retry. It now succeeds and deducts a deposit from your balance.

**This completes the "create a place before transferring to a new user" scenario.**Payment, airdrop, and withdrawal products must decide at design time who funds that deposit.

AI Lab

AI LabHave AI compare the account models, then explain the conditions required for parallel executionLevel 2 · AI Copilot

Ask in three steps. The third most readily exposes mistakes.

Step one:
Compare EVM and Solana account models in a table: data location, how user data is addressed,
who can modify it, how storage is paid for, and whether execution can be parallel.
Explain why each row is so, not just what it is.

Step two:
Explain why Solana can execute in parallel while EVM cannot.
State the conditions for parallelism and when it degrades to serial execution.

Step three:
Give concrete examples of two transactions that can run in parallel and two that cannot.
List all their accounts, marking each as read-only or writable.

Step three is the test. A model can sound convincing on the first two steps and fail when it must list real accounts. It may mark read and write permissions incorrectly or omit required program accounts.

Compare its list with the fields you observed using solana account in the Lab.

Ask one more question to expose a costly vulnerability:

If my program accepts an account parameter without checking its owner,
what can an attacker do? Give complete attack steps.

This is a common vulnerability on this chain. The Real-World Case section shows its consequences. A model may describe it correctly and still omit the check in its own sample code. **Knowing the rule and writing correct code are different skills for models and people alike.**T10 covers account validation in full.

Verify these points yourself after using AI

  • It does not wrongly claim a program stores code and user data in the same account
  • It does not say a PDA private key is held by the program; a PDA has no private key
  • It defines owner as the program authorized to modify data, not the person owning the money
  • It states parallelism accurately: reads can share, but overlapping writes must serialize
  • Its CLI commands, arguments, and RPC methods exist in official documentation
  • It converts lamports and SOL correctly, with a factor of a billion
  • It does not treat inclusion in a block as instruction success; transactions can fail here too
  • It describes Rent as a recoverable deposit, not a time-based deduction

Real-World Case

One missing account check allowed unlimited minting2022

A stablecoin program failed to validate one supplied account.

An attacker passed an account under their control with bytes shaped like valid data. The program trusted it and minted a large amount of tokens according to that data, draining protocol funds.

The root cause is this chapter's rule: data is raw bytes; the program must verify an account's owner and derived address to know whether it is the intended account.

This bug is less likely in EVM, where data lives in a contract's own storage and an attacker cannot pass in a fake storage row. The models have different attack surfaces; that alone does not make one safer.

Popular minting turns parallel execution into serial execution

During a highly popular mint, everyone's transaction writes the same counter account.

Every write set overlaps. The scheduler cannot separate them, so parallelism vanishes. Many transactions fail after their blockhash expires. Users retry and add more congestion.

Anyone building for high concurrency here should ask whether every user writes the same account. If so, split it: sharded counters, per-user accounts, or off-chain aggregation with batch submission.

Compare this with T5: on EVM, everyone writing one slot is costly; here it disables the chain's main advantage.

Airdrop transfers fail for addresses without token accounts

A team runs a script to send an airdrop to tens of thousands of addresses. Most transfers fail.

Most recipients have never held that token and have no matching token account. The transfer instruction cannot find a destination and reverts.

Create the account and transfer in one transaction—the instructions are atomic—and decide who funds the deposit. For tens of thousands of accounts, issuer-funded deposits are a real cost. If users pay, it is no longer a straightforward airdrop.

Many Solana payment teams encounter this once. On EVM, sending tokens to any address requires no prepared token account.

A fixed account size blocks a later protocol upgrade

An account's size is set at creation. Later, a protocol adds a field to user data and discovers that existing accounts cannot hold it.

It can write a migration program and ask each user to expand their account and add the deposit difference, or support old and new layouts indefinitely. Neither is pleasant.

**This is the cost of freezing a data layout at deployment.**Reserving spare bytes in account layouts is a basic practice here. It sounds inelegant but can avoid a painful migration.

Change One Variable

What ifTransactions no longer declare their account lists in advance

A scheduler cannot know whether two transactions conflict and must run them one by one.

**You get T5's model.**This is not just a quip: its designers did not merely forget parallelism. When state changes are known only after execution, scheduling cannot determine conflicts ahead of time.

Conversely, EVM has an optional access-list mechanism, but it changes gas pricing and is not used for scheduling. Make it mandatory and feed it to a scheduler, and you approach this chapter's model.

What ifYou carry EVM mapping habits over unchanged

You create one big account for all user data, addressed by offset inside it.

Three things happen: its size limit forces additional accounts eventually; all writes overlap and become serial; changing one user's bytes marks the whole account writable and blocks other users.

This is a common architectural error among EVM engineers migrating here. It may remain invisible in tests and appear only as the user base grows.

What ifThe deposit becomes a nonrefundable one-time fee

That returns to T5's pay-once model. The short-term difference is small; over time it matters because there is no reward for closing accounts no longer needed, and state only grows.

A refundable deposit creates a reverse incentive: close an account when finished and reclaim the money. It illustrates an economic answer to a technical problem. State growth is addressed through pricing, not just limits.

What ifTwo transactions write the same account but touch different fields

They still execute serially. The scheduler sees account-level read/write flags, not which bytes of data actually change.

Parallelism is at account granularity, not field granularity. The number of accounts into which you divide data determines available concurrency.

Data-structure design is directly performance design here, even more than on EVM. T10 returns to this judgment when you write a program.

Takeaway Questions

1
What problem does it solve?

T5 and this chapter answer the same question: where on-chain data lives and who may change it, expressed as a structure you can draw rather than a vague intuition. The models differ, but both answer how data is addressed, who may write it, and who pays for storage. Understand those three and you can interpret a third model yourself.

5
Who is paying?

The storage deposit makes "who pays?" unavoidable. Transferring a token to a new user requires creating an account first. Either you pay or the user does. That deposit shapes onboarding in many Solana products.

16
What permissions does an agent have?

An AI Agent's authority boundary can be clearer and more dangerous here: every transaction must list the accounts it touches, so you could inspect them one by one before signing. But if the Agent holds the private key and builds transactions itself, that review point disappears. Make account-list review mandatory to use this model's built-in safety opportunity. T33 expands on this.

Chapter Check

Key idea

Solana separates code from data completely; transactions must declare in advance which accounts they will touch.

Finished this chapter’s lab? Mark it completeView all progress

On this page