import {CodeBlock} from ‘@theme/CodeBlock’
import {CodeTabs, Language, Github} from "@site/components/codetabs"
import Tabs from ‘@theme/Tabs’;
import TabItem from ‘@theme/TabItem’;
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:
# If you already used dev-deploy the same account will be used
near dev-deploy --wasmFile <new-contract>
# If you logged in
near deploy <account-id> --wasmFile <new-contract>
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
How to Invoke Such Method?
“`bash
# Load the contract’s raw bytes
CONTRACT_BYTES=`cat ./path/to/wasm.wasm | base64`
# Call the update_contract method
near call
“`
“`js
// 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”});
“`
- :::tip DAO Factories
- 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
- :::tip DAO Update
- 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:
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:
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
:
- 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. -
::tip
You can follow a migration step by step in the official migration example - ::