Upgradable Ethereum Contracts

Jakub Wojciechowski
Alice
Published in
10 min readSep 9, 2017

If there’s one killer app for the Ethereum platform right now, it has to be… the famous ICO Token! Tokens are virtual assets that enable you to place bets on the prediction market, buy computation power, run decentralised organisation … they can also simply be a way to waste your money on totally useless stuff.

My startup, Alice, uses tokens to manage transparent donations to charities. We launched our first pilot a couple of months ago, and now that we’re onboarding more charities with different use cases, the challenge is to find a sustainable way to update our protocols, so we can build in different functionalities like governance systems and different kinds of funding as we go along.

Here’s the problem though: tokens are created by smart contracts, which by their very nature are immutable because they’re on the blockchain. So how can all those revolutionary Ethereum startups out there ensure that they can upgrade their tokens if their projects evolve and pivot after an ICO, without having to start everything over?

And what if they discover a security bug and need to implement a fix without affecting the trust of their token holders?

In this post, I analyse the pros and cons of two ways of updating tokens in a way that is transparent to token holders and convenient for developers: migration and cloning. I’ll take the Golem Network Token and Giveth’s MiniMe token as examples. I’ll also sketch out how to build hybrid models of these two very different paths.

Migration

In a nutshell, migration is the process of transforming one token into another. Tokens are first burnt and then recreated again on a different contract. The new tokens are created from scratch and are totally independent from the previous version.

I’ll explain this model by analysing the Golem Network Token. Golem uses the tokens to pay for accessing computing resources and were one of the pioneers of the ICO market raising $8.6m in less than half an hour in November 2016. Their source code is publicly available on GitHub.

They were also one of the first to really focus on the security and flexibility of token sale contracts and wanted to create an easy way to upgrade their tokens, without affecting its core functionalities.

Let’s take a deeper look a the migration code:

function migrate(uint256 _value) external {
// Abort if not in Operational Migration state.
if (funding) throw;
if (migrationAgent == 0) throw;
// Validate input value.
if (_value == 0) throw;
if (_value > balances[msg.sender]) throw;
balances[msg.sender] -= _value;
totalTokens -= _value;
totalMigrated += _value;
MigrationAgent(migrationAgent).migrateFrom(msg.sender, _value);
Migrate(msg.sender, migrationAgent, _value);
}

Here’s what we can observe:

  • The code reflects Golem’s commitment to the KISS principle: they extract the control of migration process to outside modules to leave the implementation of the core token clean, simple and easy to audit.
  • The migration mechanism is triggered by each token holder. This approach gives users the freedom to decide if they want to take part in the migration or not.
  • There’s an obvious communication challenge here: token holders need to be informed of the migration to act on it, and less savvy token holders may not have the skills to execute the process without a lot of support from the Golem team, wallet providers or exchanges.
  • The migration is not a binary choice. This is great from a “democratic” perspective because every user can decide for herself which part of the tokens she wants to migrate. It might lead to the need to maintain past token versions if users don’t all migrate through.
  • The migration process can last for as long as token holders need to convert: the transition period is closed manually by the contract owner calling a finalizeMigration() function.
  • Each migration requires the creation of a MigrationAgent, which is a smart contract containing the migration logic. This is a great solution to keep everything simple and modular, and because Golem reserves the privilege to propose migrations as migrationMaster, there is virtually no risk of triggering a malicious migration. Nevertheless, each migration contract should be carefully audited each time.
  • As the process requires burning tokens and recreating them on a different contract, blockchain storage needs to be updated for every token holder account.

As expressed by Viggith, the main idea behind this migration design was to give users as much freedom as possible:

As you can see, our approach to migration enables each user to choose how much GNT she wants to migrate, while leaving the rest in the original contract.[…]No voting mechanism is necessary, and each participant is in full control of her tokens without the need to wait for voting results of any kind.

Cloning

In this approach, a new token contract is forked from the original, which is unaffected by the change. Token holders, therefore, own both tokens and are free to use each one at the same time. As clones, the new tokens inherit the internal structures of the original ones, with newly added functionalities.

I will explain this approach by analysing the MiniMe token created by Jordi Baylina and Griff Green at Giveth. This contract was used by several companies that recently launched their ICOs: Aragon, District0x and Status. Another platform, Swarm City, first used a normal token for their ICO but converted to MiniMe soon after to make their reputation tokens more flexible.

Let’s take a look at the code to pick up a few insights:

function createCloneToken(
string _cloneTokenName,
uint8 _cloneDecimalUnits,
string _cloneTokenSymbol,
uint _snapshotBlock,
bool _transfersEnabled
) returns(address) {
if (_snapshotBlock == 0) _snapshotBlock = block.number;
MiniMeToken cloneToken = tokenFactory.createCloneToken(
this,
_snapshotBlock,
_cloneTokenName,
_cloneDecimalUnits,
_cloneTokenSymbol,
_transfersEnabled
);

cloneToken.changeController(msg.sender);

// An event to make the token easy to find on the blockchain
NewCloneToken(address(cloneToken), _snapshotBlock);
return address(cloneToken);
}

Here are my observations:

  • Cloning is a centrally governed process. The initiator creates a new version of the token for all of the token holders, defines token properties and becomes a token controller. Token users don’t have the option to opt-out from the process, and automatically own the same amount of new tokens as they did original ones at the time of the cloning process.
  • Cloning is an atomic operation. It is executed instantly at the same block as it was requested. This can be extremely useful to facilitate quick, new use cases for token holders, like creating auxiliary tokens for ad hoc voting, staking, or coupons to redeem periodic payments.
  • It’s possible to select any point in time (block) as a reference state for the cloned token. This point is effectively a snapshot of the original token distribution and defines the initial state of the cloned token.
struct  Checkpoint {

// `fromBlock` is the block number that the value was generated from
uint128 fromBlock;

// `value` is the amount of tokens at a specific block number
uint128 value;
}
  • At the core of MiniMe implementation lays the Checkpoint concept. This structure enriches balance storage with time indexing which allows for fast queries of historical changes. There is obviously some redundancy included as the blockchain maintains the state history on its own. However, having this double accounting greatly simplifies operations like obtaining a snapshot of tokens distribution at a given time.

Here’s how Griff Green explains the best part about MiniMe tokens:

The most amazing thing is that ANYONE can give new functionality to MiniMe token holders in a permissionless yet safe manner without affecting the parent token’s intended functionality.

Performance

Now we’ve looked at the designs and implementation of the core features of both types of contracts, how does it all translate in terms of efficiency? In other words, how expensive it is to use both of them? Let’s check to see how much it costs to mint, transfer and upgrade tokens before you receive the gas bill.

Golem Token registers the balance only in a single mapping. MimeMe is more than twice as expensive because it needs to create additional indexed structures of checkpoints to record block number on which tokens were minted.

When Golem tokens are used on a day to day — users just routinely transferring tokens to each other to pay for computing power for example — the token contract registers holder balances in just a single mapping, whereas the MiniMe token is more than twice as expensive, because it needs to create the additional indexed structures of its checkpoints to record the block numbers at which tokens were minted.

In order to migrate its tokens, Golem deploys a relatively small (which in Ethereum terms means cheap, because it requires less gas to compute) MigrationAgent contract that contains the logic of the migration process. MiniMe, however, needs to deploy another instance of itself which contains all of the logic of token issuance and transfers.

The MiniMe cloning is an atomic operation, so there is no variable cost to be paid for each holder’s account. Golem, on the contrary, needs to burn and recreate balances on an account by account basis which consumes a similar amount of gas as the transfer. In other words, the MiniMe token is comparatively expensive to run and deploy a clone, but that is amortised by the absence of an account by account upgrade cost, thanks to the additional data structures that allow an instant fork.

If you’d like to check the gas costs for yourself, I’ve published the code I used to obtain these statistics on Alice’s GitHub repository.

The bottom line

So what’s the best, and cheapest way to upgrade a token then? Well, the bottom line is that there are tradeoffs, and it probably depends on how your tokens are used, and how often you intend to upgrade them.

Overall, I would probably use a MiniMe contract if my token needed frequent upgrades because that would amortise its running costs. But if I wanted to give my token holders more freedom, and only planned to upgrade from time to time, then I’d go for Golem-style tokens.

This is why:

  • The Golem token contract is cheaper to run for routine uses, and its migration process is more democratic (in that it gives token holders the freedom not to migrate), but it’s likely that each migration will take a long time to be fully completed. In all likelihood, each migration is likely to create legacy tokens. It also incurs additional costs each time a token holder activates the migration.
  • MiniMe tokens have a higher overhead for regular usage, but when cloned, they have the advantage of not requiring any action from token holders.

Third way

Now that we’ve analysed the pros and cons, I wanted to see if it was possible to mix both solutions. I was imagining a token that is cheaper than MiniMe to transfer between users on a day-to-day basis but also allows the creation of multiple clones that can be used for voting or staking purposes.

All we need to do is take Golem’s architecture and adjust it to the MiniMe vision. Let me show how this hybrid model can be implemented in practice:

First, we need to modify the Golem token so it no longer burns tokens after initiating a migration, but deposits them in a cloning contract.

function clone(uint256 _value) external {
migrate(_value);

// Put deposit on cloning contract
balances[migrationAgent] += _value;
}

function collectDeposit() external {
ICloningAgent(migrationAgent).collectDeposit(msg.sender);
}

Second, we need to record deposits and return them after the cloning process is over.

function migrateFrom(address _from, uint256 _value) {
super.migrateFrom(_from, _value);

//Register deposits (overflow check done in balance updating)
deposits[_from] += _value;
}

function collectDeposit(address _from) {
require(msg.sender == gntSourceToken);
require(isFinalized);

uint value = deposits[_from];
require(value > 0);
deposits[_from] = 0;

Source.GolemNetworkToken(gntSourceToken).transfer(_from, value);
}

It’s important to note that the whole cloning process is spread out in time to allow all users the possibility of cloning their assets. During this period it’s necessary to freeze tokens that were used as a deposit for cloning to avoid any attempts to clone the same tokens more than once. So it requires users to execute two operations: initiate cloning by putting in their deposit, and then collecting the deposited tokens once the process is finished. This would be a hassle, but on the other hand, it gives users the freedom to enter the cloning process and maintains lower operating costs on tokens.

That being said, cloning is still more expensive than a classical migration due to the additional costs of freezing token holder deposits. Whether it lowers the average cost of the contract would completely depend on the use case, and bear in mind that without MiniMe’s auxiliary data structures, it wouldn’t be possible to clone tokens from a historical state.

The full implementation of this design accompanied with gas consumption tests is available on Alice’s GitHub repository.

MIGRATING WITH MINEME

We can create the reverse hybrid model by executing a migration on top of the MiniMe token. To implement that we simply need to block the transfer function after the migration process is over by substituting the controller of the base token. It’s not a 100% pure migration as the old tokens still exist but as they cannot be transferred any longer, they can be considered defunct.

function onTransfer(address _from, address _to, uint _amount) returns(bool) {
return false;
}

Well, it’s certainly possible to mix both solutions, but I’m not convinced it would actually be any cheaper on average, and it involves different tradeoffs.

Summary

The main design principle for Golem-type tokens is to keep the implementation simple and effective and extract all of the migration logic into an external module. Routine minting and transfer operations are cheap but we need to expect an additional gas expense for every account when a migration occurs.

MiniMe, on the contrary, incorporates all of the cloning logic directly into its token code. The need to maintain additional data structures, that keep a full revision history of balance changes, means that minting and transfer costs are twice as much as Golem, but the marginal cost of cloning per user is zero: everyone gets an upgrade at the same time!

My intention was to present both designs in an objective and impartial way. They’re both pioneering solutions that have been really well thought through and implemented. Ultimately, the choice is down to analysing the tradeoffs between each solution, and I’ve laid out some hybrid models that could also give you some additional ideas to build the perfect upgrade solution for your token!

If you have any comments or questions about this article we’d love to discuss this with you on our Slack channel.

--

--

Jakub Wojciechowski
Alice
Writer for

CTO at Alice.si, a company that builds social impact engine on top of the Ethereum platform.