Exploring upgradeability governance in ZeppelinOS with a Gnosis MultiSig

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.

With the first release of ZeppelinOS two months ago, the key issue we wanted to tackle was upgradeability: that is, the ability to modify the logic of a smart contract already deployed to the Ethereum blockchain. We are very happy with the solution that’s been built, and have received lots of feedback from the community since then.

One of the main questions that pops up is whether upgradeability goes against the immutability principles of a blockchain. After all, one of the main advantages of building on top of a smart contract is having an unmodifiable agreement among the parties involved.

The key here is that these two concepts are not incompatible between each other: ZeppelinOS provides the tools to have upgradeable smart contracts, which follow the immutability rules guaranteed by the Ethereum blockchain. However, it is desirable to have a mechanism that allows all parties involved to decide on whether a contract should be upgraded or not, instead of having an unilateral decision.

That’s when another part of the big picture comes up, which is the project governance. Even though ZeppelinOS allows for an account to upgrade some of the smart contracts that comprise an application, that doesn’t mean that such account is centrally controlled. A project could enforce any kind of rules for deciding how and when to upgrade their code.

To illustrate such a scenario, in this post we’ll go through one of the most canonical examples of decentralized governance: a multisignature wallet (or multisig, for short). Keep in mind that this could be generalized to any kind of contract, with rules as complex as needed, depending on the governance requirements of your project.

If you want to reproduce this setup on your workstation, download the governance-with-multisig project from our labs repository, which contains all the necessary code and detailed instructions. While some of the commands in this post are trimmed to improve readability, you can find the full versions on GitHub._

Getting started

A multisig is a contract that can execute arbitrary transactions, with the restriction that a certain number of its owners must agree upon them. A sample classic setup is a multisig with three owners, requiring two of them to approve every action, though these numbers can be increased depending on the sensitivity of the transactions.

We’ll be using the Gnosis MultiSig Wallet, which was audited by the Zeppelin team and has a useful GUI for submitting and confirming transactions. Our first step will be creating the multisig wallet contract, which will control how and when the contracts in our projects are upgraded.

The addresses in the screenshots correspond to a full test run on the Ropsten network. You can check out the deployed multisig contract on Etherscan.

We’ll also create a new project with a sample contract named EthBox. This contract’s only method is a deposit function that accepts ETH and keeps track of its sender.

contract EthBox {  
    mapping(address => uint256) balances;  
    function deposit() public payable {     
        balances[msg.sender] += msg.value;  
    }
}

We’ll enable ZeppelinOS in our project, register this contract, push it to the network, and create a new instance of it.

$ zos init governance-with-multisig
Successfully written zos.json

**$ zos add EthBox**
Compiling contracts
Adding EthBox
Successfully written zos.json

**$ zos push**
[...]
Setting implementation of EthBox in directory...
Implementation set: 0xa3725318d6ccb88ce8a64094bb3be09a6a43d582

**$ zos create EthBox**
Creating EthBox proxy without initializing...
EthBox proxy: 0x6e14585e57504e89a82e3cd68c30c849bb7c3583

At this point, we have our project deployed to the Ropsten network, with an instance of our EthBox contract up and running. Ownership of the project is centrally controlled by a single Ethereum account, which can unilaterally decide when to upgrade any of its contracts.

Transferring control

Since we want to avoid having a single account with full control over our EthBox instance, we’ll transfer control of it to our multisig contract. To do this, we’ll use the changeProxyAdmin function and yield control to the multisig account.

awaitapp.changeProxyAdmin(proxyAddress, newAdmin);

At the time of this writing, the ZeppelinOS CLI doesn’t yet have a command to perform this action. Nevertheless, you can make use of the change-admin script provided on the sample repository for this guide to easily do that._

Next, if we want to upgrade our EthBox instance to a new version, we’ll need to perform the operation from the multisig contract. Note that we have transferred ownership only of our EthBox instance. If we had created more instances of EthBox, or of any other contract, they would still be under control of the deployer account.

The same applies to our ZeppelinOS app. This allows us to keep interacting with our project via the CLI, by creating new instances or registering new logic contracts, as we’ll be doing in the next step.

Uploading a new version

Let’s upgrade our contract instance to a new version, which provides a handy getBalance function to query the balance of a contributor.

Contract EthBox {
    [...]
    function getBalance(address owner) public view returns(uint256) {
        return balances[owner];
    }
}

The first step is to actually upload this new logic contract to the blockchain, so we can upgrade our EthBox instance to it. Since this is still managed by our deployer account, we can easily do that from the CLI.

$ zos add EthBox

Adding EthBox

$ zos push

Uploading EthBox contract
Implementation set: 0xb51050a29152346457ee41169dfa27dcd5350be3

Now that our new logic contract is uploaded to the network, we can proceed to upgrade our EthBox instance.

Upgrading our contract instance

At this point, if we attempt to upgrade our EthBox instance to the new version from the CLI, we’ll get an error. We need to go through the multisig to perform this operation, as the CLI’s account no longer has rights over the contract instance.

**$ zos update --all**
Upgrading EthBox proxy without running migrations...
Proxy EthBox at 0x6e14585e57504e89a82e3cd68c30c849bb7c3583 failed
to upgrade with Transaction: 
0xad977914a94cab12cb0751b7ab921304411eb983fdc5270a3c505ef90fed2757 
exited with an error (status 0).

Let’s submit a transaction to the multisig wallet for our contract instance to be upgraded. We can do this from the Gnosis GUI by including the contract’s ABI and choosing to invoke upgradeTo. We also need to supply the address of the new implementation, which can be found in the output of the last zos push command or in the zos.ropsten.json file.

This will create a new transaction in the multisig wallet for the EthBox instance to be upgraded to the latest EthBox implementation. However, since this requires the approval of at least another multisig owner, the upgrade is still pending.

As soon as another owner of the multisig account confirms this transaction, it will be executed on the spot and our EthBox instance contract will be upgraded to the desired version, allowing us to make use of the new getBalance function it provides.

truffle(ropsten)> 
EthBox.deployed().

getBalance

('0x09902a56d04a9446601a0d451e07459dc5af0820')
.then(n => n.toNumber())

=> 1000000000000000000

Wrapping up

In this post, we’ve gone over the steps needed to transfer upgradeability control of a contract instance to a multisig wallet. This allows us to decentralize control over upgrading a contract instance and delegate it to the consensus of a set of trusted accounts. While transferring control is still manual, requiring some scripts not yet baked into the ZeppelinOS CLI, the upgrade process can be easily controlled from the Gnosis MultiSig GUI.

This is the first step we are taking towards decentralized project governance. We hope it encourages the ZeppelinOS development community to explore other governance patterns, all the way to full-blown DAOs, while we improve our tools to make the process as smooth as possible. Stay tuned!