Skip to content

An example on how to perform self-updates and state migrations in NEAR Rust contracts

Notifications You must be signed in to change notification settings

near-examples/update-migrate-rust

Repository files navigation

Contract's Update & State Migration

Three examples on how to handle updates and state migration:

  1. State Migration: How to implement a migrate method to migrate state between contract updates.
  2. State Versioning: How to use readily use versioning on a state, to simplify updating it later.
  3. 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:

  1. Base: A Guest Book where people can write messages.
  2. 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:

  1. Base: The guest-book contract using versioned PostedMessages (PostedMessagesV1).
  2. 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,
            },
        }
    }
}

The examples at ./self-updates shows how to implement a contract that can update itself.

It is composed by 2 contracts:

  1. Base: A Guest Book were people can write messages, implementing a update_contract method.
  2. 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()
}

Quickstart

Clone this repository locally or open it in a codespace using the green <> Code button above. Then follow these steps:

0. Test the Contract

Deploy your contract in a sandbox and simulate interactions from users.

cargo test --workspace

1. Examples' cli-s versions

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.

2. Accounts for deploying contracts from these examples can be created by:

# 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

  1. Learn more on each contract's README.
  2. Check our documentation.

About

An example on how to perform self-updates and state migrations in NEAR Rust contracts

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages