Zeppelin OS Tutorial 101
Zeppelin is all about Smart Contract development. The team has helped many developers build Smart Contracts with openzeppelin-solidity, one most downloaded web3 libraries on NPM (with over 10k downloads every week!).
Building Upgradable Solidity Smart Contracts
Zeppelin OS Introduction
Zeppelin is all about Smart Contract development. The team has helped many developers build Smart Contracts with openzeppelin-solidity
, one most downloaded web3 libraries on NPM (with over 10k downloads every week!). According to Zeppelin’s website:
“ZeppelinOS is a development platform designed specifically for smart contract projects. It allows for seamless upgrades and provides economic incentives to create a healthy ecosystem of secure applications.”
What problem does it solve?
ZeppelinOS helps developers build upgradable smart contracts using libraries that anyone can use on the Ethereum blockchain. Unlike traditional contracts (which can remain frozen forever on the blockchain, with mistakes, limited functionalities, etc...), ZeppelinOS allows users to opt-in & allow upgrades of contracts, opening the doors to a more sustainable process for developing web3 blockchain projects. With upgrades, we can make iterative releases, quickly add small pieces of functionalities (that we can adjust according to the always changing goals of our users), and of course, we can implement fixes for bugs we had introduced on previous iterations.
Building on ZeppelinOS
Let’s build an upgradable CrudApp contract! We will deploy CrudApp.sol
and then update it while preserving its data… so let’s start:
1- Create a project and install dependencies
mkdir crud
cd crud
npm init
npm install --global zos
2- Initialize your project
zos init crud
This command will create a zos.json
file, which contains all the information about the project. For details about this file, see the configuration files page.
The command will also initialize Truffle. So by now, inside the crud
directory you should have a package.json
file (created by npm
), two empty directories named contracts
and migrations
, a truffle-config.js
file (created by zos
for Truffle), and a zos.json
file (created by zos
for ZeppelinOS).
3- Add CrudApp.sol contract
Let’s add our CurdApp.sol now. We will make some changes in our previous Smart Contract, so let’s understand them:
pragma solidity ^0.4.23;
contract CrudApp {
struct country{
string name;
string leader;
uint256 population;
}
country[] public countries;
uint256 public totalCountries;
constructor() public {
totalCountries = 0;
}
event CountryEvent(string countryName , string leader, uint256 population);
event LeaderUpdated(string countryName , string leader);
event CountryDelete(string countryName);
function insert( string countryName , string leader , uint256 population) public returns (uint256 totalCountries){
country memory newCountry = country(countryName , leader, population);
countries.push(newCountry);
totalCountries++;
//emit event
emit CountryEvent (countryName, leader, population);
return totalCountries;
}
function updateLeader(string countryName, string newLeader) public returns (bool success){
//This has a problem we need loop
for(uint256 i =0; i< totalCountries; i++){
if(compareStrings(countries[i].name ,countryName)){
countries[i].leader = newLeader;
emit LeaderUpdated(countryName, newLeader);
return true;
}
}
return false;
}
function deleteCountry(string countryName) public returns(bool success){
require(totalCountries > 0);
for(uint256 i =0; i< totalCountries; i++){
if(compareStrings(countries[i].name , countryName)){
countries[i] = countries[totalCountries-1]; // pushing last into current arrray index which we gonna delete
delete countries[totalCountries-1]; // now deleteing last index
totalCountries--; //total count decrease
countries.length--; // array length decrease
//emit event
emit CountryDelete(countryName);
return true;
}
}
return false;
}
function getCountry(string countryName) public view returns(string name , string leader , uint256 population){
for(uint256 i =0; i< totalCountries; i++){
if(compareStrings(countries[i].name, countryName)){
//emit event
return (countries[i].name , countries[i].leader , countries[i].population);
}
}
revert('country not found');
}
function compareStrings (string a, string b) internal pure returns (bool){
return keccak256(a) == keccak256(b);
}
function getTotalCountries() public view returns (uint256 length){
return countries.length;
}
}
Removing Constructor and adding an Initializer
“You can use your Solidity contracts in ZeppelinOS without any modifications, except for their constructors. Due to a requirement of the proxy-based upgradeability system, no constructors can be used in upgradeable contracts. You can read in-depth about the reasons behind this restriction in the ZeppelinOS Upgrades Pattern page.”
This means that, when using a contract within ZeppelinOS, you need to change its constructor into a regular function, typically named initialize
, where you run all the setup logic:
function initialize(uint256 countryLimit) initializer public {
totalCountries = 0;
limit = countryLimit;
}
You need to import Initializable.sol
too for this to work:
import "zos-lib/contracts/Initializable.sol";
Install Ganache and deploy your project
We will use Ganache (local blockchain) to deploy our Smart Contract. You can change network settings in truffle-config.js
if you want to use a different network. Open a different terminal and run below commands:
npm install -g ganache-cliganache-cli --port 9545 --deterministic
Now go back to the previous terminal and run below commands to start a session. We are using --network
option for local network and --from [address]
to provide an Ethereum address (which we are going use to deploy the smart contract). We are using a default Ganache address (you can see it on the Ganache terminal) and we are also using an additional expires
flag to define the number of seconds this session will be valid (1 hour in this case).
zos session --network local --from 0x1df62f291b2e969fb0849d99d9ce41e2f137006e --expires 3600
Now let’s deploy our project:
zos push
This command deploys CrudApp
to the local network and prints its address. You can deploy more contracts using add
command and they will also get deployed after this command.
The push
command also creates a zos.dev-<network_id>.json
file with all the information about your project in this specific network, including the addresses of the deployed contract implementations in contracts["MyContract"].address
. You can read more about this file format in the configuration files section.
Upgrade the Project
push
command deploys logic contracts which are not intended to be used directly, so instead we need an upgradable instance (we will cover logic contracts and ZeppelinOS Upgrades Pattern page in our future blogs).
Now let’s create an upgradeable instance of our CrudApp.sol
:
zos create CrudApp --init initialize --args 200
The zos create
command uses optional --init [function-name]
with the function name (which is initialize
in our case). You can also pass arguments for this function (which is 200, limitation of countries) in our case. This will also print a Smart Contract address, which we will use while interacting with it.
Interacting with the Contract
Now let’s interact with our contract and insert some countries. To get to the Truffle console, run below command:
npx truffle console --network local
You will now see the Truffle console. Let’s run some commands and insert some countries:
truffle(local)> crud = CrudApp.at('<your-contract-address>')
truffle(local)> crud.limit()
truffle(local)> crud.insert("USA", "Elizabeth Warren", 33000000)
truffle(local)> crud.getCountry("USA")
truffle(local)> crud.getTotalCountries()
We have inserted one entry in our Smart Contract. Let’s update our CrudApp!
Adding a function
We will add an extra function at the end of our Smart Contract and update it. So, for now, we already have a getCountry()
function… we will add one more getLeader()
function in our Smart Contract.
function getLeader(string leaderName) public view returns(string name , string leader , uint256 population){
for(uint256 i =0; i< totalCountries; i++){
if(compareStrings(countries[i].leader, leaderName)){
//emit event
return (countries[i].name , countries[i].leader , countries[i].population);
}
}
revert('Leader not found');
}
Now let’s update our Smart Contract. We need to run below commands for that:
zos push
zos update CrudApp
This will print a new Smart Contract address which we’ll use for testing our updated contract.
Testing updated Smart Contract
Now let’s test our Smart Contract. It should preserve our previous data and we can access our new getLeader()
function. So let’s interact with our Smart Contract again:
truffle(local)> crud = CrudApp.at('<your-contract-address>')
truffle(local)> crud.limit()
truffle(local)> crud.getTotalCountries()
truffle(local)> crud.getLeader("Elizabeth Warren")
You will see able to see the results, that the Smart Contract was updated just like any other piece of code. 🍰 — We will build an upgradable ERC-20 token in our next article!
Conclusion
Though it seems pretty easy, we need to be pretty careful while using ZeppelingOS because of Solidity storage management. You can check here about the thing you need to pay extra attention to while updating your Smart Contract. ZeppelinOS is a great value addition to the Ethereum ecosystem!
Let us know what you want to learn about in the comment section.👇
Need help with your project or have questions? Contact us via this form, on Twitter @QuickNode, or ping us on Discord!
About QuickNode
QuickNode is building infrastructure to support the future of Web3. Since 2017, we’ve worked with hundreds of developers and companies, helping scale dApps and providing high-performance access to 16+ blockchains. Subscribe to our newsletter for more content like this and stay in the loop with what’s happening in Web3! 😃