8-Upgradeable contracts

Overview

Code of a smart contract deployed on chain is immutable, however, we can update the code hash of a contract to point to a different code (therefore changing the code of the smart contract). This functionality can be used for bug fixing and potential product improvements. For this type of scenario, there are different upgrade strategies.

  • Replacing Contract Code with set_code_hash()
  • Proxy Forwarding

In this tutorial we will focus on the first strategy, ie replacing the contract code with the method set_code_hash().

Exemple - V1

The logic layer

In this tutorial, we use “a renter of cars” as example and we define the logic layer via the following trait :

pub type CarId = AccountId;

#[openbrush::trait_definition]
pub trait Renter {
    #[ink(message)]
    fn add_car(&mut self, car_id: CarId, owner: AccountId) -> Result<(), Error>;

    #[ink(message)]
    fn rent(&mut self, car_id: CarId) -> Result<(), Error>;

    #[ink(message)]
    fn give_back(&mut self, car_id: CarId) -> Result<(), Error>;
}


We defined three methods:

  • add_car: the caller add a new car for rent
  • rent: the caller rents the car
  • give_back: the caller returns the previously rented car

The storage

The default implentation of this trait can be found here.

Mainly we will define this storage:

#[derive(Default, Debug)]
#[openbrush::storage_item]
pub struct Data {
    owners: Mapping<CarId, AccountId>,
    leaseholders: Mapping<CarId, AccountId>,
}


We use the openbrush::storage_item macro that implements the required traits for a struct, as well as automatically generating unique storage keys for each of the struct's fields which are either marked as #[lazy] or are of type Mapping/MultiMapping. You can have more information here to know how the storage works.

The macro openbrush::storage_item is an easy way to automatically generate unique storage keys and help the developer to build upgradable contracts.

The contract

The contract uses the storage item defined in the previous logic layer and exposes the method upradable_code_hash() to update the code hash of the contract.

#[openbrush::contract]
pub mod renter {

    use logics::impls::renter::{self, *};
    use openbrush::traits::Storage;

    #[ink(storage)]
    #[derive(Default, Storage)]
    pub struct MyContract {
        #[storage_field]
        renter: renter::Data,
    }

    impl Renter for MyContract {}

    impl MyContract {
        #[ink(constructor)]
        pub fn new() -> Self {
            let instance = Self::default();
            instance
        }

        #[ink(message)]
        pub fn upgrade_contract(&mut self, new_code_hash: [u8; 32]) -> Result<(), ContractError> {
            ink::env::set_code_hash(&new_code_hash).map_err(|_| ContractError::UpgradeError)?;
            Ok(())
        }
    }

    #[derive(Debug, Eq, PartialEq, scale::Encode, scale::Decode)]
    #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
    pub enum ContractError {
        UpgradeError,
    }
}


It’s important to not forget to expose the method set_code_hash() from te beginning if you want your contract can be upgraded because you will not be able to do it later.

It’s also very important to restrict the access to this method, only the admin of the contract should be able to call the function. You can find moer information about the security here.

Upgrade the version to v2

Let’s imagine we want to add extra features in our contract:

  • set a rental price by car when the car is rented.
  • get the leaseholder of a car
  • know if a car is rent or not

The logic layer

Here the new version of our logic layer

#[openbrush::trait_definition]
pub trait Renter {
    #[ink(message)]
    fn add_car(&mut self, car_id: CarId, owner: AccountId) -> Result<(), Error>;

    #[ink(message)]
    fn update_rental_price(
        &mut self,
        car_id: CarId,
        rental_price: Option<Balance>,
    ) -> Result<(), Error>;

    #[ink(message)]
    fn get_leaseholder(&self, car_id: CarId) -> Result<Option<AccountId>, Error>;

    #[ink(message)]
    fn is_rent(&self, car_id: CarId) -> Result<bool, Error>;

    #[ink(message, payable)]
    fn rent(&mut self, car_id: CarId) -> Result<(), Error>;

    #[ink(message)]
    fn give_back(&mut self, car_id: CarId) -> Result<(), Error>;
}


The storage

The new storage used to implement this trait:

#[derive(Default, Debug)]
#[openbrush::storage_item]
pub struct Data {
    owners: Mapping<CarId, AccountId>,
    leaseholders: Mapping<CarId, AccountId>,
    rental_prices: Mapping<CarId, Balance>,
    received_rents: Mapping<(CarId, AccountId), Balance>,
}

We still to use the openbrush::storage_item macro to automatically generates unique storage keys and we respect the following rules about the storage compatibility:

  • You must not change the order in which the contract state variables are declared, nor their type!
  • You must not remove an existing variable
  • You must not change the type of a variable
  • You must not add a new variable before any of the existing ones.

The contract

There is no change in the contract except that we are using the new version of our logic layer.

To upgrade the first version of the contract, we need to first deploy a smart contract with the new code to register its code hash to the chain, and then call the ink::env::set_code_hash function.

Conclusion

Upgradeability allows experimenting and deploying the product at the early stage, always leaving the chance to fix vulnerabilities and progressively add features. Upgradeable contracts are not a Bug if they are developed consciously with decentralization in mind.

Decentralization can be achieved by providing the right to upgrade only to decentralized authority like governance, multisig, or another analog.

There is also a possibility of smart contract upgradeability via Proxy and Diamond patterns, which use DelegateCall to perform operations over own storage with code of a different deployed contract.

Git repository

https://github.com/GuiGou12358/astar-tutorials/tree/main/tuto8

Source

https://use.ink/basics/upgradeable-contracts

https://learn.brushfam.io/docs/OpenBrush/smart-contracts/upgradeable

0
GuiGouPost author

Crypto-enthusiast, Defi & NFT believer, Dotsam Fam Astar Tech Amb & Phala Amb Web2 builder gradually migrating to Web3

Tutorials to write Smart Contracts in Rust and Ink!

0 comments

Tutorials to write Smart Contracts in Rust and Ink!