Three examples on how to handle updates and state migration:
- State Migration: How to implement a
migrate
method to migrate state between contract updates. - State Versioning: How to use readily use versioning on a state, to simplify updating it later.
- Self Update: How to implement a contract that can update itself.
The examples at ./basic-updates show how to handle state-breaking changes between contract updates.
It is composed by 2 contracts:
- Base: A Guest Book where people can write messages.
- Update: An update in which we remove a parameter and change the internal structure.
#[private]
#[init(ignore_state)]
pub fn migrate() -> Self {
// retrieve the current state from the contract
let old_state: OldState = env::state_read().expect("failed");
// iterate through the state migrating it to the new version
let mut new_messages: Vector<PostedMessage> = Vector::new(b"p");
for (idx, posted) in old_state.messages.iter().enumerate() {
let payment = old_state
.payments
.get(idx as u64)
.unwrap_or(NearToken::from_near(0));
new_messages.push(&PostedMessage {
payment,
premium: posted.premium,
sender: posted.sender,
text: posted.text,
})
}
// return the new state
Self {
messages: new_messages,
}
}
The example at ./enum-updates/ shows how to use Enums to implement versioning.
Versioning simplifies updating the contract since you only need to add a new new version of the structure. All versions can coexist, thus you will not need to change previously existing structures.
The example is composed by 2 contracts:
- Base: The guest-book contract using versioned
PostedMessages
(PostedMessagesV1
). - Update: An update that adds a new version of
PostedMessages
(PostedMessagesV2
).
#[near(serializers=[borsh])]
pub enum VersionedPostedMessage {
V1(PostedMessageV1),
V2(PostedMessageV2),
}
impl From<VersionedPostedMessage> for PostedMessageV2 {
fn from(message: VersionedPostedMessage) -> Self {
match message {
VersionedPostedMessage::V2(posted) => posted,
VersionedPostedMessage::V1(posted) => PostedMessageV2 {
payment: NearToken::from_near(0),
premium: posted.premium,
sender: posted.sender,
text: posted.text,
},
}
}
}
3. Self Update
The examples at ./self-updates shows how to implement a contract that can update itself.
It is composed by 2 contracts:
- Base: A Guest Book were people can write messages, implementing a
update_contract
method. - Update: An update in which we remove a parameter and change the internal structure.
pub fn update_contract(&self) -> Promise {
// Check the caller is authorized to update the code
assert!(
env::predecessor_account_id() == self.manager,
"Only the manager can update the code"
);
// Receive the code directly from the input to avoid the
// GAS overhead of deserializing parameters
let code = env::input().expect("Error: No input").to_vec();
// Deploy the contract on self
Promise::new(env::current_account_id())
.deploy_contract(code)
.function_call(
"migrate".to_string(),
NO_ARGS,
NearToken::from_near(0),
CALL_GAS,
)
.as_return()
}
Clone this repository locally or open it in a codespace using the green <> Code
button above. Then follow these steps:
Deploy your contract in a sandbox and simulate interactions from users.
cargo test --workspace
Commands in each contract's README
are valid for following versions of programs.
# NEAR CLI
❯ near --version
4.0.10
# near-cli-rs
❯ near --version
near-cli-rs 0.8.1
❯ cargo near --version
cargo-near-near 0.6.1
NOTE
: default devcontainer for Codespaces contains only near-cli-rs
and cargo-near
commands.
# NEAR CLI
near create-account <target-account-id> --useFaucet
# near-cli-rs
near account create-account sponsor-by-faucet-service <target-account-id> autogenerate-new-keypair save-to-keychain network-config testnet create
NOTE
: default devcontainer for Codespaces only supports save-to-legacy-keychain
option instead of save-to-keychain
of near-cli-rs
.
- Learn more on each contract's
README
. - Check our documentation.