How to create an upgradeable smart contract using ZeppelinOS — example of fixing smart contract vulnerable to underflow/overflow attacks

Paulina Błaszkiewicz
Coinmonks
8 min readApr 23, 2019

--

In this tutorial we will use ZeppelinOS to fix smart contract vulnerable to underflow/overflow attacks. Firstly we will prepare our environment and create the upgradeable smart contract with some bug and test it. Then we will update the code, upgrade the smart contract and finally, we will check if it is safe.

You can go to updated version of this article — with OpenZeppelin SDK v.2.5.2

Upgradeability in smart contracts

In principle smart contracts are immutable. Once deployed to the network, they cannot be changed. Unfortunately in smart contracts, like in every software, mistakes frequently happens. Sometimes they may cost millions of dollars… But don’t worry! Now we have ZeppelinOS — powerful tool to upgrade our smart contracts and fix mistakes. ZeppelinOS is a platform to develop, deploy and operate smart contract projects on Ethereum and every other EVM and eWASM-powered blockchain as its documentation says. And one of its most important features is smart contracts upgradeability.

Environment preparation

You need to have installed essential software for creating and upgrading the smart contracts. You can find the instructions for installing Node.js and NPM, Truffle, Ganache in my previous article.

ZeppelinOS installation

To install ZeppelinOS globally we run a command:

To check if you have already installed ZeppelinOs or to make sure that the installation process was successful you can verify the version of your software:

I am currently using zos v2.2.3

Project setup

We have to create a new directory for our project and then navigate to it:

Project initiation

In the beginning, we need to create the package.json file:

We run this command with -y flag to generate package.json without having it ask any questions - it will take default values. Of course, if you want to personalize it, you can just use npm init and answer given questions.

Next, we can finally init our first ZeppelinOs project:

If it asks us for version — we can accept 1.0.0 by pressing enter. A command zos init creates file zos.json in our directory. It stores the general configuration of our zos project (ZeppelinOS version, name of the initialized project, its version and information about our contracts that our project contains).

What is more, zos initalso initializes Truffle. This means that the standard structure of truffle project has been created in our hello-zosdirectory. Now there are two new directories (contracts and migrations) and truffle-config.js file. Remember to check if truffle-config.js is compatible with your Ganache configuration.

Creating sample smart contract

The problem with smart contracts immutability may especially arise when we find a bug in our code. Without upgradeability, we can do nothing with such a smart contract and it can be a great target of hacker attacks. For our example, we will create the smart contract vulnerable to underflow/overflow attack (you can read more about it here)

In the zos.json file, we can see that object storing our contracts is empty, so it is time to create our smart contract. First we add new file in contracts directory. We can call it HelloZos.sol. Our smart contract will contains:

  • string state variable name
  • uint256 state variable MaxNumber
  • uint256 state variable inc
  • uint256 state variable dec
  • function decrement (that subtracts 1 from the choosen number)
  • function increment (that adds 1 to the choosen number)

In the upgradeable smart contract we use init function instead of constructor. Why? The constructor is executed when the smart contract instance is deployed. In the proxy-based upgradeability system, it would never happen. That is why we use initializer. To do so, at the beginning of HelloZos.sol, just after pragma, we have to import contract Initializable.sol from ZeppelinOS Library:

Of course, to use ZeppelinOS library we have to install it in our project directory, so we have to run a command:

Now we can define our smart contract as Initializable and add init function.

Due to a Medium issue — the github code is not displayed in Safari. Use a different browser to view all content

The whole contract code is:

It is time for the best fun — let’s deploy our smart contract to the network, test it and fix the bug!

Registering smart contract in zos project

First, we have to register the smart contract in our ZeppelinOS project. To do this we run a command:

If we have more than one smart contract, we can add all their names at once (separated by spaces) or use --all flag.

Now our smart contracts (both: HelloZos and Initializable) are compiled (their artifacts are written to build/contracts directory). And we can find HelloZos contract under contracts filed in our zos.json

Deploying smart contract to the network

Then we deploy our smart contract to a specified network. In our case it is Ganache local network (make sure that it is running) and use a command:

Remember to replace the address after --from flag with one of your Ganache accounts, but do not use the first one (account[0]). I usually choose the second (accounts[1]) or the last one (accounts[9]). You should also use network specified in your truffle-config.js (in our case it is local).

After these preparations we can push HelloZos smart contract to our local network by running a command:

It creates file zos.dev-<your-network-number>.json

Creating an upgradeable instance of smart contract

We have just deployed HelloZos contract to the network, but it only implements the logic. If we want to interact with our smart contract we have to create its upgradeable instance by:

After “create” we add the name of our smart contract, then we call initialize function and we pass the arguments after --args flag (if there is more than one argument they should be separated with commas, without spaces)

The result is:

We can also find our smart contract in the bottom part of zos.dev-<number>.json

The address of your HelloZos proxy is the white address that we can see in the console and we will interact with it later. The “implementation” is the address of current smart contract version and it will change when we upgrade our smart contract.

Interacting with our smart contract

Now we can test how our smart contract works. To communicate with it we will use the truffle console. We open it with the command:

In the beginning, we will save the instance of HelloZos contract in the myContract variable:

Remember to replace the address above with address of your HelloZos proxy!

Now we can check values of our state variables:

Now we can check our functions with a simple number:

On the face of it, everything looks fine:

Then we call our function with 0 and max number and check the results:

Now we know that our smart contract has a bug and is vulnerable for attacks. So what to do? Don’t worry, we use ZeppelinOS so we can upgrade our code in few simple steps.

Upgrading contract

How can we fix it? We should use SafeMath library in our smart contract for all arithmetic operations.

We will use OpenZeppelin contract:

Please notice that we import openzeppelin-eth not openzeppelin-solidity as in usual. What is the difference? Openzeppelin-eth is the library of EVM packages, that have been already deployed to the blockchain ( you can read more about differences between openzeppelin-eth and openzeppelin-solidity in this article).

To use it, we have to link the EVM package to our project:

Our zos.json file is updated and contains a new object with dependencies:

Now we have to update the code of our HelloZos smart contract. We need to change the add and subtraction characters to the SafeMath functions.

We also have to add the statement below just under the state variables:

Due to a Medium issue — the github code is not displayed in Safari. Use a different browser to view all content

The code of our whole updated HelloZos:

Important!

When upgrading smart contract we cannot:

-change the type of existing variables,

-change the order in which variables are declared,

-remove the existing variable,

-introduce a new variable before the existing one.

You can read more about upgrades pattern at ZeppelinOS Documentation .

But let’s go back to our brand new HelloZos. Now we can run the command:

We use --deploy-dependencies to deploy EVM package to our local network (we don’t have to do it when we use the mainnet, ropsten, rinkeby or kovan because those packages are already deployed).

Finally we can update our project by using the command:

Now we can test our smart contract again in the truffle console.

Remember to replace the address above with address of your HelloZos proxy!

Now we can check values of our state variables:

When we try to overflow/underflow we will see

Thanks to ZeppelinOS we have updated the code of our smart contract and we are protected from overflow/underflow attacks!

You can find the whole project on my GitHub.

Get Best Software Deals Directly In Your Inbox

--

--

Paulina Błaszkiewicz
Coinmonks

I work as a full-stack developer in Featly start-up. I experience “flow” in programming, learning new things and practicing yoga. Love good coffee