OpenZeppelin Blog

Getting started with ZeppelinOS - OpenZeppelin blog

Written by OpenZeppelin Security | August 3, 2018

Thank you for your interest in this post! We’re undergoing a rebranding process, so please excuse us if some names are out of date. Also have in mind that this post might not reference the latest version of our products. For up-to-date guides, please check our documentation site.

By Elena Nadolinski

Why Upgradeability Matters

Blockchains are immutable, and Ethereum is no exception. Ethereum has a rich smart contracts ecosystem that greatly lowers the barriers to creating new decentralized applications (DApps), governance models, and cryptocurrency tokens. It makes it so easy that you can create your own ERC-20 token (the most popular type of token for ICOs) in less than 20 minutes. As a consequence, there are new projects, services, and entire companies with core technologies that are wholly based on smart contacts which can never be amended once deployed. Companies with decentralized offerings based on smart contracts are trying to overtake mainstream tech giants such as Airbnb, YouTube, Twitter, Venmo, Facebook, and many others. But these successful companies didn’t start out the way they are now. When they first launched, their products were a lot different than what they are today. This is what some of our favorite products looked like when they first launched:

Credit: https://twitter.com/spencernoon/status/1016713147036655616

These tech companies launched an initial nascent product and upgraded it over time, with many iterations being done over the years to both their front-end and their back-end infrastructure. If DApps expect to compete against such existing offerings, they have to be able to iteratively upgrade their underlying smart contracts over time. But upgrading smart contracts is not an easy task. In order to write smart contracts so that they can be upgradeable, you have to understand the complexities of various proxy patterns, be aware of memory corruption implications, and distract yourself from building your awesome DApp by worrying about how to deploy an upgradeable architecture.

Introducing ZeppelinOS

ZeppelinOS makes writing upgradeable contracts extremely easy. It is a command line tool specifically made to abstract away the complexities of upgradeability so that you can focus all your attention on writing a great DApp. Let me show you how it works! I’ll walk you through how to make a simple smart contract that you can deploy and upgrade with the help of ZeppelinOS. Note: Upgradeability cannot be applied to already-deployed contracts.

1- We’ll use the command line tool for ZeppelinOS, zos, that you can get with a. npm install --global zos

2- Next, we’ll make a new directory, navigate to it, and initialize our zos application: a. mkdir zos-demo && cd zos-demo b. npm init -y c. zos init zos-demo Notice the zos.json file that was produced. This is the configuration file that makes zos aware of your smart contract architecture. Feel free to read up on the zos.json file format to understand how zos sees your project.

3- Next, we’ll need to install zos-lib: a. npm install --save zos-lib

4- Notice the contracts/ folder that was created for you. Go ahead and make a new file in it called CounterContract.sol, and paste in the following code:

pragma solidity ^ 0.4 .21;
import "zos-lib/contracts/migrations/Migratable.sol";
contract CounterContract is Migratable {
  uint256 public counter;

  function initialize(uint256 _counter) isInitializer("CounterContract", "0") public {
    counter = _counter;
  }

  function increment() public {
    counter += 1;
  }
}

5- When writing upgradeable smart contracts, you cannot rely on the constructor; instead, you replace the constructor with an initialize method to set up your initial smart contract state. Make sure to mark the method with the isInitializer modifier that takes the name of your contract and version ID. This is so that your new contract version can only be initialized once. Note: If you do use a constructor in an upgradeable smart contract, everything you set in the constructor will be ignored.

6- Great! Now let’s test it out on our local network. We’ll use Ganache CLI to emulate a localhost version of the Ethereum network. If you don’t have it already, go ahead and install it: a. npm install --save ganache-cli b. Then run it in a separate window on port 9545 npx ganache-cli --port 9545

7- We’re now ready to deploy our smart contract to our local network. Go back to your first terminal window and add your smart contract to zos, and then push it to the local network: a. zos add CounterContract b. zos push --network local

8- Next, we’ll create and initialize an upgradeable instance of CounterContract with zos. The --args option corresponds with the parameters of our initialize method. a. zos create CounterContract --init initialize --args 42 --network local b. Note the output line ‘CounterContract proxy: <address>’. This is the address you will use for CounterContract._ Save this address, as it is the permanent address for CounterContract._

9- Let’s test it out in the truffle terminal! a. npx truffle console --network=local b. Now that you’re in the interactive truffle terminal, grab the instance of the smart contract you deployed using the CounterContract proxy address mentioned above: counterContract = CounterContract.deployed() or CounterContract.at("permanent address for CounterContract") c. We can call our increment method on our smart contract and see the new value of our variable x: counterContract.increment() counterContract.counter().then(counter =&gt; counter.toNumber()) d. You should see the output as 43, since we started out with the counter at 42.So far, we’ve deployed our initial version of the CounterContract. If this was for a real product, we could stop right here and use this smart contract in our app. In the next steps, we’ll go over how to upgrade this contract to add more features.

10.Let’s upgrade our deployed  CounterContract! Let’s say that now we want to add a new method and even a new mapping, even though we already deployed our smart contract. Go back to the code for CounterContract.sol and add a new method incrementByTwo and a new mapping to keep historical track of who changed the counter to a particular value.

pragma solidity ^ 0.4 .21;
import "zos-lib/contracts/migrations/Migratable.sol";
contract CounterContract is Migratable {
  uint256 public counter;
  mapping(uint256 => address) public history;

  function initialize(uint256 _counter) isInitializer("CounterContract", "0") public {  
    counter = _counter;
  }

  function increment() public {  
    counter += 1;  
    history[counter] = msg.sender;
  }

  function incrementByTwo() public {  
    counter += 2;  
    history[counter] = msg.sender;
  }
}

Note: When writing new versions of your contract, you must preserve all variables that appear in previous versions of your smart contract in the same order. _You should only make additive changes. You can find more details in the advanced topics page._

11- We’re ready to push our new version. Exit out of the truffle console (type .exit to exit) and run: zos push --network local

12- Now let’s update CounterContract to this new version we wrote! a. zos update CounterContract --network [local

13- To test it out, we can use the truffle console again: a. npx truffle console --network=local to access the truffle console. b. counterContract = CounterContract.deployed() or counterContract = CounterContract.at("permanent address for CounterContract") c. counterContract.incrementByTwo() We can call our new method! d. counterContract.counter().then(counter =&gt; counter.toNumber()) And now our counter is incremented by two! e. Similarly, we can call counterContract.history(45) to see which address changed the counter to value 45.

Note: If at any point you’ve stopped Ganache and need to restart this process all over, make sure you delete zos.local.json file as well. This isn’t a problem for other networks, since typically networks don’t get wiped out 🙂 Great! You’ve successfully deployed and upgraded a smart contract with the help of ZeppelinOS! To learn how to deploy your upgradeable contract to other networks such as mainnet, check out this guide. If you’re ready for more advanced tutorials, check out tutorial demo apps Basil and Crafty. You can also use ZeppelinOS for a lot more, such as using already-deployed on-chain standard libraries in your smart contract and even adding your own. With the help of ZeppelinOS, you can now easily make upgradeable smart contracts and incrementally add more features and complexity to your DApps over time as you grow your user base 😀 If you’re curious to learn exactly how ZeppelinOS uses proxies for upgradeability, you can watch my Zepcon0 talk that explains proxy patterns in detail. Isn’t this amazing???