Upgrade your smart contract using Openzeppelin CLI [2020]

Asmita Dhungana
Jul 30 · 8 min read

Just over a month ago, the office I’m interning at gave me the task of making one of their smart contracts “upgradeable”. Hence, began my journey. Consulting Openzeppelin docs , forum and numerous internet articles, I was able to accomplish the task. I presented it to my office and it’s high time that I shared it with all of you! I tried to make this as simple and straightforward a code guide with as minimal side-info as I could. As a sidenote, you need to be somewhat familiar with basic smart contract development on Ethereum to follow along… If you are…Grab a cup of coffee and hop in! 💁

Upgradeability is a feature of blockchain that’s not advertised as much as is implemented. Where…? in almost every smart contract that’s deployed to the mainnet! Well… and why not? Upgradeability is required everywhere! More so for blockchains where they deal with the movements of sensitive data and assets including money! Irreversible apocalyptic damages can occur if a smart contract containing some bugs or dysfunctionalities falls upon the evil sights of greedy hackers (check out these big hacks as an example). What happens in an upgrade is you fix the errors, bugs and add functionalities and variables in your existing smart contract without changing the address that you and your users interact with in the blockchain. And all this happens while keeping the values or states of your variables intact in between upgrades! Now you might be questioning…How can this happen? What happened to “immutable” blockchains!?!

Well…relax! Before you start questioning the integrity of blockchains, let me tell you…they are still immutable! What you don’t know about is this brilliant idea called proxy-pattern which lets blockchain maintain its nature while being able to advance and adapt with changes! This pattern does so by creating a wrapper contract(proxy) which delegates(forwards) the call to our actual implementation contract while mapping the storage slots in itself with the state variables of the implementation smart contract. For every transactions, the state’s storage is used from the proxy while the function logic is applied from the implementation contract. So when a implementation contract is updated, the whole contract is deployed to another address where the proxy will point to next… giving us an illusion as if our contract got “updated” right from the previous state!

It is rather complex to implement on your own. That’s why we use tools like Openzeppelin CLI that let us do all this using very simple commands while abstracting from us the heavy-loaded details that’re running in the hindsight!

We’ll cover the following topics in this article:
1. Setting up Openzeppelin CLI
2. Adding configuration files
3. Points to consider while writing an upgradeable smart contract
4. Example contract: SimpleStorage
5. Compile and Deploy the smart contract
6. Upgrade to version 2… Adding function and states
7. Deploy version 2 to the network
8. Interact with the upgraded instance
9. Upgrade to version 3…Using Libraries
10. Re-interact to recheck

NOTE: You can find the code for this guide in this repo ;)

1. Setting up Openzeppelin CLI

To be able to use openzeppelin’s built-in functionalities (which is robust and comparable to truffle), you’ll first need to use its CLI. First, make sure that you have nodeJS and npm installed globally in your computer. Now, make a new directory in whichever drive you want and hop in! Once inside:

  • Initialize an empty node project
    $ npm init

2. Adding Configuration files

You can copy these contents into the respective files in your project:

  • package.json
fig 1: package.json

Now… run $ npm install
Here, dependencies in line 13 and 14 and 19 are the main dependencies [read 3.3] while others are optional. They’re the openzeppelin libraries that allow us to make upgradable smart contracts. In 15, the dependency is Truffle HDWallet provider, a convenient and easy tool to configure network connection to Ethereum through infura.io or any other compatible provider. While dotenv in line 16 allows you to separate secrets from your source code. This is useful in a collaborative environment where you may not want to share your database login credentials or private keys with others.

  • networks.js
fig 2: networks.js

These are the network configuration that allow your openzeppelin project to connect to the respective networks. Here, we’re adding network configurations for Ganache, a local ethereum simulator and for rinkeby, an Ethereum testnet(which will be used in this tutorial).

.env

fig 3: .env

Add the mnemonic from your metamask wallet account (make sure it’s loaded with some Test Ether) and your Infura key for rinkeby which you can generate by following this article!

Warning!!! You should not commit this file to version control as it contains your wallet seed phrase and your API keys; there are bots waiting to sweep funds from any accounts with Ether! To avoid this, add “.env” in a new line in your .gitignore

3. Points to consider while writing an upgradeable smart contract

These constraints come to play due to the proxy-pattern that governs upgradeable smart contract development on EVM. These are due to limitations in EVM and not openzeppelin:

  1. No constructor() function:
    You cannot use solidity constructors for initializing your setup logic. Instead, use an initialize function in place of constructor with initializer modifier inherited from openzeppelin/contracts-ethereum-package/contracts/Intializeable.sol for that.

4. Example contract: SimpleStorage

Inside the “contracts” folder, let’s make a normal smart contract in Solidity called “SimpleStorage.sol”. It has one state variable called storedValue and a function retrieve() through which any user can retrieve the storedValue [note that solidity will provide a getter function for public state variables by default, this is only for demonstrating purpose]

fig 4: vanilla Unupgradeable SimpleStorage.sol

Now, to make it upgradeable, you’ll have to make some tweaks following the rules in [3] as follows:

fig 5: modified Upgradeable SimpleStorage.sol

5. Compile and deploy the smart contract

Use openzeppelin cli commands to do so as below:

  1. Compile the contract:
    $ npx oz compile
? choose the kind of deployment: upgradeable
? Pick a network: rinkeby
? Pick a contract to deploy: SimpleStorage
? Call a function to initialize the instance after creating it? Y
? Select which function: *initialize()
If all goes well, the returned text should be similar to this:✓ Setting everything up to create contract instances
✓ Instance created at 0xC9F4797BeAA8c56af23760e7EB57A6e7196c693a
To upgrade this instance run ‘oz upgrade’ 0xC9F4797BeAA8c56af23760e7EB57A6e7196c693a

6. Upgrade to v2… Adding functions and states

Let’s say we want to add a functionality that can increment the value of storedValue. So in the previous contract, let’s add a new state variable addedValue that stores the passed argument and an incrementValue( ) function which adds it to storedValue.

fig 6: SimpleStorage.sol Version 2

7. Deploy version 2 to the network

To do so, you’ll have to use the CLI command “oz upgrade” which again will take to you to an interactive console. Choose the options as shown below:

$ npx oz upgrade? Pick a network:  rinkeby
? Which instances would you like to upgrade?: Choose by name
? Pick an instance to upgrade ? SimpleStorage
? Call a function on the instance after upgrading it? No
New variable 'uint256 addedValue' was added in contract SimpleStorage in contracts/SimpleStorage.sol:1 at the end of the contract.
See https://docs.openzeppelin.com/upgrades/2.6//writing-upgradeable#modifying-your-contracts for more info.
✓ Contract SimpleStorage deployed
All implementations have been deployed
✓ Instance upgraded at 0xC9F4797BeAA8c56af23760e7EB57A6e7196c693a. Transaction receipt: 0x43834b9b13c20db353b657fff15f4a3881da0cd092dc994cfb3400513e229d0f
✓ Instance at 0xC9F4797BeAA8c56af23760e7EB57A6e7196c693a upgraded

8. Interact with the upgraded (v2) instance

Now, let’s interact with our upgraded instance to check if the upgrade did add the increment() function or not...
Openzeppelin provides us with two interactive commands:
oz send-tx” to call functions that modify the state variables and
oz call” to call functions that do not modify the state variables.

  • Firstly, we’ll call our newly introduced increment() function with argument 10 for adding to the storedValue:
$ npx oz send-tx? Pick a network: rinkeby
? Pick an instance: SimpleStorage at 0xC9F4797BeAA8c56af23760e7EB57A6e7196c693a
? Select which function: incrementValue(x: uint256)
? x: uint256: 10
✓ Transaction successful. Transaction hash: 0x9a93f387a1166f324fa9221bc8d29abc948c97244f344c25fa237b786ce57f19
  • Then, we’ll call the retrieve() function to see if the value of storedValue was incremented or not:
$ npx oz call? Pick a network rinkeby
? Pick an instance SimpleStorage at 0xC9F4797BeAA8c56af23760e7EB57A6e7196c693a
? Select which function retrieve()
✓ Method ‘retrieve()’ returned: 10
10

As we can see, retrieve() method returned 10. Which means that our increment() function was added successfully! Cheers!

9. Let’s upgrade again… Using libraries:

Now, let’s see how we can add utility libraries to our upgradeable smart contract. We’ll add Openzeppelin’s Safemath library to our contract and modify the increment() function so as to avoid any kind of overflow during the addition! Note that we can only add upgradeable libraries[3.3]

fig 7: SimpleStorage.sol Version 3

Warning! Changing or adding parent contracts can change the storage variables of your contract.

This is based on: https://docs.openzeppelin.com/upgrades/2.8/writing-upgradeable#modifying-your-contracts

Note that you may also be inadvertently changing the storage variables of your contract by changing its parent contracts … swapping the order in which the base contracts are declared, or introducing new base contracts, will change how the variables are actually stored … You also cannot add new variables to base contracts, if the child has any variables of its own.

10. Re-interact to recheck:

Now, let’s deploy the upgraded SimpleStorage (version 3) to the rinkeby testnet:

$ npx oz upgrade? Pick a network rinkeby
? Which instances would you like to upgrade? Choose by name
? Pick an instance to upgrade SimpleStorage
? Call a function on the instance after upgrading it? No
Nothing to compile, all contracts are up to date.
All implementations are up to date
✓ Instance upgraded at 0xC9F4797BeAA8c56af23760e7EB57A6e7196c693a. Transaction receipt: 0xe22a159bfaf3486a919db0d3cf66a5f199858f8cf4452efd2ef378a3ea07f1b8
✓ Instance at 0xC9F4797BeAA8c56af23760e7EB57A6e7196c693a upgraded

…and call the retrieve() function to check if the storage was retained between these upgrades or not:

$ npx oz call? Pick a network rinkeby
? Pick an instance SimpleStorage at 0xC9F4797BeAA8c56af23760e7EB57A6e7196c693a
? Select which function retrieve()
✓ Method ‘retrieve()’ returned: 10
10

As we can clearly see, retrieve function returned the value of storedValue as 10, intact from the last version. This way, we could modify the previous function, add libraries and fix our bugs while keeping the state of our variables unchanged and intact. The best part is… we could interact with the same address as before!

This is all for this tutorial…I hope this was useful for you! In the next part of this series, I’ll guide you on how we can make upgrades to our smart contract programmatically (using our own scripts)! And I’m going to continue from the last state (v3) of the SimpleStorage smart contract to show it to you!
Stay tuned… ;)

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store