Updating Contracts
NEAR accounts separate their logic (contract's code) from their state (storage), allowing the code to be changed.
Contract's can be updated in two ways:
- Through tools such as NEAR CLI or near-api-js (if you hold the account's full access key).
- Programmatically, by implementing a method that takes the new code and deploys it.
Updating Through Tools
Simply re-deploy another contract using your preferred tool, for example, using NEAR CLI:
- near-cli
- near-cli-rs
# (optional) If you don't have an account, create one
near create-account <account-id> --useFaucet
# Deploy the contract
near deploy <account-id> <wasm-file>
# (optional) If you don't have an account, create one
near account create-account sponsor-by-faucet-service somrnd.testnet autogenerate-new-keypair save-to-keychain network-config testnet create
# Deploy the contract
near contract deploy <accountId> use-file <route_to_wasm> without-init-call network-config testnet sign-with-keychain send
Programmatic Update
A smart contract can also update itself by implementing a method that:
- Takes the new wasm contract as input
- Creates a Promise to deploy it on itself
- 🦀 Rust
Loading...
How to Invoke Such Method?
- near-cli
- near-cli-rs
- 🌐 JavaScript
# Load the contract's raw bytes
CONTRACT_BYTES=`cat ./path/to/wasm.wasm | base64`
# Call the update_contract method
near call <contract-account> update_contract "$CONTRACT_BYTES" --base64 --accountId <manager-account> --gas 300000000000000
# Call the update_contract method
near contract call-function as-transaction <contract-account> update_contract file-args </path/to/wasm.wasm> prepaid-gas '300.0 Tgas' attached-deposit '0 NEAR' sign-as <manager-account> network-config testnet sign-with-keychain send
// Load the contract's raw bytes
const code = fs.readFileSync("./path/to/wasm.wasm");
// Call the update_contract method
await wallet.callMethod({contractId: guestBook, method: "update_contract", args: code, gas: "300000000000000"});
This is how DAO factories update their contracts
Migrating the State
Since the account's logic (smart contract) is separated from the account's state (storage), the account's state persists when re-deploying a contract.
Because of this, adding methods or modifying existing ones will yield no problems.
However, deploying a contract that modifies or removes structures stored in the state will raise an
error: Cannot deserialize the contract state
, in which case you can choose to:
- Use a different account
- Rollback to the previous contract code
- Add a method to migrate the contract's state
The Migration Method
If you have no option but to migrate the state, then you need to implement a method that:
- Reads the current state of the contract
- Applies different functions to transform it into the new state
- Returns the new state
This is how DAOs update themselves
Example: Guest Book Migration
Imagine you have a Guest Book where you store messages, and the users can pay for such messages to be "premium". You keep track of the messages and payments using the following state:
- 🌐 Javascript
- 🦀 Rust
Loading...
Loading...
Update Contract
At some point you realize that you could keep track of the payments
inside of the PostedMessage
itself,
so you change the contract to:
- 🌐 Javascript
- 🦀 Rust
Loading...
Loading...
Incompatible States
If you deploy the update into an initialized account the contract will fail to deserialize the account's state, because:
- There is an extra
payments
vector saved in the state (from the previous contract) - The stored
PostedMessages
are missing thepayment
field (as in the previous contract)
Migrating the State
To fix the problem, you need to implement a method that goes through the old state, removes the payments
vector and
adds the information to the PostedMessages
:
- 🌐 Javascript
- 🦀 Rust
Loading...
Loading...
Notice that migrate
is actually an initialization method that ignores the existing state ([#init(ignore_state)]
), thus being able to execute and rewrite the state.
You can follow a migration step by step in the official migration example
Javascript migration example testfile can be found on here: test-basic-updates.ava.js, run by this command: pnpm run test:basic-update
in examples directory.