0

I've been struggling to find a solution to the problem of upgradeability that maximizes trustlessness while also putting minimal burden on users. I really wanted to use signed messages to validate user authorization of certain transactions that are sent by the contract owner. However, based on reading the EIP discussions and experimenting with MetaMask and Cipher, because message signing still seems to be evolving and signing methods are not universally supported by existing wallets or guaranteed to be supported in perpetuity, the next best solution would be to make the contract upgradeable. But patterns like eternal storage or proxy delegate appear to compromise trustlessness and so are not optimal.

I'm fairly new to Ethereum, so if anyone disagrees with the above, I'd be interested to hear why.

In the meantime, I think the (simplified) solution below will work so that a dapp could be upgraded with only minimal cost to users (gas cost for calling transferBalance), while not compromising trustlessness.

The progression would go like this:

  1. First contract (inherits from Transferable) is created and users add balances
  2. Second contract (also inherits from Transferable but differs from the first contract to the extent that upgrades are needed) is created and a reference to it is set on the first contract with setNext, which is only accessible by the owner of the first contract.
  3. Users initiate balance transfers by calling transferBalance, which empties their balance on the old contract and sets it on the new contract. Sophisticated users could review the new contract on EtherScan before transferring to ensure that the new contract is not malicious.

pragma solidity ^0.5.8;

contract Transferable {

    address owner;
    Transferable next;
    mapping(address => uint) public balances;

    constructor() public {
        owner = msg.sender;
    }

    function addBalance() public payable {
        balances[msg.sender] += msg.value;
    }

    function transferBalance() public payable {
        require(address(next) != address(0), "next not set");
        uint value = balances[msg.sender];
        balances[msg.sender] -= value;
        next.receiveBalanceTransfer.value(value)(msg.sender);
    }

    function receiveBalanceTransfer(address _beneficiary) public payable {
        balances[_beneficiary] += msg.value;
    }

    function setNext(address _next) public {
        require(msg.sender == owner);
        next = Transferable(_next);
    }

}

I'd be curious if anyone sees any flaws in this approach or the rationale behind it.

  • 1
    i think people tend to refer to this as the "migration pattern", and it's great. – user19510 Sep 7 '19 at 7:33
  • That's great news. This has been plaguing me for a couple weeks! – SuperCodeBrah Sep 7 '19 at 8:06
  • The owner has extraordinary power in this case. Being the only one who can choose next and potentially disabling this means users can be coerced into the upgrade. Some experimentation on this problem: github.com/rob-Hitchens/TrustlessUpgrades – Rob Hitchens - B9lab Sep 7 '19 at 19:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.