adsick / rust-upgradable-example

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Description

This is an example of upgrading rust smart contracts on near. You can use a couple different approaches, depending on the complexity of your contract.

There are two popular ways:

Contracts

  1. Initial contract initial-contract
  2. Upgrade using migration method
  3. Upgrade using enums
  4. Upgradable contract with enums

Migration

When you have a deployed contract and you need to change something in the main structure of the contract without losing the old state. It is possible to do using migration method.

Example of migration

At first, let's deploy our initial smart contract.

With the main structure:

pub struct Contract {
    sales: UnorderedMap<SaleId, Sale>,
}

Inside of initial-contract/ directory execute

./build.sh
rm -rf neardev # In case you already have neardev
near dev-deploy res/initial_sale_contract.wasm
source neardev/dev-account.env

Add some data to our state

near call $CONTRACT_NAME add_sale '{"item": "foo", "price": "42"}' --accountId $CONTRACT_NAME
near call $CONTRACT_NAME add_sale '{"item": "bar", "price": "10000"}' --accountId $CONTRACT_NAME
near call $CONTRACT_NAME buy '{"sale_id": 0}' --accountId $CONTRACT_NAME --depositYocto 42

Later we realised that we also want to have discounts for our users. The main structure will look like this:

pub struct Contract {
    sales: UnorderedMap<SaleId, Sale>,
    discount: UnorderedMap<AccountId, u32>,
}

To do so we should keep OldContract structure and create migrate method like that

Let's migrate our upgraded contract

Inside migration-upgraded-contract/ directory:

./build.sh
near deploy $CONTRACT_NAME res/migration_upgraded_sale_contract.wasm migrate '{}'

Now we can test it

near call $CONTRACT_NAME buy '{"sale_id": 1}' --accountId $CONTRACT_NAME --depositYocto 10000
near view $CONTRACT_NAME get_discount '{"user": "'$CONTRACT_NAME'"}'

result:

1

So the discount field added to the contract.

Enums

You have a deployed contract and you want to change something in the sub structure of the contract without losing old state. Or you can't move huge structure in single call of migration method. Here the enums can help you to do that.

Example of enums upgrade

At first, let's deploy our initial smart contract.

With the structures:

pub struct Sale {
    item: String,
    price: u128,
    sold: bool,
}

pub struct Contract {
    sales: UnorderedMap<SaleId, Sale>,
}

Inside initial-contract/ directory:

./build.sh
rm -rf neardev # In case you already have neardev
near dev-deploy res/initial_sale_contract.wasm
source neardev/dev-account.env

And fill the state a little

near call $CONTRACT_NAME add_sale '{"item": "foo", "price": "42"}' --accountId $CONTRACT_NAME
near call $CONTRACT_NAME add_sale '{"item": "bar", "price": "10000"}' --accountId $CONTRACT_NAME
near call $CONTRACT_NAME buy '{"sale_id": 0}' --accountId $CONTRACT_NAME --depositYocto 42

Now we want to upgrade our Sale structure, so it looks like this:

pub struct Sale {
    seller: AccountId, // now anyone can sell their items on this contract
    item: String,
    price: u128,
    amount: u64, // and we can sell more then one item
}

To do so we can use enum and have to implement From<UpgradableSale> for Sale so your contract know how to use adopt old structure.

Also would be useful to implement From<Sale> for UpgradableSale so we can call .into() method when you insert your Sales.

Add migrate method so we can use UpgradableSale and replace get(&sale_id) calls. With method that will keep legacy_sales, or the method, that will move old sales to the sale(if you want to get rid of legacy_sales at the end).

Inside enum-upgraded-contract/ directory:

./build.sh
near deploy $CONTRACT_NAME res/enum_upgraded_sale_contract.wasm migrate '{}' 

Now we can test it

near call $CONTRACT_NAME buy '{"sale_id": 1}' --accountId $CONTRACT_NAME --depositYocto 10000
near call $CONTRACT_NAME add_sale '{"item": "banana", "price": "500", "amount": "5"}' --accountId $CONTRACT_NAME
near view $CONTRACT_NAME get_sale '{"sale_id": 2}'

result

'{"seller":"dev-1650279768195-86068399571956","item":"banana","price":"500","amount":"5"}'

And now you can add new versions to UpgradableSale, without migrating it.

Upgradable contract

If you plan to upgrade your contracts throughout their lifetime, start with enums. Adding them only after you decide to upgrade is (usually) possible, but will result in harder-to-follow (and thus more error-prone) code.

Example of upgradable contract

Contract already using enums, so you can just upgrade it by adding new Enum variants to UpgradableSale. And fixing implementation From<UpgradableSale> for Sale so your contract know how to use adopt old variants of Sale.

About


Languages

Language:Rust 92.6%Language:Shell 7.4%