Upgrades and state migrations
NEAR contract upgrades: redeploying keeps old state, layout changes need a migrate method, and full-access keys decide who can change your code.
Advanced6 min read3-question check
On NEAR, code and state live on the same account, but separately. Deploying new Wasm replaces the code and leaves the state exactly as it was — there is no proxy pattern to learn. That makes upgrades easy, and makes two mistakes easy too: changing the state layout without migrating it, and leaving upgrade power with whoever happens to hold a key.
The bug#
Rust
// v1 (deployed, holds real data)
// pub struct Guestbook { owner: AccountId, messages: Vector<String> }
// v2: a new field in the root struct
#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct Guestbook {
owner: AccountId,
messages: Vector<String>,
paused: bool, // BUG: the stored bytes don't contain this field
}
// deploy v2 -> every method that loads state now panics while
// deserializing it, including the owner's admin methods.The fix#
Rust
use near_sdk::store::Vector;
use near_sdk::{env, near, AccountId, PanicOnDefault};
// the v1 layout, kept only to decode the existing state
#[near(serializers = [borsh])]
pub struct OldGuestbook {
owner: AccountId,
messages: Vector<String>,
}
#[near(contract_state)]
#[derive(PanicOnDefault)]
pub struct Guestbook {
owner: AccountId,
messages: Vector<String>, // same storage prefix, so the data carries over
paused: bool,
}
#[near]
impl Guestbook {
#[private] // only the contract account itself can run it
#[init(ignore_state)] // allowed to run even though state exists
pub fn migrate() -> Self {
let old: OldGuestbook = env::state_read()
.unwrap_or_else(|| env::panic_str("failed to read old state"));
Self { owner: old.owner, messages: old.messages, paused: false }
}
}Shell
near contract deploy guestbook.near \
use-file ./target/near/guestbook.wasm \
with-init-call migrate json-args '{}' \
prepaid-gas '100.0 Tgas' attached-deposit '0 NEAR' \
network-config mainnet sign-with-keychain sendWho can upgrade?#
| Setup | Who can change the code | Use when |
|---|---|---|
| Full-access key on the contract account | Whoever holds that key | Early development only |
| DAO / multisig controls upgrades | The DAO or multisig, via proposals (e.g. it holds the key, or calls a guarded self-upgrade method) | Production contracts holding user funds |
| No full-access keys, no upgrade method (locked) | Nobody | You want immutability — test migrations first |
| Verifiability (NEP-330) | Publishes source + build info via a contract_source_metadata view | Always — lets users check deployed Wasm matches the source |
Check yourself
3 questions · progress saved in this browser