The Easy Way to Upgrade Smart Contracts

Anton Bukov
BitClave
Published in
3 min readMar 20, 2018

--

Please take a look at my newest article about upgradability: https://medium.com/@k06a/the-safest-and-probably-the-best-way-to-upgrade-smart-contracts-ea6e619d5dfd

Recently, we at BitClave discussed smart contract upgrading problems and we are going to propose a new way to deliver upgrades. As all Ethereum developers know, once deployed to pseudo-random address, smart contract can’t be replaced with newer version. Usually token upgrades are accompanied by migrating all holder’s balances from one version to another. This is quite difficult in case of hundreds of thousands of holders — the amount of transactions (even batched) for migration purposes can take a few days to be mined and take hundreds of thousands of dollars Ethereum fees.

There are several interesting articles proposing upgradable smart contract patterns (usually with external storage):

But we are trying to propose a solution to already existing (deployed) smart contracts with millions of records. We just imagined a way to upgrade smart contracts without all records migration. And we think it is possible. Look at simplest token smart contract:

contract MyToken is ERC20Basic, Pausable {    mapping(address => uint256) balances;    function MyToken() {
balances[msg.sender] = 1000000;
}
function balanceOf(address _acc) public view returns(uint256) {
return balances[_acc];
}
function transfer(address _to, uint256 _amount) public {
require(balances[msg.sender] >= _amount);
balances[msg.sender] -= _amount;
balances[_to] += _amount;
}
}

The first and the only requirement is to pause/stop old smart contract to prevent it state changing forever:

contract MyToken2 is ERC20Basic, Pausable {    MyToken prevVersion;    function MyToken2(MyToken _prevVersion) public {
require(_prevVersion.paused());
prevVersion = _prevVersion;
}
}

The main idea is to perform migration lazily for every account on any transaction from it:

mapping(address => bool) migratedBalances;function transfer(address _to, uint256 amount) public {
if (!migratedBalances[msg.sender]) {
balances[msg.sender] += prevToken.balanceOf(msg.sender);
migratedBalances[msg.sender] = true;
}
// Usual ERC20.transfer implementation
}

All constant/view methods should delegate its calls to previous version of the contract in cases where an account is not yet migrated:

function balanceOf(address account) public view returns(uint256) {
if (!migratedBalances[account]) {
return prevVersion.balanceOf(account) + balances[account];
}
return balances[account];
}

Since the old smart contract is paused and the new contract is deployed, all holders are immediately ported and migration will be performed lazily in the first transaction of every holder.

We have developed base classes to perform lazy migration of BasicToken,StandardToken and BurnableToken based on ERC20 implementation of OpenZeppelin solidity library: https://github.com/bitclave/TokenWrapper

You can discuss the idea here: https://github.com/OpenZeppelin/zeppelin-solidity/issues/795

One more thing 👍

One of the most frustrating aspects of ERC20 tokens, that there are no legal reason to send tokens directly to smart contracts with transfer method (wallet developers, if you read this, please warn users about transfering tokens to any smart contracts with transfer method). Within smart contract, it will be impossible to identify who sent this tokens to it. For this purpose ERC827 was proposed to combine in single transaction calls of approve method on the holder’s side and transferFrom method on the smart contract side. But as you may suspect some people have already sent some tokens to smart contracts with transfer and sometimes it is the token smart contract itself:

  1. 22K lost QTUM tokens (worth $400k on March 2018)
  2. 100K lost EOS tokens (worth $600k on March 2018)
  3. 10K lost OMG tokens (worth $150k on March 2018)
  4. 5M lost TRX tokens (worth $150k on March 2018)
  5. 350K lost GNT tokens (worth $100k on March 2018)

There is a way to handle especially this mistake: sending tokens to the token smart contract itself: https://github.com/OpenZeppelin/zeppelin-solidity/issues/748

This method will give your smart contract to reclaim any ERC20 tokens, mistakenly sent to it:

function recoverLost(ERC20 token, address loser) public onlyOwner {
token.transfer(loser, token.balanceOf(this));
}

We dropped an e-mail to the listed projects, which tokens are pausable/stoppable: OMG, EOS, TRX. Hope they will like the idea to allow recovering some lost tokens in future. As you may see on links, people are still continue sending their tokens to smart contracts.

--

--