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:
- Using migration method
- Using enums
Contracts
- Initial contract initial-contract
- Upgrade using migration method
- Upgrade using enums
- 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.