Sign in

Program upgrades

On Solana, a program isn't a special kind of thing. It's just an account, like any other, with a flag that says "this account holds executable code." That fact has a useful consequence: you can replace the code without disturbing anything else. This lecture covers what program upgrades are, who is allowed to do them, and what it means for the people using your program.

Code is just another account

Solana's account model treats programs the same as everything else. A program account has the same shape as a user account or a vault account. The only difference is one flag: executable: true. That flag tells the runtime "this account's data is BPF bytecode rather than regular data, and you should run it when someone calls this address."

So a program is really two things: a pubkey that never changes (the program's address), and the bytecode in that account's data field, which can change. Everything users interact with, including the address they call, the instructions they invoke, and the IDL they generate clients from, points at the address rather than at the bytecode. The bytecode is just whatever happens to be sitting in that account right now.

Replacing the bytecode is what we call an upgrade. The address stays the same. Users keep calling the same program. But the next time they do, they're running a different version of the code.

State stays put

The reason this works cleanly is that user data lives in other accounts. Your staking program doesn't store stake positions inside its own bytecode account. It stores them in separate StakePosition accounts, one per user. The Vault sits in its own account. The Config sits in its own account. The program is just code that operates on those accounts when called.

Upgrading replaces code, never state Before the upgrade Program account executable: true data: bytecode v1 "the code" State accounts UserState: balance = 500 UserState: balance = 1200 UserState: balance = 300 Config: admin = ... Vault: tokens = 50000 "the data the code operates on" stored in separate accounts upgrade After the upgrade Program account executable: true data: bytecode v2 ↑ new code State accounts UserState: balance = 500 UserState: balance = 1200 UserState: balance = 300 Config: admin = ... Vault: tokens = 50000 untouched. same accounts, same balances, same data. Code and state are separate accounts. Replacing the code doesn't move the data. Users keep their balances. The program just starts running new logic on them.

This is the part worth pausing on. Upgrading a Solana program doesn't require a migration. You don't have to copy data from a v1 program to a v2 program. You don't have to redeploy at a new address and ask users to move. The address stays. The state stays. Only the code changes.

The catch is that your new code has to be compatible with the existing state. If your old Vault struct had three fields and your new one has four, the v2 code reading old accounts is going to fail, because the bytes on disk don't match the new struct. So in practice, upgrades require careful thought about account layouts. Adding fields is easy if you handle the migration in code, by reading the old format and writing the new format. Changing field types or removing fields is hard. Most upgrades stick to adding new instructions and being conservative about changing existing structs.

The upgrade authority

Programs don't upgrade themselves. Every upgradeable program has an upgrade authority, which is a pubkey stored alongside the bytecode. Only that authority can deploy new code to that program. If you don't hold the upgrade authority's private key, you cannot upgrade the program.

The upgrade authority is set when the program is first deployed. It defaults to the deployer's keypair, but you can change it later: transfer it to a multisig, transfer it to a DAO-controlled account, or set it to None to make the program permanently immutable.

That last option is important. Setting the upgrade authority to None is irreversible. Once you do it, no one can ever upgrade the program again, ever. The bytecode is frozen forever. Some protocols do this on purpose, as a credibility signal. They're saying "we've audited this code and we promise you the rules won't change."

Most production protocols don't go that far, at least not initially. They keep the upgrade authority on a multisig controlled by the team, so they can ship bug fixes if something goes wrong. Some transfer it to a governance program later, so the community votes on upgrades instead of the team deciding unilaterally. The choice is a trust signal to users.

What this means for users

When you're using a Solana program, you're trusting whoever holds its upgrade authority. They can change the rules. They can fix bugs. They can also, in theory, push code that drains the vault.

Users who care about this check three things:

  • Is there an upgrade authority? Block explorers show this. If the field says null, the program is frozen.
  • Who holds it? A single team wallet, a multisig with a published signer set, a governance program. Each of these has different trust implications.
  • Does the on-chain code match the audited source code? This is where verifiable builds come in. If a project publishes its source code on GitHub, anyone can compile it and check whether the resulting bytecode matches what's on chain. There are tools that automate this comparison and tags on explorers showing whether a program has a verified build. A program that's verifiable is one where you can be sure the code you're reading is the code that's actually running.

You don't have to perform these checks yourself for every program you interact with. Auditors and explorer integrations do most of the work. But the concept is what matters: a program's behavior is whatever its current bytecode says, and the people who can change that bytecode are the ones with the upgrade authority. Knowing who they are tells you who you're really trusting.

Summary

A Solana program is an account with executable bytecode. That bytecode can be replaced by whoever holds the upgrade authority, without touching any of the state the program operates on. Set the authority to None and the program becomes permanent. Verifiable builds let users confirm the code on chain matches a published source. That's the whole picture.